commit 3ab350bb93ed3a4ad79c0bdb6b133faf0eab2c29 Author: Jörg Prante Date: Sun Jan 19 15:34:00 2020 +0100 clone of Guava 28.1 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3e42bcc --- /dev/null +++ b/.gitignore @@ -0,0 +1,12 @@ +/data +/work +/logs +/.idea +/target +.DS_Store +*.iml +/.settings +/.classpath +/.project +/.gradle +/build \ No newline at end of file diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..94d2a22 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,3 @@ +language: java +jdk: + - openjdk11 diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 0000000..d645695 --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/README.adoc b/README.adoc new file mode 100644 index 0000000..c3e42ab --- /dev/null +++ b/README.adoc @@ -0,0 +1,29 @@ +# xbib Guava + +This is xbib Guava, a build of Google Guava with the following differences to the +original [Google Guava Library](https://github.com/google/guava): + +- forked master branch on November 21, 2019 ("28.1+") +- removed all external annotations, so this library does not have any dependencies +- removed duplicate JDK classes (LongAdder, Striped64) +- replaced sun.misc.Unsafe dependent classes with safe versions (LongAdders, UnsignedBytes, LittleEndianByteArray, AbstractFuture) +- the guava failureaccess dependency is included +- removed listenablefuture empty dependency hack +- compiled under and for Java 11 and with a module info for JPMS (module org.xbib.guava) +- Gradle as build system + +All credits belong to the original authors + +## License + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000..a6fea58 --- /dev/null +++ b/build.gradle @@ -0,0 +1,163 @@ +plugins { + id "io.codearte.nexus-staging" version "0.21.1" +} + +apply plugin: 'java' +apply plugin: 'maven' + +repositories { + mavenCentral() +} + +compileJava { + sourceCompatibility = JavaVersion.VERSION_11 + targetCompatibility = JavaVersion.VERSION_11 +} + +compileTestJava { + sourceCompatibility = JavaVersion.VERSION_11 + targetCompatibility = JavaVersion.VERSION_11 +} + +tasks.withType(JavaCompile) { + options.compilerArgs << "-Xlint:all" +} + +test { + testLogging { + showStandardStreams = false + exceptionFormat = 'full' + } +} + +task sourcesJar(type: Jar, dependsOn: classes) { + classifier 'sources' + from sourceSets.main.allSource +} + +task javadocJar(type: Jar, dependsOn: javadoc) { + classifier 'javadoc' +} + +artifacts { + archives sourcesJar, javadocJar +} + +ext { + user = 'xbib' + projectName = 'guava' + projectDescription = 'Guava for Java' + scmUrl = 'https://github.com/xbib/guava' + scmConnection = 'scm:git:git://github.com/xbib/guava.git' + scmDeveloperConnection = 'scm:git:git://github.com/xbib/guava.git' +} + +task sonatypeUpload(type: Upload) { + group = 'publish' + configuration = configurations.archives + uploadDescriptor = true + repositories { + if (project.hasProperty('ossrhUsername')) { + mavenDeployer { + beforeDeployment { MavenDeployment deployment -> signing.signPom(deployment) } + repository(url: uri(ossrhReleaseUrl)) { + authentication(userName: ossrhUsername, password: ossrhPassword) + } + snapshotRepository(url: uri(ossrhSnapshotUrl)) { + authentication(userName: ossrhUsername, password: ossrhPassword) + } + pom.project { + groupId project.group + artifactId project.name + version project.version + name project.name + description projectDescription + packaging 'jar' + inceptionYear '2019' + url scmUrl + organization { + name 'xbib' + url 'http://xbib.org' + } + developers { + developer { + id user + name 'Jörg Prante' + email 'joergprante@gmail.com' + url 'https://github.com/jprante' + } + } + scm { + url scmUrl + connection scmConnection + developerConnection scmDeveloperConnection + } + licenses { + license { + name 'The Apache License, Version 2.0' + url 'http://www.apache.org/licenses/LICENSE-2.0.txt' + } + } + } + } + } + } +} + +nexusStaging { + packageGroup = "org.xbib" +} + +/* +spotbugs { + effort = "max" + reportLevel = "low" +} + +tasks.withType(com.github.spotbugs.SpotBugsTask) { + ignoreFailures = true + reports { + xml.enabled = false + html.enabled = true + } +} + +pmd { + toolVersion = '6.11.0' + ruleSets = ['category/java/bestpractices.xml'] +} + +tasks.withType(Pmd) { + ignoreFailures = true + reports { + xml.enabled = true + html.enabled = true + } +} + +checkstyle { + toolVersion = '8.26' + configFile = rootProject.file('config/checkstyle/checkstyle.xml') + ignoreFailures = true + checkstyleMain { + source = sourceSets.main.allSource + } +} + +tasks.withType(Checkstyle) { + ignoreFailures = true + reports { + xml.enabled = true + html.enabled = true + } +} + +sonarqube { + properties { + property "sonar.projectName", "${project.group} ${project.name}" + property "sonar.sourceEncoding", "UTF-8" + property "sonar.tests", "src/test/java" + property "sonar.scm.provider", "git" + } +} +*/ \ No newline at end of file diff --git a/config/checkstyle/checkstyle.xml b/config/checkstyle/checkstyle.xml new file mode 100644 index 0000000..ffaa04f --- /dev/null +++ b/config/checkstyle/checkstyle.xml @@ -0,0 +1,177 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/config/checkstyle/suppressions.xml b/config/checkstyle/suppressions.xml new file mode 100644 index 0000000..dcc9f23 --- /dev/null +++ b/config/checkstyle/suppressions.xml @@ -0,0 +1,7 @@ + + + + + diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 0000000..7e612cc --- /dev/null +++ b/gradle.properties @@ -0,0 +1,3 @@ +group = org.xbib +name = guava +version = 28.1 diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..5c2d1cf Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..4f7d8a0 --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Sat Nov 23 21:29:06 CET 2019 +distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.2-all.zip +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStorePath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME diff --git a/gradlew b/gradlew new file mode 100755 index 0000000..83f2acf --- /dev/null +++ b/gradlew @@ -0,0 +1,188 @@ +#!/usr/bin/env sh + +# +# Copyright 2015 the original author or authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn () { + echo "$*" +} + +die () { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin or MSYS, switch paths to Windows format before running java +if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " +} +APP_ARGS=$(save "$@") + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong +if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then + cd "$(dirname "$0")" +fi + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000..24467a1 --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,100 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windows variants + +if not "%OS%" == "Windows_NT" goto win9xME_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/settings.gradle b/settings.gradle new file mode 100644 index 0000000..89f4110 --- /dev/null +++ b/settings.gradle @@ -0,0 +1 @@ +rootProject.name = name diff --git a/src/main/java/com/google/common/annotations/Beta.java b/src/main/java/com/google/common/annotations/Beta.java new file mode 100644 index 0000000..47dafe8 --- /dev/null +++ b/src/main/java/com/google/common/annotations/Beta.java @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2010 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.annotations; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Signifies that a public API (public class, method or field) is subject to incompatible changes, + * or even removal, in a future release. An API bearing this annotation is exempt from any + * compatibility guarantees made by its containing library. Note that the presence of this + * annotation implies nothing about the quality or performance of the API in question, only the fact + * that it is not "API-frozen." + * + *

It is generally safe for applications to depend on beta APIs, at the cost of some extra + * work during upgrades. However it is generally inadvisable for libraries (which get + * included on users' CLASSPATHs, outside the library developers' control) to do so. + * + * + * @author Kevin Bourrillion + */ +@Retention(RetentionPolicy.CLASS) +@Target({ + ElementType.ANNOTATION_TYPE, + ElementType.CONSTRUCTOR, + ElementType.FIELD, + ElementType.METHOD, + ElementType.TYPE +}) +@Documented +@GwtCompatible +public @interface Beta {} diff --git a/src/main/java/com/google/common/annotations/GwtCompatible.java b/src/main/java/com/google/common/annotations/GwtCompatible.java new file mode 100644 index 0000000..1391728 --- /dev/null +++ b/src/main/java/com/google/common/annotations/GwtCompatible.java @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2009 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.annotations; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * The presence of this annotation on a type indicates that the type may be used with the Google Web Toolkit (GWT). When applied to a method, + * the return type of the method is GWT compatible. It's useful to indicate that an instance created + * by factory methods has a GWT serializable type. In the following example, + * + *

+ * {@literal @}GwtCompatible
+ * class Lists {
+ *   ...
+ *   {@literal @}GwtCompatible(serializable = true)
+ *   {@literal static  List} newArrayList(E... elements) {
+ *     ...
+ *   }
+ * }
+ * 
+ * + *

The return value of {@code Lists.newArrayList(E[])} has GWT serializable type. It is also + * useful in specifying contracts of interface methods. In the following example, + * + *

+ * {@literal @}GwtCompatible
+ * interface ListFactory {
+ *   ...
+ *   {@literal @}GwtCompatible(serializable = true)
+ *   {@literal  List} newArrayList(E... elements);
+ * }
+ * 
+ * + *

The {@code newArrayList(E[])} method of all implementations of {@code ListFactory} is expected + * to return a value with a GWT serializable type. + * + *

Note that a {@code GwtCompatible} type may have some {@link GwtIncompatible} methods. + * + * + * @author Charles Fry + * @author Hayward Chan + */ +@Retention(RetentionPolicy.CLASS) +@Target({ElementType.TYPE, ElementType.METHOD}) +@Documented +@GwtCompatible +public @interface GwtCompatible { + + /** + * When {@code true}, the annotated type or the type of the method return value is GWT + * serializable. + * + * @see + * Documentation about GWT serialization + */ + boolean serializable() default false; + + /** + * When {@code true}, the annotated type is emulated in GWT. The emulated source (also known as + * super-source) is different from the implementation used by the JVM. + * + * @see + * Documentation about GWT emulated source + */ + boolean emulated() default false; +} diff --git a/src/main/java/com/google/common/annotations/GwtIncompatible.java b/src/main/java/com/google/common/annotations/GwtIncompatible.java new file mode 100644 index 0000000..4b7e4a9 --- /dev/null +++ b/src/main/java/com/google/common/annotations/GwtIncompatible.java @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2009 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.annotations; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * The presence of this annotation on an API indicates that the method may not be used with + * the Google Web Toolkit (GWT). + * + *

This annotation behaves identically to the + * {@code @GwtIncompatible} annotation in GWT itself. + * + * @author Charles Fry + */ +@Retention(RetentionPolicy.CLASS) +@Target({ElementType.TYPE, ElementType.METHOD, ElementType.CONSTRUCTOR, ElementType.FIELD}) +@Documented +@GwtCompatible +public @interface GwtIncompatible { + /** + * Describes why the annotated element is incompatible with GWT. Since this is generally due to a + * dependence on a type/method which GWT doesn't support, it is sufficient to simply reference the + * unsupported type/method. E.g. "Class.isInstance". + * + *

As of Guava 20.0, this value is optional. We encourage authors who wish to describe why an + * API is {@code @GwtIncompatible} to instead leave an implementation comment. + */ + String value() default ""; +} diff --git a/src/main/java/com/google/common/annotations/VisibleForTesting.java b/src/main/java/com/google/common/annotations/VisibleForTesting.java new file mode 100644 index 0000000..4540cfd --- /dev/null +++ b/src/main/java/com/google/common/annotations/VisibleForTesting.java @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2006 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.annotations; + +/** + * Annotates a program element that exists, or is more widely visible than otherwise necessary, only + * for use in test code. + * + * @author Johannes Henkel + */ +@GwtCompatible +public @interface VisibleForTesting { +} diff --git a/src/main/java/com/google/common/base/Absent.java b/src/main/java/com/google/common/base/Absent.java new file mode 100644 index 0000000..2f0bc91 --- /dev/null +++ b/src/main/java/com/google/common/base/Absent.java @@ -0,0 +1,98 @@ +/* + * Copyright (C) 2011 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.base; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.annotations.GwtCompatible; +import java.util.Collections; +import java.util.Set; + +/** Implementation of an {@link Optional} not containing a reference. */ +@GwtCompatible +final class Absent extends Optional { + static final Absent INSTANCE = new Absent<>(); + + @SuppressWarnings("unchecked") // implementation is "fully variant" + static Optional withType() { + return (Optional) INSTANCE; + } + + private Absent() {} + + @Override + public boolean isPresent() { + return false; + } + + @Override + public T get() { + throw new IllegalStateException("Optional.get() cannot be called on an absent value"); + } + + @Override + public T or(T defaultValue) { + return checkNotNull(defaultValue, "use Optional.orNull() instead of Optional.or(null)"); + } + + @SuppressWarnings("unchecked") // safe covariant cast + @Override + public Optional or(Optional secondChoice) { + return (Optional) checkNotNull(secondChoice); + } + + @Override + public T or(Supplier supplier) { + return checkNotNull( + supplier.get(), "use Optional.orNull() instead of a Supplier that returns null"); + } + + @Override + public T orNull() { + return null; + } + + @Override + public Set asSet() { + return Collections.emptySet(); + } + + @Override + public Optional transform(Function function) { + checkNotNull(function); + return Optional.absent(); + } + + @Override + public boolean equals(Object object) { + return object == this; + } + + @Override + public int hashCode() { + return 0x79a31aac; + } + + @Override + public String toString() { + return "Optional.absent()"; + } + + private Object readResolve() { + return INSTANCE; + } + + private static final long serialVersionUID = 0; +} diff --git a/src/main/java/com/google/common/base/AbstractIterator.java b/src/main/java/com/google/common/base/AbstractIterator.java new file mode 100644 index 0000000..338b771 --- /dev/null +++ b/src/main/java/com/google/common/base/AbstractIterator.java @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.base; + +import static com.google.common.base.Preconditions.checkState; + +import com.google.common.annotations.GwtCompatible; +import java.util.Iterator; +import java.util.NoSuchElementException; + +/** + * Note this class is a copy of {@link com.google.common.collect.AbstractIterator} (for dependency + * reasons). + */ +@GwtCompatible +abstract class AbstractIterator implements Iterator { + private State state = State.NOT_READY; + + protected AbstractIterator() {} + + private enum State { + READY, + NOT_READY, + DONE, + FAILED, + } + + private T next; + + protected abstract T computeNext(); + + protected final T endOfData() { + state = State.DONE; + return null; + } + + @Override + public final boolean hasNext() { + checkState(state != State.FAILED); + switch (state) { + case DONE: + return false; + case READY: + return true; + default: + } + return tryToComputeNext(); + } + + private boolean tryToComputeNext() { + state = State.FAILED; // temporary pessimism + next = computeNext(); + if (state != State.DONE) { + state = State.READY; + return true; + } + return false; + } + + @Override + public final T next() { + if (!hasNext()) { + throw new NoSuchElementException(); + } + state = State.NOT_READY; + T result = next; + next = null; + return result; + } + + @Override + public final void remove() { + throw new UnsupportedOperationException(); + } +} diff --git a/src/main/java/com/google/common/base/Ascii.java b/src/main/java/com/google/common/base/Ascii.java new file mode 100644 index 0000000..0a8ec50 --- /dev/null +++ b/src/main/java/com/google/common/base/Ascii.java @@ -0,0 +1,635 @@ +/* + * Copyright (C) 2010 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.base; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.annotations.GwtCompatible; + +/** + * Static methods pertaining to ASCII characters (those in the range of values {@code 0x00} through + * {@code 0x7F}), and to strings containing such characters. + * + *

ASCII utilities also exist in other classes of this package: + * + *

    + * + *
  • {@link Charsets#US_ASCII} specifies the {@code Charset} of ASCII characters. + *
  • {@link CharMatcher#ascii} matches ASCII characters and provides text processing methods + * which operate only on the ASCII characters of a string. + *
+ * + * @author Catherine Berry + * @author Gregory Kick + * @since 7.0 + */ +@GwtCompatible +public final class Ascii { + + private Ascii() {} + + /* The ASCII control characters, per RFC 20. */ + /** + * Null ('\0'): The all-zeros character which may serve to accomplish time fill and media fill. + * Normally used as a C string terminator. + * + *

Although RFC 20 names this as "Null", note that it is distinct from the C/C++ "NULL" + * pointer. + * + * @since 8.0 + */ + public static final byte NUL = 0; + + /** + * Start of Heading: A communication control character used at the beginning of a sequence of + * characters which constitute a machine-sensible address or routing information. Such a sequence + * is referred to as the "heading." An STX character has the effect of terminating a heading. + * + * @since 8.0 + */ + public static final byte SOH = 1; + + /** + * Start of Text: A communication control character which precedes a sequence of characters that + * is to be treated as an entity and entirely transmitted through to the ultimate destination. + * Such a sequence is referred to as "text." STX may be used to terminate a sequence of characters + * started by SOH. + * + * @since 8.0 + */ + public static final byte STX = 2; + + /** + * End of Text: A communication control character used to terminate a sequence of characters + * started with STX and transmitted as an entity. + * + * @since 8.0 + */ + public static final byte ETX = 3; + + /** + * End of Transmission: A communication control character used to indicate the conclusion of a + * transmission, which may have contained one or more texts and any associated headings. + * + * @since 8.0 + */ + public static final byte EOT = 4; + + /** + * Enquiry: A communication control character used in data communication systems as a request for + * a response from a remote station. It may be used as a "Who Are You" (WRU) to obtain + * identification, or may be used to obtain station status, or both. + * + * @since 8.0 + */ + public static final byte ENQ = 5; + + /** + * Acknowledge: A communication control character transmitted by a receiver as an affirmative + * response to a sender. + * + * @since 8.0 + */ + public static final byte ACK = 6; + + /** + * Bell ('\a'): A character for use when there is a need to call for human attention. It may + * control alarm or attention devices. + * + * @since 8.0 + */ + public static final byte BEL = 7; + + /** + * Backspace ('\b'): A format effector which controls the movement of the printing position one + * printing space backward on the same printing line. (Applicable also to display devices.) + * + * @since 8.0 + */ + public static final byte BS = 8; + + /** + * Horizontal Tabulation ('\t'): A format effector which controls the movement of the printing + * position to the next in a series of predetermined positions along the printing line. + * (Applicable also to display devices and the skip function on punched cards.) + * + * @since 8.0 + */ + public static final byte HT = 9; + + /** + * Line Feed ('\n'): A format effector which controls the movement of the printing position to the + * next printing line. (Applicable also to display devices.) Where appropriate, this character may + * have the meaning "New Line" (NL), a format effector which controls the movement of the printing + * point to the first printing position on the next printing line. Use of this convention requires + * agreement between sender and recipient of data. + * + * @since 8.0 + */ + public static final byte LF = 10; + + /** + * Alternate name for {@link #LF}. ({@code LF} is preferred.) + * + * @since 8.0 + */ + public static final byte NL = 10; + + /** + * Vertical Tabulation ('\v'): A format effector which controls the movement of the printing + * position to the next in a series of predetermined printing lines. (Applicable also to display + * devices.) + * + * @since 8.0 + */ + public static final byte VT = 11; + + /** + * Form Feed ('\f'): A format effector which controls the movement of the printing position to the + * first pre-determined printing line on the next form or page. (Applicable also to display + * devices.) + * + * @since 8.0 + */ + public static final byte FF = 12; + + /** + * Carriage Return ('\r'): A format effector which controls the movement of the printing position + * to the first printing position on the same printing line. (Applicable also to display devices.) + * + * @since 8.0 + */ + public static final byte CR = 13; + + /** + * Shift Out: A control character indicating that the code combinations which follow shall be + * interpreted as outside of the character set of the standard code table until a Shift In + * character is reached. + * + * @since 8.0 + */ + public static final byte SO = 14; + + /** + * Shift In: A control character indicating that the code combinations which follow shall be + * interpreted according to the standard code table. + * + * @since 8.0 + */ + public static final byte SI = 15; + + /** + * Data Link Escape: A communication control character which will change the meaning of a limited + * number of contiguously following characters. It is used exclusively to provide supplementary + * controls in data communication networks. + * + * @since 8.0 + */ + public static final byte DLE = 16; + + /** + * Device Control 1. Characters for the control of ancillary devices associated with data + * processing or telecommunication systems, more especially switching devices "on" or "off." (If a + * single "stop" control is required to interrupt or turn off ancillary devices, DC4 is the + * preferred assignment.) + * + * @since 8.0 + */ + public static final byte DC1 = 17; // aka XON + + /** + * Transmission On: Although originally defined as DC1, this ASCII control character is now better + * known as the XON code used for software flow control in serial communications. The main use is + * restarting the transmission after the communication has been stopped by the XOFF control code. + * + * @since 8.0 + */ + public static final byte XON = 17; // aka DC1 + + /** + * Device Control 2. Characters for the control of ancillary devices associated with data + * processing or telecommunication systems, more especially switching devices "on" or "off." (If a + * single "stop" control is required to interrupt or turn off ancillary devices, DC4 is the + * preferred assignment.) + * + * @since 8.0 + */ + public static final byte DC2 = 18; + + /** + * Device Control 3. Characters for the control of ancillary devices associated with data + * processing or telecommunication systems, more especially switching devices "on" or "off." (If a + * single "stop" control is required to interrupt or turn off ancillary devices, DC4 is the + * preferred assignment.) + * + * @since 8.0 + */ + public static final byte DC3 = 19; // aka XOFF + + /** + * Transmission off. See {@link #XON} for explanation. + * + * @since 8.0 + */ + public static final byte XOFF = 19; // aka DC3 + + /** + * Device Control 4. Characters for the control of ancillary devices associated with data + * processing or telecommunication systems, more especially switching devices "on" or "off." (If a + * single "stop" control is required to interrupt or turn off ancillary devices, DC4 is the + * preferred assignment.) + * + * @since 8.0 + */ + public static final byte DC4 = 20; + + /** + * Negative Acknowledge: A communication control character transmitted by a receiver as a negative + * response to the sender. + * + * @since 8.0 + */ + public static final byte NAK = 21; + + /** + * Synchronous Idle: A communication control character used by a synchronous transmission system + * in the absence of any other character to provide a signal from which synchronism may be + * achieved or retained. + * + * @since 8.0 + */ + public static final byte SYN = 22; + + /** + * End of Transmission Block: A communication control character used to indicate the end of a + * block of data for communication purposes. ETB is used for blocking data where the block + * structure is not necessarily related to the processing format. + * + * @since 8.0 + */ + public static final byte ETB = 23; + + /** + * Cancel: A control character used to indicate that the data with which it is sent is in error or + * is to be disregarded. + * + * @since 8.0 + */ + public static final byte CAN = 24; + + /** + * End of Medium: A control character associated with the sent data which may be used to identify + * the physical end of the medium, or the end of the used, or wanted, portion of information + * recorded on a medium. (The position of this character does not necessarily correspond to the + * physical end of the medium.) + * + * @since 8.0 + */ + public static final byte EM = 25; + + /** + * Substitute: A character that may be substituted for a character which is determined to be + * invalid or in error. + * + * @since 8.0 + */ + public static final byte SUB = 26; + + /** + * Escape: A control character intended to provide code extension (supplementary characters) in + * general information interchange. The Escape character itself is a prefix affecting the + * interpretation of a limited number of contiguously following characters. + * + * @since 8.0 + */ + public static final byte ESC = 27; + + /** + * File Separator: These four information separators may be used within data in optional fashion, + * except that their hierarchical relationship shall be: FS is the most inclusive, then GS, then + * RS, and US is least inclusive. (The content and length of a File, Group, Record, or Unit are + * not specified.) + * + * @since 8.0 + */ + public static final byte FS = 28; + + /** + * Group Separator: These four information separators may be used within data in optional fashion, + * except that their hierarchical relationship shall be: FS is the most inclusive, then GS, then + * RS, and US is least inclusive. (The content and length of a File, Group, Record, or Unit are + * not specified.) + * + * @since 8.0 + */ + public static final byte GS = 29; + + /** + * Record Separator: These four information separators may be used within data in optional + * fashion, except that their hierarchical relationship shall be: FS is the most inclusive, then + * GS, then RS, and US is least inclusive. (The content and length of a File, Group, Record, or + * Unit are not specified.) + * + * @since 8.0 + */ + public static final byte RS = 30; + + /** + * Unit Separator: These four information separators may be used within data in optional fashion, + * except that their hierarchical relationship shall be: FS is the most inclusive, then GS, then + * RS, and US is least inclusive. (The content and length of a File, Group, Record, or Unit are + * not specified.) + * + * @since 8.0 + */ + public static final byte US = 31; + + /** + * Space: A normally non-printing graphic character used to separate words. It is also a format + * effector which controls the movement of the printing position, one printing position forward. + * (Applicable also to display devices.) + * + * @since 8.0 + */ + public static final byte SP = 32; + + /** + * Alternate name for {@link #SP}. + * + * @since 8.0 + */ + public static final byte SPACE = 32; + + /** + * Delete: This character is used primarily to "erase" or "obliterate" erroneous or unwanted + * characters in perforated tape. + * + * @since 8.0 + */ + public static final byte DEL = 127; + + /** + * The minimum value of an ASCII character. + * + * @since 9.0 (was type {@code int} before 12.0) + */ + public static final char MIN = 0; + + /** + * The maximum value of an ASCII character. + * + * @since 9.0 (was type {@code int} before 12.0) + */ + public static final char MAX = 127; + + /** A bit mask which selects the bit encoding ASCII character case. */ + private static final char CASE_MASK = 0x20; + + /** + * Returns a copy of the input string in which all {@linkplain #isUpperCase(char) uppercase ASCII + * characters} have been converted to lowercase. All other characters are copied without + * modification. + */ + public static String toLowerCase(String string) { + int length = string.length(); + for (int i = 0; i < length; i++) { + if (isUpperCase(string.charAt(i))) { + char[] chars = string.toCharArray(); + for (; i < length; i++) { + char c = chars[i]; + if (isUpperCase(c)) { + chars[i] = (char) (c ^ CASE_MASK); + } + } + return String.valueOf(chars); + } + } + return string; + } + + /** + * Returns a copy of the input character sequence in which all {@linkplain #isUpperCase(char) + * uppercase ASCII characters} have been converted to lowercase. All other characters are copied + * without modification. + * + * @since 14.0 + */ + public static String toLowerCase(CharSequence chars) { + if (chars instanceof String) { + return toLowerCase((String) chars); + } + char[] newChars = new char[chars.length()]; + for (int i = 0; i < newChars.length; i++) { + newChars[i] = toLowerCase(chars.charAt(i)); + } + return String.valueOf(newChars); + } + + /** + * If the argument is an {@linkplain #isUpperCase(char) uppercase ASCII character} returns the + * lowercase equivalent. Otherwise returns the argument. + */ + public static char toLowerCase(char c) { + return isUpperCase(c) ? (char) (c ^ CASE_MASK) : c; + } + + /** + * Returns a copy of the input string in which all {@linkplain #isLowerCase(char) lowercase ASCII + * characters} have been converted to uppercase. All other characters are copied without + * modification. + */ + public static String toUpperCase(String string) { + int length = string.length(); + for (int i = 0; i < length; i++) { + if (isLowerCase(string.charAt(i))) { + char[] chars = string.toCharArray(); + for (; i < length; i++) { + char c = chars[i]; + if (isLowerCase(c)) { + chars[i] = (char) (c ^ CASE_MASK); + } + } + return String.valueOf(chars); + } + } + return string; + } + + /** + * Returns a copy of the input character sequence in which all {@linkplain #isLowerCase(char) + * lowercase ASCII characters} have been converted to uppercase. All other characters are copied + * without modification. + * + * @since 14.0 + */ + public static String toUpperCase(CharSequence chars) { + if (chars instanceof String) { + return toUpperCase((String) chars); + } + char[] newChars = new char[chars.length()]; + for (int i = 0; i < newChars.length; i++) { + newChars[i] = toUpperCase(chars.charAt(i)); + } + return String.valueOf(newChars); + } + + /** + * If the argument is a {@linkplain #isLowerCase(char) lowercase ASCII character} returns the + * uppercase equivalent. Otherwise returns the argument. + */ + public static char toUpperCase(char c) { + return isLowerCase(c) ? (char) (c ^ CASE_MASK) : c; + } + + /** + * Indicates whether {@code c} is one of the twenty-six lowercase ASCII alphabetic characters + * between {@code 'a'} and {@code 'z'} inclusive. All others (including non-ASCII characters) + * return {@code false}. + */ + public static boolean isLowerCase(char c) { + // Note: This was benchmarked against the alternate expression "(char)(c - 'a') < 26" (Nov '13) + // and found to perform at least as well, or better. + return (c >= 'a') && (c <= 'z'); + } + + /** + * Indicates whether {@code c} is one of the twenty-six uppercase ASCII alphabetic characters + * between {@code 'A'} and {@code 'Z'} inclusive. All others (including non-ASCII characters) + * return {@code false}. + */ + public static boolean isUpperCase(char c) { + return (c >= 'A') && (c <= 'Z'); + } + + /** + * Truncates the given character sequence to the given maximum length. If the length of the + * sequence is greater than {@code maxLength}, the returned string will be exactly {@code + * maxLength} chars in length and will end with the given {@code truncationIndicator}. Otherwise, + * the sequence will be returned as a string with no changes to the content. + * + *

Examples: + * + *

{@code
+   * Ascii.truncate("foobar", 7, "..."); // returns "foobar"
+   * Ascii.truncate("foobar", 5, "..."); // returns "fo..."
+   * }
+ * + *

Note: This method may work with certain non-ASCII text but is not safe for use + * with arbitrary Unicode text. It is mostly intended for use with text that is known to be safe + * for use with it (such as all-ASCII text) and for simple debugging text. When using this method, + * consider the following: + * + *

    + *
  • it may split surrogate pairs + *
  • it may split characters and combining characters + *
  • it does not consider word boundaries + *
  • if truncating for display to users, there are other considerations that must be taken + * into account + *
  • the appropriate truncation indicator may be locale-dependent + *
  • it is safe to use non-ASCII characters in the truncation indicator + *
+ * + * + * @throws IllegalArgumentException if {@code maxLength} is less than the length of {@code + * truncationIndicator} + * @since 16.0 + */ + public static String truncate(CharSequence seq, int maxLength, String truncationIndicator) { + checkNotNull(seq); + + // length to truncate the sequence to, not including the truncation indicator + int truncationLength = maxLength - truncationIndicator.length(); + + // in this worst case, this allows a maxLength equal to the length of the truncationIndicator, + // meaning that a string will be truncated to just the truncation indicator itself + checkArgument( + truncationLength >= 0, + "maxLength (%s) must be >= length of the truncation indicator (%s)", + maxLength, + truncationIndicator.length()); + + if (seq.length() <= maxLength) { + String string = seq.toString(); + if (string.length() <= maxLength) { + return string; + } + // if the length of the toString() result was > maxLength for some reason, truncate that + seq = string; + } + + return new StringBuilder(maxLength) + .append(seq, 0, truncationLength) + .append(truncationIndicator) + .toString(); + } + + /** + * Indicates whether the contents of the given character sequences {@code s1} and {@code s2} are + * equal, ignoring the case of any ASCII alphabetic characters between {@code 'a'} and {@code 'z'} + * or {@code 'A'} and {@code 'Z'} inclusive. + * + *

This method is significantly faster than {@link String#equalsIgnoreCase} and should be used + * in preference if at least one of the parameters is known to contain only ASCII characters. + * + *

Note however that this method does not always behave identically to expressions such as: + * + *

    + *
  • {@code string.toUpperCase().equals("UPPER CASE ASCII")} + *
  • {@code string.toLowerCase().equals("lower case ascii")} + *
+ * + *

due to case-folding of some non-ASCII characters (which does not occur in {@link + * String#equalsIgnoreCase}). However in almost all cases that ASCII strings are used, the author + * probably wanted the behavior provided by this method rather than the subtle and sometimes + * surprising behavior of {@code toUpperCase()} and {@code toLowerCase()}. + * + * @since 16.0 + */ + public static boolean equalsIgnoreCase(CharSequence s1, CharSequence s2) { + // Calling length() is the null pointer check (so do it before we can exit early). + int length = s1.length(); + if (s1 == s2) { + return true; + } + if (length != s2.length()) { + return false; + } + for (int i = 0; i < length; i++) { + char c1 = s1.charAt(i); + char c2 = s2.charAt(i); + if (c1 == c2) { + continue; + } + int alphaIndex = getAlphaIndex(c1); + // This was also benchmarked using '&' to avoid branching (but always evaluate the rhs), + // however this showed no obvious improvement. + if (alphaIndex < 26 && alphaIndex == getAlphaIndex(c2)) { + continue; + } + return false; + } + return true; + } + + /** + * Returns the non-negative index value of the alpha character {@code c}, regardless of case. Ie, + * 'a'/'A' returns 0 and 'z'/'Z' returns 25. Non-alpha characters return a value of 26 or greater. + */ + private static int getAlphaIndex(char c) { + // Fold upper-case ASCII to lower-case and make zero-indexed and unsigned (by casting to char). + return (char) ((c | CASE_MASK) - 'a'); + } +} diff --git a/src/main/java/com/google/common/base/CaseFormat.java b/src/main/java/com/google/common/base/CaseFormat.java new file mode 100644 index 0000000..4992f5e --- /dev/null +++ b/src/main/java/com/google/common/base/CaseFormat.java @@ -0,0 +1,213 @@ +/* + * Copyright (C) 2006 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.base; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.annotations.GwtCompatible; +import java.io.Serializable; + +/** + * Utility class for converting between various ASCII case formats. Behavior is undefined for + * non-ASCII input. + * + * @author Mike Bostock + * @since 1.0 + */ +@GwtCompatible +public enum CaseFormat { + /** Hyphenated variable naming convention, e.g., "lower-hyphen". */ + LOWER_HYPHEN(CharMatcher.is('-'), "-") { + @Override + String normalizeWord(String word) { + return Ascii.toLowerCase(word); + } + + @Override + String convert(CaseFormat format, String s) { + if (format == LOWER_UNDERSCORE) { + return s.replace('-', '_'); + } + if (format == UPPER_UNDERSCORE) { + return Ascii.toUpperCase(s.replace('-', '_')); + } + return super.convert(format, s); + } + }, + + /** C++ variable naming convention, e.g., "lower_underscore". */ + LOWER_UNDERSCORE(CharMatcher.is('_'), "_") { + @Override + String normalizeWord(String word) { + return Ascii.toLowerCase(word); + } + + @Override + String convert(CaseFormat format, String s) { + if (format == LOWER_HYPHEN) { + return s.replace('_', '-'); + } + if (format == UPPER_UNDERSCORE) { + return Ascii.toUpperCase(s); + } + return super.convert(format, s); + } + }, + + /** Java variable naming convention, e.g., "lowerCamel". */ + LOWER_CAMEL(CharMatcher.inRange('A', 'Z'), "") { + @Override + String normalizeWord(String word) { + return firstCharOnlyToUpper(word); + } + + @Override + String normalizeFirstWord(String word) { + return Ascii.toLowerCase(word); + } + }, + + /** Java and C++ class naming convention, e.g., "UpperCamel". */ + UPPER_CAMEL(CharMatcher.inRange('A', 'Z'), "") { + @Override + String normalizeWord(String word) { + return firstCharOnlyToUpper(word); + } + }, + + /** Java and C++ constant naming convention, e.g., "UPPER_UNDERSCORE". */ + UPPER_UNDERSCORE(CharMatcher.is('_'), "_") { + @Override + String normalizeWord(String word) { + return Ascii.toUpperCase(word); + } + + @Override + String convert(CaseFormat format, String s) { + if (format == LOWER_HYPHEN) { + return Ascii.toLowerCase(s.replace('_', '-')); + } + if (format == LOWER_UNDERSCORE) { + return Ascii.toLowerCase(s); + } + return super.convert(format, s); + } + }; + + private final CharMatcher wordBoundary; + private final String wordSeparator; + + CaseFormat(CharMatcher wordBoundary, String wordSeparator) { + this.wordBoundary = wordBoundary; + this.wordSeparator = wordSeparator; + } + + /** + * Converts the specified {@code String str} from this format to the specified {@code format}. A + * "best effort" approach is taken; if {@code str} does not conform to the assumed format, then + * the behavior of this method is undefined but we make a reasonable effort at converting anyway. + */ + public final String to(CaseFormat format, String str) { + checkNotNull(format); + checkNotNull(str); + return (format == this) ? str : convert(format, str); + } + + /** Enum values can override for performance reasons. */ + String convert(CaseFormat format, String s) { + // deal with camel conversion + StringBuilder out = null; + int i = 0; + int j = -1; + while ((j = wordBoundary.indexIn(s, ++j)) != -1) { + if (i == 0) { + // include some extra space for separators + out = new StringBuilder(s.length() + 4 * wordSeparator.length()); + out.append(format.normalizeFirstWord(s.substring(i, j))); + } else { + out.append(format.normalizeWord(s.substring(i, j))); + } + out.append(format.wordSeparator); + i = j + wordSeparator.length(); + } + return (i == 0) + ? format.normalizeFirstWord(s) + : out.append(format.normalizeWord(s.substring(i))).toString(); + } + + /** + * Returns a {@code Converter} that converts strings from this format to {@code targetFormat}. + * + * @since 16.0 + */ + public Converter converterTo(CaseFormat targetFormat) { + return new StringConverter(this, targetFormat); + } + + private static final class StringConverter extends Converter + implements Serializable { + + private final CaseFormat sourceFormat; + private final CaseFormat targetFormat; + + StringConverter(CaseFormat sourceFormat, CaseFormat targetFormat) { + this.sourceFormat = checkNotNull(sourceFormat); + this.targetFormat = checkNotNull(targetFormat); + } + + @Override + protected String doForward(String s) { + return sourceFormat.to(targetFormat, s); + } + + @Override + protected String doBackward(String s) { + return targetFormat.to(sourceFormat, s); + } + + @Override + public boolean equals(Object object) { + if (object instanceof StringConverter) { + StringConverter that = (StringConverter) object; + return sourceFormat.equals(that.sourceFormat) && targetFormat.equals(that.targetFormat); + } + return false; + } + + @Override + public int hashCode() { + return sourceFormat.hashCode() ^ targetFormat.hashCode(); + } + + @Override + public String toString() { + return sourceFormat + ".converterTo(" + targetFormat + ")"; + } + + private static final long serialVersionUID = 0L; + } + + abstract String normalizeWord(String word); + + String normalizeFirstWord(String word) { + return normalizeWord(word); + } + + private static String firstCharOnlyToUpper(String word) { + return word.isEmpty() + ? word + : Ascii.toUpperCase(word.charAt(0)) + Ascii.toLowerCase(word.substring(1)); + } +} diff --git a/src/main/java/com/google/common/base/CharMatcher.java b/src/main/java/com/google/common/base/CharMatcher.java new file mode 100644 index 0000000..0291086 --- /dev/null +++ b/src/main/java/com/google/common/base/CharMatcher.java @@ -0,0 +1,1818 @@ +/* + * Copyright (C) 2008 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.base; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.base.Preconditions.checkPositionIndex; + +import com.google.common.annotations.GwtCompatible; +import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.VisibleForTesting; +import java.util.Arrays; +import java.util.BitSet; + +/** + * Determines a true or false value for any Java {@code char} value, just as {@link Predicate} does + * for any {@link Object}. Also offers basic text processing methods based on this function. + * Implementations are strongly encouraged to be side-effect-free and immutable. + * + *

Throughout the documentation of this class, the phrase "matching character" is used to mean + * "any {@code char} value {@code c} for which {@code this.matches(c)} returns {@code true}". + * + *

Warning: This class deals only with {@code char} values, that is, BMP characters. It does not understand + * supplementary Unicode code + * points in the range {@code 0x10000} to {@code 0x10FFFF} which includes the majority of + * assigned characters, including important CJK characters and emoji. + * + *

Supplementary characters are encoded + * into a {@code String} using surrogate pairs, and a {@code CharMatcher} treats these just as + * two separate characters. {@link #countIn} counts each supplementary character as 2 {@code char}s. + * + *

For up-to-date Unicode character properties (digit, letter, etc.) and support for + * supplementary code points, use ICU4J UCharacter and UnicodeSet (freeze() after building). For + * basic text processing based on UnicodeSet use the ICU4J UnicodeSetSpanner. + * + *

Example usages: + * + *

+ *   String trimmed = {@link #whitespace() whitespace()}.{@link #trimFrom trimFrom}(userInput);
+ *   if ({@link #ascii() ascii()}.{@link #matchesAllOf matchesAllOf}(s)) { ... }
+ * + *

See the Guava User Guide article on {@code CharMatcher} + * . + * + * @author Kevin Bourrillion + * @since 1.0 + */ +@GwtCompatible(emulated = true) +public abstract class CharMatcher implements Predicate { + /* + * N777777777NO + * N7777777777777N + * M777777777777777N + * $N877777777D77777M + * N M77777777ONND777M + * MN777777777NN D777 + * N7ZN777777777NN ~M7778 + * N777777777777MMNN88777N + * N777777777777MNZZZ7777O + * DZN7777O77777777777777 + * N7OONND7777777D77777N + * 8$M++++?N???$77777$ + * M7++++N+M77777777N + * N77O777777777777$ M + * DNNM$$$$777777N D + * N$N:=N$777N7777M NZ + * 77Z::::N777777777 ODZZZ + * 77N::::::N77777777M NNZZZ$ + * $777:::::::77777777MN ZM8ZZZZZ + * 777M::::::Z7777777Z77 N++ZZZZNN + * 7777M:::::M7777777$777M $++IZZZZM + * M777$:::::N777777$M7777M +++++ZZZDN + * NN$::::::7777$$M777777N N+++ZZZZNZ + * N::::::N:7$O:77777777 N++++ZZZZN + * M::::::::::::N77777777+ +?+++++ZZZM + * 8::::::::::::D77777777M O+++++ZZ + * ::::::::::::M777777777N O+?D + * M:::::::::::M77777777778 77= + * D=::::::::::N7777777777N 777 + * INN===::::::=77777777777N I777N + * ?777N========N7777777777787M N7777 + * 77777$D======N77777777777N777N? N777777 + * I77777$$$N7===M$$77777777$77777777$MMZ77777777N + * $$$$$$$$$$$NIZN$$$$$$$$$M$$7777777777777777ON + * M$$$$$$$$M M$$$$$$$$N=N$$$$7777777$$$ND + * O77Z$$$$$$$ M$$$$$$$$MNI==$DNNNNM=~N + * 7 :N MNN$$$$M$ $$$777$8 8D8I + * NMM.:7O 777777778 + * 7777777MN + * M NO .7: + * M : M + * 8 + */ + + // Constant matcher factory methods + + /** + * Matches any character. + * + * @since 19.0 (since 1.0 as constant {@code ANY}) + */ + public static CharMatcher any() { + return Any.INSTANCE; + } + + /** + * Matches no characters. + * + * @since 19.0 (since 1.0 as constant {@code NONE}) + */ + public static CharMatcher none() { + return None.INSTANCE; + } + + /** + * Determines whether a character is whitespace according to the latest Unicode standard, as + * illustrated here. + * This is not the same definition used by other Java APIs. (See a comparison of several definitions of "whitespace".) + * + *

All Unicode White_Space characters are on the BMP and thus supported by this API. + * + *

Note: as the Unicode definition evolves, we will modify this matcher to keep it up to + * date. + * + * @since 19.0 (since 1.0 as constant {@code WHITESPACE}) + */ + public static CharMatcher whitespace() { + return Whitespace.INSTANCE; + } + + /** + * Determines whether a character is a breaking whitespace (that is, a whitespace which can be + * interpreted as a break between words for formatting purposes). See {@link #whitespace()} for a + * discussion of that term. + * + * @since 19.0 (since 2.0 as constant {@code BREAKING_WHITESPACE}) + */ + public static CharMatcher breakingWhitespace() { + return BreakingWhitespace.INSTANCE; + } + + /** + * Determines whether a character is ASCII, meaning that its code point is less than 128. + * + * @since 19.0 (since 1.0 as constant {@code ASCII}) + */ + public static CharMatcher ascii() { + return Ascii.INSTANCE; + } + + /** + * Determines whether a character is a BMP digit according to Unicode. If + * you only care to match ASCII digits, you can use {@code inRange('0', '9')}. + * + * @deprecated Many digits are supplementary characters; see the class documentation. + * @since 19.0 (since 1.0 as constant {@code DIGIT}) + */ + @Deprecated + public static CharMatcher digit() { + return Digit.INSTANCE; + } + + /** + * Determines whether a character is a BMP digit according to {@linkplain Character#isDigit(char) + * Java's definition}. If you only care to match ASCII digits, you can use {@code inRange('0', + * '9')}. + * + * @deprecated Many digits are supplementary characters; see the class documentation. + * @since 19.0 (since 1.0 as constant {@code JAVA_DIGIT}) + */ + @Deprecated + public static CharMatcher javaDigit() { + return JavaDigit.INSTANCE; + } + + /** + * Determines whether a character is a BMP letter according to {@linkplain + * Character#isLetter(char) Java's definition}. If you only care to match letters of the Latin + * alphabet, you can use {@code inRange('a', 'z').or(inRange('A', 'Z'))}. + * + * @deprecated Most letters are supplementary characters; see the class documentation. + * @since 19.0 (since 1.0 as constant {@code JAVA_LETTER}) + */ + @Deprecated + public static CharMatcher javaLetter() { + return JavaLetter.INSTANCE; + } + + /** + * Determines whether a character is a BMP letter or digit according to {@linkplain + * Character#isLetterOrDigit(char) Java's definition}. + * + * @deprecated Most letters and digits are supplementary characters; see the class documentation. + * @since 19.0 (since 1.0 as constant {@code JAVA_LETTER_OR_DIGIT}). + */ + @Deprecated + public static CharMatcher javaLetterOrDigit() { + return JavaLetterOrDigit.INSTANCE; + } + + /** + * Determines whether a BMP character is upper case according to {@linkplain + * Character#isUpperCase(char) Java's definition}. + * + * @deprecated Some uppercase characters are supplementary characters; see the class + * documentation. + * @since 19.0 (since 1.0 as constant {@code JAVA_UPPER_CASE}) + */ + @Deprecated + public static CharMatcher javaUpperCase() { + return JavaUpperCase.INSTANCE; + } + + /** + * Determines whether a BMP character is lower case according to {@linkplain + * Character#isLowerCase(char) Java's definition}. + * + * @deprecated Some lowercase characters are supplementary characters; see the class + * documentation. + * @since 19.0 (since 1.0 as constant {@code JAVA_LOWER_CASE}) + */ + @Deprecated + public static CharMatcher javaLowerCase() { + return JavaLowerCase.INSTANCE; + } + + /** + * Determines whether a character is an ISO control character as specified by {@link + * Character#isISOControl(char)}. + * + *

All ISO control codes are on the BMP and thus supported by this API. + * + * @since 19.0 (since 1.0 as constant {@code JAVA_ISO_CONTROL}) + */ + public static CharMatcher javaIsoControl() { + return JavaIsoControl.INSTANCE; + } + + /** + * Determines whether a character is invisible; that is, if its Unicode category is any of + * SPACE_SEPARATOR, LINE_SEPARATOR, PARAGRAPH_SEPARATOR, CONTROL, FORMAT, SURROGATE, and + * PRIVATE_USE according to ICU4J. + * + *

See also the Unicode Default_Ignorable_Code_Point property (available via ICU). + * + * @deprecated Most invisible characters are supplementary characters; see the class + * documentation. + * @since 19.0 (since 1.0 as constant {@code INVISIBLE}) + */ + @Deprecated + public static CharMatcher invisible() { + return Invisible.INSTANCE; + } + + /** + * Determines whether a character is single-width (not double-width). When in doubt, this matcher + * errs on the side of returning {@code false} (that is, it tends to assume a character is + * double-width). + * + *

Note: as the reference file evolves, we will modify this matcher to keep it up to + * date. + * + *

See also UAX #11 East Asian Width. + * + * @deprecated Many such characters are supplementary characters; see the class documentation. + * @since 19.0 (since 1.0 as constant {@code SINGLE_WIDTH}) + */ + @Deprecated + public static CharMatcher singleWidth() { + return SingleWidth.INSTANCE; + } + + // Static factories + + /** Returns a {@code char} matcher that matches only one specified BMP character. */ + public static CharMatcher is(final char match) { + return new Is(match); + } + + /** + * Returns a {@code char} matcher that matches any character except the BMP character specified. + * + *

To negate another {@code CharMatcher}, use {@link #negate()}. + */ + public static CharMatcher isNot(final char match) { + return new IsNot(match); + } + + /** + * Returns a {@code char} matcher that matches any BMP character present in the given character + * sequence. Returns a bogus matcher if the sequence contains supplementary characters. + */ + public static CharMatcher anyOf(final CharSequence sequence) { + switch (sequence.length()) { + case 0: + return none(); + case 1: + return is(sequence.charAt(0)); + case 2: + return isEither(sequence.charAt(0), sequence.charAt(1)); + default: + // TODO(lowasser): is it potentially worth just going ahead and building a precomputed + // matcher? + return new AnyOf(sequence); + } + } + + /** + * Returns a {@code char} matcher that matches any BMP character not present in the given + * character sequence. Returns a bogus matcher if the sequence contains supplementary characters. + */ + public static CharMatcher noneOf(CharSequence sequence) { + return anyOf(sequence).negate(); + } + + /** + * Returns a {@code char} matcher that matches any character in a given BMP range (both endpoints + * are inclusive). For example, to match any lowercase letter of the English alphabet, use {@code + * CharMatcher.inRange('a', 'z')}. + * + * @throws IllegalArgumentException if {@code endInclusive < startInclusive} + */ + public static CharMatcher inRange(final char startInclusive, final char endInclusive) { + return new InRange(startInclusive, endInclusive); + } + + /** + * Returns a matcher with identical behavior to the given {@link Character}-based predicate, but + * which operates on primitive {@code char} instances instead. + */ + public static CharMatcher forPredicate(final Predicate predicate) { + return predicate instanceof CharMatcher ? (CharMatcher) predicate : new ForPredicate(predicate); + } + + // Constructors + + /** + * Constructor for use by subclasses. When subclassing, you may want to override {@code + * toString()} to provide a useful description. + */ + protected CharMatcher() {} + + // Abstract methods + + /** Determines a true or false value for the given character. */ + public abstract boolean matches(char c); + + // Non-static factories + + /** Returns a matcher that matches any character not matched by this matcher. */ + // @Override under Java 8 but not under Java 7 + @Override + public CharMatcher negate() { + return new Negated(this); + } + + /** + * Returns a matcher that matches any character matched by both this matcher and {@code other}. + */ + public CharMatcher and(CharMatcher other) { + return new And(this, other); + } + + /** + * Returns a matcher that matches any character matched by either this matcher or {@code other}. + */ + public CharMatcher or(CharMatcher other) { + return new Or(this, other); + } + + /** + * Returns a {@code char} matcher functionally equivalent to this one, but which may be faster to + * query than the original; your mileage may vary. Precomputation takes time and is likely to be + * worthwhile only if the precomputed matcher is queried many thousands of times. + * + *

This method has no effect (returns {@code this}) when called in GWT: it's unclear whether a + * precomputed matcher is faster, but it certainly consumes more memory, which doesn't seem like a + * worthwhile tradeoff in a browser. + */ + public CharMatcher precomputed() { + return Platform.precomputeCharMatcher(this); + } + + private static final int DISTINCT_CHARS = Character.MAX_VALUE - Character.MIN_VALUE + 1; + + /** + * This is the actual implementation of {@link #precomputed}, but we bounce calls through a method + * on {@link Platform} so that we can have different behavior in GWT. + * + *

This implementation tries to be smart in a number of ways. It recognizes cases where the + * negation is cheaper to precompute than the matcher itself; it tries to build small hash tables + * for matchers that only match a few characters, and so on. In the worst-case scenario, it + * constructs an eight-kilobyte bit array and queries that. In many situations this produces a + * matcher which is faster to query than the original. + */ + @GwtIncompatible // SmallCharMatcher + CharMatcher precomputedInternal() { + final BitSet table = new BitSet(); + setBits(table); + int totalCharacters = table.cardinality(); + if (totalCharacters * 2 <= DISTINCT_CHARS) { + return precomputedPositive(totalCharacters, table, toString()); + } else { + // TODO(lowasser): is it worth it to worry about the last character of large matchers? + table.flip(Character.MIN_VALUE, Character.MAX_VALUE + 1); + int negatedCharacters = DISTINCT_CHARS - totalCharacters; + String suffix = ".negate()"; + final String description = toString(); + String negatedDescription = + description.endsWith(suffix) + ? description.substring(0, description.length() - suffix.length()) + : description + suffix; + return new NegatedFastMatcher( + precomputedPositive(negatedCharacters, table, negatedDescription)) { + @Override + public String toString() { + return description; + } + }; + } + } + + /** + * Helper method for {@link #precomputedInternal} that doesn't test if the negation is cheaper. + */ + @GwtIncompatible // SmallCharMatcher + private static CharMatcher precomputedPositive( + int totalCharacters, BitSet table, String description) { + switch (totalCharacters) { + case 0: + return none(); + case 1: + return is((char) table.nextSetBit(0)); + case 2: + char c1 = (char) table.nextSetBit(0); + char c2 = (char) table.nextSetBit(c1 + 1); + return isEither(c1, c2); + default: + return isSmall(totalCharacters, table.length()) + ? SmallCharMatcher.from(table, description) + : new BitSetMatcher(table, description); + } + } + + @GwtIncompatible // SmallCharMatcher + private static boolean isSmall(int totalCharacters, int tableLength) { + return totalCharacters <= SmallCharMatcher.MAX_SIZE + && tableLength > (totalCharacters * 4 * Character.SIZE); + // err on the side of BitSetMatcher + } + + /** Sets bits in {@code table} matched by this matcher. */ + @GwtIncompatible // used only from other GwtIncompatible code + void setBits(BitSet table) { + for (int c = Character.MAX_VALUE; c >= Character.MIN_VALUE; c--) { + if (matches((char) c)) { + table.set(c); + } + } + } + + // Text processing routines + + /** + * Returns {@code true} if a character sequence contains at least one matching BMP character. + * Equivalent to {@code !matchesNoneOf(sequence)}. + * + *

The default implementation iterates over the sequence, invoking {@link #matches} for each + * character, until this returns {@code true} or the end is reached. + * + * @param sequence the character sequence to examine, possibly empty + * @return {@code true} if this matcher matches at least one character in the sequence + * @since 8.0 + */ + public boolean matchesAnyOf(CharSequence sequence) { + return !matchesNoneOf(sequence); + } + + /** + * Returns {@code true} if a character sequence contains only matching BMP characters. + * + *

The default implementation iterates over the sequence, invoking {@link #matches} for each + * character, until this returns {@code false} or the end is reached. + * + * @param sequence the character sequence to examine, possibly empty + * @return {@code true} if this matcher matches every character in the sequence, including when + * the sequence is empty + */ + public boolean matchesAllOf(CharSequence sequence) { + for (int i = sequence.length() - 1; i >= 0; i--) { + if (!matches(sequence.charAt(i))) { + return false; + } + } + return true; + } + + /** + * Returns {@code true} if a character sequence contains no matching BMP characters. Equivalent to + * {@code !matchesAnyOf(sequence)}. + * + *

The default implementation iterates over the sequence, invoking {@link #matches} for each + * character, until this returns {@code true} or the end is reached. + * + * @param sequence the character sequence to examine, possibly empty + * @return {@code true} if this matcher matches no characters in the sequence, including when the + * sequence is empty + */ + public boolean matchesNoneOf(CharSequence sequence) { + return indexIn(sequence) == -1; + } + + /** + * Returns the index of the first matching BMP character in a character sequence, or {@code -1} if + * no matching character is present. + * + *

The default implementation iterates over the sequence in forward order calling {@link + * #matches} for each character. + * + * @param sequence the character sequence to examine from the beginning + * @return an index, or {@code -1} if no character matches + */ + public int indexIn(CharSequence sequence) { + return indexIn(sequence, 0); + } + + /** + * Returns the index of the first matching BMP character in a character sequence, starting from a + * given position, or {@code -1} if no character matches after that position. + * + *

The default implementation iterates over the sequence in forward order, beginning at {@code + * start}, calling {@link #matches} for each character. + * + * @param sequence the character sequence to examine + * @param start the first index to examine; must be nonnegative and no greater than {@code + * sequence.length()} + * @return the index of the first matching character, guaranteed to be no less than {@code start}, + * or {@code -1} if no character matches + * @throws IndexOutOfBoundsException if start is negative or greater than {@code + * sequence.length()} + */ + public int indexIn(CharSequence sequence, int start) { + int length = sequence.length(); + checkPositionIndex(start, length); + for (int i = start; i < length; i++) { + if (matches(sequence.charAt(i))) { + return i; + } + } + return -1; + } + + /** + * Returns the index of the last matching BMP character in a character sequence, or {@code -1} if + * no matching character is present. + * + *

The default implementation iterates over the sequence in reverse order calling {@link + * #matches} for each character. + * + * @param sequence the character sequence to examine from the end + * @return an index, or {@code -1} if no character matches + */ + public int lastIndexIn(CharSequence sequence) { + for (int i = sequence.length() - 1; i >= 0; i--) { + if (matches(sequence.charAt(i))) { + return i; + } + } + return -1; + } + + /** + * Returns the number of matching {@code char}s found in a character sequence. + * + *

Counts 2 per supplementary character, such as for {@link #whitespace}().{@link #negate}(). + */ + public int countIn(CharSequence sequence) { + int count = 0; + for (int i = 0; i < sequence.length(); i++) { + if (matches(sequence.charAt(i))) { + count++; + } + } + return count; + } + + /** + * Returns a string containing all non-matching characters of a character sequence, in order. For + * example: + * + *

{@code
+   * CharMatcher.is('a').removeFrom("bazaar")
+   * }
+ * + * ... returns {@code "bzr"}. + */ + public String removeFrom(CharSequence sequence) { + String string = sequence.toString(); + int pos = indexIn(string); + if (pos == -1) { + return string; + } + + char[] chars = string.toCharArray(); + int spread = 1; + + // This unusual loop comes from extensive benchmarking + OUT: + while (true) { + pos++; + while (true) { + if (pos == chars.length) { + break OUT; + } + if (matches(chars[pos])) { + break; + } + chars[pos - spread] = chars[pos]; + pos++; + } + spread++; + } + return new String(chars, 0, pos - spread); + } + + /** + * Returns a string containing all matching BMP characters of a character sequence, in order. For + * example: + * + *
{@code
+   * CharMatcher.is('a').retainFrom("bazaar")
+   * }
+ * + * ... returns {@code "aaa"}. + */ + public String retainFrom(CharSequence sequence) { + return negate().removeFrom(sequence); + } + + /** + * Returns a string copy of the input character sequence, with each matching BMP character + * replaced by a given replacement character. For example: + * + *
{@code
+   * CharMatcher.is('a').replaceFrom("radar", 'o')
+   * }
+ * + * ... returns {@code "rodor"}. + * + *

The default implementation uses {@link #indexIn(CharSequence)} to find the first matching + * character, then iterates the remainder of the sequence calling {@link #matches(char)} for each + * character. + * + * @param sequence the character sequence to replace matching characters in + * @param replacement the character to append to the result string in place of each matching + * character in {@code sequence} + * @return the new string + */ + public String replaceFrom(CharSequence sequence, char replacement) { + String string = sequence.toString(); + int pos = indexIn(string); + if (pos == -1) { + return string; + } + char[] chars = string.toCharArray(); + chars[pos] = replacement; + for (int i = pos + 1; i < chars.length; i++) { + if (matches(chars[i])) { + chars[i] = replacement; + } + } + return new String(chars); + } + + /** + * Returns a string copy of the input character sequence, with each matching BMP character + * replaced by a given replacement sequence. For example: + * + *

{@code
+   * CharMatcher.is('a').replaceFrom("yaha", "oo")
+   * }
+ * + * ... returns {@code "yoohoo"}. + * + *

Note: If the replacement is a fixed string with only one character, you are better + * off calling {@link #replaceFrom(CharSequence, char)} directly. + * + * @param sequence the character sequence to replace matching characters in + * @param replacement the characters to append to the result string in place of each matching + * character in {@code sequence} + * @return the new string + */ + public String replaceFrom(CharSequence sequence, CharSequence replacement) { + int replacementLen = replacement.length(); + if (replacementLen == 0) { + return removeFrom(sequence); + } + if (replacementLen == 1) { + return replaceFrom(sequence, replacement.charAt(0)); + } + + String string = sequence.toString(); + int pos = indexIn(string); + if (pos == -1) { + return string; + } + + int len = string.length(); + StringBuilder buf = new StringBuilder((len * 3 / 2) + 16); + + int oldpos = 0; + do { + buf.append(string, oldpos, pos); + buf.append(replacement); + oldpos = pos + 1; + pos = indexIn(string, oldpos); + } while (pos != -1); + + buf.append(string, oldpos, len); + return buf.toString(); + } + + /** + * Returns a substring of the input character sequence that omits all matching BMP characters from + * the beginning and from the end of the string. For example: + * + *

{@code
+   * CharMatcher.anyOf("ab").trimFrom("abacatbab")
+   * }
+ * + * ... returns {@code "cat"}. + * + *

Note that: + * + *

{@code
+   * CharMatcher.inRange('\0', ' ').trimFrom(str)
+   * }
+ * + * ... is equivalent to {@link String#trim()}. + */ + public String trimFrom(CharSequence sequence) { + int len = sequence.length(); + int first; + int last; + + for (first = 0; first < len; first++) { + if (!matches(sequence.charAt(first))) { + break; + } + } + for (last = len - 1; last > first; last--) { + if (!matches(sequence.charAt(last))) { + break; + } + } + + return sequence.subSequence(first, last + 1).toString(); + } + + /** + * Returns a substring of the input character sequence that omits all matching BMP characters from + * the beginning of the string. For example: + * + *
{@code
+   * CharMatcher.anyOf("ab").trimLeadingFrom("abacatbab")
+   * }
+ * + * ... returns {@code "catbab"}. + */ + public String trimLeadingFrom(CharSequence sequence) { + int len = sequence.length(); + for (int first = 0; first < len; first++) { + if (!matches(sequence.charAt(first))) { + return sequence.subSequence(first, len).toString(); + } + } + return ""; + } + + /** + * Returns a substring of the input character sequence that omits all matching BMP characters from + * the end of the string. For example: + * + *
{@code
+   * CharMatcher.anyOf("ab").trimTrailingFrom("abacatbab")
+   * }
+ * + * ... returns {@code "abacat"}. + */ + public String trimTrailingFrom(CharSequence sequence) { + int len = sequence.length(); + for (int last = len - 1; last >= 0; last--) { + if (!matches(sequence.charAt(last))) { + return sequence.subSequence(0, last + 1).toString(); + } + } + return ""; + } + + /** + * Returns a string copy of the input character sequence, with each group of consecutive matching + * BMP characters replaced by a single replacement character. For example: + * + *
{@code
+   * CharMatcher.anyOf("eko").collapseFrom("bookkeeper", '-')
+   * }
+ * + * ... returns {@code "b-p-r"}. + * + *

The default implementation uses {@link #indexIn(CharSequence)} to find the first matching + * character, then iterates the remainder of the sequence calling {@link #matches(char)} for each + * character. + * + * @param sequence the character sequence to replace matching groups of characters in + * @param replacement the character to append to the result string in place of each group of + * matching characters in {@code sequence} + * @return the new string + */ + public String collapseFrom(CharSequence sequence, char replacement) { + // This implementation avoids unnecessary allocation. + int len = sequence.length(); + for (int i = 0; i < len; i++) { + char c = sequence.charAt(i); + if (matches(c)) { + if (c == replacement && (i == len - 1 || !matches(sequence.charAt(i + 1)))) { + // a no-op replacement + i++; + } else { + StringBuilder builder = new StringBuilder(len).append(sequence, 0, i).append(replacement); + return finishCollapseFrom(sequence, i + 1, len, replacement, builder, true); + } + } + } + // no replacement needed + return sequence.toString(); + } + + /** + * Collapses groups of matching characters exactly as {@link #collapseFrom} does, except that + * groups of matching BMP characters at the start or end of the sequence are removed without + * replacement. + */ + public String trimAndCollapseFrom(CharSequence sequence, char replacement) { + // This implementation avoids unnecessary allocation. + int len = sequence.length(); + int first = 0; + int last = len - 1; + + while (first < len && matches(sequence.charAt(first))) { + first++; + } + + while (last > first && matches(sequence.charAt(last))) { + last--; + } + + return (first == 0 && last == len - 1) + ? collapseFrom(sequence, replacement) + : finishCollapseFrom( + sequence, first, last + 1, replacement, new StringBuilder(last + 1 - first), false); + } + + private String finishCollapseFrom( + CharSequence sequence, + int start, + int end, + char replacement, + StringBuilder builder, + boolean inMatchingGroup) { + for (int i = start; i < end; i++) { + char c = sequence.charAt(i); + if (matches(c)) { + if (!inMatchingGroup) { + builder.append(replacement); + inMatchingGroup = true; + } + } else { + builder.append(c); + inMatchingGroup = false; + } + } + return builder.toString(); + } + + /** + * @deprecated Provided only to satisfy the {@link Predicate} interface; use {@link #matches} + * instead. + */ + @Deprecated + @Override + public boolean apply(Character character) { + return matches(character); + } + + /** + * Returns a string representation of this {@code CharMatcher}, such as {@code + * CharMatcher.or(WHITESPACE, JAVA_DIGIT)}. + */ + @Override + public String toString() { + return super.toString(); + } + + /** + * Returns the Java Unicode escape sequence for the given {@code char}, in the form "\u12AB" where + * "12AB" is the four hexadecimal digits representing the 16-bit code unit. + */ + private static String showCharacter(char c) { + String hex = "0123456789ABCDEF"; + char[] tmp = {'\\', 'u', '\0', '\0', '\0', '\0'}; + for (int i = 0; i < 4; i++) { + tmp[5 - i] = hex.charAt(c & 0xF); + c = (char) (c >> 4); + } + return String.copyValueOf(tmp); + } + + // Fast matchers + + /** A matcher for which precomputation will not yield any significant benefit. */ + abstract static class FastMatcher extends CharMatcher { + + @Override + public final CharMatcher precomputed() { + return this; + } + + @Override + public CharMatcher negate() { + return new NegatedFastMatcher(this); + } + } + + /** {@link FastMatcher} which overrides {@code toString()} with a custom name. */ + abstract static class NamedFastMatcher extends FastMatcher { + + private final String description; + + NamedFastMatcher(String description) { + this.description = checkNotNull(description); + } + + @Override + public final String toString() { + return description; + } + } + + /** Negation of a {@link FastMatcher}. */ + static class NegatedFastMatcher extends Negated { + + NegatedFastMatcher(CharMatcher original) { + super(original); + } + + @Override + public final CharMatcher precomputed() { + return this; + } + } + + /** Fast matcher using a {@link BitSet} table of matching characters. */ + @GwtIncompatible // used only from other GwtIncompatible code + private static final class BitSetMatcher extends NamedFastMatcher { + + private final BitSet table; + + private BitSetMatcher(BitSet table, String description) { + super(description); + if (table.length() + Long.SIZE < table.size()) { + table = (BitSet) table.clone(); + // If only we could actually call BitSet.trimToSize() ourselves... + } + this.table = table; + } + + @Override + public boolean matches(char c) { + return table.get(c); + } + + @Override + void setBits(BitSet bitSet) { + bitSet.or(table); + } + } + + // Static constant implementation classes + + /** Implementation of {@link #any()}. */ + private static final class Any extends NamedFastMatcher { + + static final Any INSTANCE = new Any(); + + private Any() { + super("CharMatcher.any()"); + } + + @Override + public boolean matches(char c) { + return true; + } + + @Override + public int indexIn(CharSequence sequence) { + return (sequence.length() == 0) ? -1 : 0; + } + + @Override + public int indexIn(CharSequence sequence, int start) { + int length = sequence.length(); + checkPositionIndex(start, length); + return (start == length) ? -1 : start; + } + + @Override + public int lastIndexIn(CharSequence sequence) { + return sequence.length() - 1; + } + + @Override + public boolean matchesAllOf(CharSequence sequence) { + checkNotNull(sequence); + return true; + } + + @Override + public boolean matchesNoneOf(CharSequence sequence) { + return sequence.length() == 0; + } + + @Override + public String removeFrom(CharSequence sequence) { + checkNotNull(sequence); + return ""; + } + + @Override + public String replaceFrom(CharSequence sequence, char replacement) { + char[] array = new char[sequence.length()]; + Arrays.fill(array, replacement); + return new String(array); + } + + @Override + public String replaceFrom(CharSequence sequence, CharSequence replacement) { + StringBuilder result = new StringBuilder(sequence.length() * replacement.length()); + for (int i = 0; i < sequence.length(); i++) { + result.append(replacement); + } + return result.toString(); + } + + @Override + public String collapseFrom(CharSequence sequence, char replacement) { + return (sequence.length() == 0) ? "" : String.valueOf(replacement); + } + + @Override + public String trimFrom(CharSequence sequence) { + checkNotNull(sequence); + return ""; + } + + @Override + public int countIn(CharSequence sequence) { + return sequence.length(); + } + + @Override + public CharMatcher and(CharMatcher other) { + return checkNotNull(other); + } + + @Override + public CharMatcher or(CharMatcher other) { + checkNotNull(other); + return this; + } + + @Override + public CharMatcher negate() { + return none(); + } + } + + /** Implementation of {@link #none()}. */ + private static final class None extends NamedFastMatcher { + + static final None INSTANCE = new None(); + + private None() { + super("CharMatcher.none()"); + } + + @Override + public boolean matches(char c) { + return false; + } + + @Override + public int indexIn(CharSequence sequence) { + checkNotNull(sequence); + return -1; + } + + @Override + public int indexIn(CharSequence sequence, int start) { + int length = sequence.length(); + checkPositionIndex(start, length); + return -1; + } + + @Override + public int lastIndexIn(CharSequence sequence) { + checkNotNull(sequence); + return -1; + } + + @Override + public boolean matchesAllOf(CharSequence sequence) { + return sequence.length() == 0; + } + + @Override + public boolean matchesNoneOf(CharSequence sequence) { + checkNotNull(sequence); + return true; + } + + @Override + public String removeFrom(CharSequence sequence) { + return sequence.toString(); + } + + @Override + public String replaceFrom(CharSequence sequence, char replacement) { + return sequence.toString(); + } + + @Override + public String replaceFrom(CharSequence sequence, CharSequence replacement) { + checkNotNull(replacement); + return sequence.toString(); + } + + @Override + public String collapseFrom(CharSequence sequence, char replacement) { + return sequence.toString(); + } + + @Override + public String trimFrom(CharSequence sequence) { + return sequence.toString(); + } + + @Override + public String trimLeadingFrom(CharSequence sequence) { + return sequence.toString(); + } + + @Override + public String trimTrailingFrom(CharSequence sequence) { + return sequence.toString(); + } + + @Override + public int countIn(CharSequence sequence) { + checkNotNull(sequence); + return 0; + } + + @Override + public CharMatcher and(CharMatcher other) { + checkNotNull(other); + return this; + } + + @Override + public CharMatcher or(CharMatcher other) { + return checkNotNull(other); + } + + @Override + public CharMatcher negate() { + return any(); + } + } + + /** Implementation of {@link #whitespace()}. */ + @VisibleForTesting + static final class Whitespace extends NamedFastMatcher { + + // TABLE is a precomputed hashset of whitespace characters. MULTIPLIER serves as a hash function + // whose key property is that it maps 25 characters into the 32-slot table without collision. + // Basically this is an opportunistic fast implementation as opposed to "good code". For most + // other use-cases, the reduction in readability isn't worth it. + static final String TABLE = + "\u2002\u3000\r\u0085\u200A\u2005\u2000\u3000" + + "\u2029\u000B\u3000\u2008\u2003\u205F\u3000\u1680" + + "\u0009\u0020\u2006\u2001\u202F\u00A0\u000C\u2009" + + "\u3000\u2004\u3000\u3000\u2028\n\u2007\u3000"; + static final int MULTIPLIER = 1682554634; + static final int SHIFT = Integer.numberOfLeadingZeros(TABLE.length() - 1); + + static final Whitespace INSTANCE = new Whitespace(); + + Whitespace() { + super("CharMatcher.whitespace()"); + } + + @Override + public boolean matches(char c) { + return TABLE.charAt((MULTIPLIER * c) >>> SHIFT) == c; + } + + @GwtIncompatible // used only from other GwtIncompatible code + @Override + void setBits(BitSet table) { + for (int i = 0; i < TABLE.length(); i++) { + table.set(TABLE.charAt(i)); + } + } + } + + /** Implementation of {@link #breakingWhitespace()}. */ + private static final class BreakingWhitespace extends CharMatcher { + + static final CharMatcher INSTANCE = new BreakingWhitespace(); + + @Override + public boolean matches(char c) { + switch (c) { + case '\t': + case '\n': + case '\013': + case '\f': + case '\r': + case ' ': + case '\u0085': + case '\u1680': + case '\u2028': + case '\u2029': + case '\u205f': + case '\u3000': + return true; + case '\u2007': + return false; + default: + return c >= '\u2000' && c <= '\u200a'; + } + } + + @Override + public String toString() { + return "CharMatcher.breakingWhitespace()"; + } + } + + /** Implementation of {@link #ascii()}. */ + private static final class Ascii extends NamedFastMatcher { + + static final Ascii INSTANCE = new Ascii(); + + Ascii() { + super("CharMatcher.ascii()"); + } + + @Override + public boolean matches(char c) { + return c <= '\u007f'; + } + } + + /** Implementation that matches characters that fall within multiple ranges. */ + private static class RangesMatcher extends CharMatcher { + + private final String description; + private final char[] rangeStarts; + private final char[] rangeEnds; + + RangesMatcher(String description, char[] rangeStarts, char[] rangeEnds) { + this.description = description; + this.rangeStarts = rangeStarts; + this.rangeEnds = rangeEnds; + checkArgument(rangeStarts.length == rangeEnds.length); + for (int i = 0; i < rangeStarts.length; i++) { + checkArgument(rangeStarts[i] <= rangeEnds[i]); + if (i + 1 < rangeStarts.length) { + checkArgument(rangeEnds[i] < rangeStarts[i + 1]); + } + } + } + + @Override + public boolean matches(char c) { + int index = Arrays.binarySearch(rangeStarts, c); + if (index >= 0) { + return true; + } else { + index = ~index - 1; + return index >= 0 && c <= rangeEnds[index]; + } + } + + @Override + public String toString() { + return description; + } + } + + /** Implementation of {@link #digit()}. */ + private static final class Digit extends RangesMatcher { + // Plug the following UnicodeSet pattern into + // https://unicode.org/cldr/utility/list-unicodeset.jsp + // [[:Nd:]&[:nv=0:]&[\u0000-\uFFFF]] + // and get the zeroes from there. + + // Must be in ascending order. + private static final String ZEROES = + "0\u0660\u06f0\u07c0\u0966\u09e6\u0a66\u0ae6\u0b66\u0be6\u0c66\u0ce6\u0d66\u0de6" + + "\u0e50\u0ed0\u0f20\u1040\u1090\u17e0\u1810\u1946\u19d0\u1a80\u1a90\u1b50\u1bb0" + + "\u1c40\u1c50\ua620\ua8d0\ua900\ua9d0\ua9f0\uaa50\uabf0\uff10"; + + private static char[] zeroes() { + return ZEROES.toCharArray(); + } + + private static char[] nines() { + char[] nines = new char[ZEROES.length()]; + for (int i = 0; i < ZEROES.length(); i++) { + nines[i] = (char) (ZEROES.charAt(i) + 9); + } + return nines; + } + + static final Digit INSTANCE = new Digit(); + + private Digit() { + super("CharMatcher.digit()", zeroes(), nines()); + } + } + + /** Implementation of {@link #javaDigit()}. */ + private static final class JavaDigit extends CharMatcher { + + static final JavaDigit INSTANCE = new JavaDigit(); + + @Override + public boolean matches(char c) { + return Character.isDigit(c); + } + + @Override + public String toString() { + return "CharMatcher.javaDigit()"; + } + } + + /** Implementation of {@link #javaLetter()}. */ + private static final class JavaLetter extends CharMatcher { + + static final JavaLetter INSTANCE = new JavaLetter(); + + @Override + public boolean matches(char c) { + return Character.isLetter(c); + } + + @Override + public String toString() { + return "CharMatcher.javaLetter()"; + } + } + + /** Implementation of {@link #javaLetterOrDigit()}. */ + private static final class JavaLetterOrDigit extends CharMatcher { + + static final JavaLetterOrDigit INSTANCE = new JavaLetterOrDigit(); + + @Override + public boolean matches(char c) { + return Character.isLetterOrDigit(c); + } + + @Override + public String toString() { + return "CharMatcher.javaLetterOrDigit()"; + } + } + + /** Implementation of {@link #javaUpperCase()}. */ + private static final class JavaUpperCase extends CharMatcher { + + static final JavaUpperCase INSTANCE = new JavaUpperCase(); + + @Override + public boolean matches(char c) { + return Character.isUpperCase(c); + } + + @Override + public String toString() { + return "CharMatcher.javaUpperCase()"; + } + } + + /** Implementation of {@link #javaLowerCase()}. */ + private static final class JavaLowerCase extends CharMatcher { + + static final JavaLowerCase INSTANCE = new JavaLowerCase(); + + @Override + public boolean matches(char c) { + return Character.isLowerCase(c); + } + + @Override + public String toString() { + return "CharMatcher.javaLowerCase()"; + } + } + + /** Implementation of {@link #javaIsoControl()}. */ + private static final class JavaIsoControl extends NamedFastMatcher { + + static final JavaIsoControl INSTANCE = new JavaIsoControl(); + + private JavaIsoControl() { + super("CharMatcher.javaIsoControl()"); + } + + @Override + public boolean matches(char c) { + return c <= '\u001f' || (c >= '\u007f' && c <= '\u009f'); + } + } + + /** Implementation of {@link #invisible()}. */ + private static final class Invisible extends RangesMatcher { + // Plug the following UnicodeSet pattern into + // https://unicode.org/cldr/utility/list-unicodeset.jsp + // [[[:Zs:][:Zl:][:Zp:][:Cc:][:Cf:][:Cs:][:Co:]]&[\u0000-\uFFFF]] + // with the "Abbreviate" option, and get the ranges from there. + private static final String RANGE_STARTS = + "\u0000\u007f\u00ad\u0600\u061c\u06dd\u070f\u08e2\u1680\u180e\u2000\u2028\u205f\u2066" + + "\u3000\ud800\ufeff\ufff9"; + private static final String RANGE_ENDS = // inclusive ends + "\u0020\u00a0\u00ad\u0605\u061c\u06dd\u070f\u08e2\u1680\u180e\u200f\u202f\u2064\u206f" + + "\u3000\uf8ff\ufeff\ufffb"; + + static final Invisible INSTANCE = new Invisible(); + + private Invisible() { + super("CharMatcher.invisible()", RANGE_STARTS.toCharArray(), RANGE_ENDS.toCharArray()); + } + } + + /** Implementation of {@link #singleWidth()}. */ + private static final class SingleWidth extends RangesMatcher { + + static final SingleWidth INSTANCE = new SingleWidth(); + + private SingleWidth() { + super( + "CharMatcher.singleWidth()", + "\u0000\u05be\u05d0\u05f3\u0600\u0750\u0e00\u1e00\u2100\ufb50\ufe70\uff61".toCharArray(), + "\u04f9\u05be\u05ea\u05f4\u06ff\u077f\u0e7f\u20af\u213a\ufdff\ufeff\uffdc".toCharArray()); + } + } + + // Non-static factory implementation classes + + /** Implementation of {@link #negate()}. */ + private static class Negated extends CharMatcher { + + final CharMatcher original; + + Negated(CharMatcher original) { + this.original = checkNotNull(original); + } + + @Override + public boolean matches(char c) { + return !original.matches(c); + } + + @Override + public boolean matchesAllOf(CharSequence sequence) { + return original.matchesNoneOf(sequence); + } + + @Override + public boolean matchesNoneOf(CharSequence sequence) { + return original.matchesAllOf(sequence); + } + + @Override + public int countIn(CharSequence sequence) { + return sequence.length() - original.countIn(sequence); + } + + @GwtIncompatible // used only from other GwtIncompatible code + @Override + void setBits(BitSet table) { + BitSet tmp = new BitSet(); + original.setBits(tmp); + tmp.flip(Character.MIN_VALUE, Character.MAX_VALUE + 1); + table.or(tmp); + } + + @Override + public CharMatcher negate() { + return original; + } + + @Override + public String toString() { + return original + ".negate()"; + } + } + + /** Implementation of {@link #and(CharMatcher)}. */ + private static final class And extends CharMatcher { + + final CharMatcher first; + final CharMatcher second; + + And(CharMatcher a, CharMatcher b) { + first = checkNotNull(a); + second = checkNotNull(b); + } + + @Override + public boolean matches(char c) { + return first.matches(c) && second.matches(c); + } + + @GwtIncompatible // used only from other GwtIncompatible code + @Override + void setBits(BitSet table) { + BitSet tmp1 = new BitSet(); + first.setBits(tmp1); + BitSet tmp2 = new BitSet(); + second.setBits(tmp2); + tmp1.and(tmp2); + table.or(tmp1); + } + + @Override + public String toString() { + return "CharMatcher.and(" + first + ", " + second + ")"; + } + } + + /** Implementation of {@link #or(CharMatcher)}. */ + private static final class Or extends CharMatcher { + + final CharMatcher first; + final CharMatcher second; + + Or(CharMatcher a, CharMatcher b) { + first = checkNotNull(a); + second = checkNotNull(b); + } + + @GwtIncompatible // used only from other GwtIncompatible code + @Override + void setBits(BitSet table) { + first.setBits(table); + second.setBits(table); + } + + @Override + public boolean matches(char c) { + return first.matches(c) || second.matches(c); + } + + @Override + public String toString() { + return "CharMatcher.or(" + first + ", " + second + ")"; + } + } + + // Static factory implementations + + /** Implementation of {@link #is(char)}. */ + private static final class Is extends FastMatcher { + + private final char match; + + Is(char match) { + this.match = match; + } + + @Override + public boolean matches(char c) { + return c == match; + } + + @Override + public String replaceFrom(CharSequence sequence, char replacement) { + return sequence.toString().replace(match, replacement); + } + + @Override + public CharMatcher and(CharMatcher other) { + return other.matches(match) ? this : none(); + } + + @Override + public CharMatcher or(CharMatcher other) { + return other.matches(match) ? other : super.or(other); + } + + @Override + public CharMatcher negate() { + return isNot(match); + } + + @GwtIncompatible // used only from other GwtIncompatible code + @Override + void setBits(BitSet table) { + table.set(match); + } + + @Override + public String toString() { + return "CharMatcher.is('" + showCharacter(match) + "')"; + } + } + + /** Implementation of {@link #isNot(char)}. */ + private static final class IsNot extends FastMatcher { + + private final char match; + + IsNot(char match) { + this.match = match; + } + + @Override + public boolean matches(char c) { + return c != match; + } + + @Override + public CharMatcher and(CharMatcher other) { + return other.matches(match) ? super.and(other) : other; + } + + @Override + public CharMatcher or(CharMatcher other) { + return other.matches(match) ? any() : this; + } + + @GwtIncompatible // used only from other GwtIncompatible code + @Override + void setBits(BitSet table) { + table.set(0, match); + table.set(match + 1, Character.MAX_VALUE + 1); + } + + @Override + public CharMatcher negate() { + return is(match); + } + + @Override + public String toString() { + return "CharMatcher.isNot('" + showCharacter(match) + "')"; + } + } + + private static CharMatcher.IsEither isEither(char c1, char c2) { + return new CharMatcher.IsEither(c1, c2); + } + + /** Implementation of {@link #anyOf(CharSequence)} for exactly two characters. */ + private static final class IsEither extends FastMatcher { + + private final char match1; + private final char match2; + + IsEither(char match1, char match2) { + this.match1 = match1; + this.match2 = match2; + } + + @Override + public boolean matches(char c) { + return c == match1 || c == match2; + } + + @GwtIncompatible // used only from other GwtIncompatible code + @Override + void setBits(BitSet table) { + table.set(match1); + table.set(match2); + } + + @Override + public String toString() { + return "CharMatcher.anyOf(\"" + showCharacter(match1) + showCharacter(match2) + "\")"; + } + } + + /** Implementation of {@link #anyOf(CharSequence)} for three or more characters. */ + private static final class AnyOf extends CharMatcher { + + private final char[] chars; + + public AnyOf(CharSequence chars) { + this.chars = chars.toString().toCharArray(); + Arrays.sort(this.chars); + } + + @Override + public boolean matches(char c) { + return Arrays.binarySearch(chars, c) >= 0; + } + + @Override + @GwtIncompatible // used only from other GwtIncompatible code + void setBits(BitSet table) { + for (char c : chars) { + table.set(c); + } + } + + @Override + public String toString() { + StringBuilder description = new StringBuilder("CharMatcher.anyOf(\""); + for (char c : chars) { + description.append(showCharacter(c)); + } + description.append("\")"); + return description.toString(); + } + } + + /** Implementation of {@link #inRange(char, char)}. */ + private static final class InRange extends FastMatcher { + + private final char startInclusive; + private final char endInclusive; + + InRange(char startInclusive, char endInclusive) { + checkArgument(endInclusive >= startInclusive); + this.startInclusive = startInclusive; + this.endInclusive = endInclusive; + } + + @Override + public boolean matches(char c) { + return startInclusive <= c && c <= endInclusive; + } + + @GwtIncompatible // used only from other GwtIncompatible code + @Override + void setBits(BitSet table) { + table.set(startInclusive, endInclusive + 1); + } + + @Override + public String toString() { + return "CharMatcher.inRange('" + + showCharacter(startInclusive) + + "', '" + + showCharacter(endInclusive) + + "')"; + } + } + + /** Implementation of {@link #forPredicate(Predicate)}. */ + private static final class ForPredicate extends CharMatcher { + + private final Predicate predicate; + + ForPredicate(Predicate predicate) { + this.predicate = checkNotNull(predicate); + } + + @Override + public boolean matches(char c) { + return predicate.apply(c); + } + + @SuppressWarnings("deprecation") // intentional; deprecation is for callers primarily + @Override + public boolean apply(Character character) { + return predicate.apply(checkNotNull(character)); + } + + @Override + public String toString() { + return "CharMatcher.forPredicate(" + predicate + ")"; + } + } +} diff --git a/src/main/java/com/google/common/base/Charsets.java b/src/main/java/com/google/common/base/Charsets.java new file mode 100644 index 0000000..2c9563d --- /dev/null +++ b/src/main/java/com/google/common/base/Charsets.java @@ -0,0 +1,102 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.base; + +import com.google.common.annotations.GwtCompatible; +import com.google.common.annotations.GwtIncompatible; +import java.nio.charset.Charset; + +/** + * Contains constant definitions for the six standard {@link Charset} instances, which are + * guaranteed to be supported by all Java platform implementations. + * + *

Assuming you're free to choose, note that {@link #UTF_8} is widely preferred. + * + *

See the Guava User Guide article on {@code Charsets}. + * + * @author Mike Bostock + * @since 1.0 + */ +@GwtCompatible(emulated = true) +public final class Charsets { + private Charsets() {} + + /** + * US-ASCII: seven-bit ASCII, the Basic Latin block of the Unicode character set (ISO646-US). + * + *

Note for Java 7 and later: this constant should be treated as deprecated; use {@link + * java.nio.charset.StandardCharsets#US_ASCII} instead. + * + */ + @GwtIncompatible // Charset not supported by GWT + public static final Charset US_ASCII = Charset.forName("US-ASCII"); + + /** + * ISO-8859-1: ISO Latin Alphabet Number 1 (ISO-LATIN-1). + * + *

Note for Java 7 and later: this constant should be treated as deprecated; use {@link + * java.nio.charset.StandardCharsets#ISO_8859_1} instead. + * + */ + public static final Charset ISO_8859_1 = Charset.forName("ISO-8859-1"); + + /** + * UTF-8: eight-bit UCS Transformation Format. + * + *

Note for Java 7 and later: this constant should be treated as deprecated; use {@link + * java.nio.charset.StandardCharsets#UTF_8} instead. + * + */ + public static final Charset UTF_8 = Charset.forName("UTF-8"); + + /** + * UTF-16BE: sixteen-bit UCS Transformation Format, big-endian byte order. + * + *

Note for Java 7 and later: this constant should be treated as deprecated; use {@link + * java.nio.charset.StandardCharsets#UTF_16BE} instead. + * + */ + @GwtIncompatible // Charset not supported by GWT + public static final Charset UTF_16BE = Charset.forName("UTF-16BE"); + + /** + * UTF-16LE: sixteen-bit UCS Transformation Format, little-endian byte order. + * + *

Note for Java 7 and later: this constant should be treated as deprecated; use {@link + * java.nio.charset.StandardCharsets#UTF_16LE} instead. + * + */ + @GwtIncompatible // Charset not supported by GWT + public static final Charset UTF_16LE = Charset.forName("UTF-16LE"); + + /** + * UTF-16: sixteen-bit UCS Transformation Format, byte order identified by an optional byte-order + * mark. + * + *

Note for Java 7 and later: this constant should be treated as deprecated; use {@link + * java.nio.charset.StandardCharsets#UTF_16} instead. + * + */ + @GwtIncompatible // Charset not supported by GWT + public static final Charset UTF_16 = Charset.forName("UTF-16"); + + /* + * Please do not add new Charset references to this class, unless those character encodings are + * part of the set required to be supported by all Java platform implementations! Any Charsets + * initialized here may cause unexpected delays when this class is loaded. See the Charset + * Javadocs for the list of built-in character encodings. + */ +} diff --git a/src/main/java/com/google/common/base/CommonMatcher.java b/src/main/java/com/google/common/base/CommonMatcher.java new file mode 100644 index 0000000..6d14c6b --- /dev/null +++ b/src/main/java/com/google/common/base/CommonMatcher.java @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2016 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.base; + +import com.google.common.annotations.GwtCompatible; + +/** + * The subset of the {@link java.util.regex.Matcher} API which is used by this package, and also + * shared with the {@code re2j} library. For internal use only. Please refer to the {@code Matcher} + * javadoc for details. + */ +@GwtCompatible +abstract class CommonMatcher { + public abstract boolean matches(); + + public abstract boolean find(); + + public abstract boolean find(int index); + + public abstract String replaceAll(String replacement); + + public abstract int end(); + + public abstract int start(); +} diff --git a/src/main/java/com/google/common/base/CommonPattern.java b/src/main/java/com/google/common/base/CommonPattern.java new file mode 100644 index 0000000..6be5b01 --- /dev/null +++ b/src/main/java/com/google/common/base/CommonPattern.java @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2016 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.base; + +import com.google.common.annotations.GwtCompatible; + +/** + * The subset of the {@link java.util.regex.Pattern} API which is used by this package, and also + * shared with the {@code re2j} library. For internal use only. Please refer to the {@code Pattern} + * javadoc for details. + */ +@GwtCompatible +abstract class CommonPattern { + public abstract CommonMatcher matcher(CharSequence t); + + public abstract String pattern(); + + public abstract int flags(); + + // Re-declare this as abstract to force subclasses to override. + @Override + public abstract String toString(); + + public static CommonPattern compile(String pattern) { + return Platform.compilePattern(pattern); + } + + public static boolean isPcreLike() { + return Platform.patternCompilerIsPcreLike(); + } +} diff --git a/src/main/java/com/google/common/base/Converter.java b/src/main/java/com/google/common/base/Converter.java new file mode 100644 index 0000000..4a1962e --- /dev/null +++ b/src/main/java/com/google/common/base/Converter.java @@ -0,0 +1,506 @@ +/* + * Copyright (C) 2008 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.base; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.annotations.GwtCompatible; +import java.io.Serializable; +import java.util.Iterator; + +/** + * A function from {@code A} to {@code B} with an associated reverse function from {@code B} + * to {@code A}; used for converting back and forth between different representations of the same + * information. + * + *

Invertibility

+ * + *

The reverse operation may be a strict inverse (meaning that {@code + * converter.reverse().convert(converter.convert(a)).equals(a)} is always true). However, it is very + * common (perhaps more common) for round-trip conversion to be lossy. Consider an + * example round-trip using {@link com.google.common.primitives.Doubles#stringConverter}: + * + *

    + *
  1. {@code stringConverter().convert("1.00")} returns the {@code Double} value {@code 1.0} + *
  2. {@code stringConverter().reverse().convert(1.0)} returns the string {@code "1.0"} -- + * not the same string ({@code "1.00"}) we started with + *
+ * + *

Note that it should still be the case that the round-tripped and original objects are + * similar. + * + *

Nullability

+ * + *

A converter always converts {@code null} to {@code null} and non-null references to non-null + * references. It would not make sense to consider {@code null} and a non-null reference to be + * "different representations of the same information", since one is distinguishable from + * missing information and the other is not. The {@link #convert} method handles this null + * behavior for all converters; implementations of {@link #doForward} and {@link #doBackward} are + * guaranteed to never be passed {@code null}, and must never return {@code null}. + * + * + *

Common ways to use

+ * + *

Getting a converter: + * + *

    + *
  • Use a provided converter implementation, such as {@link Enums#stringConverter}, {@link + * com.google.common.primitives.Ints#stringConverter Ints.stringConverter} or the {@linkplain + * #reverse reverse} views of these. + *
  • Convert between specific preset values using {@link + * com.google.common.collect.Maps#asConverter Maps.asConverter}. For example, use this to + * create a "fake" converter for a unit test. It is unnecessary (and confusing) to mock + * the {@code Converter} type using a mocking framework. + *
  • Extend this class and implement its {@link #doForward} and {@link #doBackward} methods. + *
  • Java 8 users: you may prefer to pass two lambda expressions or method references to + * the {@link #from from} factory method. + *
+ * + *

Using a converter: + * + *

    + *
  • Convert one instance in the "forward" direction using {@code converter.convert(a)}. + *
  • Convert multiple instances "forward" using {@code converter.convertAll(as)}. + *
  • Convert in the "backward" direction using {@code converter.reverse().convert(b)} or {@code + * converter.reverse().convertAll(bs)}. + *
  • Use {@code converter} or {@code converter.reverse()} anywhere a {@link + * java.util.function.Function} is accepted (for example {@link java.util.stream.Stream#map + * Stream.map}). + *
  • Do not call {@link #doForward} or {@link #doBackward} directly; these exist only to + * be overridden. + *
+ * + *

Example

+ * + *
+ *   return new Converter<Integer, String>() {
+ *     protected String doForward(Integer i) {
+ *       return Integer.toHexString(i);
+ *     }
+ *
+ *     protected Integer doBackward(String s) {
+ *       return parseUnsignedInt(s, 16);
+ *     }
+ *   };
+ * + *

An alternative using Java 8: + * + *

{@code
+ * return Converter.from(
+ *     Integer::toHexString,
+ *     s -> parseUnsignedInt(s, 16));
+ * }
+ * + * @author Mike Ward + * @author Kurt Alfred Kluever + * @author Gregory Kick + * @since 16.0 + */ +@GwtCompatible +public abstract class Converter implements Function { + private final boolean handleNullAutomatically; + + // We lazily cache the reverse view to avoid allocating on every call to reverse(). + private transient Converter reverse; + + /** Constructor for use by subclasses. */ + protected Converter() { + this(true); + } + + /** Constructor used only by {@code LegacyConverter} to suspend automatic null-handling. */ + Converter(boolean handleNullAutomatically) { + this.handleNullAutomatically = handleNullAutomatically; + } + + // SPI methods (what subclasses must implement) + + /** + * Returns a representation of {@code a} as an instance of type {@code B}. If {@code a} cannot be + * converted, an unchecked exception (such as {@link IllegalArgumentException}) should be thrown. + * + * @param a the instance to convert; will never be null + * @return the converted instance; must not be null + */ + protected abstract B doForward(A a); + + /** + * Returns a representation of {@code b} as an instance of type {@code A}. If {@code b} cannot be + * converted, an unchecked exception (such as {@link IllegalArgumentException}) should be thrown. + * + * @param b the instance to convert; will never be null + * @return the converted instance; must not be null + * @throws UnsupportedOperationException if backward conversion is not implemented; this should be + * very rare. Note that if backward conversion is not only unimplemented but + * unimplementable (for example, consider a {@code Converter}), + * then this is not logically a {@code Converter} at all, and should just implement {@link + * Function}. + */ + protected abstract A doBackward(B b); + + // API (consumer-side) methods + + /** + * Returns a representation of {@code a} as an instance of type {@code B}. + * + * @return the converted value; is null if and only if {@code a} is null + */ + public final B convert(A a) { + return correctedDoForward(a); + } + + B correctedDoForward(A a) { + if (handleNullAutomatically) { + // TODO(kevinb): we shouldn't be checking for a null result at runtime. Assert? + return a == null ? null : checkNotNull(doForward(a)); + } else { + return doForward(a); + } + } + + A correctedDoBackward(B b) { + if (handleNullAutomatically) { + // TODO(kevinb): we shouldn't be checking for a null result at runtime. Assert? + return b == null ? null : checkNotNull(doBackward(b)); + } else { + return doBackward(b); + } + } + + /** + * Returns an iterable that applies {@code convert} to each element of {@code fromIterable}. The + * conversion is done lazily. + * + *

The returned iterable's iterator supports {@code remove()} if the input iterator does. After + * a successful {@code remove()} call, {@code fromIterable} no longer contains the corresponding + * element. + */ + public Iterable convertAll(final Iterable fromIterable) { + checkNotNull(fromIterable, "fromIterable"); + return new Iterable() { + @Override + public Iterator iterator() { + return new Iterator() { + private final Iterator fromIterator = fromIterable.iterator(); + + @Override + public boolean hasNext() { + return fromIterator.hasNext(); + } + + @Override + public B next() { + return convert(fromIterator.next()); + } + + @Override + public void remove() { + fromIterator.remove(); + } + }; + } + }; + } + + /** + * Returns the reversed view of this converter, which converts {@code this.convert(a)} back to a + * value roughly equivalent to {@code a}. + * + *

The returned converter is serializable if {@code this} converter is. + * + *

Note: you should not override this method. It is non-final for legacy reasons. + */ + public Converter reverse() { + Converter result = reverse; + return (result == null) ? reverse = new ReverseConverter<>(this) : result; + } + + private static final class ReverseConverter extends Converter + implements Serializable { + final Converter original; + + ReverseConverter(Converter original) { + this.original = original; + } + + /* + * These gymnastics are a little confusing. Basically this class has neither legacy nor + * non-legacy behavior; it just needs to let the behavior of the backing converter shine + * through. So, we override the correctedDo* methods, after which the do* methods should never + * be reached. + */ + + @Override + protected A doForward(B b) { + throw new AssertionError(); + } + + @Override + protected B doBackward(A a) { + throw new AssertionError(); + } + + @Override + A correctedDoForward(B b) { + return original.correctedDoBackward(b); + } + + @Override + B correctedDoBackward(A a) { + return original.correctedDoForward(a); + } + + @Override + public Converter reverse() { + return original; + } + + @Override + public boolean equals(Object object) { + if (object instanceof ReverseConverter) { + ReverseConverter that = (ReverseConverter) object; + return this.original.equals(that.original); + } + return false; + } + + @Override + public int hashCode() { + return ~original.hashCode(); + } + + @Override + public String toString() { + return original + ".reverse()"; + } + + private static final long serialVersionUID = 0L; + } + + /** + * Returns a converter whose {@code convert} method applies {@code secondConverter} to the result + * of this converter. Its {@code reverse} method applies the converters in reverse order. + * + *

The returned converter is serializable if {@code this} converter and {@code secondConverter} + * are. + */ + public final Converter andThen(Converter secondConverter) { + return doAndThen(secondConverter); + } + + /** Package-private non-final implementation of andThen() so only we can override it. */ + Converter doAndThen(Converter secondConverter) { + return new ConverterComposition<>(this, checkNotNull(secondConverter)); + } + + private static final class ConverterComposition extends Converter + implements Serializable { + final Converter first; + final Converter second; + + ConverterComposition(Converter first, Converter second) { + this.first = first; + this.second = second; + } + + /* + * These gymnastics are a little confusing. Basically this class has neither legacy nor + * non-legacy behavior; it just needs to let the behaviors of the backing converters shine + * through (which might even differ from each other!). So, we override the correctedDo* methods, + * after which the do* methods should never be reached. + */ + + @Override + protected C doForward(A a) { + throw new AssertionError(); + } + + @Override + protected A doBackward(C c) { + throw new AssertionError(); + } + + @Override + C correctedDoForward(A a) { + return second.correctedDoForward(first.correctedDoForward(a)); + } + + @Override + A correctedDoBackward(C c) { + return first.correctedDoBackward(second.correctedDoBackward(c)); + } + + @Override + public boolean equals(Object object) { + if (object instanceof ConverterComposition) { + ConverterComposition that = (ConverterComposition) object; + return this.first.equals(that.first) && this.second.equals(that.second); + } + return false; + } + + @Override + public int hashCode() { + return 31 * first.hashCode() + second.hashCode(); + } + + @Override + public String toString() { + return first + ".andThen(" + second + ")"; + } + + private static final long serialVersionUID = 0L; + } + + /** + * @deprecated Provided to satisfy the {@code Function} interface; use {@link #convert} instead. + */ + @Deprecated + @Override + public final B apply(A a) { + return convert(a); + } + + /** + * Indicates whether another object is equal to this converter. + * + *

Most implementations will have no reason to override the behavior of {@link Object#equals}. + * However, an implementation may also choose to return {@code true} whenever {@code object} is a + * {@link Converter} that it considers interchangeable with this one. "Interchangeable" + * typically means that {@code Objects.equal(this.convert(a), that.convert(a))} is true for + * all {@code a} of type {@code A} (and similarly for {@code reverse}). Note that a {@code false} + * result from this method does not imply that the converters are known not to be + * interchangeable. + */ + @Override + public boolean equals(Object object) { + return super.equals(object); + } + + // Static converters + + /** + * Returns a converter based on separate forward and backward functions. This is useful if the + * function instances already exist, or so that you can supply lambda expressions. If those + * circumstances don't apply, you probably don't need to use this; subclass {@code Converter} and + * implement its {@link #doForward} and {@link #doBackward} methods directly. + * + *

These functions will never be passed {@code null} and must not under any circumstances + * return {@code null}. If a value cannot be converted, the function should throw an unchecked + * exception (typically, but not necessarily, {@link IllegalArgumentException}). + * + *

The returned converter is serializable if both provided functions are. + * + * @since 17.0 + */ + public static Converter from( + Function forwardFunction, + Function backwardFunction) { + return new FunctionBasedConverter<>(forwardFunction, backwardFunction); + } + + private static final class FunctionBasedConverter extends Converter + implements Serializable { + private final Function forwardFunction; + private final Function backwardFunction; + + private FunctionBasedConverter( + Function forwardFunction, + Function backwardFunction) { + this.forwardFunction = checkNotNull(forwardFunction); + this.backwardFunction = checkNotNull(backwardFunction); + } + + @Override + protected B doForward(A a) { + return forwardFunction.apply(a); + } + + @Override + protected A doBackward(B b) { + return backwardFunction.apply(b); + } + + @Override + public boolean equals(Object object) { + if (object instanceof FunctionBasedConverter) { + FunctionBasedConverter that = (FunctionBasedConverter) object; + return this.forwardFunction.equals(that.forwardFunction) + && this.backwardFunction.equals(that.backwardFunction); + } + return false; + } + + @Override + public int hashCode() { + return forwardFunction.hashCode() * 31 + backwardFunction.hashCode(); + } + + @Override + public String toString() { + return "Converter.from(" + forwardFunction + ", " + backwardFunction + ")"; + } + } + + /** Returns a serializable converter that always converts or reverses an object to itself. */ + @SuppressWarnings("unchecked") // implementation is "fully variant" + public static Converter identity() { + return (IdentityConverter) IdentityConverter.INSTANCE; + } + + /** + * A converter that always converts or reverses an object to itself. Note that T is now a + * "pass-through type". + */ + private static final class IdentityConverter extends Converter implements Serializable { + static final IdentityConverter INSTANCE = new IdentityConverter<>(); + + @Override + protected T doForward(T t) { + return t; + } + + @Override + protected T doBackward(T t) { + return t; + } + + @Override + public IdentityConverter reverse() { + return this; + } + + @Override + Converter doAndThen(Converter otherConverter) { + return checkNotNull(otherConverter, "otherConverter"); + } + + /* + * We *could* override convertAll() to return its input, but it's a rather pointless + * optimization and opened up a weird type-safety problem. + */ + + @Override + public String toString() { + return "Converter.identity()"; + } + + private Object readResolve() { + return INSTANCE; + } + + private static final long serialVersionUID = 0L; + } +} diff --git a/src/main/java/com/google/common/base/Defaults.java b/src/main/java/com/google/common/base/Defaults.java new file mode 100644 index 0000000..09b353f --- /dev/null +++ b/src/main/java/com/google/common/base/Defaults.java @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.base; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.annotations.GwtIncompatible; + +/** + * This class provides default values for all Java types, as defined by the JLS. + * + * @author Ben Yu + * @since 1.0 + */ +@GwtIncompatible +public final class Defaults { + private Defaults() {} + + private static final Double DOUBLE_DEFAULT = Double.valueOf(0d); + private static final Float FLOAT_DEFAULT = Float.valueOf(0f); + + /** + * Returns the default value of {@code type} as defined by JLS --- {@code 0} for numbers, {@code + * false} for {@code boolean} and {@code '\0'} for {@code char}. For non-primitive types and + * {@code void}, {@code null} is returned. + */ + @SuppressWarnings("unchecked") + public static T defaultValue(Class type) { + checkNotNull(type); + if (type == boolean.class) { + return (T) Boolean.FALSE; + } else if (type == char.class) { + return (T) Character.valueOf('\0'); + } else if (type == byte.class) { + return (T) Byte.valueOf((byte) 0); + } else if (type == short.class) { + return (T) Short.valueOf((short) 0); + } else if (type == int.class) { + return (T) Integer.valueOf(0); + } else if (type == long.class) { + return (T) Long.valueOf(0L); + } else if (type == float.class) { + return (T) FLOAT_DEFAULT; + } else if (type == double.class) { + return (T) DOUBLE_DEFAULT; + } else { + return null; + } + } +} diff --git a/src/main/java/com/google/common/base/Enums.java b/src/main/java/com/google/common/base/Enums.java new file mode 100644 index 0000000..39b7d86 --- /dev/null +++ b/src/main/java/com/google/common/base/Enums.java @@ -0,0 +1,150 @@ +/* + * Copyright (C) 2011 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.base; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.annotations.GwtCompatible; +import com.google.common.annotations.GwtIncompatible; +import java.io.Serializable; +import java.lang.ref.WeakReference; +import java.lang.reflect.Field; +import java.util.EnumSet; +import java.util.HashMap; +import java.util.Map; +import java.util.WeakHashMap; + +/** + * Utility methods for working with {@link Enum} instances. + * + * @author Steve McKay + * @since 9.0 + */ +@GwtCompatible(emulated = true) +public final class Enums { + + private Enums() {} + + /** + * Returns the {@link Field} in which {@code enumValue} is defined. For example, to get the {@code + * Description} annotation on the {@code GOLF} constant of enum {@code Sport}, use {@code + * Enums.getField(Sport.GOLF).getAnnotation(Description.class)}. + * + * @since 12.0 + */ + @GwtIncompatible // reflection + public static Field getField(Enum enumValue) { + Class clazz = enumValue.getDeclaringClass(); + try { + return clazz.getDeclaredField(enumValue.name()); + } catch (NoSuchFieldException impossible) { + throw new AssertionError(impossible); + } + } + + /** + * Returns an optional enum constant for the given type, using {@link Enum#valueOf}. If the + * constant does not exist, {@link Optional#absent} is returned. A common use case is for parsing + * user input or falling back to a default enum constant. For example, {@code + * Enums.getIfPresent(Country.class, countryInput).or(Country.DEFAULT);} + * + * @since 12.0 + */ + public static > Optional getIfPresent(Class enumClass, String value) { + checkNotNull(enumClass); + checkNotNull(value); + return Platform.getEnumIfPresent(enumClass, value); + } + + @GwtIncompatible // java.lang.ref.WeakReference + private static final Map>, Map>>> + enumConstantCache = new WeakHashMap<>(); + + @GwtIncompatible // java.lang.ref.WeakReference + private static > Map>> populateCache( + Class enumClass) { + Map>> result = new HashMap<>(); + for (T enumInstance : EnumSet.allOf(enumClass)) { + result.put(enumInstance.name(), new WeakReference>(enumInstance)); + } + enumConstantCache.put(enumClass, result); + return result; + } + + @GwtIncompatible // java.lang.ref.WeakReference + static > Map>> getEnumConstants( + Class enumClass) { + synchronized (enumConstantCache) { + Map>> constants = enumConstantCache.get(enumClass); + if (constants == null) { + constants = populateCache(enumClass); + } + return constants; + } + } + + /** + * Returns a converter that converts between strings and {@code enum} values of type {@code + * enumClass} using {@link Enum#valueOf(Class, String)} and {@link Enum#name()}. The converter + * will throw an {@code IllegalArgumentException} if the argument is not the name of any enum + * constant in the specified enum. + * + * @since 16.0 + */ + public static > Converter stringConverter(final Class enumClass) { + return new StringConverter(enumClass); + } + + private static final class StringConverter> extends Converter + implements Serializable { + + private final Class enumClass; + + StringConverter(Class enumClass) { + this.enumClass = checkNotNull(enumClass); + } + + @Override + protected T doForward(String value) { + return Enum.valueOf(enumClass, value); + } + + @Override + protected String doBackward(T enumValue) { + return enumValue.name(); + } + + @Override + public boolean equals(Object object) { + if (object instanceof StringConverter) { + StringConverter that = (StringConverter) object; + return this.enumClass.equals(that.enumClass); + } + return false; + } + + @Override + public int hashCode() { + return enumClass.hashCode(); + } + + @Override + public String toString() { + return "Enums.stringConverter(" + enumClass.getName() + ".class)"; + } + + private static final long serialVersionUID = 0L; + } +} diff --git a/src/main/java/com/google/common/base/Equivalence.java b/src/main/java/com/google/common/base/Equivalence.java new file mode 100644 index 0000000..9cb4368 --- /dev/null +++ b/src/main/java/com/google/common/base/Equivalence.java @@ -0,0 +1,374 @@ +/* + * Copyright (C) 2010 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.base; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.annotations.GwtCompatible; +import java.io.Serializable; +import java.util.function.BiPredicate; + +/** + * A strategy for determining whether two instances are considered equivalent, and for computing + * hash codes in a manner consistent with that equivalence. Two examples of equivalences are the + * {@linkplain #identity() identity equivalence} and the {@linkplain #equals "equals" equivalence}. + * + * @author Bob Lee + * @author Ben Yu + * @author Gregory Kick + * @since 10.0 (mostly + * source-compatible since 4.0) + */ +@GwtCompatible +public abstract class Equivalence implements BiPredicate { + /** Constructor for use by subclasses. */ + protected Equivalence() {} + + /** + * Returns {@code true} if the given objects are considered equivalent. + * + *

This method describes an equivalence relation on object references, meaning that for + * all references {@code x}, {@code y}, and {@code z} (any of which may be null): + * + *

    + *
  • {@code equivalent(x, x)} is true (reflexive property) + *
  • {@code equivalent(x, y)} and {@code equivalent(y, x)} each return the same result + * (symmetric property) + *
  • If {@code equivalent(x, y)} and {@code equivalent(y, z)} are both true, then {@code + * equivalent(x, z)} is also true (transitive property) + *
+ * + *

Note that all calls to {@code equivalent(x, y)} are expected to return the same result as + * long as neither {@code x} nor {@code y} is modified. + */ + public final boolean equivalent(T a, T b) { + if (a == b) { + return true; + } + if (a == null || b == null) { + return false; + } + return doEquivalent(a, b); + } + + /** + * @deprecated Provided only to satisfy the {@link BiPredicate} interface; use {@link #equivalent} + * instead. + * @since 21.0 + */ + @Deprecated + @Override + public final boolean test(T t, T u) { + return equivalent(t, u); + } + + /** + * Implemented by the user to determine whether {@code a} and {@code b} are considered equivalent, + * subject to the requirements specified in {@link #equivalent}. + * + *

This method should not be called except by {@link #equivalent}. When {@link #equivalent} + * calls this method, {@code a} and {@code b} are guaranteed to be distinct, non-null instances. + * + * @since 10.0 (previously, subclasses would override equivalent()) + */ + protected abstract boolean doEquivalent(T a, T b); + + /** + * Returns a hash code for {@code t}. + * + *

The {@code hash} has the following properties: + * + *

    + *
  • It is consistent: for any reference {@code x}, multiple invocations of {@code + * hash(x}} consistently return the same value provided {@code x} remains unchanged + * according to the definition of the equivalence. The hash need not remain consistent from + * one execution of an application to another execution of the same application. + *
  • It is distributable across equivalence: for any references {@code x} and {@code + * y}, if {@code equivalent(x, y)}, then {@code hash(x) == hash(y)}. It is not + * necessary that the hash be distributable across inequivalence. If {@code + * equivalence(x, y)} is false, {@code hash(x) == hash(y)} may still be true. + *
  • {@code hash(null)} is {@code 0}. + *
+ */ + public final int hash(T t) { + if (t == null) { + return 0; + } + return doHash(t); + } + + /** + * Implemented by the user to return a hash code for {@code t}, subject to the requirements + * specified in {@link #hash}. + * + *

This method should not be called except by {@link #hash}. When {@link #hash} calls this + * method, {@code t} is guaranteed to be non-null. + * + * @since 10.0 (previously, subclasses would override hash()) + */ + protected abstract int doHash(T t); + + /** + * Returns a new equivalence relation for {@code F} which evaluates equivalence by first applying + * {@code function} to the argument, then evaluating using {@code this}. That is, for any pair of + * non-null objects {@code x} and {@code y}, {@code equivalence.onResultOf(function).equivalent(a, + * b)} is true if and only if {@code equivalence.equivalent(function.apply(a), function.apply(b))} + * is true. + * + *

For example: + * + *

{@code
+   * Equivalence SAME_AGE = Equivalence.equals().onResultOf(GET_PERSON_AGE);
+   * }
+ * + *

{@code function} will never be invoked with a null value. + * + *

Note that {@code function} must be consistent according to {@code this} equivalence + * relation. That is, invoking {@link Function#apply} multiple times for a given value must return + * equivalent results. For example, {@code + * Equivalence.identity().onResultOf(Functions.toStringFunction())} is broken because it's not + * guaranteed that {@link Object#toString}) always returns the same string instance. + * + * @since 10.0 + */ + public final Equivalence onResultOf(Function function) { + return new FunctionalEquivalence<>(function, this); + } + + /** + * Returns a wrapper of {@code reference} that implements {@link Wrapper#equals(Object) + * Object.equals()} such that {@code wrap(a).equals(wrap(b))} if and only if {@code equivalent(a, + * b)}. + * + * @since 10.0 + */ + public final Wrapper wrap(S reference) { + return new Wrapper(this, reference); + } + + /** + * Wraps an object so that {@link #equals(Object)} and {@link #hashCode()} delegate to an {@link + * Equivalence}. + * + *

For example, given an {@link Equivalence} for {@link String strings} named {@code equiv} + * that tests equivalence using their lengths: + * + *

{@code
+   * equiv.wrap("a").equals(equiv.wrap("b")) // true
+   * equiv.wrap("a").equals(equiv.wrap("hello")) // false
+   * }
+ * + *

Note in particular that an equivalence wrapper is never equal to the object it wraps. + * + *

{@code
+   * equiv.wrap(obj).equals(obj) // always false
+   * }
+ * + * @since 10.0 + */ + public static final class Wrapper implements Serializable { + private final Equivalence equivalence; + private final T reference; + + private Wrapper(Equivalence equivalence, T reference) { + this.equivalence = checkNotNull(equivalence); + this.reference = reference; + } + + /** Returns the (possibly null) reference wrapped by this instance. */ + public T get() { + return reference; + } + + /** + * Returns {@code true} if {@link Equivalence#equivalent(Object, Object)} applied to the wrapped + * references is {@code true} and both wrappers use the {@link Object#equals(Object) same} + * equivalence. + */ + @Override + public boolean equals(Object obj) { + if (obj == this) { + return true; + } + if (obj instanceof Wrapper) { + Wrapper that = (Wrapper) obj; // note: not necessarily a Wrapper + + if (this.equivalence.equals(that.equivalence)) { + /* + * We'll accept that as sufficient "proof" that either equivalence should be able to + * handle either reference, so it's safe to circumvent compile-time type checking. + */ + @SuppressWarnings("unchecked") + Equivalence equivalence = (Equivalence) this.equivalence; + return equivalence.equivalent(this.reference, that.reference); + } + } + return false; + } + + /** Returns the result of {@link Equivalence#hash(Object)} applied to the wrapped reference. */ + @Override + public int hashCode() { + return equivalence.hash(reference); + } + + /** + * Returns a string representation for this equivalence wrapper. The form of this string + * representation is not specified. + */ + @Override + public String toString() { + return equivalence + ".wrap(" + reference + ")"; + } + + private static final long serialVersionUID = 0; + } + + /** + * Returns an equivalence over iterables based on the equivalence of their elements. More + * specifically, two iterables are considered equivalent if they both contain the same number of + * elements, and each pair of corresponding elements is equivalent according to {@code this}. Null + * iterables are equivalent to one another. + * + *

Note that this method performs a similar function for equivalences as {@link + * com.google.common.collect.Ordering#lexicographical} does for orderings. + * + * @since 10.0 + */ + @GwtCompatible(serializable = true) + public final Equivalence> pairwise() { + // Ideally, the returned equivalence would support Iterable. However, + // the need for this is so rare that it's not worth making callers deal with the ugly wildcard. + return new PairwiseEquivalence(this); + } + + /** + * Returns a predicate that evaluates to true if and only if the input is equivalent to {@code + * target} according to this equivalence relation. + * + * @since 10.0 + */ + public final Predicate equivalentTo(T target) { + return new EquivalentToPredicate(this, target); + } + + private static final class EquivalentToPredicate implements Predicate, Serializable { + + private final Equivalence equivalence; + private final T target; + + EquivalentToPredicate(Equivalence equivalence, T target) { + this.equivalence = checkNotNull(equivalence); + this.target = target; + } + + @Override + public boolean apply(T input) { + return equivalence.equivalent(input, target); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj instanceof EquivalentToPredicate) { + EquivalentToPredicate that = (EquivalentToPredicate) obj; + return equivalence.equals(that.equivalence) && Objects.equal(target, that.target); + } + return false; + } + + @Override + public int hashCode() { + return Objects.hashCode(equivalence, target); + } + + @Override + public String toString() { + return equivalence + ".equivalentTo(" + target + ")"; + } + + private static final long serialVersionUID = 0; + } + + /** + * Returns an equivalence that delegates to {@link Object#equals} and {@link Object#hashCode}. + * {@link Equivalence#equivalent} returns {@code true} if both values are null, or if neither + * value is null and {@link Object#equals} returns {@code true}. {@link Equivalence#hash} returns + * {@code 0} if passed a null value. + * + * @since 13.0 + * @since 8.0 (in Equivalences with null-friendly behavior) + * @since 4.0 (in Equivalences) + */ + public static Equivalence equals() { + return Equals.INSTANCE; + } + + /** + * Returns an equivalence that uses {@code ==} to compare values and {@link + * System#identityHashCode(Object)} to compute the hash code. {@link Equivalence#equivalent} + * returns {@code true} if {@code a == b}, including in the case that a and b are both null. + * + * @since 13.0 + * @since 4.0 (in Equivalences) + */ + public static Equivalence identity() { + return Identity.INSTANCE; + } + + static final class Equals extends Equivalence implements Serializable { + + static final Equals INSTANCE = new Equals(); + + @Override + protected boolean doEquivalent(Object a, Object b) { + return a.equals(b); + } + + @Override + protected int doHash(Object o) { + return o.hashCode(); + } + + private Object readResolve() { + return INSTANCE; + } + + private static final long serialVersionUID = 1; + } + + static final class Identity extends Equivalence implements Serializable { + + static final Identity INSTANCE = new Identity(); + + @Override + protected boolean doEquivalent(Object a, Object b) { + return false; + } + + @Override + protected int doHash(Object o) { + return System.identityHashCode(o); + } + + private Object readResolve() { + return INSTANCE; + } + + private static final long serialVersionUID = 1; + } +} diff --git a/src/main/java/com/google/common/base/ExtraObjectsMethodsForWeb.java b/src/main/java/com/google/common/base/ExtraObjectsMethodsForWeb.java new file mode 100644 index 0000000..21cca2c --- /dev/null +++ b/src/main/java/com/google/common/base/ExtraObjectsMethodsForWeb.java @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2016 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.base; + +import com.google.common.annotations.GwtCompatible; + +/** + * Holder for extra methods of {@code Objects} only in web. Intended to be empty for regular + * version. + */ +@GwtCompatible(emulated = true) +abstract class ExtraObjectsMethodsForWeb {} diff --git a/src/main/java/com/google/common/base/FinalizablePhantomReference.java b/src/main/java/com/google/common/base/FinalizablePhantomReference.java new file mode 100644 index 0000000..f920575 --- /dev/null +++ b/src/main/java/com/google/common/base/FinalizablePhantomReference.java @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.base; + +import com.google.common.annotations.GwtIncompatible; +import java.lang.ref.PhantomReference; +import java.lang.ref.ReferenceQueue; + +/** + * Phantom reference with a {@code finalizeReferent()} method which a background thread invokes + * after the garbage collector reclaims the referent. This is a simpler alternative to using a + * {@link ReferenceQueue}. + * + *

Unlike a normal phantom reference, this reference will be cleared automatically. + * + * @author Bob Lee + * @since 2.0 + */ +@GwtIncompatible +public abstract class FinalizablePhantomReference extends PhantomReference + implements FinalizableReference { + /** + * Constructs a new finalizable phantom reference. + * + * @param referent to phantom reference + * @param queue that should finalize the referent + */ + protected FinalizablePhantomReference(T referent, FinalizableReferenceQueue queue) { + super(referent, queue.queue); + queue.cleanUp(); + } +} diff --git a/src/main/java/com/google/common/base/FinalizableReference.java b/src/main/java/com/google/common/base/FinalizableReference.java new file mode 100644 index 0000000..f7e5cf8 --- /dev/null +++ b/src/main/java/com/google/common/base/FinalizableReference.java @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.base; + +import com.google.common.annotations.GwtIncompatible; + +/** + * Implemented by references that have code to run after garbage collection of their referents. + * + * @see FinalizableReferenceQueue + * @author Bob Lee + * @since 2.0 + */ +@GwtIncompatible +public interface FinalizableReference { + /** + * Invoked on a background thread after the referent has been garbage collected unless security + * restrictions prevented starting a background thread, in which case this method is invoked when + * new references are created. + */ + void finalizeReferent(); +} diff --git a/src/main/java/com/google/common/base/FinalizableReferenceQueue.java b/src/main/java/com/google/common/base/FinalizableReferenceQueue.java new file mode 100644 index 0000000..34a6e82 --- /dev/null +++ b/src/main/java/com/google/common/base/FinalizableReferenceQueue.java @@ -0,0 +1,350 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.base; + +import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.VisibleForTesting; +import java.io.Closeable; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.lang.ref.PhantomReference; +import java.lang.ref.Reference; +import java.lang.ref.ReferenceQueue; +import java.lang.reflect.Method; +import java.net.URL; +import java.net.URLClassLoader; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * A reference queue with an associated background thread that dequeues references and invokes + * {@link FinalizableReference#finalizeReferent()} on them. + * + *

Keep a strong reference to this object until all of the associated referents have been + * finalized. If this object is garbage collected earlier, the backing thread will not invoke {@code + * finalizeReferent()} on the remaining references. + * + *

As an example of how this is used, imagine you have a class {@code MyServer} that creates a a + * {@link java.net.ServerSocket ServerSocket}, and you would like to ensure that the {@code + * ServerSocket} is closed even if the {@code MyServer} object is garbage-collected without calling + * its {@code close} method. You could use a finalizer to accomplish this, but that has a + * number of well-known problems. Here is how you might use this class instead: + * + *

{@code
+ * public class MyServer implements Closeable {
+ *   private static final FinalizableReferenceQueue frq = new FinalizableReferenceQueue();
+ *   // You might also share this between several objects.
+ *
+ *   private static final Set> references = Sets.newConcurrentHashSet();
+ *   // This ensures that the FinalizablePhantomReference itself is not garbage-collected.
+ *
+ *   private final ServerSocket serverSocket;
+ *
+ *   private MyServer(...) {
+ *     ...
+ *     this.serverSocket = new ServerSocket(...);
+ *     ...
+ *   }
+ *
+ *   public static MyServer create(...) {
+ *     MyServer myServer = new MyServer(...);
+ *     final ServerSocket serverSocket = myServer.serverSocket;
+ *     Reference reference = new FinalizablePhantomReference(myServer, frq) {
+ *       public void finalizeReferent() {
+ *         references.remove(this):
+ *         if (!serverSocket.isClosed()) {
+ *           ...log a message about how nobody called close()...
+ *           try {
+ *             serverSocket.close();
+ *           } catch (IOException e) {
+ *             ...
+ *           }
+ *         }
+ *       }
+ *     };
+ *     references.add(reference);
+ *     return myServer;
+ *   }
+ *
+ *   public void close() {
+ *     serverSocket.close();
+ *   }
+ * }
+ * }
+ * + * @author Bob Lee + * @since 2.0 + */ +@GwtIncompatible +public class FinalizableReferenceQueue implements Closeable { + /* + * The Finalizer thread keeps a phantom reference to this object. When the client (for example, a + * map built by MapMaker) no longer has a strong reference to this object, the garbage collector + * will reclaim it and enqueue the phantom reference. The enqueued reference will trigger the + * Finalizer to stop. + * + * If this library is loaded in the system class loader, FinalizableReferenceQueue can load + * Finalizer directly with no problems. + * + * If this library is loaded in an application class loader, it's important that Finalizer not + * have a strong reference back to the class loader. Otherwise, you could have a graph like this: + * + * Finalizer Thread runs instance of -> Finalizer.class loaded by -> Application class loader + * which loaded -> ReferenceMap.class which has a static -> FinalizableReferenceQueue instance + * + * Even if no other references to classes from the application class loader remain, the Finalizer + * thread keeps an indirect strong reference to the queue in ReferenceMap, which keeps the + * Finalizer running, and as a result, the application class loader can never be reclaimed. + * + * This means that dynamically loaded web applications and OSGi bundles can't be unloaded. + * + * If the library is loaded in an application class loader, we try to break the cycle by loading + * Finalizer in its own independent class loader: + * + * System class loader -> Application class loader -> ReferenceMap -> FinalizableReferenceQueue -> + * etc. -> Decoupled class loader -> Finalizer + * + * Now, Finalizer no longer keeps an indirect strong reference to the static + * FinalizableReferenceQueue field in ReferenceMap. The application class loader can be reclaimed + * at which point the Finalizer thread will stop and its decoupled class loader can also be + * reclaimed. + * + * If any of this fails along the way, we fall back to loading Finalizer directly in the + * application class loader. + * + * NOTE: The tests for this behavior (FinalizableReferenceQueueClassLoaderUnloadingTest) fail + * strangely when run in JDK 9. We are considering this a known issue. Please see + * https://github.com/google/guava/issues/3086 for more information. + */ + + private static final Logger logger = Logger.getLogger(FinalizableReferenceQueue.class.getName()); + + private static final String FINALIZER_CLASS_NAME = "com.google.common.base.internal.Finalizer"; + + /** Reference to Finalizer.startFinalizer(). */ + private static final Method startFinalizer; + + static { + Class finalizer = + loadFinalizer(new SystemLoader(), new DecoupledLoader(), new DirectLoader()); + startFinalizer = getStartFinalizer(finalizer); + } + + /** The actual reference queue that our background thread will poll. */ + final ReferenceQueue queue; + + final PhantomReference frqRef; + + /** Whether or not the background thread started successfully. */ + final boolean threadStarted; + + /** Constructs a new queue. */ + public FinalizableReferenceQueue() { + // We could start the finalizer lazily, but I'd rather it blow up early. + queue = new ReferenceQueue<>(); + frqRef = new PhantomReference(this, queue); + boolean threadStarted = false; + try { + startFinalizer.invoke(null, FinalizableReference.class, queue, frqRef); + threadStarted = true; + } catch (IllegalAccessException impossible) { + throw new AssertionError(impossible); // startFinalizer() is public + } catch (Throwable t) { + logger.log( + Level.INFO, + "Failed to start reference finalizer thread." + + " Reference cleanup will only occur when new references are created.", + t); + } + + this.threadStarted = threadStarted; + } + + @Override + public void close() { + frqRef.enqueue(); + cleanUp(); + } + + /** + * Repeatedly dequeues references from the queue and invokes {@link + * FinalizableReference#finalizeReferent()} on them until the queue is empty. This method is a + * no-op if the background thread was created successfully. + */ + void cleanUp() { + if (threadStarted) { + return; + } + + Reference reference; + while ((reference = queue.poll()) != null) { + /* + * This is for the benefit of phantom references. Weak and soft references will have already + * been cleared by this point. + */ + reference.clear(); + try { + ((FinalizableReference) reference).finalizeReferent(); + } catch (Throwable t) { + logger.log(Level.SEVERE, "Error cleaning up after reference.", t); + } + } + } + + /** + * Iterates through the given loaders until it finds one that can load Finalizer. + * + * @return Finalizer.class + */ + private static Class loadFinalizer(FinalizerLoader... loaders) { + for (FinalizerLoader loader : loaders) { + Class finalizer = loader.loadFinalizer(); + if (finalizer != null) { + return finalizer; + } + } + + throw new AssertionError(); + } + + /** Loads Finalizer.class. */ + interface FinalizerLoader { + + /** + * Returns Finalizer.class or null if this loader shouldn't or can't load it. + * + * @throws SecurityException if we don't have the appropriate privileges + */ + Class loadFinalizer(); + } + + /** + * Tries to load Finalizer from the system class loader. If Finalizer is in the system class path, + * we needn't create a separate loader. + */ + static class SystemLoader implements FinalizerLoader { + // This is used by the ClassLoader-leak test in FinalizableReferenceQueueTest to disable + // finding Finalizer on the system class path even if it is there. + @VisibleForTesting static boolean disabled; + + @Override + public Class loadFinalizer() { + if (disabled) { + return null; + } + ClassLoader systemLoader; + try { + systemLoader = ClassLoader.getSystemClassLoader(); + } catch (SecurityException e) { + logger.info("Not allowed to access system class loader."); + return null; + } + if (systemLoader != null) { + try { + return systemLoader.loadClass(FINALIZER_CLASS_NAME); + } catch (ClassNotFoundException e) { + // Ignore. Finalizer is simply in a child class loader. + return null; + } + } else { + return null; + } + } + } + + /** + * Try to load Finalizer in its own class loader. If Finalizer's thread had a direct reference to + * our class loader (which could be that of a dynamically loaded web application or OSGi bundle), + * it would prevent our class loader from getting garbage collected. + */ + static class DecoupledLoader implements FinalizerLoader { + private static final String LOADING_ERROR = + "Could not load Finalizer in its own class loader. Loading Finalizer in the current class " + + "loader instead. As a result, you will not be able to garbage collect this class " + + "loader. To support reclaiming this class loader, either resolve the underlying " + + "issue, or move Guava to your system class path."; + + @Override + public Class loadFinalizer() { + try { + /* + * We use URLClassLoader because it's the only concrete class loader implementation in the + * JDK. If we used our own ClassLoader subclass, Finalizer would indirectly reference this + * class loader: + * + * Finalizer.class -> CustomClassLoader -> CustomClassLoader.class -> This class loader + * + * System class loader will (and must) be the parent. + */ + ClassLoader finalizerLoader = newLoader(getBaseUrl()); + return finalizerLoader.loadClass(FINALIZER_CLASS_NAME); + } catch (Exception e) { + logger.log(Level.WARNING, LOADING_ERROR, e); + return null; + } + } + + /** Gets URL for base of path containing Finalizer.class. */ + URL getBaseUrl() throws IOException { + // Find URL pointing to Finalizer.class file. + String finalizerPath = FINALIZER_CLASS_NAME.replace('.', '/') + ".class"; + URL finalizerUrl = getClass().getClassLoader().getResource(finalizerPath); + if (finalizerUrl == null) { + throw new FileNotFoundException(finalizerPath); + } + + // Find URL pointing to base of class path. + String urlString = finalizerUrl.toString(); + if (!urlString.endsWith(finalizerPath)) { + throw new IOException("Unsupported path style: " + urlString); + } + urlString = urlString.substring(0, urlString.length() - finalizerPath.length()); + return new URL(finalizerUrl, urlString); + } + + /** Creates a class loader with the given base URL as its classpath. */ + URLClassLoader newLoader(URL base) { + // We use the bootstrap class loader as the parent because Finalizer by design uses + // only standard Java classes. That also means that FinalizableReferenceQueueTest + // doesn't pick up the wrong version of the Finalizer class. + return new URLClassLoader(new URL[] {base}, null); + } + } + + /** + * Loads Finalizer directly using the current class loader. We won't be able to garbage collect + * this class loader, but at least the world doesn't end. + */ + static class DirectLoader implements FinalizerLoader { + @Override + public Class loadFinalizer() { + try { + return Class.forName(FINALIZER_CLASS_NAME); + } catch (ClassNotFoundException e) { + throw new AssertionError(e); + } + } + } + + /** Looks up Finalizer.startFinalizer(). */ + static Method getStartFinalizer(Class finalizer) { + try { + return finalizer.getMethod( + "startFinalizer", Class.class, ReferenceQueue.class, PhantomReference.class); + } catch (NoSuchMethodException e) { + throw new AssertionError(e); + } + } +} diff --git a/src/main/java/com/google/common/base/FinalizableSoftReference.java b/src/main/java/com/google/common/base/FinalizableSoftReference.java new file mode 100644 index 0000000..45ecc65 --- /dev/null +++ b/src/main/java/com/google/common/base/FinalizableSoftReference.java @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.base; + +import com.google.common.annotations.GwtIncompatible; +import java.lang.ref.ReferenceQueue; +import java.lang.ref.SoftReference; + +/** + * Soft reference with a {@code finalizeReferent()} method which a background thread invokes after + * the garbage collector reclaims the referent. This is a simpler alternative to using a {@link + * ReferenceQueue}. + * + * @author Bob Lee + * @since 2.0 + */ +@GwtIncompatible +public abstract class FinalizableSoftReference extends SoftReference + implements FinalizableReference { + /** + * Constructs a new finalizable soft reference. + * + * @param referent to softly reference + * @param queue that should finalize the referent + */ + protected FinalizableSoftReference(T referent, FinalizableReferenceQueue queue) { + super(referent, queue.queue); + queue.cleanUp(); + } +} diff --git a/src/main/java/com/google/common/base/FinalizableWeakReference.java b/src/main/java/com/google/common/base/FinalizableWeakReference.java new file mode 100644 index 0000000..fb3b09b --- /dev/null +++ b/src/main/java/com/google/common/base/FinalizableWeakReference.java @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.base; + +import com.google.common.annotations.GwtIncompatible; +import java.lang.ref.ReferenceQueue; +import java.lang.ref.WeakReference; + +/** + * Weak reference with a {@code finalizeReferent()} method which a background thread invokes after + * the garbage collector reclaims the referent. This is a simpler alternative to using a {@link + * ReferenceQueue}. + * + * @author Bob Lee + * @since 2.0 + */ +@GwtIncompatible +public abstract class FinalizableWeakReference extends WeakReference + implements FinalizableReference { + /** + * Constructs a new finalizable weak reference. + * + * @param referent to weakly reference + * @param queue that should finalize the referent + */ + protected FinalizableWeakReference(T referent, FinalizableReferenceQueue queue) { + super(referent, queue.queue); + queue.cleanUp(); + } +} diff --git a/src/main/java/com/google/common/base/Function.java b/src/main/java/com/google/common/base/Function.java new file mode 100644 index 0000000..a61399f --- /dev/null +++ b/src/main/java/com/google/common/base/Function.java @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.base; + +import com.google.common.annotations.GwtCompatible; + +/** + * Legacy version of {@link java.util.function.Function java.util.function.Function}. + * + *

The {@link Functions} class provides common functions and related utilities. + * + *

As this interface extends {@code java.util.function.Function}, an instance of this type can be + * used as a {@code java.util.function.Function} directly. To use a {@code + * java.util.function.Function} in a context where a {@code com.google.common.base.Function} is + * needed, use {@code function::apply}. + * + *

This interface is now a legacy type. Use {@code java.util.function.Function} (or the + * appropriate primitive specialization such as {@code ToIntFunction}) instead whenever possible. + * Otherwise, at least reduce explicit dependencies on this type by using lambda expressions + * or method references instead of classes, leaving your code easier to migrate in the future. + * + *

See the Guava User Guide article on the use of {@code Function}. + * + * @author Kevin Bourrillion + * @since 2.0 + */ +@GwtCompatible +@FunctionalInterface +public interface Function extends java.util.function.Function { + @Override + T apply(F input); + + /** + * May return {@code true} if {@code object} is a {@code Function} that behaves identically + * to this function. + * + *

Warning: do not depend on the behavior of this method. + * + *

Historically, {@code Function} instances in this library have implemented this method to + * recognize certain cases where distinct {@code Function} instances would in fact behave + * identically. However, as code migrates to {@code java.util.function}, that behavior will + * disappear. It is best not to depend on it. + */ + @Override + boolean equals(Object object); +} diff --git a/src/main/java/com/google/common/base/FunctionalEquivalence.java b/src/main/java/com/google/common/base/FunctionalEquivalence.java new file mode 100644 index 0000000..e550a5b --- /dev/null +++ b/src/main/java/com/google/common/base/FunctionalEquivalence.java @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2011 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.base; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.annotations.Beta; +import com.google.common.annotations.GwtCompatible; +import java.io.Serializable; + +/** + * Equivalence applied on functional result. + * + * @author Bob Lee + * @since 10.0 + */ +@Beta +@GwtCompatible +final class FunctionalEquivalence extends Equivalence implements Serializable { + + private static final long serialVersionUID = 0; + + private final Function function; + private final Equivalence resultEquivalence; + + FunctionalEquivalence(Function function, Equivalence resultEquivalence) { + this.function = checkNotNull(function); + this.resultEquivalence = checkNotNull(resultEquivalence); + } + + @Override + protected boolean doEquivalent(F a, F b) { + return resultEquivalence.equivalent(function.apply(a), function.apply(b)); + } + + @Override + protected int doHash(F a) { + return resultEquivalence.hash(function.apply(a)); + } + + @Override + public boolean equals(Object obj) { + if (obj == this) { + return true; + } + if (obj instanceof FunctionalEquivalence) { + FunctionalEquivalence that = (FunctionalEquivalence) obj; + return function.equals(that.function) && resultEquivalence.equals(that.resultEquivalence); + } + return false; + } + + @Override + public int hashCode() { + return Objects.hashCode(function, resultEquivalence); + } + + @Override + public String toString() { + return resultEquivalence + ".onResultOf(" + function + ")"; + } +} diff --git a/src/main/java/com/google/common/base/Functions.java b/src/main/java/com/google/common/base/Functions.java new file mode 100644 index 0000000..d2ae6e7 --- /dev/null +++ b/src/main/java/com/google/common/base/Functions.java @@ -0,0 +1,400 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.base; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.annotations.GwtCompatible; +import java.io.Serializable; +import java.util.Map; + +/** + * Static utility methods pertaining to {@code com.google.common.base.Function} instances; see that + * class for information about migrating to {@code java.util.function}. + * + *

All methods return serializable functions as long as they're given serializable parameters. + * + *

See the Guava User Guide article on the use of {@code Function}. + * + * @author Mike Bostock + * @author Jared Levy + * @since 2.0 + */ +@GwtCompatible +public final class Functions { + private Functions() {} + + /** + * A function equivalent to the method reference {@code Object::toString}, for users not yet using + * Java 8. The function simply invokes {@code toString} on its argument and returns the result. It + * throws a {@link NullPointerException} on null input. + * + *

Warning: The returned function may not be consistent with equals (as + * documented at {@link Function#apply}). For example, this function yields different results for + * the two equal instances {@code ImmutableSet.of(1, 2)} and {@code ImmutableSet.of(2, 1)}. + * + *

Warning: as with all function types in this package, avoid depending on the specific + * {@code equals}, {@code hashCode} or {@code toString} behavior of the returned function. A + * future migration to {@code java.util.function} will not preserve this behavior. + * + *

For Java 8 users: use the method reference {@code Object::toString} instead. In the + * future, when this class requires Java 8, this method will be deprecated. See {@link Function} + * for more important information about the Java 8 transition. + */ + public static Function toStringFunction() { + return ToStringFunction.INSTANCE; + } + + // enum singleton pattern + private enum ToStringFunction implements Function { + INSTANCE; + + @Override + public String apply(Object o) { + checkNotNull(o); // eager for GWT. + return o.toString(); + } + + @Override + public String toString() { + return "Functions.toStringFunction()"; + } + } + + /** Returns the identity function. */ + // implementation is "fully variant"; E has become a "pass-through" type + @SuppressWarnings("unchecked") + public static Function identity() { + return (Function) IdentityFunction.INSTANCE; + } + + // enum singleton pattern + private enum IdentityFunction implements Function { + INSTANCE; + + @Override + public Object apply(Object o) { + return o; + } + + @Override + public String toString() { + return "Functions.identity()"; + } + } + + /** + * Returns a function which performs a map lookup. The returned function throws an {@link + * IllegalArgumentException} if given a key that does not exist in the map. See also {@link + * #forMap(Map, Object)}, which returns a default value in this case. + * + *

Note: if {@code map} is a {@link com.google.common.collect.BiMap BiMap} (or can be one), you + * can use {@link com.google.common.collect.Maps#asConverter Maps.asConverter} instead to get a + * function that also supports reverse conversion. + * + *

Java 8 users: if you are okay with {@code null} being returned for an unrecognized + * key (instead of an exception being thrown), you can use the method reference {@code map::get} + * instead. + */ + public static Function forMap(Map map) { + return new FunctionForMapNoDefault<>(map); + } + + /** + * Returns a function which performs a map lookup with a default value. The function created by + * this method returns {@code defaultValue} for all inputs that do not belong to the map's key + * set. See also {@link #forMap(Map)}, which throws an exception in this case. + * + *

Java 8 users: you can just write the lambda expression {@code k -> + * map.getOrDefault(k, defaultValue)} instead. + * + * @param map source map that determines the function behavior + * @param defaultValue the value to return for inputs that aren't map keys + * @return function that returns {@code map.get(a)} when {@code a} is a key, or {@code + * defaultValue} otherwise + */ + public static Function forMap(Map map, V defaultValue) { + return new ForMapWithDefault<>(map, defaultValue); + } + + private static class FunctionForMapNoDefault implements Function, Serializable { + final Map map; + + FunctionForMapNoDefault(Map map) { + this.map = checkNotNull(map); + } + + @Override + public V apply(K key) { + V result = map.get(key); + checkArgument(result != null || map.containsKey(key), "Key '%s' not present in map", key); + return result; + } + + @Override + public boolean equals(Object o) { + if (o instanceof FunctionForMapNoDefault) { + FunctionForMapNoDefault that = (FunctionForMapNoDefault) o; + return map.equals(that.map); + } + return false; + } + + @Override + public int hashCode() { + return map.hashCode(); + } + + @Override + public String toString() { + return "Functions.forMap(" + map + ")"; + } + + private static final long serialVersionUID = 0; + } + + private static class ForMapWithDefault implements Function, Serializable { + final Map map; + final V defaultValue; + + ForMapWithDefault(Map map, V defaultValue) { + this.map = checkNotNull(map); + this.defaultValue = defaultValue; + } + + @Override + public V apply(K key) { + V result = map.get(key); + return (result != null || map.containsKey(key)) ? result : defaultValue; + } + + @Override + public boolean equals(Object o) { + if (o instanceof ForMapWithDefault) { + ForMapWithDefault that = (ForMapWithDefault) o; + return map.equals(that.map) && Objects.equal(defaultValue, that.defaultValue); + } + return false; + } + + @Override + public int hashCode() { + return Objects.hashCode(map, defaultValue); + } + + @Override + public String toString() { + // TODO(cpovirk): maybe remove "defaultValue=" to make this look like the method call does + return "Functions.forMap(" + map + ", defaultValue=" + defaultValue + ")"; + } + + private static final long serialVersionUID = 0; + } + + /** + * Returns the composition of two functions. For {@code f: A->B} and {@code g: B->C}, composition + * is defined as the function h such that {@code h(a) == g(f(a))} for each {@code a}. + * + *

Java 8 users: use {@code g.compose(f)} or (probably clearer) {@code f.andThen(g)} + * instead. + * + * @param g the second function to apply + * @param f the first function to apply + * @return the composition of {@code f} and {@code g} + * @see function composition + */ + public static Function compose(Function g, Function f) { + return new FunctionComposition<>(g, f); + } + + private static class FunctionComposition implements Function, Serializable { + private final Function g; + private final Function f; + + public FunctionComposition(Function g, Function f) { + this.g = checkNotNull(g); + this.f = checkNotNull(f); + } + + @Override + public C apply(A a) { + return g.apply(f.apply(a)); + } + + @Override + public boolean equals(Object obj) { + if (obj instanceof FunctionComposition) { + FunctionComposition that = (FunctionComposition) obj; + return f.equals(that.f) && g.equals(that.g); + } + return false; + } + + @Override + public int hashCode() { + return f.hashCode() ^ g.hashCode(); + } + + @Override + public String toString() { + // TODO(cpovirk): maybe make this look like the method call does ("Functions.compose(...)") + return g + "(" + f + ")"; + } + + private static final long serialVersionUID = 0; + } + + /** + * Creates a function that returns the same boolean output as the given predicate for all inputs. + * + *

The returned function is consistent with equals (as documented at {@link + * Function#apply}) if and only if {@code predicate} is itself consistent with equals. + * + *

Java 8 users: use the method reference {@code predicate::test} instead. + */ + public static Function forPredicate(Predicate predicate) { + return new PredicateFunction(predicate); + } + + /** @see Functions#forPredicate */ + private static class PredicateFunction implements Function, Serializable { + private final Predicate predicate; + + private PredicateFunction(Predicate predicate) { + this.predicate = checkNotNull(predicate); + } + + @Override + public Boolean apply(T t) { + return predicate.apply(t); + } + + @Override + public boolean equals(Object obj) { + if (obj instanceof PredicateFunction) { + PredicateFunction that = (PredicateFunction) obj; + return predicate.equals(that.predicate); + } + return false; + } + + @Override + public int hashCode() { + return predicate.hashCode(); + } + + @Override + public String toString() { + return "Functions.forPredicate(" + predicate + ")"; + } + + private static final long serialVersionUID = 0; + } + + /** + * Returns a function that ignores its input and always returns {@code value}. + * + *

Java 8 users: use the lambda expression {@code o -> value} instead. + * + * @param value the constant value for the function to return + * @return a function that always returns {@code value} + */ + public static Function constant(E value) { + return new ConstantFunction(value); + } + + private static class ConstantFunction implements Function, Serializable { + private final E value; + + public ConstantFunction(E value) { + this.value = value; + } + + @Override + public E apply(Object from) { + return value; + } + + @Override + public boolean equals(Object obj) { + if (obj instanceof ConstantFunction) { + ConstantFunction that = (ConstantFunction) obj; + return Objects.equal(value, that.value); + } + return false; + } + + @Override + public int hashCode() { + return (value == null) ? 0 : value.hashCode(); + } + + @Override + public String toString() { + return "Functions.constant(" + value + ")"; + } + + private static final long serialVersionUID = 0; + } + + /** + * Returns a function that ignores its input and returns the result of {@code supplier.get()}. + * + *

Java 8 users: use the lambda expression {@code o -> supplier.get()} instead. + * + * @since 10.0 + */ + public static Function forSupplier(Supplier supplier) { + return new SupplierFunction(supplier); + } + + /** @see Functions#forSupplier */ + private static class SupplierFunction implements Function, Serializable { + + private final Supplier supplier; + + private SupplierFunction(Supplier supplier) { + this.supplier = checkNotNull(supplier); + } + + @Override + public T apply(Object input) { + return supplier.get(); + } + + @Override + public boolean equals(Object obj) { + if (obj instanceof SupplierFunction) { + SupplierFunction that = (SupplierFunction) obj; + return this.supplier.equals(that.supplier); + } + return false; + } + + @Override + public int hashCode() { + return supplier.hashCode(); + } + + @Override + public String toString() { + return "Functions.forSupplier(" + supplier + ")"; + } + + private static final long serialVersionUID = 0; + } +} diff --git a/src/main/java/com/google/common/base/JdkPattern.java b/src/main/java/com/google/common/base/JdkPattern.java new file mode 100644 index 0000000..f7791db --- /dev/null +++ b/src/main/java/com/google/common/base/JdkPattern.java @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2016 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.base; + +import com.google.common.annotations.GwtIncompatible; +import java.io.Serializable; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** A regex pattern implementation which is backed by the {@link Pattern}. */ +@GwtIncompatible +final class JdkPattern extends CommonPattern implements Serializable { + private final Pattern pattern; + + JdkPattern(Pattern pattern) { + this.pattern = Preconditions.checkNotNull(pattern); + } + + @Override + public CommonMatcher matcher(CharSequence t) { + return new JdkMatcher(pattern.matcher(t)); + } + + @Override + public String pattern() { + return pattern.pattern(); + } + + @Override + public int flags() { + return pattern.flags(); + } + + @Override + public String toString() { + return pattern.toString(); + } + + private static final class JdkMatcher extends CommonMatcher { + final Matcher matcher; + + JdkMatcher(Matcher matcher) { + this.matcher = Preconditions.checkNotNull(matcher); + } + + @Override + public boolean matches() { + return matcher.matches(); + } + + @Override + public boolean find() { + return matcher.find(); + } + + @Override + public boolean find(int index) { + return matcher.find(index); + } + + @Override + public String replaceAll(String replacement) { + return matcher.replaceAll(replacement); + } + + @Override + public int end() { + return matcher.end(); + } + + @Override + public int start() { + return matcher.start(); + } + } + + private static final long serialVersionUID = 0; +} diff --git a/src/main/java/com/google/common/base/Joiner.java b/src/main/java/com/google/common/base/Joiner.java new file mode 100644 index 0000000..601a6a3 --- /dev/null +++ b/src/main/java/com/google/common/base/Joiner.java @@ -0,0 +1,462 @@ +/* + * Copyright (C) 2008 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.base; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.annotations.Beta; +import com.google.common.annotations.GwtCompatible; +import java.io.IOException; +import java.util.AbstractList; +import java.util.Arrays; +import java.util.Iterator; +import java.util.Map; +import java.util.Map.Entry; + +/** + * An object which joins pieces of text (specified as an array, {@link Iterable}, varargs or even a + * {@link Map}) with a separator. It either appends the results to an {@link Appendable} or returns + * them as a {@link String}. Example: + * + *

{@code
+ * Joiner joiner = Joiner.on("; ").skipNulls();
+ *  . . .
+ * return joiner.join("Harry", null, "Ron", "Hermione");
+ * }
+ * + *

This returns the string {@code "Harry; Ron; Hermione"}. Note that all input elements are + * converted to strings using {@link Object#toString()} before being appended. + * + *

If neither {@link #skipNulls()} nor {@link #useForNull(String)} is specified, the joining + * methods will throw {@link NullPointerException} if any given element is null. + * + *

Warning: joiner instances are always immutable; a configuration method such as {@code + * useForNull} has no effect on the instance it is invoked on! You must store and use the new joiner + * instance returned by the method. This makes joiners thread-safe, and safe to store as {@code + * static final} constants. + * + *

{@code
+ * // Bad! Do not do this!
+ * Joiner joiner = Joiner.on(',');
+ * joiner.skipNulls(); // does nothing!
+ * return joiner.join("wrong", null, "wrong");
+ * }
+ * + *

See the Guava User Guide article on {@code Joiner}. + * + * @author Kevin Bourrillion + * @since 2.0 + */ +@GwtCompatible +public class Joiner { + /** Returns a joiner which automatically places {@code separator} between consecutive elements. */ + public static Joiner on(String separator) { + return new Joiner(separator); + } + + /** Returns a joiner which automatically places {@code separator} between consecutive elements. */ + public static Joiner on(char separator) { + return new Joiner(String.valueOf(separator)); + } + + private final String separator; + + private Joiner(String separator) { + this.separator = checkNotNull(separator); + } + + private Joiner(Joiner prototype) { + this.separator = prototype.separator; + } + + /** + * Appends the string representation of each of {@code parts}, using the previously configured + * separator between each, to {@code appendable}. + */ + public A appendTo(A appendable, Iterable parts) throws IOException { + return appendTo(appendable, parts.iterator()); + } + + /** + * Appends the string representation of each of {@code parts}, using the previously configured + * separator between each, to {@code appendable}. + * + * @since 11.0 + */ + public A appendTo(A appendable, Iterator parts) throws IOException { + checkNotNull(appendable); + if (parts.hasNext()) { + appendable.append(toString(parts.next())); + while (parts.hasNext()) { + appendable.append(separator); + appendable.append(toString(parts.next())); + } + } + return appendable; + } + + /** + * Appends the string representation of each of {@code parts}, using the previously configured + * separator between each, to {@code appendable}. + */ + public final A appendTo(A appendable, Object[] parts) throws IOException { + return appendTo(appendable, Arrays.asList(parts)); + } + + /** Appends to {@code appendable} the string representation of each of the remaining arguments. */ + public final A appendTo( + A appendable, Object first, Object second, Object... rest) + throws IOException { + return appendTo(appendable, iterable(first, second, rest)); + } + + /** + * Appends the string representation of each of {@code parts}, using the previously configured + * separator between each, to {@code builder}. Identical to {@link #appendTo(Appendable, + * Iterable)}, except that it does not throw {@link IOException}. + */ + public final StringBuilder appendTo(StringBuilder builder, Iterable parts) { + return appendTo(builder, parts.iterator()); + } + + /** + * Appends the string representation of each of {@code parts}, using the previously configured + * separator between each, to {@code builder}. Identical to {@link #appendTo(Appendable, + * Iterable)}, except that it does not throw {@link IOException}. + * + * @since 11.0 + */ + public final StringBuilder appendTo(StringBuilder builder, Iterator parts) { + try { + appendTo((Appendable) builder, parts); + } catch (IOException impossible) { + throw new AssertionError(impossible); + } + return builder; + } + + /** + * Appends the string representation of each of {@code parts}, using the previously configured + * separator between each, to {@code builder}. Identical to {@link #appendTo(Appendable, + * Iterable)}, except that it does not throw {@link IOException}. + */ + public final StringBuilder appendTo(StringBuilder builder, Object[] parts) { + return appendTo(builder, Arrays.asList(parts)); + } + + /** + * Appends to {@code builder} the string representation of each of the remaining arguments. + * Identical to {@link #appendTo(Appendable, Object, Object, Object...)}, except that it does not + * throw {@link IOException}. + */ + public final StringBuilder appendTo( + StringBuilder builder, Object first, Object second, Object... rest) { + return appendTo(builder, iterable(first, second, rest)); + } + + /** + * Returns a string containing the string representation of each of {@code parts}, using the + * previously configured separator between each. + */ + public final String join(Iterable parts) { + return join(parts.iterator()); + } + + /** + * Returns a string containing the string representation of each of {@code parts}, using the + * previously configured separator between each. + * + * @since 11.0 + */ + public final String join(Iterator parts) { + return appendTo(new StringBuilder(), parts).toString(); + } + + /** + * Returns a string containing the string representation of each of {@code parts}, using the + * previously configured separator between each. + */ + public final String join(Object[] parts) { + return join(Arrays.asList(parts)); + } + + /** + * Returns a string containing the string representation of each argument, using the previously + * configured separator between each. + */ + public final String join(Object first, Object second, Object... rest) { + return join(iterable(first, second, rest)); + } + + /** + * Returns a joiner with the same behavior as this one, except automatically substituting {@code + * nullText} for any provided null elements. + */ + public Joiner useForNull(final String nullText) { + checkNotNull(nullText); + return new Joiner(this) { + @Override + CharSequence toString(Object part) { + return (part == null) ? nullText : Joiner.this.toString(part); + } + + @Override + public Joiner useForNull(String nullText) { + throw new UnsupportedOperationException("already specified useForNull"); + } + + @Override + public Joiner skipNulls() { + throw new UnsupportedOperationException("already specified useForNull"); + } + }; + } + + /** + * Returns a joiner with the same behavior as this joiner, except automatically skipping over any + * provided null elements. + */ + public Joiner skipNulls() { + return new Joiner(this) { + @Override + public A appendTo(A appendable, Iterator parts) throws IOException { + checkNotNull(appendable, "appendable"); + checkNotNull(parts, "parts"); + while (parts.hasNext()) { + Object part = parts.next(); + if (part != null) { + appendable.append(Joiner.this.toString(part)); + break; + } + } + while (parts.hasNext()) { + Object part = parts.next(); + if (part != null) { + appendable.append(separator); + appendable.append(Joiner.this.toString(part)); + } + } + return appendable; + } + + @Override + public Joiner useForNull(String nullText) { + throw new UnsupportedOperationException("already specified skipNulls"); + } + + @Override + public MapJoiner withKeyValueSeparator(String kvs) { + throw new UnsupportedOperationException("can't use .skipNulls() with maps"); + } + }; + } + + /** + * Returns a {@code MapJoiner} using the given key-value separator, and the same configuration as + * this {@code Joiner} otherwise. + * + * @since 20.0 + */ + public MapJoiner withKeyValueSeparator(char keyValueSeparator) { + return withKeyValueSeparator(String.valueOf(keyValueSeparator)); + } + + /** + * Returns a {@code MapJoiner} using the given key-value separator, and the same configuration as + * this {@code Joiner} otherwise. + */ + public MapJoiner withKeyValueSeparator(String keyValueSeparator) { + return new MapJoiner(this, keyValueSeparator); + } + + /** + * An object that joins map entries in the same manner as {@code Joiner} joins iterables and + * arrays. Like {@code Joiner}, it is thread-safe and immutable. + * + *

In addition to operating on {@code Map} instances, {@code MapJoiner} can operate on {@code + * Multimap} entries in two distinct modes: + * + *

+ * + * @since 2.0 + */ + public static final class MapJoiner { + private final Joiner joiner; + private final String keyValueSeparator; + + private MapJoiner(Joiner joiner, String keyValueSeparator) { + this.joiner = joiner; // only "this" is ever passed, so don't checkNotNull + this.keyValueSeparator = checkNotNull(keyValueSeparator); + } + + /** + * Appends the string representation of each entry of {@code map}, using the previously + * configured separator and key-value separator, to {@code appendable}. + */ + public A appendTo(A appendable, Map map) throws IOException { + return appendTo(appendable, map.entrySet()); + } + + /** + * Appends the string representation of each entry of {@code map}, using the previously + * configured separator and key-value separator, to {@code builder}. Identical to {@link + * #appendTo(Appendable, Map)}, except that it does not throw {@link IOException}. + */ + public StringBuilder appendTo(StringBuilder builder, Map map) { + return appendTo(builder, map.entrySet()); + } + + /** + * Appends the string representation of each entry in {@code entries}, using the previously + * configured separator and key-value separator, to {@code appendable}. + * + * @since 10.0 + */ + @Beta + public A appendTo(A appendable, Iterable> entries) + throws IOException { + return appendTo(appendable, entries.iterator()); + } + + /** + * Appends the string representation of each entry in {@code entries}, using the previously + * configured separator and key-value separator, to {@code appendable}. + * + * @since 11.0 + */ + @Beta + public A appendTo(A appendable, Iterator> parts) + throws IOException { + checkNotNull(appendable); + if (parts.hasNext()) { + Entry entry = parts.next(); + appendable.append(joiner.toString(entry.getKey())); + appendable.append(keyValueSeparator); + appendable.append(joiner.toString(entry.getValue())); + while (parts.hasNext()) { + appendable.append(joiner.separator); + Entry e = parts.next(); + appendable.append(joiner.toString(e.getKey())); + appendable.append(keyValueSeparator); + appendable.append(joiner.toString(e.getValue())); + } + } + return appendable; + } + + /** + * Appends the string representation of each entry in {@code entries}, using the previously + * configured separator and key-value separator, to {@code builder}. Identical to {@link + * #appendTo(Appendable, Iterable)}, except that it does not throw {@link IOException}. + * + * @since 10.0 + */ + @Beta + public StringBuilder appendTo(StringBuilder builder, Iterable> entries) { + return appendTo(builder, entries.iterator()); + } + + /** + * Appends the string representation of each entry in {@code entries}, using the previously + * configured separator and key-value separator, to {@code builder}. Identical to {@link + * #appendTo(Appendable, Iterable)}, except that it does not throw {@link IOException}. + * + * @since 11.0 + */ + @Beta + public StringBuilder appendTo(StringBuilder builder, Iterator> entries) { + try { + appendTo((Appendable) builder, entries); + } catch (IOException impossible) { + throw new AssertionError(impossible); + } + return builder; + } + + /** + * Returns a string containing the string representation of each entry of {@code map}, using the + * previously configured separator and key-value separator. + */ + public String join(Map map) { + return join(map.entrySet()); + } + + /** + * Returns a string containing the string representation of each entry in {@code entries}, using + * the previously configured separator and key-value separator. + * + * @since 10.0 + */ + @Beta + public String join(Iterable> entries) { + return join(entries.iterator()); + } + + /** + * Returns a string containing the string representation of each entry in {@code entries}, using + * the previously configured separator and key-value separator. + * + * @since 11.0 + */ + @Beta + public String join(Iterator> entries) { + return appendTo(new StringBuilder(), entries).toString(); + } + + /** + * Returns a map joiner with the same behavior as this one, except automatically substituting + * {@code nullText} for any provided null keys or values. + */ + public MapJoiner useForNull(String nullText) { + return new MapJoiner(joiner.useForNull(nullText), keyValueSeparator); + } + } + + CharSequence toString(Object part) { + checkNotNull(part); // checkNotNull for GWT (do not optimize). + return (part instanceof CharSequence) ? (CharSequence) part : part.toString(); + } + + private static Iterable iterable( + final Object first, final Object second, final Object[] rest) { + checkNotNull(rest); + return new AbstractList() { + @Override + public int size() { + return rest.length + 2; + } + + @Override + public Object get(int index) { + switch (index) { + case 0: + return first; + case 1: + return second; + default: + return rest[index - 2]; + } + } + }; + } +} diff --git a/src/main/java/com/google/common/base/MoreObjects.java b/src/main/java/com/google/common/base/MoreObjects.java new file mode 100644 index 0000000..c7c5df0 --- /dev/null +++ b/src/main/java/com/google/common/base/MoreObjects.java @@ -0,0 +1,373 @@ +/* + * Copyright (C) 2014 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.base; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.annotations.GwtCompatible; +import java.util.Arrays; + +/** + * Helper functions that operate on any {@code Object}, and are not already provided in {@link + * java.util.Objects}. + * + *

See the Guava User Guide on writing {@code Object} + * methods with {@code MoreObjects}. + * + * @author Laurence Gonsalves + * @since 18.0 (since 2.0 as {@code Objects}) + */ +@GwtCompatible +public final class MoreObjects { + /** + * Returns the first of two given parameters that is not {@code null}, if either is, or otherwise + * throws a {@link NullPointerException}. + * + *

To find the first non-null element in an iterable, use {@code Iterables.find(iterable, + * Predicates.notNull())}. For varargs, use {@code Iterables.find(Arrays.asList(a, b, c, ...), + * Predicates.notNull())}, static importing as necessary. + * + *

Note: if {@code first} is represented as an {@link Optional}, this can be + * accomplished with {@link Optional#or(Object) first.or(second)}. That approach also allows for + * lazy evaluation of the fallback instance, using {@link Optional#or(Supplier) + * first.or(supplier)}. + * + *

Java 9 users: use {@code java.util.Objects.requireNonNullElse(first, second)} + * instead. + * + * @return {@code first} if it is non-null; otherwise {@code second} if it is non-null + * @throws NullPointerException if both {@code first} and {@code second} are null + * @since 18.0 (since 3.0 as {@code Objects.firstNonNull()}). + */ + public static T firstNonNull(T first, T second) { + if (first != null) { + return first; + } + if (second != null) { + return second; + } + throw new NullPointerException("Both parameters are null"); + } + + /** + * Creates an instance of {@link ToStringHelper}. + * + *

This is helpful for implementing {@link Object#toString()}. Specification by example: + * + *

{@code
+   * // Returns "ClassName{}"
+   * MoreObjects.toStringHelper(this)
+   *     .toString();
+   *
+   * // Returns "ClassName{x=1}"
+   * MoreObjects.toStringHelper(this)
+   *     .add("x", 1)
+   *     .toString();
+   *
+   * // Returns "MyObject{x=1}"
+   * MoreObjects.toStringHelper("MyObject")
+   *     .add("x", 1)
+   *     .toString();
+   *
+   * // Returns "ClassName{x=1, y=foo}"
+   * MoreObjects.toStringHelper(this)
+   *     .add("x", 1)
+   *     .add("y", "foo")
+   *     .toString();
+   *
+   * // Returns "ClassName{x=1}"
+   * MoreObjects.toStringHelper(this)
+   *     .omitNullValues()
+   *     .add("x", 1)
+   *     .add("y", null)
+   *     .toString();
+   * }
+ * + *

Note that in GWT, class names are often obfuscated. + * + * @param self the object to generate the string for (typically {@code this}), used only for its + * class name + * @since 18.0 (since 2.0 as {@code Objects.toStringHelper()}). + */ + public static ToStringHelper toStringHelper(Object self) { + return new ToStringHelper(self.getClass().getSimpleName()); + } + + /** + * Creates an instance of {@link ToStringHelper} in the same manner as {@link + * #toStringHelper(Object)}, but using the simple name of {@code clazz} instead of using an + * instance's {@link Object#getClass()}. + * + *

Note that in GWT, class names are often obfuscated. + * + * @param clazz the {@link Class} of the instance + * @since 18.0 (since 7.0 as {@code Objects.toStringHelper()}). + */ + public static ToStringHelper toStringHelper(Class clazz) { + return new ToStringHelper(clazz.getSimpleName()); + } + + /** + * Creates an instance of {@link ToStringHelper} in the same manner as {@link + * #toStringHelper(Object)}, but using {@code className} instead of using an instance's {@link + * Object#getClass()}. + * + * @param className the name of the instance type + * @since 18.0 (since 7.0 as {@code Objects.toStringHelper()}). + */ + public static ToStringHelper toStringHelper(String className) { + return new ToStringHelper(className); + } + + /** + * Support class for {@link MoreObjects#toStringHelper}. + * + * @author Jason Lee + * @since 18.0 (since 2.0 as {@code Objects.ToStringHelper}). + */ + public static final class ToStringHelper { + private final String className; + private final ValueHolder holderHead = new ValueHolder(); + private ValueHolder holderTail = holderHead; + private boolean omitNullValues = false; + + /** Use {@link MoreObjects#toStringHelper(Object)} to create an instance. */ + private ToStringHelper(String className) { + this.className = checkNotNull(className); + } + + /** + * Configures the {@link ToStringHelper} so {@link #toString()} will ignore properties with null + * value. The order of calling this method, relative to the {@code add()}/{@code addValue()} + * methods, is not significant. + * + * @since 18.0 (since 12.0 as {@code Objects.ToStringHelper.omitNullValues()}). + */ + public ToStringHelper omitNullValues() { + omitNullValues = true; + return this; + } + + /** + * Adds a name/value pair to the formatted output in {@code name=value} format. If {@code value} + * is {@code null}, the string {@code "null"} is used, unless {@link #omitNullValues()} is + * called, in which case this name/value pair will not be added. + */ + public ToStringHelper add(String name, Object value) { + return addHolder(name, value); + } + + /** + * Adds a name/value pair to the formatted output in {@code name=value} format. + * + * @since 18.0 (since 11.0 as {@code Objects.ToStringHelper.add()}). + */ + public ToStringHelper add(String name, boolean value) { + return addHolder(name, String.valueOf(value)); + } + + /** + * Adds a name/value pair to the formatted output in {@code name=value} format. + * + * @since 18.0 (since 11.0 as {@code Objects.ToStringHelper.add()}). + */ + public ToStringHelper add(String name, char value) { + return addHolder(name, String.valueOf(value)); + } + + /** + * Adds a name/value pair to the formatted output in {@code name=value} format. + * + * @since 18.0 (since 11.0 as {@code Objects.ToStringHelper.add()}). + */ + public ToStringHelper add(String name, double value) { + return addHolder(name, String.valueOf(value)); + } + + /** + * Adds a name/value pair to the formatted output in {@code name=value} format. + * + * @since 18.0 (since 11.0 as {@code Objects.ToStringHelper.add()}). + */ + public ToStringHelper add(String name, float value) { + return addHolder(name, String.valueOf(value)); + } + + /** + * Adds a name/value pair to the formatted output in {@code name=value} format. + * + * @since 18.0 (since 11.0 as {@code Objects.ToStringHelper.add()}). + */ + public ToStringHelper add(String name, int value) { + return addHolder(name, String.valueOf(value)); + } + + /** + * Adds a name/value pair to the formatted output in {@code name=value} format. + * + * @since 18.0 (since 11.0 as {@code Objects.ToStringHelper.add()}). + */ + public ToStringHelper add(String name, long value) { + return addHolder(name, String.valueOf(value)); + } + + /** + * Adds an unnamed value to the formatted output. + * + *

It is strongly encouraged to use {@link #add(String, Object)} instead and give value a + * readable name. + */ + public ToStringHelper addValue(Object value) { + return addHolder(value); + } + + /** + * Adds an unnamed value to the formatted output. + * + *

It is strongly encouraged to use {@link #add(String, boolean)} instead and give value a + * readable name. + * + * @since 18.0 (since 11.0 as {@code Objects.ToStringHelper.addValue()}). + */ + public ToStringHelper addValue(boolean value) { + return addHolder(String.valueOf(value)); + } + + /** + * Adds an unnamed value to the formatted output. + * + *

It is strongly encouraged to use {@link #add(String, char)} instead and give value a + * readable name. + * + * @since 18.0 (since 11.0 as {@code Objects.ToStringHelper.addValue()}). + */ + public ToStringHelper addValue(char value) { + return addHolder(String.valueOf(value)); + } + + /** + * Adds an unnamed value to the formatted output. + * + *

It is strongly encouraged to use {@link #add(String, double)} instead and give value a + * readable name. + * + * @since 18.0 (since 11.0 as {@code Objects.ToStringHelper.addValue()}). + */ + public ToStringHelper addValue(double value) { + return addHolder(String.valueOf(value)); + } + + /** + * Adds an unnamed value to the formatted output. + * + *

It is strongly encouraged to use {@link #add(String, float)} instead and give value a + * readable name. + * + * @since 18.0 (since 11.0 as {@code Objects.ToStringHelper.addValue()}). + */ + public ToStringHelper addValue(float value) { + return addHolder(String.valueOf(value)); + } + + /** + * Adds an unnamed value to the formatted output. + * + *

It is strongly encouraged to use {@link #add(String, int)} instead and give value a + * readable name. + * + * @since 18.0 (since 11.0 as {@code Objects.ToStringHelper.addValue()}). + */ + public ToStringHelper addValue(int value) { + return addHolder(String.valueOf(value)); + } + + /** + * Adds an unnamed value to the formatted output. + * + *

It is strongly encouraged to use {@link #add(String, long)} instead and give value a + * readable name. + * + * @since 18.0 (since 11.0 as {@code Objects.ToStringHelper.addValue()}). + */ + public ToStringHelper addValue(long value) { + return addHolder(String.valueOf(value)); + } + + /** + * Returns a string in the format specified by {@link MoreObjects#toStringHelper(Object)}. + * + *

After calling this method, you can keep adding more properties to later call toString() + * again and get a more complete representation of the same object; but properties cannot be + * removed, so this only allows limited reuse of the helper instance. The helper allows + * duplication of properties (multiple name/value pairs with the same name can be added). + */ + @Override + public String toString() { + // create a copy to keep it consistent in case value changes + boolean omitNullValuesSnapshot = omitNullValues; + String nextSeparator = ""; + StringBuilder builder = new StringBuilder(32).append(className).append('{'); + for (ValueHolder valueHolder = holderHead.next; + valueHolder != null; + valueHolder = valueHolder.next) { + Object value = valueHolder.value; + if (!omitNullValuesSnapshot || value != null) { + builder.append(nextSeparator); + nextSeparator = ", "; + + if (valueHolder.name != null) { + builder.append(valueHolder.name).append('='); + } + if (value != null && value.getClass().isArray()) { + Object[] objectArray = {value}; + String arrayString = Arrays.deepToString(objectArray); + builder.append(arrayString, 1, arrayString.length() - 1); + } else { + builder.append(value); + } + } + } + return builder.append('}').toString(); + } + + private ValueHolder addHolder() { + ValueHolder valueHolder = new ValueHolder(); + holderTail = holderTail.next = valueHolder; + return valueHolder; + } + + private ToStringHelper addHolder(Object value) { + ValueHolder valueHolder = addHolder(); + valueHolder.value = value; + return this; + } + + private ToStringHelper addHolder(String name, Object value) { + ValueHolder valueHolder = addHolder(); + valueHolder.value = value; + valueHolder.name = checkNotNull(name); + return this; + } + + private static final class ValueHolder { + String name; + Object value; + ValueHolder next; + } + } + + private MoreObjects() {} +} diff --git a/src/main/java/com/google/common/base/Objects.java b/src/main/java/com/google/common/base/Objects.java new file mode 100644 index 0000000..a6c604c --- /dev/null +++ b/src/main/java/com/google/common/base/Objects.java @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.base; + +import com.google.common.annotations.GwtCompatible; +import java.util.Arrays; + +/** + * Helper functions that can operate on any {@code Object}. + * + *

See the Guava User Guide on writing {@code Object} + * methods with {@code Objects}. + * + * @author Laurence Gonsalves + * @since 2.0 + */ +@GwtCompatible +public final class Objects extends ExtraObjectsMethodsForWeb { + private Objects() {} + + /** + * Determines whether two possibly-null objects are equal. Returns: + * + *

    + *
  • {@code true} if {@code a} and {@code b} are both null. + *
  • {@code true} if {@code a} and {@code b} are both non-null and they are equal according to + * {@link Object#equals(Object)}. + *
  • {@code false} in all other situations. + *
+ * + *

This assumes that any non-null objects passed to this function conform to the {@code + * equals()} contract. + * + *

Note for Java 7 and later: This method should be treated as deprecated; use {@link + * java.util.Objects#equals} instead. + */ + public static boolean equal(Object a, Object b) { + return a == b || (a != null && a.equals(b)); + } + + /** + * Generates a hash code for multiple values. The hash code is generated by calling {@link + * Arrays#hashCode(Object[])}. Note that array arguments to this method, with the exception of a + * single Object array, do not get any special handling; their hash codes are based on identity + * and not contents. + * + *

This is useful for implementing {@link Object#hashCode()}. For example, in an object that + * has three properties, {@code x}, {@code y}, and {@code z}, one could write: + * + *

{@code
+   * public int hashCode() {
+   *   return Objects.hashCode(getX(), getY(), getZ());
+   * }
+   * }
+ * + *

Warning: When a single object is supplied, the returned hash code does not equal the + * hash code of that object. + * + *

Note for Java 7 and later: This method should be treated as deprecated; use {@link + * java.util.Objects#hash} instead. + */ + public static int hashCode(Object ... objects) { + return Arrays.hashCode(objects); + } +} diff --git a/src/main/java/com/google/common/base/Optional.java b/src/main/java/com/google/common/base/Optional.java new file mode 100644 index 0000000..30784f8 --- /dev/null +++ b/src/main/java/com/google/common/base/Optional.java @@ -0,0 +1,356 @@ +/* + * Copyright (C) 2011 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.base; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.annotations.Beta; +import com.google.common.annotations.GwtCompatible; +import java.io.Serializable; +import java.util.Iterator; +import java.util.Set; + +/** + * An immutable object that may contain a non-null reference to another object. Each instance of + * this type either contains a non-null reference, or contains nothing (in which case we say that + * the reference is "absent"); it is never said to "contain {@code null}". + * + *

A non-null {@code Optional} reference can be used as a replacement for a nullable {@code T} + * reference. It allows you to represent "a {@code T} that must be present" and a "a {@code T} that + * might be absent" as two distinct types in your program, which can aid clarity. + * + *

Some uses of this class include + * + *

    + *
  • As a method return type, as an alternative to returning {@code null} to indicate that no + * value was available + *
  • To distinguish between "unknown" (for example, not present in a map) and "known to have no + * value" (present in the map, with value {@code Optional.absent()}) + *
  • To wrap nullable references for storage in a collection that does not support {@code null} + * (though there are several other + * approaches to this that should be considered first) + *
+ * + *

A common alternative to using this class is to find or create a suitable null object for the type in question. + * + *

This class is not intended as a direct analogue of any existing "option" or "maybe" construct + * from other programming environments, though it may bear some similarities. + * + *

Comparison to {@code java.util.Optional} (JDK 8 and higher): A new {@code Optional} + * class was added for Java 8. The two classes are extremely similar, but incompatible (they cannot + * share a common supertype). All known differences are listed either here or with the + * relevant methods below. + * + *

    + *
  • This class is serializable; {@code java.util.Optional} is not. + *
  • {@code java.util.Optional} has the additional methods {@code ifPresent}, {@code filter}, + * {@code flatMap}, and {@code orElseThrow}. + *
  • {@code java.util} offers the primitive-specialized versions {@code OptionalInt}, {@code + * OptionalLong} and {@code OptionalDouble}, the use of which is recommended; Guava does not + * have these. + *
+ * + *

There are no plans to deprecate this class in the foreseeable future. However, we do + * gently recommend that you prefer the new, standard Java class whenever possible. + * + *

See the Guava User Guide article on using {@code + * Optional}. + * + * @param the type of instance that can be contained. {@code Optional} is naturally covariant on + * this type, so it is safe to cast an {@code Optional} to {@code Optional} for any + * supertype {@code S} of {@code T}. + * @author Kurt Alfred Kluever + * @author Kevin Bourrillion + * @since 10.0 + */ +@GwtCompatible(serializable = true) +public abstract class Optional implements Serializable { + /** + * Returns an {@code Optional} instance with no contained reference. + * + *

Comparison to {@code java.util.Optional}: this method is equivalent to Java 8's + * {@code Optional.empty}. + */ + public static Optional absent() { + return Absent.withType(); + } + + /** + * Returns an {@code Optional} instance containing the given non-null reference. To have {@code + * null} treated as {@link #absent}, use {@link #fromNullable} instead. + * + *

Comparison to {@code java.util.Optional}: no differences. + * + * @throws NullPointerException if {@code reference} is null + */ + public static Optional of(T reference) { + return new Present(checkNotNull(reference)); + } + + /** + * If {@code nullableReference} is non-null, returns an {@code Optional} instance containing that + * reference; otherwise returns {@link Optional#absent}. + * + *

Comparison to {@code java.util.Optional}: this method is equivalent to Java 8's + * {@code Optional.ofNullable}. + */ + public static Optional fromNullable(T nullableReference) { + return (nullableReference == null) ? Optional.absent() : new Present(nullableReference); + } + + /** + * Returns the equivalent {@code com.google.common.base.Optional} value to the given {@code + * java.util.Optional}, or {@code null} if the argument is null. + * + * @since 21.0 + */ + public static Optional fromJavaUtil( + java.util.Optional javaUtilOptional) { + return (javaUtilOptional == null) ? null : fromNullable(javaUtilOptional.orElse(null)); + } + + /** + * Returns the equivalent {@code java.util.Optional} value to the given {@code + * com.google.common.base.Optional}, or {@code null} if the argument is null. + * + *

If {@code googleOptional} is known to be non-null, use {@code googleOptional.toJavaUtil()} + * instead. + * + *

Unfortunately, the method reference {@code Optional::toJavaUtil} will not work, because it + * could refer to either the static or instance version of this method. Write out the lambda + * expression {@code o -> Optional.toJavaUtil(o)} instead. + * + * @since 21.0 + */ + public static java.util.Optional toJavaUtil( + Optional googleOptional) { + return googleOptional == null ? null : googleOptional.toJavaUtil(); + } + + /** + * Returns the equivalent {@code java.util.Optional} value to this optional. + * + *

Unfortunately, the method reference {@code Optional::toJavaUtil} will not work, because it + * could refer to either the static or instance version of this method. Write out the lambda + * expression {@code o -> o.toJavaUtil()} instead. + * + * @since 21.0 + */ + public java.util.Optional toJavaUtil() { + return java.util.Optional.ofNullable(orNull()); + } + + Optional() {} + + /** + * Returns {@code true} if this holder contains a (non-null) instance. + * + *

Comparison to {@code java.util.Optional}: no differences. + */ + public abstract boolean isPresent(); + + /** + * Returns the contained instance, which must be present. If the instance might be absent, use + * {@link #or(Object)} or {@link #orNull} instead. + * + *

Comparison to {@code java.util.Optional}: when the value is absent, this method + * throws {@link IllegalStateException}, whereas the Java 8 counterpart throws {@link + * java.util.NoSuchElementException NoSuchElementException}. + * + * @throws IllegalStateException if the instance is absent ({@link #isPresent} returns {@code + * false}); depending on this specific exception type (over the more general {@link + * RuntimeException}) is discouraged + */ + public abstract T get(); + + /** + * Returns the contained instance if it is present; {@code defaultValue} otherwise. If no default + * value should be required because the instance is known to be present, use {@link #get()} + * instead. For a default value of {@code null}, use {@link #orNull}. + * + *

Note about generics: The signature {@code public T or(T defaultValue)} is overly + * restrictive. However, the ideal signature, {@code public S or(S)}, is not legal + * Java. As a result, some sensible operations involving subtypes are compile errors: + * + *

{@code
+   * Optional optionalInt = getSomeOptionalInt();
+   * Number value = optionalInt.or(0.5); // error
+   *
+   * FluentIterable numbers = getSomeNumbers();
+   * Optional first = numbers.first();
+   * Number value = first.or(0.5); // error
+   * }
+ * + *

As a workaround, it is always safe to cast an {@code Optional} to {@code + * Optional}. Casting either of the above example {@code Optional} instances to {@code + * Optional} (where {@code Number} is the desired output type) solves the problem: + * + *

{@code
+   * Optional optionalInt = (Optional) getSomeOptionalInt();
+   * Number value = optionalInt.or(0.5); // fine
+   *
+   * FluentIterable numbers = getSomeNumbers();
+   * Optional first = (Optional) numbers.first();
+   * Number value = first.or(0.5); // fine
+   * }
+ * + *

Comparison to {@code java.util.Optional}: this method is similar to Java 8's {@code + * Optional.orElse}, but will not accept {@code null} as a {@code defaultValue} ({@link #orNull} + * must be used instead). As a result, the value returned by this method is guaranteed non-null, + * which is not the case for the {@code java.util} equivalent. + */ + public abstract T or(T defaultValue); + + /** + * Returns this {@code Optional} if it has a value present; {@code secondChoice} otherwise. + * + *

Comparison to {@code java.util.Optional}: this method has no equivalent in Java 8's + * {@code Optional} class; write {@code thisOptional.isPresent() ? thisOptional : secondChoice} + * instead. + */ + public abstract Optional or(Optional secondChoice); + + /** + * Returns the contained instance if it is present; {@code supplier.get()} otherwise. + * + *

Comparison to {@code java.util.Optional}: this method is similar to Java 8's {@code + * Optional.orElseGet}, except when {@code supplier} returns {@code null}. In this case this + * method throws an exception, whereas the Java 8 method returns the {@code null} to the caller. + * + * @throws NullPointerException if this optional's value is absent and the supplier returns {@code + * null} + */ + @Beta + public abstract T or(Supplier supplier); + + /** + * Returns the contained instance if it is present; {@code null} otherwise. If the instance is + * known to be present, use {@link #get()} instead. + * + *

Comparison to {@code java.util.Optional}: this method is equivalent to Java 8's + * {@code Optional.orElse(null)}. + */ + public abstract T orNull(); + + /** + * Returns an immutable singleton {@link Set} whose only element is the contained instance if it + * is present; an empty immutable {@link Set} otherwise. + * + *

Comparison to {@code java.util.Optional}: this method has no equivalent in Java 8's + * {@code Optional} class. However, this common usage: + * + *

{@code
+   * for (Foo foo : possibleFoo.asSet()) {
+   *   doSomethingWith(foo);
+   * }
+   * }
+ * + * ... can be replaced with: + * + *
{@code
+   * possibleFoo.ifPresent(foo -> doSomethingWith(foo));
+   * }
+ * + *

Java 9 users: some use cases can be written with calls to {@code optional.stream()}. + * + * @since 11.0 + */ + public abstract Set asSet(); + + /** + * If the instance is present, it is transformed with the given {@link Function}; otherwise, + * {@link Optional#absent} is returned. + * + *

Comparison to {@code java.util.Optional}: this method is similar to Java 8's {@code + * Optional.map}, except when {@code function} returns {@code null}. In this case this method + * throws an exception, whereas the Java 8 method returns {@code Optional.absent()}. + * + * @throws NullPointerException if the function returns {@code null} + * @since 12.0 + */ + public abstract Optional transform(Function function); + + /** + * Returns {@code true} if {@code object} is an {@code Optional} instance, and either the + * contained references are {@linkplain Object#equals equal} to each other or both are absent. + * Note that {@code Optional} instances of differing parameterized types can be equal. + * + *

Comparison to {@code java.util.Optional}: no differences. + */ + @Override + public abstract boolean equals(Object object); + + /** + * Returns a hash code for this instance. + * + *

Comparison to {@code java.util.Optional}: this class leaves the specific choice of + * hash code unspecified, unlike the Java 8 equivalent. + */ + @Override + public abstract int hashCode(); + + /** + * Returns a string representation for this instance. + * + *

Comparison to {@code java.util.Optional}: this class leaves the specific string + * representation unspecified, unlike the Java 8 equivalent. + */ + @Override + public abstract String toString(); + + /** + * Returns the value of each present instance from the supplied {@code optionals}, in order, + * skipping over occurrences of {@link Optional#absent}. Iterators are unmodifiable and are + * evaluated lazily. + * + *

Comparison to {@code java.util.Optional}: this method has no equivalent in Java 8's + * {@code Optional} class; use {@code + * optionals.stream().filter(Optional::isPresent).map(Optional::get)} instead. + * + *

Java 9 users: use {@code optionals.stream().flatMap(Optional::stream)} instead. + * + * @since 11.0 (generics widened in 13.0) + */ + @Beta + public static Iterable presentInstances( + final Iterable> optionals) { + checkNotNull(optionals); + return new Iterable() { + @Override + public Iterator iterator() { + return new AbstractIterator() { + private final Iterator> iterator = + checkNotNull(optionals.iterator()); + + @Override + protected T computeNext() { + while (iterator.hasNext()) { + Optional optional = iterator.next(); + if (optional.isPresent()) { + return optional.get(); + } + } + return endOfData(); + } + }; + } + }; + } + + private static final long serialVersionUID = 0; +} diff --git a/src/main/java/com/google/common/base/PairwiseEquivalence.java b/src/main/java/com/google/common/base/PairwiseEquivalence.java new file mode 100644 index 0000000..e353868 --- /dev/null +++ b/src/main/java/com/google/common/base/PairwiseEquivalence.java @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2011 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.base; + +import com.google.common.annotations.GwtCompatible; +import java.io.Serializable; +import java.util.Iterator; + +@GwtCompatible(serializable = true) +final class PairwiseEquivalence extends Equivalence> implements Serializable { + + final Equivalence elementEquivalence; + + PairwiseEquivalence(Equivalence elementEquivalence) { + this.elementEquivalence = Preconditions.checkNotNull(elementEquivalence); + } + + @Override + protected boolean doEquivalent(Iterable iterableA, Iterable iterableB) { + Iterator iteratorA = iterableA.iterator(); + Iterator iteratorB = iterableB.iterator(); + + while (iteratorA.hasNext() && iteratorB.hasNext()) { + if (!elementEquivalence.equivalent(iteratorA.next(), iteratorB.next())) { + return false; + } + } + + return !iteratorA.hasNext() && !iteratorB.hasNext(); + } + + @Override + protected int doHash(Iterable iterable) { + int hash = 78721; + for (T element : iterable) { + hash = hash * 24943 + elementEquivalence.hash(element); + } + return hash; + } + + @Override + public boolean equals(Object object) { + if (object instanceof PairwiseEquivalence) { + PairwiseEquivalence that = (PairwiseEquivalence) object; + return this.elementEquivalence.equals(that.elementEquivalence); + } + + return false; + } + + @Override + public int hashCode() { + return elementEquivalence.hashCode() ^ 0x46a3eb07; + } + + @Override + public String toString() { + return elementEquivalence + ".pairwise()"; + } + + private static final long serialVersionUID = 1; +} diff --git a/src/main/java/com/google/common/base/PatternCompiler.java b/src/main/java/com/google/common/base/PatternCompiler.java new file mode 100644 index 0000000..813a25f --- /dev/null +++ b/src/main/java/com/google/common/base/PatternCompiler.java @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2016 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.base; + +import com.google.common.annotations.GwtIncompatible; + +/** + * Pluggable interface for compiling a regex pattern. By default this package uses the {@code + * java.util.regex} library, but an alternate implementation can be supplied using the {@link + * java.util.ServiceLoader} mechanism. + */ +@GwtIncompatible +interface PatternCompiler { + /** + * Compiles the given pattern. + * + * @throws IllegalArgumentException if the pattern is invalid + */ + CommonPattern compile(String pattern); + + /** + * Returns {@code true} if the regex implementation behaves like Perl -- notably, by supporting + * possessive quantifiers but also being susceptible to catastrophic backtracking. + */ + boolean isPcreLike(); +} diff --git a/src/main/java/com/google/common/base/Platform.java b/src/main/java/com/google/common/base/Platform.java new file mode 100644 index 0000000..949a1d9 --- /dev/null +++ b/src/main/java/com/google/common/base/Platform.java @@ -0,0 +1,121 @@ +/* + * Copyright (C) 2009 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.base; + +import static com.google.common.base.Strings.lenientFormat; +import static java.lang.Boolean.parseBoolean; + +import com.google.common.annotations.GwtCompatible; +import java.lang.ref.WeakReference; +import java.util.Locale; +import java.util.ServiceConfigurationError; +import java.util.logging.Level; +import java.util.logging.Logger; +import java.util.regex.Pattern; + +/** + * Methods factored out so that they can be emulated differently in GWT. + * + * @author Jesse Wilson + */ +@GwtCompatible(emulated = true) +final class Platform { + private static final Logger logger = Logger.getLogger(Platform.class.getName()); + private static final PatternCompiler patternCompiler = loadPatternCompiler(); + + private Platform() {} + + /** Calls {@link System#nanoTime()}. */ + @SuppressWarnings("GoodTime") // reading system time without TimeSource + static long systemNanoTime() { + return System.nanoTime(); + } + + static CharMatcher precomputeCharMatcher(CharMatcher matcher) { + return matcher.precomputedInternal(); + } + + static > Optional getEnumIfPresent(Class enumClass, String value) { + WeakReference> ref = Enums.getEnumConstants(enumClass).get(value); + return ref == null ? Optional.absent() : Optional.of(enumClass.cast(ref.get())); + } + + static String formatCompact4Digits(double value) { + return String.format(Locale.ROOT, "%.4g", value); + } + + static boolean stringIsNullOrEmpty(String string) { + return string == null || string.isEmpty(); + } + + static String nullToEmpty(String string) { + return (string == null) ? "" : string; + } + + static String emptyToNull(String string) { + return stringIsNullOrEmpty(string) ? null : string; + } + + static CommonPattern compilePattern(String pattern) { + Preconditions.checkNotNull(pattern); + return patternCompiler.compile(pattern); + } + + static boolean patternCompilerIsPcreLike() { + return patternCompiler.isPcreLike(); + } + + private static PatternCompiler loadPatternCompiler() { + return new JdkPatternCompiler(); + } + + private static void logPatternCompilerError(ServiceConfigurationError e) { + logger.log(Level.WARNING, "Error loading regex compiler, falling back to next option", e); + } + + private static final class JdkPatternCompiler implements PatternCompiler { + @Override + public CommonPattern compile(String pattern) { + return new JdkPattern(Pattern.compile(pattern)); + } + + @Override + public boolean isPcreLike() { + return true; + } + } + + private static final String GWT_RPC_PROPERTY_NAME = "guava.gwt.emergency_reenable_rpc"; + + static void checkGwtRpcEnabled() { + if (!parseBoolean(System.getProperty(GWT_RPC_PROPERTY_NAME, "true"))) { + throw new UnsupportedOperationException( + lenientFormat( + "We are removing GWT-RPC support for Guava types. You can temporarily reenable" + + " support by setting the system property %s to true. For more about system" + + " properties, see %s. For more about Guava's GWT-RPC support, see %s.", + GWT_RPC_PROPERTY_NAME, + "https://stackoverflow.com/q/5189914/28465", + "https://groups.google.com/d/msg/guava-announce/zHZTFg7YF3o/rQNnwdHeEwAJ")); + } + logger.log( + java.util.logging.Level.WARNING, + "In January 2020, we will remove GWT-RPC support for Guava types. You are seeing this" + + " warning because you are sending a Guava type over GWT-RPC, which will break. You" + + " can identify which type by looking at the class name in the attached stack trace.", + new Throwable()); + + } +} diff --git a/src/main/java/com/google/common/base/Preconditions.java b/src/main/java/com/google/common/base/Preconditions.java new file mode 100644 index 0000000..3b7be96 --- /dev/null +++ b/src/main/java/com/google/common/base/Preconditions.java @@ -0,0 +1,1404 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.base; + +import static com.google.common.base.Strings.lenientFormat; + +import com.google.common.annotations.GwtCompatible; + +/** + * Static convenience methods that help a method or constructor check whether it was invoked + * correctly (that is, whether its preconditions were met). + * + *

If the precondition is not met, the {@code Preconditions} method throws an unchecked exception + * of a specified type, which helps the method in which the exception was thrown communicate that + * its caller has made a mistake. This allows constructs such as + * + *

{@code
+ * public static double sqrt(double value) {
+ *   if (value < 0) {
+ *     throw new IllegalArgumentException("input is negative: " + value);
+ *   }
+ *   // calculate square root
+ * }
+ * }
+ * + *

to be replaced with the more compact + * + *

{@code
+ * public static double sqrt(double value) {
+ *   checkArgument(value >= 0, "input is negative: %s", value);
+ *   // calculate square root
+ * }
+ * }
+ * + *

so that a hypothetical bad caller of this method, such as: + * + *

{@code
+ * void exampleBadCaller() {
+ *   double d = sqrt(-1.0);
+ * }
+ * }
+ * + *

would be flagged as having called {@code sqrt()} with an illegal argument. + * + *

Performance

+ * + *

Avoid passing message arguments that are expensive to compute; your code will always compute + * them, even though they usually won't be needed. If you have such arguments, use the conventional + * if/throw idiom instead. + * + *

Depending on your message arguments, memory may be allocated for boxing and varargs array + * creation. However, the methods of this class have a large number of overloads that prevent such + * allocations in many common cases. + * + *

The message string is not formatted unless the exception will be thrown, so the cost of the + * string formatting itself should not be a concern. + * + *

As with any performance concerns, you should consider profiling your code (in a production + * environment if possible) before spending a lot of effort on tweaking a particular element. + * + *

Other types of preconditions

+ * + *

Not every type of precondition failure is supported by these methods. Continue to throw + * standard JDK exceptions such as {@link java.util.NoSuchElementException} or {@link + * UnsupportedOperationException} in the situations they are intended for. + * + *

Non-preconditions

+ * + *

It is of course possible to use the methods of this class to check for invalid conditions + * which are not the caller's fault. Doing so is not recommended because it is + * misleading to future readers of the code and of stack traces. See Conditional failures + * explained in the Guava User Guide for more advice. Notably, {@link Verify} offers assertions + * similar to those in this class for non-precondition checks. + * + *

{@code java.util.Objects.requireNonNull()}

+ * + *

Projects which use {@code com.google.common} should generally avoid the use of {@link + * java.util.Objects#requireNonNull(Object)}. Instead, use whichever of {@link + * #checkNotNull(Object)} or {@link Verify#verifyNotNull(Object)} is appropriate to the situation. + * (The same goes for the message-accepting overloads.) + * + *

Only {@code %s} is supported

+ * + *

{@code Preconditions} uses {@link Strings#lenientFormat} to format error message template + * strings. This only supports the {@code "%s"} specifier, not the full range of {@link + * java.util.Formatter} specifiers. However, note that if the number of arguments does not match the + * number of occurrences of {@code "%s"} in the format string, {@code Preconditions} will still + * behave as expected, and will still include all argument values in the error message; the message + * will simply not be formatted exactly as intended. + * + *

More information

+ * + *

See the Guava User Guide on using {@code + * Preconditions}. + * + * @author Kevin Bourrillion + * @since 2.0 + */ +@GwtCompatible +public final class Preconditions { + private Preconditions() {} + + /** + * Ensures the truth of an expression involving one or more parameters to the calling method. + * + * @param expression a boolean expression + * @throws IllegalArgumentException if {@code expression} is false + */ + public static void checkArgument(boolean expression) { + if (!expression) { + throw new IllegalArgumentException(); + } + } + + /** + * Ensures the truth of an expression involving one or more parameters to the calling method. + * + * @param expression a boolean expression + * @param errorMessage the exception message to use if the check fails; will be converted to a + * string using {@link String#valueOf(Object)} + * @throws IllegalArgumentException if {@code expression} is false + */ + public static void checkArgument(boolean expression, Object errorMessage) { + if (!expression) { + throw new IllegalArgumentException(String.valueOf(errorMessage)); + } + } + + /** + * Ensures the truth of an expression involving one or more parameters to the calling method. + * + * @param expression a boolean expression + * @param errorMessageTemplate a template for the exception message should the check fail. The + * message is formed by replacing each {@code %s} placeholder in the template with an + * argument. These are matched by position - the first {@code %s} gets {@code + * errorMessageArgs[0]}, etc. Unmatched arguments will be appended to the formatted message in + * square braces. Unmatched placeholders will be left as-is. + * @param errorMessageArgs the arguments to be substituted into the message template. Arguments + * are converted to strings using {@link String#valueOf(Object)}. + * @throws IllegalArgumentException if {@code expression} is false + */ + public static void checkArgument( + boolean expression, + String errorMessageTemplate, + Object ... errorMessageArgs) { + if (!expression) { + throw new IllegalArgumentException(lenientFormat(errorMessageTemplate, errorMessageArgs)); + } + } + + /** + * Ensures the truth of an expression involving one or more parameters to the calling method. + * + *

See {@link #checkArgument(boolean, String, Object...)} for details. + * + * @since 20.0 (varargs overload since 2.0) + */ + public static void checkArgument(boolean b, String errorMessageTemplate, char p1) { + if (!b) { + throw new IllegalArgumentException(lenientFormat(errorMessageTemplate, p1)); + } + } + + /** + * Ensures the truth of an expression involving one or more parameters to the calling method. + * + *

See {@link #checkArgument(boolean, String, Object...)} for details. + * + * @since 20.0 (varargs overload since 2.0) + */ + public static void checkArgument(boolean b, String errorMessageTemplate, int p1) { + if (!b) { + throw new IllegalArgumentException(lenientFormat(errorMessageTemplate, p1)); + } + } + + /** + * Ensures the truth of an expression involving one or more parameters to the calling method. + * + *

See {@link #checkArgument(boolean, String, Object...)} for details. + * + * @since 20.0 (varargs overload since 2.0) + */ + public static void checkArgument(boolean b, String errorMessageTemplate, long p1) { + if (!b) { + throw new IllegalArgumentException(lenientFormat(errorMessageTemplate, p1)); + } + } + + /** + * Ensures the truth of an expression involving one or more parameters to the calling method. + * + *

See {@link #checkArgument(boolean, String, Object...)} for details. + * + * @since 20.0 (varargs overload since 2.0) + */ + public static void checkArgument( + boolean b, String errorMessageTemplate, Object p1) { + if (!b) { + throw new IllegalArgumentException(lenientFormat(errorMessageTemplate, p1)); + } + } + + /** + * Ensures the truth of an expression involving one or more parameters to the calling method. + * + *

See {@link #checkArgument(boolean, String, Object...)} for details. + * + * @since 20.0 (varargs overload since 2.0) + */ + public static void checkArgument( + boolean b, String errorMessageTemplate, char p1, char p2) { + if (!b) { + throw new IllegalArgumentException(lenientFormat(errorMessageTemplate, p1, p2)); + } + } + + /** + * Ensures the truth of an expression involving one or more parameters to the calling method. + * + *

See {@link #checkArgument(boolean, String, Object...)} for details. + * + * @since 20.0 (varargs overload since 2.0) + */ + public static void checkArgument( + boolean b, String errorMessageTemplate, char p1, int p2) { + if (!b) { + throw new IllegalArgumentException(lenientFormat(errorMessageTemplate, p1, p2)); + } + } + + /** + * Ensures the truth of an expression involving one or more parameters to the calling method. + * + *

See {@link #checkArgument(boolean, String, Object...)} for details. + * + * @since 20.0 (varargs overload since 2.0) + */ + public static void checkArgument( + boolean b, String errorMessageTemplate, char p1, long p2) { + if (!b) { + throw new IllegalArgumentException(lenientFormat(errorMessageTemplate, p1, p2)); + } + } + + /** + * Ensures the truth of an expression involving one or more parameters to the calling method. + * + *

See {@link #checkArgument(boolean, String, Object...)} for details. + * + * @since 20.0 (varargs overload since 2.0) + */ + public static void checkArgument( + boolean b, String errorMessageTemplate, char p1, Object p2) { + if (!b) { + throw new IllegalArgumentException(lenientFormat(errorMessageTemplate, p1, p2)); + } + } + + /** + * Ensures the truth of an expression involving one or more parameters to the calling method. + * + *

See {@link #checkArgument(boolean, String, Object...)} for details. + * + * @since 20.0 (varargs overload since 2.0) + */ + public static void checkArgument( + boolean b, String errorMessageTemplate, int p1, char p2) { + if (!b) { + throw new IllegalArgumentException(lenientFormat(errorMessageTemplate, p1, p2)); + } + } + + /** + * Ensures the truth of an expression involving one or more parameters to the calling method. + * + *

See {@link #checkArgument(boolean, String, Object...)} for details. + * + * @since 20.0 (varargs overload since 2.0) + */ + public static void checkArgument( + boolean b, String errorMessageTemplate, int p1, int p2) { + if (!b) { + throw new IllegalArgumentException(lenientFormat(errorMessageTemplate, p1, p2)); + } + } + + /** + * Ensures the truth of an expression involving one or more parameters to the calling method. + * + *

See {@link #checkArgument(boolean, String, Object...)} for details. + * + * @since 20.0 (varargs overload since 2.0) + */ + public static void checkArgument( + boolean b, String errorMessageTemplate, int p1, long p2) { + if (!b) { + throw new IllegalArgumentException(lenientFormat(errorMessageTemplate, p1, p2)); + } + } + + /** + * Ensures the truth of an expression involving one or more parameters to the calling method. + * + *

See {@link #checkArgument(boolean, String, Object...)} for details. + * + * @since 20.0 (varargs overload since 2.0) + */ + public static void checkArgument( + boolean b, String errorMessageTemplate, int p1, Object p2) { + if (!b) { + throw new IllegalArgumentException(lenientFormat(errorMessageTemplate, p1, p2)); + } + } + + /** + * Ensures the truth of an expression involving one or more parameters to the calling method. + * + *

See {@link #checkArgument(boolean, String, Object...)} for details. + * + * @since 20.0 (varargs overload since 2.0) + */ + public static void checkArgument( + boolean b, String errorMessageTemplate, long p1, char p2) { + if (!b) { + throw new IllegalArgumentException(lenientFormat(errorMessageTemplate, p1, p2)); + } + } + + /** + * Ensures the truth of an expression involving one or more parameters to the calling method. + * + *

See {@link #checkArgument(boolean, String, Object...)} for details. + * + * @since 20.0 (varargs overload since 2.0) + */ + public static void checkArgument( + boolean b, String errorMessageTemplate, long p1, int p2) { + if (!b) { + throw new IllegalArgumentException(lenientFormat(errorMessageTemplate, p1, p2)); + } + } + + /** + * Ensures the truth of an expression involving one or more parameters to the calling method. + * + *

See {@link #checkArgument(boolean, String, Object...)} for details. + * + * @since 20.0 (varargs overload since 2.0) + */ + public static void checkArgument( + boolean b, String errorMessageTemplate, long p1, long p2) { + if (!b) { + throw new IllegalArgumentException(lenientFormat(errorMessageTemplate, p1, p2)); + } + } + + /** + * Ensures the truth of an expression involving one or more parameters to the calling method. + * + *

See {@link #checkArgument(boolean, String, Object...)} for details. + * + * @since 20.0 (varargs overload since 2.0) + */ + public static void checkArgument( + boolean b, String errorMessageTemplate, long p1, Object p2) { + if (!b) { + throw new IllegalArgumentException(lenientFormat(errorMessageTemplate, p1, p2)); + } + } + + /** + * Ensures the truth of an expression involving one or more parameters to the calling method. + * + *

See {@link #checkArgument(boolean, String, Object...)} for details. + * + * @since 20.0 (varargs overload since 2.0) + */ + public static void checkArgument( + boolean b, String errorMessageTemplate, Object p1, char p2) { + if (!b) { + throw new IllegalArgumentException(lenientFormat(errorMessageTemplate, p1, p2)); + } + } + + /** + * Ensures the truth of an expression involving one or more parameters to the calling method. + * + *

See {@link #checkArgument(boolean, String, Object...)} for details. + * + * @since 20.0 (varargs overload since 2.0) + */ + public static void checkArgument( + boolean b, String errorMessageTemplate, Object p1, int p2) { + if (!b) { + throw new IllegalArgumentException(lenientFormat(errorMessageTemplate, p1, p2)); + } + } + + /** + * Ensures the truth of an expression involving one or more parameters to the calling method. + * + *

See {@link #checkArgument(boolean, String, Object...)} for details. + * + * @since 20.0 (varargs overload since 2.0) + */ + public static void checkArgument( + boolean b, String errorMessageTemplate, Object p1, long p2) { + if (!b) { + throw new IllegalArgumentException(lenientFormat(errorMessageTemplate, p1, p2)); + } + } + + /** + * Ensures the truth of an expression involving one or more parameters to the calling method. + * + *

See {@link #checkArgument(boolean, String, Object...)} for details. + * + * @since 20.0 (varargs overload since 2.0) + */ + public static void checkArgument( + boolean b, String errorMessageTemplate, Object p1, Object p2) { + if (!b) { + throw new IllegalArgumentException(lenientFormat(errorMessageTemplate, p1, p2)); + } + } + + /** + * Ensures the truth of an expression involving one or more parameters to the calling method. + * + *

See {@link #checkArgument(boolean, String, Object...)} for details. + * + * @since 20.0 (varargs overload since 2.0) + */ + public static void checkArgument( + boolean b, + String errorMessageTemplate, + Object p1, + Object p2, + Object p3) { + if (!b) { + throw new IllegalArgumentException(lenientFormat(errorMessageTemplate, p1, p2, p3)); + } + } + + /** + * Ensures the truth of an expression involving one or more parameters to the calling method. + * + *

See {@link #checkArgument(boolean, String, Object...)} for details. + * + * @since 20.0 (varargs overload since 2.0) + */ + public static void checkArgument( + boolean b, + String errorMessageTemplate, + Object p1, + Object p2, + Object p3, + Object p4) { + if (!b) { + throw new IllegalArgumentException(lenientFormat(errorMessageTemplate, p1, p2, p3, p4)); + } + } + + /** + * Ensures the truth of an expression involving the state of the calling instance, but not + * involving any parameters to the calling method. + * + * @param expression a boolean expression + * @throws IllegalStateException if {@code expression} is false + * @see Verify#verify Verify.verify() + */ + public static void checkState(boolean expression) { + if (!expression) { + throw new IllegalStateException(); + } + } + + /** + * Ensures the truth of an expression involving the state of the calling instance, but not + * involving any parameters to the calling method. + * + * @param expression a boolean expression + * @param errorMessage the exception message to use if the check fails; will be converted to a + * string using {@link String#valueOf(Object)} + * @throws IllegalStateException if {@code expression} is false + * @see Verify#verify Verify.verify() + */ + public static void checkState(boolean expression, Object errorMessage) { + if (!expression) { + throw new IllegalStateException(String.valueOf(errorMessage)); + } + } + + /** + * Ensures the truth of an expression involving the state of the calling instance, but not + * involving any parameters to the calling method. + * + * @param expression a boolean expression + * @param errorMessageTemplate a template for the exception message should the check fail. The + * message is formed by replacing each {@code %s} placeholder in the template with an + * argument. These are matched by position - the first {@code %s} gets {@code + * errorMessageArgs[0]}, etc. Unmatched arguments will be appended to the formatted message in + * square braces. Unmatched placeholders will be left as-is. + * @param errorMessageArgs the arguments to be substituted into the message template. Arguments + * are converted to strings using {@link String#valueOf(Object)}. + * @throws IllegalStateException if {@code expression} is false + * @see Verify#verify Verify.verify() + */ + public static void checkState( + boolean expression, + String errorMessageTemplate, + Object ... errorMessageArgs) { + if (!expression) { + throw new IllegalStateException(lenientFormat(errorMessageTemplate, errorMessageArgs)); + } + } + + /** + * Ensures the truth of an expression involving the state of the calling instance, but not + * involving any parameters to the calling method. + * + *

See {@link #checkState(boolean, String, Object...)} for details. + * + * @since 20.0 (varargs overload since 2.0) + */ + public static void checkState(boolean b, String errorMessageTemplate, char p1) { + if (!b) { + throw new IllegalStateException(lenientFormat(errorMessageTemplate, p1)); + } + } + + /** + * Ensures the truth of an expression involving the state of the calling instance, but not + * involving any parameters to the calling method. + * + *

See {@link #checkState(boolean, String, Object...)} for details. + * + * @since 20.0 (varargs overload since 2.0) + */ + public static void checkState(boolean b, String errorMessageTemplate, int p1) { + if (!b) { + throw new IllegalStateException(lenientFormat(errorMessageTemplate, p1)); + } + } + + /** + * Ensures the truth of an expression involving the state of the calling instance, but not + * involving any parameters to the calling method. + * + *

See {@link #checkState(boolean, String, Object...)} for details. + * + * @since 20.0 (varargs overload since 2.0) + */ + public static void checkState(boolean b, String errorMessageTemplate, long p1) { + if (!b) { + throw new IllegalStateException(lenientFormat(errorMessageTemplate, p1)); + } + } + + /** + * Ensures the truth of an expression involving the state of the calling instance, but not + * involving any parameters to the calling method. + * + *

See {@link #checkState(boolean, String, Object...)} for details. + * + * @since 20.0 (varargs overload since 2.0) + */ + public static void checkState( + boolean b, String errorMessageTemplate, Object p1) { + if (!b) { + throw new IllegalStateException(lenientFormat(errorMessageTemplate, p1)); + } + } + + /** + * Ensures the truth of an expression involving the state of the calling instance, but not + * involving any parameters to the calling method. + * + *

See {@link #checkState(boolean, String, Object...)} for details. + * + * @since 20.0 (varargs overload since 2.0) + */ + public static void checkState( + boolean b, String errorMessageTemplate, char p1, char p2) { + if (!b) { + throw new IllegalStateException(lenientFormat(errorMessageTemplate, p1, p2)); + } + } + + /** + * Ensures the truth of an expression involving the state of the calling instance, but not + * involving any parameters to the calling method. + * + *

See {@link #checkState(boolean, String, Object...)} for details. + * + * @since 20.0 (varargs overload since 2.0) + */ + public static void checkState(boolean b, String errorMessageTemplate, char p1, int p2) { + if (!b) { + throw new IllegalStateException(lenientFormat(errorMessageTemplate, p1, p2)); + } + } + + /** + * Ensures the truth of an expression involving the state of the calling instance, but not + * involving any parameters to the calling method. + * + *

See {@link #checkState(boolean, String, Object...)} for details. + * + * @since 20.0 (varargs overload since 2.0) + */ + public static void checkState( + boolean b, String errorMessageTemplate, char p1, long p2) { + if (!b) { + throw new IllegalStateException(lenientFormat(errorMessageTemplate, p1, p2)); + } + } + + /** + * Ensures the truth of an expression involving the state of the calling instance, but not + * involving any parameters to the calling method. + * + *

See {@link #checkState(boolean, String, Object...)} for details. + * + * @since 20.0 (varargs overload since 2.0) + */ + public static void checkState( + boolean b, String errorMessageTemplate, char p1, Object p2) { + if (!b) { + throw new IllegalStateException(lenientFormat(errorMessageTemplate, p1, p2)); + } + } + + /** + * Ensures the truth of an expression involving the state of the calling instance, but not + * involving any parameters to the calling method. + * + *

See {@link #checkState(boolean, String, Object...)} for details. + * + * @since 20.0 (varargs overload since 2.0) + */ + public static void checkState(boolean b, String errorMessageTemplate, int p1, char p2) { + if (!b) { + throw new IllegalStateException(lenientFormat(errorMessageTemplate, p1, p2)); + } + } + + /** + * Ensures the truth of an expression involving the state of the calling instance, but not + * involving any parameters to the calling method. + * + *

See {@link #checkState(boolean, String, Object...)} for details. + * + * @since 20.0 (varargs overload since 2.0) + */ + public static void checkState(boolean b, String errorMessageTemplate, int p1, int p2) { + if (!b) { + throw new IllegalStateException(lenientFormat(errorMessageTemplate, p1, p2)); + } + } + + /** + * Ensures the truth of an expression involving the state of the calling instance, but not + * involving any parameters to the calling method. + * + *

See {@link #checkState(boolean, String, Object...)} for details. + * + * @since 20.0 (varargs overload since 2.0) + */ + public static void checkState(boolean b, String errorMessageTemplate, int p1, long p2) { + if (!b) { + throw new IllegalStateException(lenientFormat(errorMessageTemplate, p1, p2)); + } + } + + /** + * Ensures the truth of an expression involving the state of the calling instance, but not + * involving any parameters to the calling method. + * + *

See {@link #checkState(boolean, String, Object...)} for details. + * + * @since 20.0 (varargs overload since 2.0) + */ + public static void checkState( + boolean b, String errorMessageTemplate, int p1, Object p2) { + if (!b) { + throw new IllegalStateException(lenientFormat(errorMessageTemplate, p1, p2)); + } + } + + /** + * Ensures the truth of an expression involving the state of the calling instance, but not + * involving any parameters to the calling method. + * + *

See {@link #checkState(boolean, String, Object...)} for details. + * + * @since 20.0 (varargs overload since 2.0) + */ + public static void checkState( + boolean b, String errorMessageTemplate, long p1, char p2) { + if (!b) { + throw new IllegalStateException(lenientFormat(errorMessageTemplate, p1, p2)); + } + } + + /** + * Ensures the truth of an expression involving the state of the calling instance, but not + * involving any parameters to the calling method. + * + *

See {@link #checkState(boolean, String, Object...)} for details. + * + * @since 20.0 (varargs overload since 2.0) + */ + public static void checkState(boolean b, String errorMessageTemplate, long p1, int p2) { + if (!b) { + throw new IllegalStateException(lenientFormat(errorMessageTemplate, p1, p2)); + } + } + + /** + * Ensures the truth of an expression involving the state of the calling instance, but not + * involving any parameters to the calling method. + * + *

See {@link #checkState(boolean, String, Object...)} for details. + * + * @since 20.0 (varargs overload since 2.0) + */ + public static void checkState( + boolean b, String errorMessageTemplate, long p1, long p2) { + if (!b) { + throw new IllegalStateException(lenientFormat(errorMessageTemplate, p1, p2)); + } + } + + /** + * Ensures the truth of an expression involving the state of the calling instance, but not + * involving any parameters to the calling method. + * + *

See {@link #checkState(boolean, String, Object...)} for details. + * + * @since 20.0 (varargs overload since 2.0) + */ + public static void checkState( + boolean b, String errorMessageTemplate, long p1, Object p2) { + if (!b) { + throw new IllegalStateException(lenientFormat(errorMessageTemplate, p1, p2)); + } + } + + /** + * Ensures the truth of an expression involving the state of the calling instance, but not + * involving any parameters to the calling method. + * + *

See {@link #checkState(boolean, String, Object...)} for details. + * + * @since 20.0 (varargs overload since 2.0) + */ + public static void checkState( + boolean b, String errorMessageTemplate, Object p1, char p2) { + if (!b) { + throw new IllegalStateException(lenientFormat(errorMessageTemplate, p1, p2)); + } + } + + /** + * Ensures the truth of an expression involving the state of the calling instance, but not + * involving any parameters to the calling method. + * + *

See {@link #checkState(boolean, String, Object...)} for details. + * + * @since 20.0 (varargs overload since 2.0) + */ + public static void checkState( + boolean b, String errorMessageTemplate, Object p1, int p2) { + if (!b) { + throw new IllegalStateException(lenientFormat(errorMessageTemplate, p1, p2)); + } + } + + /** + * Ensures the truth of an expression involving the state of the calling instance, but not + * involving any parameters to the calling method. + * + *

See {@link #checkState(boolean, String, Object...)} for details. + * + * @since 20.0 (varargs overload since 2.0) + */ + public static void checkState( + boolean b, String errorMessageTemplate, Object p1, long p2) { + if (!b) { + throw new IllegalStateException(lenientFormat(errorMessageTemplate, p1, p2)); + } + } + + /** + * Ensures the truth of an expression involving the state of the calling instance, but not + * involving any parameters to the calling method. + * + *

See {@link #checkState(boolean, String, Object...)} for details. + * + * @since 20.0 (varargs overload since 2.0) + */ + public static void checkState( + boolean b, String errorMessageTemplate, Object p1, Object p2) { + if (!b) { + throw new IllegalStateException(lenientFormat(errorMessageTemplate, p1, p2)); + } + } + + /** + * Ensures the truth of an expression involving the state of the calling instance, but not + * involving any parameters to the calling method. + * + *

See {@link #checkState(boolean, String, Object...)} for details. + * + * @since 20.0 (varargs overload since 2.0) + */ + public static void checkState( + boolean b, + String errorMessageTemplate, + Object p1, + Object p2, + Object p3) { + if (!b) { + throw new IllegalStateException(lenientFormat(errorMessageTemplate, p1, p2, p3)); + } + } + + /** + * Ensures the truth of an expression involving the state of the calling instance, but not + * involving any parameters to the calling method. + * + *

See {@link #checkState(boolean, String, Object...)} for details. + * + * @since 20.0 (varargs overload since 2.0) + */ + public static void checkState( + boolean b, + String errorMessageTemplate, + Object p1, + Object p2, + Object p3, + Object p4) { + if (!b) { + throw new IllegalStateException(lenientFormat(errorMessageTemplate, p1, p2, p3, p4)); + } + } + + /** + * Ensures that an object reference passed as a parameter to the calling method is not null. + * + * @param reference an object reference + * @return the non-null reference that was validated + * @throws NullPointerException if {@code reference} is null + * @see Verify#verifyNotNull Verify.verifyNotNull() + */ + public static T checkNotNull(T reference) { + if (reference == null) { + throw new NullPointerException(); + } + return reference; + } + + /** + * Ensures that an object reference passed as a parameter to the calling method is not null. + * + * @param reference an object reference + * @param errorMessage the exception message to use if the check fails; will be converted to a + * string using {@link String#valueOf(Object)} + * @return the non-null reference that was validated + * @throws NullPointerException if {@code reference} is null + * @see Verify#verifyNotNull Verify.verifyNotNull() + */ + public static T checkNotNull( + T reference, Object errorMessage) { + if (reference == null) { + throw new NullPointerException(String.valueOf(errorMessage)); + } + return reference; + } + + /** + * Ensures that an object reference passed as a parameter to the calling method is not null. + * + * @param reference an object reference + * @param errorMessageTemplate a template for the exception message should the check fail. The + * message is formed by replacing each {@code %s} placeholder in the template with an + * argument. These are matched by position - the first {@code %s} gets {@code + * errorMessageArgs[0]}, etc. Unmatched arguments will be appended to the formatted message in + * square braces. Unmatched placeholders will be left as-is. + * @param errorMessageArgs the arguments to be substituted into the message template. Arguments + * are converted to strings using {@link String#valueOf(Object)}. + * @return the non-null reference that was validated + * @throws NullPointerException if {@code reference} is null + * @see Verify#verifyNotNull Verify.verifyNotNull() + */ + public static T checkNotNull( + T reference, + String errorMessageTemplate, + Object ... errorMessageArgs) { + if (reference == null) { + throw new NullPointerException(lenientFormat(errorMessageTemplate, errorMessageArgs)); + } + return reference; + } + + /** + * Ensures that an object reference passed as a parameter to the calling method is not null. + * + *

See {@link #checkNotNull(Object, String, Object...)} for details. + * + * @since 20.0 (varargs overload since 2.0) + */ + public static T checkNotNull( + T obj, String errorMessageTemplate, char p1) { + if (obj == null) { + throw new NullPointerException(lenientFormat(errorMessageTemplate, p1)); + } + return obj; + } + + /** + * Ensures that an object reference passed as a parameter to the calling method is not null. + * + *

See {@link #checkNotNull(Object, String, Object...)} for details. + * + * @since 20.0 (varargs overload since 2.0) + */ + public static T checkNotNull( + T obj, String errorMessageTemplate, int p1) { + if (obj == null) { + throw new NullPointerException(lenientFormat(errorMessageTemplate, p1)); + } + return obj; + } + + /** + * Ensures that an object reference passed as a parameter to the calling method is not null. + * + *

See {@link #checkNotNull(Object, String, Object...)} for details. + * + * @since 20.0 (varargs overload since 2.0) + */ + public static T checkNotNull( + T obj, String errorMessageTemplate, long p1) { + if (obj == null) { + throw new NullPointerException(lenientFormat(errorMessageTemplate, p1)); + } + return obj; + } + + /** + * Ensures that an object reference passed as a parameter to the calling method is not null. + * + *

See {@link #checkNotNull(Object, String, Object...)} for details. + * + * @since 20.0 (varargs overload since 2.0) + */ + public static T checkNotNull( + T obj, String errorMessageTemplate, Object p1) { + if (obj == null) { + throw new NullPointerException(lenientFormat(errorMessageTemplate, p1)); + } + return obj; + } + + /** + * Ensures that an object reference passed as a parameter to the calling method is not null. + * + *

See {@link #checkNotNull(Object, String, Object...)} for details. + * + * @since 20.0 (varargs overload since 2.0) + */ + public static T checkNotNull( + T obj, String errorMessageTemplate, char p1, char p2) { + if (obj == null) { + throw new NullPointerException(lenientFormat(errorMessageTemplate, p1, p2)); + } + return obj; + } + + /** + * Ensures that an object reference passed as a parameter to the calling method is not null. + * + *

See {@link #checkNotNull(Object, String, Object...)} for details. + * + * @since 20.0 (varargs overload since 2.0) + */ + public static T checkNotNull( + T obj, String errorMessageTemplate, char p1, int p2) { + if (obj == null) { + throw new NullPointerException(lenientFormat(errorMessageTemplate, p1, p2)); + } + return obj; + } + + /** + * Ensures that an object reference passed as a parameter to the calling method is not null. + * + *

See {@link #checkNotNull(Object, String, Object...)} for details. + * + * @since 20.0 (varargs overload since 2.0) + */ + public static T checkNotNull( + T obj, String errorMessageTemplate, char p1, long p2) { + if (obj == null) { + throw new NullPointerException(lenientFormat(errorMessageTemplate, p1, p2)); + } + return obj; + } + + /** + * Ensures that an object reference passed as a parameter to the calling method is not null. + * + *

See {@link #checkNotNull(Object, String, Object...)} for details. + * + * @since 20.0 (varargs overload since 2.0) + */ + public static T checkNotNull( + T obj, String errorMessageTemplate, char p1, Object p2) { + if (obj == null) { + throw new NullPointerException(lenientFormat(errorMessageTemplate, p1, p2)); + } + return obj; + } + + /** + * Ensures that an object reference passed as a parameter to the calling method is not null. + * + *

See {@link #checkNotNull(Object, String, Object...)} for details. + * + * @since 20.0 (varargs overload since 2.0) + */ + public static T checkNotNull( + T obj, String errorMessageTemplate, int p1, char p2) { + if (obj == null) { + throw new NullPointerException(lenientFormat(errorMessageTemplate, p1, p2)); + } + return obj; + } + + /** + * Ensures that an object reference passed as a parameter to the calling method is not null. + * + *

See {@link #checkNotNull(Object, String, Object...)} for details. + * + * @since 20.0 (varargs overload since 2.0) + */ + public static T checkNotNull( + T obj, String errorMessageTemplate, int p1, int p2) { + if (obj == null) { + throw new NullPointerException(lenientFormat(errorMessageTemplate, p1, p2)); + } + return obj; + } + + /** + * Ensures that an object reference passed as a parameter to the calling method is not null. + * + *

See {@link #checkNotNull(Object, String, Object...)} for details. + * + * @since 20.0 (varargs overload since 2.0) + */ + public static T checkNotNull( + T obj, String errorMessageTemplate, int p1, long p2) { + if (obj == null) { + throw new NullPointerException(lenientFormat(errorMessageTemplate, p1, p2)); + } + return obj; + } + + /** + * Ensures that an object reference passed as a parameter to the calling method is not null. + * + *

See {@link #checkNotNull(Object, String, Object...)} for details. + * + * @since 20.0 (varargs overload since 2.0) + */ + public static T checkNotNull( + T obj, String errorMessageTemplate, int p1, Object p2) { + if (obj == null) { + throw new NullPointerException(lenientFormat(errorMessageTemplate, p1, p2)); + } + return obj; + } + + /** + * Ensures that an object reference passed as a parameter to the calling method is not null. + * + *

See {@link #checkNotNull(Object, String, Object...)} for details. + * + * @since 20.0 (varargs overload since 2.0) + */ + public static T checkNotNull( + T obj, String errorMessageTemplate, long p1, char p2) { + if (obj == null) { + throw new NullPointerException(lenientFormat(errorMessageTemplate, p1, p2)); + } + return obj; + } + + /** + * Ensures that an object reference passed as a parameter to the calling method is not null. + * + *

See {@link #checkNotNull(Object, String, Object...)} for details. + * + * @since 20.0 (varargs overload since 2.0) + */ + public static T checkNotNull( + T obj, String errorMessageTemplate, long p1, int p2) { + if (obj == null) { + throw new NullPointerException(lenientFormat(errorMessageTemplate, p1, p2)); + } + return obj; + } + + /** + * Ensures that an object reference passed as a parameter to the calling method is not null. + * + *

See {@link #checkNotNull(Object, String, Object...)} for details. + * + * @since 20.0 (varargs overload since 2.0) + */ + public static T checkNotNull( + T obj, String errorMessageTemplate, long p1, long p2) { + if (obj == null) { + throw new NullPointerException(lenientFormat(errorMessageTemplate, p1, p2)); + } + return obj; + } + + /** + * Ensures that an object reference passed as a parameter to the calling method is not null. + * + *

See {@link #checkNotNull(Object, String, Object...)} for details. + * + * @since 20.0 (varargs overload since 2.0) + */ + public static T checkNotNull( + T obj, String errorMessageTemplate, long p1, Object p2) { + if (obj == null) { + throw new NullPointerException(lenientFormat(errorMessageTemplate, p1, p2)); + } + return obj; + } + + /** + * Ensures that an object reference passed as a parameter to the calling method is not null. + * + *

See {@link #checkNotNull(Object, String, Object...)} for details. + * + * @since 20.0 (varargs overload since 2.0) + */ + public static T checkNotNull( + T obj, String errorMessageTemplate, Object p1, char p2) { + if (obj == null) { + throw new NullPointerException(lenientFormat(errorMessageTemplate, p1, p2)); + } + return obj; + } + + /** + * Ensures that an object reference passed as a parameter to the calling method is not null. + * + *

See {@link #checkNotNull(Object, String, Object...)} for details. + * + * @since 20.0 (varargs overload since 2.0) + */ + public static T checkNotNull( + T obj, String errorMessageTemplate, Object p1, int p2) { + if (obj == null) { + throw new NullPointerException(lenientFormat(errorMessageTemplate, p1, p2)); + } + return obj; + } + + /** + * Ensures that an object reference passed as a parameter to the calling method is not null. + * + *

See {@link #checkNotNull(Object, String, Object...)} for details. + * + * @since 20.0 (varargs overload since 2.0) + */ + public static T checkNotNull( + T obj, String errorMessageTemplate, Object p1, long p2) { + if (obj == null) { + throw new NullPointerException(lenientFormat(errorMessageTemplate, p1, p2)); + } + return obj; + } + + /** + * Ensures that an object reference passed as a parameter to the calling method is not null. + * + *

See {@link #checkNotNull(Object, String, Object...)} for details. + * + * @since 20.0 (varargs overload since 2.0) + */ + public static T checkNotNull( + T obj, String errorMessageTemplate, Object p1, Object p2) { + if (obj == null) { + throw new NullPointerException(lenientFormat(errorMessageTemplate, p1, p2)); + } + return obj; + } + + /** + * Ensures that an object reference passed as a parameter to the calling method is not null. + * + *

See {@link #checkNotNull(Object, String, Object...)} for details. + * + * @since 20.0 (varargs overload since 2.0) + */ + public static T checkNotNull( + T obj, + String errorMessageTemplate, + Object p1, + Object p2, + Object p3) { + if (obj == null) { + throw new NullPointerException(lenientFormat(errorMessageTemplate, p1, p2, p3)); + } + return obj; + } + + /** + * Ensures that an object reference passed as a parameter to the calling method is not null. + * + *

See {@link #checkNotNull(Object, String, Object...)} for details. + * + * @since 20.0 (varargs overload since 2.0) + */ + public static T checkNotNull( + T obj, + String errorMessageTemplate, + Object p1, + Object p2, + Object p3, + Object p4) { + if (obj == null) { + throw new NullPointerException(lenientFormat(errorMessageTemplate, p1, p2, p3, p4)); + } + return obj; + } + + /* + * All recent hotspots (as of 2009) *really* like to have the natural code + * + * if (guardExpression) { + * throw new BadException(messageExpression); + * } + * + * refactored so that messageExpression is moved to a separate String-returning method. + * + * if (guardExpression) { + * throw new BadException(badMsg(...)); + * } + * + * The alternative natural refactorings into void or Exception-returning methods are much slower. + * This is a big deal - we're talking factors of 2-8 in microbenchmarks, not just 10-20%. (This is + * a hotspot optimizer bug, which should be fixed, but that's a separate, big project). + * + * The coding pattern above is heavily used in java.util, e.g. in ArrayList. There is a + * RangeCheckMicroBenchmark in the JDK that was used to test this. + * + * But the methods in this class want to throw different exceptions, depending on the args, so it + * appears that this pattern is not directly applicable. But we can use the ridiculous, devious + * trick of throwing an exception in the middle of the construction of another exception. Hotspot + * is fine with that. + */ + + /** + * Ensures that {@code index} specifies a valid element in an array, list or string of size + * {@code size}. An element index may range from zero, inclusive, to {@code size}, exclusive. + * + * @param index a user-supplied index identifying an element of an array, list or string + * @param size the size of that array, list or string + * @return the value of {@code index} + * @throws IndexOutOfBoundsException if {@code index} is negative or is not less than {@code size} + * @throws IllegalArgumentException if {@code size} is negative + */ + public static int checkElementIndex(int index, int size) { + return checkElementIndex(index, size, "index"); + } + + /** + * Ensures that {@code index} specifies a valid element in an array, list or string of size + * {@code size}. An element index may range from zero, inclusive, to {@code size}, exclusive. + * + * @param index a user-supplied index identifying an element of an array, list or string + * @param size the size of that array, list or string + * @param desc the text to use to describe this index in an error message + * @return the value of {@code index} + * @throws IndexOutOfBoundsException if {@code index} is negative or is not less than {@code size} + * @throws IllegalArgumentException if {@code size} is negative + */ + public static int checkElementIndex(int index, int size, String desc) { + // Carefully optimized for execution by hotspot (explanatory comment above) + if (index < 0 || index >= size) { + throw new IndexOutOfBoundsException(badElementIndex(index, size, desc)); + } + return index; + } + + private static String badElementIndex(int index, int size, String desc) { + if (index < 0) { + return lenientFormat("%s (%s) must not be negative", desc, index); + } else if (size < 0) { + throw new IllegalArgumentException("negative size: " + size); + } else { // index >= size + return lenientFormat("%s (%s) must be less than size (%s)", desc, index, size); + } + } + + /** + * Ensures that {@code index} specifies a valid position in an array, list or string of + * size {@code size}. A position index may range from zero to {@code size}, inclusive. + * + * @param index a user-supplied index identifying a position in an array, list or string + * @param size the size of that array, list or string + * @return the value of {@code index} + * @throws IndexOutOfBoundsException if {@code index} is negative or is greater than {@code size} + * @throws IllegalArgumentException if {@code size} is negative + */ + + public static int checkPositionIndex(int index, int size) { + return checkPositionIndex(index, size, "index"); + } + + /** + * Ensures that {@code index} specifies a valid position in an array, list or string of + * size {@code size}. A position index may range from zero to {@code size}, inclusive. + * + * @param index a user-supplied index identifying a position in an array, list or string + * @param size the size of that array, list or string + * @param desc the text to use to describe this index in an error message + * @return the value of {@code index} + * @throws IndexOutOfBoundsException if {@code index} is negative or is greater than {@code size} + * @throws IllegalArgumentException if {@code size} is negative + */ + + public static int checkPositionIndex(int index, int size, String desc) { + // Carefully optimized for execution by hotspot (explanatory comment above) + if (index < 0 || index > size) { + throw new IndexOutOfBoundsException(badPositionIndex(index, size, desc)); + } + return index; + } + + private static String badPositionIndex(int index, int size, String desc) { + if (index < 0) { + return lenientFormat("%s (%s) must not be negative", desc, index); + } else if (size < 0) { + throw new IllegalArgumentException("negative size: " + size); + } else { // index > size + return lenientFormat("%s (%s) must not be greater than size (%s)", desc, index, size); + } + } + + /** + * Ensures that {@code start} and {@code end} specify valid positions in an array, list or + * string of size {@code size}, and are in order. A position index may range from zero to {@code + * size}, inclusive. + * + * @param start a user-supplied index identifying a starting position in an array, list or string + * @param end a user-supplied index identifying an ending position in an array, list or string + * @param size the size of that array, list or string + * @throws IndexOutOfBoundsException if either index is negative or is greater than {@code size}, + * or if {@code end} is less than {@code start} + * @throws IllegalArgumentException if {@code size} is negative + */ + public static void checkPositionIndexes(int start, int end, int size) { + // Carefully optimized for execution by hotspot (explanatory comment above) + if (start < 0 || end < start || end > size) { + throw new IndexOutOfBoundsException(badPositionIndexes(start, end, size)); + } + } + + private static String badPositionIndexes(int start, int end, int size) { + if (start < 0 || start > size) { + return badPositionIndex(start, size, "start index"); + } + if (end < 0 || end > size) { + return badPositionIndex(end, size, "end index"); + } + // end < start + return lenientFormat("end index (%s) must not be less than start index (%s)", end, start); + } +} diff --git a/src/main/java/com/google/common/base/Predicate.java b/src/main/java/com/google/common/base/Predicate.java new file mode 100644 index 0000000..2a8fa4b --- /dev/null +++ b/src/main/java/com/google/common/base/Predicate.java @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.base; + +import com.google.common.annotations.GwtCompatible; + +/** + * Legacy version of {@link java.util.function.Predicate java.util.function.Predicate}. Determines a + * true or false value for a given input. + * + *

As this interface extends {@code java.util.function.Predicate}, an instance of this type may + * be used as a {@code Predicate} directly. To use a {@code java.util.function.Predicate} where a + * {@code com.google.common.base.Predicate} is expected, use the method reference {@code + * predicate::test}. + * + *

This interface is now a legacy type. Use {@code java.util.function.Predicate} (or the + * appropriate primitive specialization such as {@code IntPredicate}) instead whenever possible. + * Otherwise, at least reduce explicit dependencies on this type by using lambda expressions + * or method references instead of classes, leaving your code easier to migrate in the future. + * + *

The {@link Predicates} class provides common predicates and related utilities. + * + *

See the Guava User Guide article on the use of {@code Predicate}. + * + * @author Kevin Bourrillion + * @since 2.0 + */ +@FunctionalInterface +@GwtCompatible +public interface Predicate extends java.util.function.Predicate { + /** + * Returns the result of applying this predicate to {@code input} (Java 8 users, see notes in the + * class documentation above). This method is generally expected, but not absolutely + * required, to have the following properties: + * + *

    + *
  • Its execution does not cause any observable side effects. + *
  • The computation is consistent with equals; that is, {@link Objects#equal + * Objects.equal}{@code (a, b)} implies that {@code predicate.apply(a) == + * predicate.apply(b))}. + *
+ * + * @throws NullPointerException if {@code input} is null and this predicate does not accept null + * arguments + */ + boolean apply(T input); + + /** + * Indicates whether another object is equal to this predicate. + * + *

Most implementations will have no reason to override the behavior of {@link Object#equals}. + * However, an implementation may also choose to return {@code true} whenever {@code object} is a + * {@link Predicate} that it considers interchangeable with this one. "Interchangeable" + * typically means that {@code this.apply(t) == that.apply(t)} for all {@code t} of type + * {@code T}). Note that a {@code false} result from this method does not imply that the + * predicates are known not to be interchangeable. + */ + @Override + boolean equals(Object object); + + @Override + default boolean test(T input) { + return apply(input); + } +} diff --git a/src/main/java/com/google/common/base/Predicates.java b/src/main/java/com/google/common/base/Predicates.java new file mode 100644 index 0000000..ae6eb90 --- /dev/null +++ b/src/main/java/com/google/common/base/Predicates.java @@ -0,0 +1,702 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.base; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.annotations.Beta; +import com.google.common.annotations.GwtCompatible; +import com.google.common.annotations.GwtIncompatible; +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.regex.Pattern; + +/** + * Static utility methods pertaining to {@code Predicate} instances. + * + *

All methods return serializable predicates as long as they're given serializable parameters. + * + *

See the Guava User Guide article on the use of {@code Predicate}. + * + * @author Kevin Bourrillion + * @since 2.0 + */ +@GwtCompatible(emulated = true) +public final class Predicates { + private Predicates() {} + + // TODO(kevinb): considering having these implement a VisitablePredicate + // interface which specifies an accept(PredicateVisitor) method. + + /** Returns a predicate that always evaluates to {@code true}. */ + @GwtCompatible(serializable = true) + public static Predicate alwaysTrue() { + return ObjectPredicate.ALWAYS_TRUE.withNarrowedType(); + } + + /** Returns a predicate that always evaluates to {@code false}. */ + @GwtCompatible(serializable = true) + public static Predicate alwaysFalse() { + return ObjectPredicate.ALWAYS_FALSE.withNarrowedType(); + } + + /** + * Returns a predicate that evaluates to {@code true} if the object reference being tested is + * null. + */ + @GwtCompatible(serializable = true) + public static Predicate isNull() { + return ObjectPredicate.IS_NULL.withNarrowedType(); + } + + /** + * Returns a predicate that evaluates to {@code true} if the object reference being tested is not + * null. + */ + @GwtCompatible(serializable = true) + public static Predicate notNull() { + return ObjectPredicate.NOT_NULL.withNarrowedType(); + } + + /** + * Returns a predicate that evaluates to {@code true} if the given predicate evaluates to {@code + * false}. + */ + public static Predicate not(Predicate predicate) { + return new NotPredicate(predicate); + } + + /** + * Returns a predicate that evaluates to {@code true} if each of its components evaluates to + * {@code true}. The components are evaluated in order, and evaluation will be "short-circuited" + * as soon as a false predicate is found. It defensively copies the iterable passed in, so future + * changes to it won't alter the behavior of this predicate. If {@code components} is empty, the + * returned predicate will always evaluate to {@code true}. + */ + public static Predicate and(Iterable> components) { + return new AndPredicate(defensiveCopy(components)); + } + + /** + * Returns a predicate that evaluates to {@code true} if each of its components evaluates to + * {@code true}. The components are evaluated in order, and evaluation will be "short-circuited" + * as soon as a false predicate is found. It defensively copies the array passed in, so future + * changes to it won't alter the behavior of this predicate. If {@code components} is empty, the + * returned predicate will always evaluate to {@code true}. + */ + @SafeVarargs + public static Predicate and(Predicate... components) { + return new AndPredicate(defensiveCopy(components)); + } + + /** + * Returns a predicate that evaluates to {@code true} if both of its components evaluate to {@code + * true}. The components are evaluated in order, and evaluation will be "short-circuited" as soon + * as a false predicate is found. + */ + public static Predicate and(Predicate first, Predicate second) { + return new AndPredicate(Predicates.asList(checkNotNull(first), checkNotNull(second))); + } + + /** + * Returns a predicate that evaluates to {@code true} if any one of its components evaluates to + * {@code true}. The components are evaluated in order, and evaluation will be "short-circuited" + * as soon as a true predicate is found. It defensively copies the iterable passed in, so future + * changes to it won't alter the behavior of this predicate. If {@code components} is empty, the + * returned predicate will always evaluate to {@code false}. + */ + public static Predicate or(Iterable> components) { + return new OrPredicate(defensiveCopy(components)); + } + + /** + * Returns a predicate that evaluates to {@code true} if any one of its components evaluates to + * {@code true}. The components are evaluated in order, and evaluation will be "short-circuited" + * as soon as a true predicate is found. It defensively copies the array passed in, so future + * changes to it won't alter the behavior of this predicate. If {@code components} is empty, the + * returned predicate will always evaluate to {@code false}. + */ + @SafeVarargs + public static Predicate or(Predicate... components) { + return new OrPredicate(defensiveCopy(components)); + } + + /** + * Returns a predicate that evaluates to {@code true} if either of its components evaluates to + * {@code true}. The components are evaluated in order, and evaluation will be "short-circuited" + * as soon as a true predicate is found. + */ + public static Predicate or(Predicate first, Predicate second) { + return new OrPredicate(Predicates.asList(checkNotNull(first), checkNotNull(second))); + } + + /** + * Returns a predicate that evaluates to {@code true} if the object being tested {@code equals()} + * the given target or both are null. + */ + public static Predicate equalTo(T target) { + return (target == null) ? Predicates.isNull() : new IsEqualToPredicate(target); + } + + /** + * Returns a predicate that evaluates to {@code true} if the object being tested is an instance of + * the given class. If the object being tested is {@code null} this predicate evaluates to {@code + * false}. + * + *

If you want to filter an {@code Iterable} to narrow its type, consider using {@link + * com.google.common.collect.Iterables#filter(Iterable, Class)} in preference. + * + *

Warning: contrary to the typical assumptions about predicates (as documented at + * {@link Predicate#apply}), the returned predicate may not be consistent with equals. For + * example, {@code instanceOf(ArrayList.class)} will yield different results for the two equal + * instances {@code Lists.newArrayList(1)} and {@code Arrays.asList(1)}. + */ + @GwtIncompatible // Class.isInstance + public static Predicate instanceOf(Class clazz) { + return new InstanceOfPredicate(clazz); + } + + /** + * Returns a predicate that evaluates to {@code true} if the class being tested is assignable to + * (is a subtype of) {@code clazz}. Example: + * + *
{@code
+   * List> classes = Arrays.asList(
+   *     Object.class, String.class, Number.class, Long.class);
+   * return Iterables.filter(classes, subtypeOf(Number.class));
+   * }
+ * + * The code above returns an iterable containing {@code Number.class} and {@code Long.class}. + * + * @since 20.0 (since 10.0 under the incorrect name {@code assignableFrom}) + */ + @GwtIncompatible // Class.isAssignableFrom + @Beta + public static Predicate> subtypeOf(Class clazz) { + return new SubtypeOfPredicate(clazz); + } + + /** + * Returns a predicate that evaluates to {@code true} if the object reference being tested is a + * member of the given collection. It does not defensively copy the collection passed in, so + * future changes to it will alter the behavior of the predicate. + * + *

This method can technically accept any {@code Collection}, but using a typed collection + * helps prevent bugs. This approach doesn't block any potential users since it is always possible + * to use {@code Predicates.in()}. + * + * @param target the collection that may contain the function input + */ + public static Predicate in(Collection target) { + return new InPredicate(target); + } + + /** + * Returns the composition of a function and a predicate. For every {@code x}, the generated + * predicate returns {@code predicate(function(x))}. + * + * @return the composition of the provided function and predicate + */ + public static Predicate compose( + Predicate predicate, Function function) { + return new CompositionPredicate<>(predicate, function); + } + + /** + * Returns a predicate that evaluates to {@code true} if the {@code CharSequence} being tested + * contains any match for the given regular expression pattern. The test used is equivalent to + * {@code Pattern.compile(pattern).matcher(arg).find()} + * + * @throws IllegalArgumentException if the pattern is invalid + * @since 3.0 + */ + @GwtIncompatible // Only used by other GWT-incompatible code. + public static Predicate containsPattern(String pattern) { + return new ContainsPatternFromStringPredicate(pattern); + } + + /** + * Returns a predicate that evaluates to {@code true} if the {@code CharSequence} being tested + * contains any match for the given regular expression pattern. The test used is equivalent to + * {@code pattern.matcher(arg).find()} + * + * @since 3.0 + */ + @GwtIncompatible(value = "java.util.regex.Pattern") + public static Predicate contains(Pattern pattern) { + return new ContainsPatternPredicate(new JdkPattern(pattern)); + } + + // End public API, begin private implementation classes. + + // Package private for GWT serialization. + enum ObjectPredicate implements Predicate { + /** @see Predicates#alwaysTrue() */ + ALWAYS_TRUE { + @Override + public boolean apply(Object o) { + return true; + } + + @Override + public String toString() { + return "Predicates.alwaysTrue()"; + } + }, + /** @see Predicates#alwaysFalse() */ + ALWAYS_FALSE { + @Override + public boolean apply(Object o) { + return false; + } + + @Override + public String toString() { + return "Predicates.alwaysFalse()"; + } + }, + /** @see Predicates#isNull() */ + IS_NULL { + @Override + public boolean apply(Object o) { + return o == null; + } + + @Override + public String toString() { + return "Predicates.isNull()"; + } + }, + /** @see Predicates#notNull() */ + NOT_NULL { + @Override + public boolean apply(Object o) { + return o != null; + } + + @Override + public String toString() { + return "Predicates.notNull()"; + } + }; + + @SuppressWarnings("unchecked") // safe contravariant cast + Predicate withNarrowedType() { + return (Predicate) this; + } + } + + /** @see Predicates#not(Predicate) */ + private static class NotPredicate implements Predicate, Serializable { + final Predicate predicate; + + NotPredicate(Predicate predicate) { + this.predicate = checkNotNull(predicate); + } + + @Override + public boolean apply(T t) { + return !predicate.apply(t); + } + + @Override + public int hashCode() { + return ~predicate.hashCode(); + } + + @Override + public boolean equals(Object obj) { + if (obj instanceof NotPredicate) { + NotPredicate that = (NotPredicate) obj; + return predicate.equals(that.predicate); + } + return false; + } + + @Override + public String toString() { + return "Predicates.not(" + predicate + ")"; + } + + private static final long serialVersionUID = 0; + } + + /** @see Predicates#and(Iterable) */ + private static class AndPredicate implements Predicate, Serializable { + private final List> components; + + private AndPredicate(List> components) { + this.components = components; + } + + @Override + public boolean apply(T t) { + // Avoid using the Iterator to avoid generating garbage (issue 820). + for (int i = 0; i < components.size(); i++) { + if (!components.get(i).apply(t)) { + return false; + } + } + return true; + } + + @Override + public int hashCode() { + // add a random number to avoid collisions with OrPredicate + return components.hashCode() + 0x12472c2c; + } + + @Override + public boolean equals(Object obj) { + if (obj instanceof AndPredicate) { + AndPredicate that = (AndPredicate) obj; + return components.equals(that.components); + } + return false; + } + + @Override + public String toString() { + return toStringHelper("and", components); + } + + private static final long serialVersionUID = 0; + } + + /** @see Predicates#or(Iterable) */ + private static class OrPredicate implements Predicate, Serializable { + private final List> components; + + private OrPredicate(List> components) { + this.components = components; + } + + @Override + public boolean apply(T t) { + // Avoid using the Iterator to avoid generating garbage (issue 820). + for (int i = 0; i < components.size(); i++) { + if (components.get(i).apply(t)) { + return true; + } + } + return false; + } + + @Override + public int hashCode() { + // add a random number to avoid collisions with AndPredicate + return components.hashCode() + 0x053c91cf; + } + + @Override + public boolean equals(Object obj) { + if (obj instanceof OrPredicate) { + OrPredicate that = (OrPredicate) obj; + return components.equals(that.components); + } + return false; + } + + @Override + public String toString() { + return toStringHelper("or", components); + } + + private static final long serialVersionUID = 0; + } + + private static String toStringHelper(String methodName, Iterable components) { + StringBuilder builder = new StringBuilder("Predicates.").append(methodName).append('('); + boolean first = true; + for (Object o : components) { + if (!first) { + builder.append(','); + } + builder.append(o); + first = false; + } + return builder.append(')').toString(); + } + + /** @see Predicates#equalTo(Object) */ + private static class IsEqualToPredicate implements Predicate, Serializable { + private final T target; + + private IsEqualToPredicate(T target) { + this.target = target; + } + + @Override + public boolean apply(T t) { + return target.equals(t); + } + + @Override + public int hashCode() { + return target.hashCode(); + } + + @Override + public boolean equals(Object obj) { + if (obj instanceof IsEqualToPredicate) { + IsEqualToPredicate that = (IsEqualToPredicate) obj; + return target.equals(that.target); + } + return false; + } + + @Override + public String toString() { + return "Predicates.equalTo(" + target + ")"; + } + + private static final long serialVersionUID = 0; + } + + /** @see Predicates#instanceOf(Class) */ + @GwtIncompatible // Class.isInstance + private static class InstanceOfPredicate implements Predicate, Serializable { + private final Class clazz; + + private InstanceOfPredicate(Class clazz) { + this.clazz = checkNotNull(clazz); + } + + @Override + public boolean apply(Object o) { + return clazz.isInstance(o); + } + + @Override + public int hashCode() { + return clazz.hashCode(); + } + + @Override + public boolean equals(Object obj) { + if (obj instanceof InstanceOfPredicate) { + InstanceOfPredicate that = (InstanceOfPredicate) obj; + return clazz == that.clazz; + } + return false; + } + + @Override + public String toString() { + return "Predicates.instanceOf(" + clazz.getName() + ")"; + } + + private static final long serialVersionUID = 0; + } + + /** @see Predicates#subtypeOf(Class) */ + @GwtIncompatible // Class.isAssignableFrom + private static class SubtypeOfPredicate implements Predicate>, Serializable { + private final Class clazz; + + private SubtypeOfPredicate(Class clazz) { + this.clazz = checkNotNull(clazz); + } + + @Override + public boolean apply(Class input) { + return clazz.isAssignableFrom(input); + } + + @Override + public int hashCode() { + return clazz.hashCode(); + } + + @Override + public boolean equals(Object obj) { + if (obj instanceof SubtypeOfPredicate) { + SubtypeOfPredicate that = (SubtypeOfPredicate) obj; + return clazz == that.clazz; + } + return false; + } + + @Override + public String toString() { + return "Predicates.subtypeOf(" + clazz.getName() + ")"; + } + + private static final long serialVersionUID = 0; + } + + /** @see Predicates#in(Collection) */ + private static class InPredicate implements Predicate, Serializable { + private final Collection target; + + private InPredicate(Collection target) { + this.target = checkNotNull(target); + } + + @Override + public boolean apply(T t) { + try { + return target.contains(t); + } catch (NullPointerException | ClassCastException e) { + return false; + } + } + + @Override + public boolean equals(Object obj) { + if (obj instanceof InPredicate) { + InPredicate that = (InPredicate) obj; + return target.equals(that.target); + } + return false; + } + + @Override + public int hashCode() { + return target.hashCode(); + } + + @Override + public String toString() { + return "Predicates.in(" + target + ")"; + } + + private static final long serialVersionUID = 0; + } + + /** @see Predicates#compose(Predicate, Function) */ + private static class CompositionPredicate implements Predicate, Serializable { + final Predicate p; + final Function f; + + private CompositionPredicate(Predicate p, Function f) { + this.p = checkNotNull(p); + this.f = checkNotNull(f); + } + + @Override + public boolean apply(A a) { + return p.apply(f.apply(a)); + } + + @Override + public boolean equals(Object obj) { + if (obj instanceof CompositionPredicate) { + CompositionPredicate that = (CompositionPredicate) obj; + return f.equals(that.f) && p.equals(that.p); + } + return false; + } + + @Override + public int hashCode() { + return f.hashCode() ^ p.hashCode(); + } + + @Override + public String toString() { + // TODO(cpovirk): maybe make this look like the method call does ("Predicates.compose(...)") + return p + "(" + f + ")"; + } + + private static final long serialVersionUID = 0; + } + + /** @see Predicates#contains(Pattern) */ + @GwtIncompatible // Only used by other GWT-incompatible code. + private static class ContainsPatternPredicate implements Predicate, Serializable { + final CommonPattern pattern; + + ContainsPatternPredicate(CommonPattern pattern) { + this.pattern = checkNotNull(pattern); + } + + @Override + public boolean apply(CharSequence t) { + return pattern.matcher(t).find(); + } + + @Override + public int hashCode() { + // Pattern uses Object.hashCode, so we have to reach + // inside to build a hashCode consistent with equals. + + return Objects.hashCode(pattern.pattern(), pattern.flags()); + } + + @Override + public boolean equals(Object obj) { + if (obj instanceof ContainsPatternPredicate) { + ContainsPatternPredicate that = (ContainsPatternPredicate) obj; + + // Pattern uses Object (identity) equality, so we have to reach + // inside to compare individual fields. + return Objects.equal(pattern.pattern(), that.pattern.pattern()) + && pattern.flags() == that.pattern.flags(); + } + return false; + } + + @Override + public String toString() { + String patternString = + MoreObjects.toStringHelper(pattern) + .add("pattern", pattern.pattern()) + .add("pattern.flags", pattern.flags()) + .toString(); + return "Predicates.contains(" + patternString + ")"; + } + + private static final long serialVersionUID = 0; + } + + /** @see Predicates#containsPattern(String) */ + @GwtIncompatible // Only used by other GWT-incompatible code. + private static class ContainsPatternFromStringPredicate extends ContainsPatternPredicate { + + ContainsPatternFromStringPredicate(String string) { + super(Platform.compilePattern(string)); + } + + @Override + public String toString() { + return "Predicates.containsPattern(" + pattern.pattern() + ")"; + } + + private static final long serialVersionUID = 0; + } + + private static List> asList( + Predicate first, Predicate second) { + // TODO(kevinb): understand why we still get a warning despite @SafeVarargs! + return Arrays.>asList(first, second); + } + + private static List defensiveCopy(T... array) { + return defensiveCopy(Arrays.asList(array)); + } + + static List defensiveCopy(Iterable iterable) { + ArrayList list = new ArrayList(); + for (T element : iterable) { + list.add(checkNotNull(element)); + } + return list; + } +} diff --git a/src/main/java/com/google/common/base/Present.java b/src/main/java/com/google/common/base/Present.java new file mode 100644 index 0000000..ef9f45d --- /dev/null +++ b/src/main/java/com/google/common/base/Present.java @@ -0,0 +1,98 @@ +/* + * Copyright (C) 2011 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.base; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.annotations.GwtCompatible; +import java.util.Collections; +import java.util.Set; + +/** Implementation of an {@link Optional} containing a reference. */ +@GwtCompatible +final class Present extends Optional { + private final T reference; + + Present(T reference) { + this.reference = reference; + } + + @Override + public boolean isPresent() { + return true; + } + + @Override + public T get() { + return reference; + } + + @Override + public T or(T defaultValue) { + checkNotNull(defaultValue, "use Optional.orNull() instead of Optional.or(null)"); + return reference; + } + + @Override + public Optional or(Optional secondChoice) { + checkNotNull(secondChoice); + return this; + } + + @Override + public T or(Supplier supplier) { + checkNotNull(supplier); + return reference; + } + + @Override + public T orNull() { + return reference; + } + + @Override + public Set asSet() { + return Collections.singleton(reference); + } + + @Override + public Optional transform(Function function) { + return new Present( + checkNotNull( + function.apply(reference), + "the Function passed to Optional.transform() must not return null.")); + } + + @Override + public boolean equals(Object object) { + if (object instanceof Present) { + Present other = (Present) object; + return reference.equals(other.reference); + } + return false; + } + + @Override + public int hashCode() { + return 0x598df91c + reference.hashCode(); + } + + @Override + public String toString() { + return "Optional.of(" + reference + ")"; + } + + private static final long serialVersionUID = 0; +} diff --git a/src/main/java/com/google/common/base/SmallCharMatcher.java b/src/main/java/com/google/common/base/SmallCharMatcher.java new file mode 100644 index 0000000..1e565c8 --- /dev/null +++ b/src/main/java/com/google/common/base/SmallCharMatcher.java @@ -0,0 +1,146 @@ +/* + * Copyright (C) 2012 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.base; + +import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.CharMatcher.NamedFastMatcher; +import java.util.BitSet; + +/** + * An immutable version of CharMatcher for smallish sets of characters that uses a hash table with + * linear probing to check for matches. + * + * @author Christopher Swenson + */ +@GwtIncompatible // no precomputation is done in GWT +final class SmallCharMatcher extends NamedFastMatcher { + static final int MAX_SIZE = 1023; + private final char[] table; + private final boolean containsZero; + private final long filter; + + private SmallCharMatcher(char[] table, long filter, boolean containsZero, String description) { + super(description); + this.table = table; + this.filter = filter; + this.containsZero = containsZero; + } + + private static final int C1 = 0xcc9e2d51; + private static final int C2 = 0x1b873593; + + /* + * This method was rewritten in Java from an intermediate step of the Murmur hash function in + * http://code.google.com/p/smhasher/source/browse/trunk/MurmurHash3.cpp, which contained the + * following header: + * + * MurmurHash3 was written by Austin Appleby, and is placed in the public domain. The author + * hereby disclaims copyright to this source code. + */ + static int smear(int hashCode) { + return C2 * Integer.rotateLeft(hashCode * C1, 15); + } + + private boolean checkFilter(int c) { + return 1 == (1 & (filter >> c)); + } + + // This is all essentially copied from ImmutableSet, but we have to duplicate because + // of dependencies. + + // Represents how tightly we can pack things, as a maximum. + private static final double DESIRED_LOAD_FACTOR = 0.5; + + /** + * Returns an array size suitable for the backing array of a hash table that uses open addressing + * with linear probing in its implementation. The returned size is the smallest power of two that + * can hold setSize elements with the desired load factor. + */ + @VisibleForTesting + static int chooseTableSize(int setSize) { + if (setSize == 1) { + return 2; + } + // Correct the size for open addressing to match desired load factor. + // Round up to the next highest power of 2. + int tableSize = Integer.highestOneBit(setSize - 1) << 1; + while (tableSize * DESIRED_LOAD_FACTOR < setSize) { + tableSize <<= 1; + } + return tableSize; + } + + static CharMatcher from(BitSet chars, String description) { + // Compute the filter. + long filter = 0; + int size = chars.cardinality(); + boolean containsZero = chars.get(0); + // Compute the hash table. + char[] table = new char[chooseTableSize(size)]; + int mask = table.length - 1; + for (int c = chars.nextSetBit(0); c != -1; c = chars.nextSetBit(c + 1)) { + // Compute the filter at the same time. + filter |= 1L << c; + int index = smear(c) & mask; + while (true) { + // Check for empty. + if (table[index] == 0) { + table[index] = (char) c; + break; + } + // Linear probing. + index = (index + 1) & mask; + } + } + return new SmallCharMatcher(table, filter, containsZero, description); + } + + @Override + public boolean matches(char c) { + if (c == 0) { + return containsZero; + } + if (!checkFilter(c)) { + return false; + } + int mask = table.length - 1; + int startingIndex = smear(c) & mask; + int index = startingIndex; + do { + if (table[index] == 0) { // Check for empty. + return false; + } else if (table[index] == c) { // Check for match. + return true; + } else { // Linear probing. + index = (index + 1) & mask; + } + // Check to see if we wrapped around the whole table. + } while (index != startingIndex); + return false; + } + + @Override + void setBits(BitSet table) { + if (containsZero) { + table.set(0); + } + for (char c : this.table) { + if (c != 0) { + table.set(c); + } + } + } +} diff --git a/src/main/java/com/google/common/base/Splitter.java b/src/main/java/com/google/common/base/Splitter.java new file mode 100644 index 0000000..9c6cba0 --- /dev/null +++ b/src/main/java/com/google/common/base/Splitter.java @@ -0,0 +1,633 @@ +/* + * Copyright (C) 2009 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.base; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.annotations.Beta; +import com.google.common.annotations.GwtCompatible; +import com.google.common.annotations.GwtIncompatible; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.regex.Pattern; +import java.util.stream.Stream; +import java.util.stream.StreamSupport; + +/** + * Extracts non-overlapping substrings from an input string, typically by recognizing appearances of + * a separator sequence. This separator can be specified as a single {@linkplain #on(char) + * character}, fixed {@linkplain #on(String) string}, {@linkplain #onPattern regular expression} or + * {@link #on(CharMatcher) CharMatcher} instance. Or, instead of using a separator at all, a + * splitter can extract adjacent substrings of a given {@linkplain #fixedLength fixed length}. + * + *

For example, this expression: + * + *

{@code
+ * Splitter.on(',').split("foo,bar,qux")
+ * }
+ * + * ... produces an {@code Iterable} containing {@code "foo"}, {@code "bar"} and {@code "qux"}, in + * that order. + * + *

By default, {@code Splitter}'s behavior is simplistic and unassuming. The following + * expression: + * + *

{@code
+ * Splitter.on(',').split(" foo,,,  bar ,")
+ * }
+ * + * ... yields the substrings {@code [" foo", "", "", " bar ", ""]}. If this is not the desired + * behavior, use configuration methods to obtain a new splitter instance with modified + * behavior: + * + *
{@code
+ * private static final Splitter MY_SPLITTER = Splitter.on(',')
+ *     .trimResults()
+ *     .omitEmptyStrings();
+ * }
+ * + *

Now {@code MY_SPLITTER.split("foo,,, bar ,")} returns just {@code ["foo", "bar"]}. Note that + * the order in which these configuration methods are called is never significant. + * + *

Warning: Splitter instances are immutable. Invoking a configuration method has no + * effect on the receiving instance; you must store and use the new splitter instance it returns + * instead. + * + *

{@code
+ * // Do NOT do this
+ * Splitter splitter = Splitter.on('/');
+ * splitter.trimResults(); // does nothing!
+ * return splitter.split("wrong / wrong / wrong");
+ * }
+ * + *

For separator-based splitters that do not use {@code omitEmptyStrings}, an input string + * containing {@code n} occurrences of the separator naturally yields an iterable of size {@code n + + * 1}. So if the separator does not occur anywhere in the input, a single substring is returned + * containing the entire input. Consequently, all splitters split the empty string to {@code [""]} + * (note: even fixed-length splitters). + * + *

Splitter instances are thread-safe immutable, and are therefore safe to store as {@code static + * final} constants. + * + *

The {@link Joiner} class provides the inverse operation to splitting, but note that a + * round-trip between the two should be assumed to be lossy. + * + *

Note: if {@link #fixedLength} is used in conjunction with {@link #limit}, the final + * split piece may be longer than the specified fixed length. This is because the splitter + * will stop splitting when the limit is reached, and just return the final piece as-is. + * + *

Exception: for consistency with separator-based splitters, {@code split("")} does not + * yield an empty iterable, but an iterable containing {@code ""}. This is the only case in which + * {@code Iterables.size(split(input))} does not equal {@code IntMath.divide(input.length(), + * length, CEILING)}. To avoid this behavior, use {@code omitEmptyStrings}. + * + * @param length the desired length of pieces after splitting, a positive integer + * @return a splitter, with default settings, that can split into fixed sized pieces + * @throws IllegalArgumentException if {@code length} is zero or negative + */ + public static Splitter fixedLength(final int length) { + checkArgument(length > 0, "The length may not be less than 1"); + + return new Splitter( + new Strategy() { + @Override + public SplittingIterator iterator(final Splitter splitter, CharSequence toSplit) { + return new SplittingIterator(splitter, toSplit) { + @Override + public int separatorStart(int start) { + int nextChunkStart = start + length; + return (nextChunkStart < toSplit.length() ? nextChunkStart : -1); + } + + @Override + public int separatorEnd(int separatorPosition) { + return separatorPosition; + } + }; + } + }); + } + + /** + * Returns a splitter that behaves equivalently to {@code this} splitter, but automatically omits + * empty strings from the results. For example, {@code + * Splitter.on(',').omitEmptyStrings().split(",a,,,b,c,,")} returns an iterable containing only + * {@code ["a", "b", "c"]}. + * + *

If either {@code trimResults} option is also specified when creating a splitter, that + * splitter always trims results first before checking for emptiness. So, for example, {@code + * Splitter.on(':').omitEmptyStrings().trimResults().split(": : : ")} returns an empty iterable. + * + *

Note that it is ordinarily not possible for {@link #split(CharSequence)} to return an empty + * iterable, but when using this option, it can (if the input sequence consists of nothing but + * separators). + * + * @return a splitter with the desired configuration + */ + public Splitter omitEmptyStrings() { + return new Splitter(strategy, true, trimmer, limit); + } + + /** + * Returns a splitter that behaves equivalently to {@code this} splitter but stops splitting after + * it reaches the limit. The limit defines the maximum number of items returned by the iterator, + * or the maximum size of the list returned by {@link #splitToList}. + * + *

For example, {@code Splitter.on(',').limit(3).split("a,b,c,d")} returns an iterable + * containing {@code ["a", "b", "c,d"]}. When omitting empty strings, the omitted strings do not + * count. Hence, {@code Splitter.on(',').limit(3).omitEmptyStrings().split("a,,,b,,,c,d")} returns + * an iterable containing {@code ["a", "b", "c,d"}. When trim is requested, all entries are + * trimmed, including the last. Hence {@code Splitter.on(',').limit(3).trimResults().split(" a , b + * , c , d ")} results in {@code ["a", "b", "c , d"]}. + * + * @param maxItems the maximum number of items returned + * @return a splitter with the desired configuration + * @since 9.0 + */ + public Splitter limit(int maxItems) { + checkArgument(maxItems > 0, "must be greater than zero: %s", maxItems); + return new Splitter(strategy, omitEmptyStrings, trimmer, maxItems); + } + + /** + * Returns a splitter that behaves equivalently to {@code this} splitter, but automatically + * removes leading and trailing {@linkplain CharMatcher#whitespace whitespace} from each returned + * substring; equivalent to {@code trimResults(CharMatcher.whitespace())}. For example, {@code + * Splitter.on(',').trimResults().split(" a, b ,c ")} returns an iterable containing {@code ["a", + * "b", "c"]}. + * + * @return a splitter with the desired configuration + */ + public Splitter trimResults() { + return trimResults(CharMatcher.whitespace()); + } + + /** + * Returns a splitter that behaves equivalently to {@code this} splitter, but removes all leading + * or trailing characters matching the given {@code CharMatcher} from each returned substring. For + * example, {@code Splitter.on(',').trimResults(CharMatcher.is('_')).split("_a ,_b_ ,c__")} + * returns an iterable containing {@code ["a ", "b_ ", "c"]}. + * + * @param trimmer a {@link CharMatcher} that determines whether a character should be removed from + * the beginning/end of a subsequence + * @return a splitter with the desired configuration + */ + // TODO(kevinb): throw if a trimmer was already specified! + public Splitter trimResults(CharMatcher trimmer) { + checkNotNull(trimmer); + return new Splitter(strategy, omitEmptyStrings, trimmer, limit); + } + + /** + * Splits {@code sequence} into string components and makes them available through an {@link + * Iterator}, which may be lazily evaluated. If you want an eagerly computed {@link List}, use + * {@link #splitToList(CharSequence)}. Java 8 users may prefer {@link #splitToStream} instead. + * + * @param sequence the sequence of characters to split + * @return an iteration over the segments split from the parameter + */ + public Iterable split(final CharSequence sequence) { + checkNotNull(sequence); + + return new Iterable() { + @Override + public Iterator iterator() { + return splittingIterator(sequence); + } + + @Override + public String toString() { + return Joiner.on(", ") + .appendTo(new StringBuilder().append('['), this) + .append(']') + .toString(); + } + }; + } + + private Iterator splittingIterator(CharSequence sequence) { + return strategy.iterator(this, sequence); + } + + /** + * Splits {@code sequence} into string components and returns them as an immutable list. If you + * want an {@link Iterable} which may be lazily evaluated, use {@link #split(CharSequence)}. + * + * @param sequence the sequence of characters to split + * @return an immutable list of the segments split from the parameter + * @since 15.0 + */ + public List splitToList(CharSequence sequence) { + checkNotNull(sequence); + + Iterator iterator = splittingIterator(sequence); + List result = new ArrayList<>(); + + while (iterator.hasNext()) { + result.add(iterator.next()); + } + + return Collections.unmodifiableList(result); + } + + /** + * Splits {@code sequence} into string components and makes them available through an {@link + * Stream}, which may be lazily evaluated. If you want an eagerly computed {@link List}, use + * {@link #splitToList(CharSequence)}. + * + * @param sequence the sequence of characters to split + * @return a stream over the segments split from the parameter + * @since NEXT + */ + @Beta + public Stream splitToStream(CharSequence sequence) { + // Can't use Streams.stream() from base + return StreamSupport.stream(split(sequence).spliterator(), false); + } + + /** + * Returns a {@code MapSplitter} which splits entries based on this splitter, and splits entries + * into keys and values using the specified separator. + * + * @since 10.0 + */ + @Beta + public MapSplitter withKeyValueSeparator(String separator) { + return withKeyValueSeparator(on(separator)); + } + + /** + * Returns a {@code MapSplitter} which splits entries based on this splitter, and splits entries + * into keys and values using the specified separator. + * + * @since 14.0 + */ + @Beta + public MapSplitter withKeyValueSeparator(char separator) { + return withKeyValueSeparator(on(separator)); + } + + /** + * Returns a {@code MapSplitter} which splits entries based on this splitter, and splits entries + * into keys and values using the specified key-value splitter. + * + *

Note: Any configuration option configured on this splitter, such as {@link #trimResults}, + * does not change the behavior of the {@code keyValueSplitter}. + * + *

Example: + * + *

{@code
+   * String toSplit = " x -> y, z-> a ";
+   * Splitter outerSplitter = Splitter.on(',').trimResults();
+   * MapSplitter mapSplitter = outerSplitter.withKeyValueSeparator(Splitter.on("->"));
+   * Map result = mapSplitter.split(toSplit);
+   * assertThat(result).isEqualTo(ImmutableMap.of("x ", " y", "z", " a"));
+   * }
+ * + * @since 10.0 + */ + @Beta + public MapSplitter withKeyValueSeparator(Splitter keyValueSplitter) { + return new MapSplitter(this, keyValueSplitter); + } + + /** + * An object that splits strings into maps as {@code Splitter} splits iterables and lists. Like + * {@code Splitter}, it is thread-safe and immutable. The common way to build instances is by + * providing an additional {@linkplain Splitter#withKeyValueSeparator key-value separator} to + * {@link Splitter}. + * + * @since 10.0 + */ + @Beta + public static final class MapSplitter { + private static final String INVALID_ENTRY_MESSAGE = "Chunk [%s] is not a valid entry"; + private final Splitter outerSplitter; + private final Splitter entrySplitter; + + private MapSplitter(Splitter outerSplitter, Splitter entrySplitter) { + this.outerSplitter = outerSplitter; // only "this" is passed + this.entrySplitter = checkNotNull(entrySplitter); + } + + /** + * Splits {@code sequence} into substrings, splits each substring into an entry, and returns an + * unmodifiable map with each of the entries. For example, {@code + * Splitter.on(';').trimResults().withKeyValueSeparator("=>").split("a=>b ; c=>b")} will return + * a mapping from {@code "a"} to {@code "b"} and {@code "c"} to {@code "b"}. + * + *

The returned map preserves the order of the entries from {@code sequence}. + * + * @throws IllegalArgumentException if the specified sequence does not split into valid map + * entries, or if there are duplicate keys + */ + public Map split(CharSequence sequence) { + Map map = new LinkedHashMap<>(); + for (String entry : outerSplitter.split(sequence)) { + Iterator entryFields = entrySplitter.splittingIterator(entry); + + checkArgument(entryFields.hasNext(), INVALID_ENTRY_MESSAGE, entry); + String key = entryFields.next(); + checkArgument(!map.containsKey(key), "Duplicate key [%s] found.", key); + + checkArgument(entryFields.hasNext(), INVALID_ENTRY_MESSAGE, entry); + String value = entryFields.next(); + map.put(key, value); + + checkArgument(!entryFields.hasNext(), INVALID_ENTRY_MESSAGE, entry); + } + return Collections.unmodifiableMap(map); + } + } + + private interface Strategy { + Iterator iterator(Splitter splitter, CharSequence toSplit); + } + + private abstract static class SplittingIterator extends AbstractIterator { + final CharSequence toSplit; + final CharMatcher trimmer; + final boolean omitEmptyStrings; + + /** + * Returns the first index in {@code toSplit} at or after {@code start} that contains the + * separator. + */ + abstract int separatorStart(int start); + + /** + * Returns the first index in {@code toSplit} after {@code separatorPosition} that does not + * contain a separator. This method is only invoked after a call to {@code separatorStart}. + */ + abstract int separatorEnd(int separatorPosition); + + int offset = 0; + int limit; + + protected SplittingIterator(Splitter splitter, CharSequence toSplit) { + this.trimmer = splitter.trimmer; + this.omitEmptyStrings = splitter.omitEmptyStrings; + this.limit = splitter.limit; + this.toSplit = toSplit; + } + + @Override + protected String computeNext() { + /* + * The returned string will be from the end of the last match to the beginning of the next + * one. nextStart is the start position of the returned substring, while offset is the place + * to start looking for a separator. + */ + int nextStart = offset; + while (offset != -1) { + int start = nextStart; + int end; + + int separatorPosition = separatorStart(offset); + if (separatorPosition == -1) { + end = toSplit.length(); + offset = -1; + } else { + end = separatorPosition; + offset = separatorEnd(separatorPosition); + } + if (offset == nextStart) { + /* + * This occurs when some pattern has an empty match, even if it doesn't match the empty + * string -- for example, if it requires lookahead or the like. The offset must be + * increased to look for separators beyond this point, without changing the start position + * of the next returned substring -- so nextStart stays the same. + */ + offset++; + if (offset > toSplit.length()) { + offset = -1; + } + continue; + } + + while (start < end && trimmer.matches(toSplit.charAt(start))) { + start++; + } + while (end > start && trimmer.matches(toSplit.charAt(end - 1))) { + end--; + } + + if (omitEmptyStrings && start == end) { + // Don't include the (unused) separator in next split string. + nextStart = offset; + continue; + } + + if (limit == 1) { + // The limit has been reached, return the rest of the string as the + // final item. This is tested after empty string removal so that + // empty strings do not count towards the limit. + end = toSplit.length(); + offset = -1; + // Since we may have changed the end, we need to trim it again. + while (end > start && trimmer.matches(toSplit.charAt(end - 1))) { + end--; + } + } else { + limit--; + } + + return toSplit.subSequence(start, end).toString(); + } + return endOfData(); + } + } +} diff --git a/src/main/java/com/google/common/base/StandardSystemProperty.java b/src/main/java/com/google/common/base/StandardSystemProperty.java new file mode 100644 index 0000000..4ffbf66 --- /dev/null +++ b/src/main/java/com/google/common/base/StandardSystemProperty.java @@ -0,0 +1,136 @@ +/* + * Copyright (C) 2012 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.base; + +import com.google.common.annotations.GwtIncompatible; + +/** + * Represents a {@linkplain System#getProperties() standard system property}. + * + * @author Kurt Alfred Kluever + * @since 15.0 + */ +@GwtIncompatible // java.lang.System#getProperty +public enum StandardSystemProperty { + + /** Java Runtime Environment version. */ + JAVA_VERSION("java.version"), + + /** Java Runtime Environment vendor. */ + JAVA_VENDOR("java.vendor"), + + /** Java vendor URL. */ + JAVA_VENDOR_URL("java.vendor.url"), + + /** Java installation directory. */ + JAVA_HOME("java.home"), + + /** Java Virtual Machine specification version. */ + JAVA_VM_SPECIFICATION_VERSION("java.vm.specification.version"), + + /** Java Virtual Machine specification vendor. */ + JAVA_VM_SPECIFICATION_VENDOR("java.vm.specification.vendor"), + + /** Java Virtual Machine specification name. */ + JAVA_VM_SPECIFICATION_NAME("java.vm.specification.name"), + + /** Java Virtual Machine implementation version. */ + JAVA_VM_VERSION("java.vm.version"), + + /** Java Virtual Machine implementation vendor. */ + JAVA_VM_VENDOR("java.vm.vendor"), + + /** Java Virtual Machine implementation name. */ + JAVA_VM_NAME("java.vm.name"), + + /** Java Runtime Environment specification version. */ + JAVA_SPECIFICATION_VERSION("java.specification.version"), + + /** Java Runtime Environment specification vendor. */ + JAVA_SPECIFICATION_VENDOR("java.specification.vendor"), + + /** Java Runtime Environment specification name. */ + JAVA_SPECIFICATION_NAME("java.specification.name"), + + /** Java class format version number. */ + JAVA_CLASS_VERSION("java.class.version"), + + /** Java class path. */ + JAVA_CLASS_PATH("java.class.path"), + + /** List of paths to search when loading libraries. */ + JAVA_LIBRARY_PATH("java.library.path"), + + /** Default temp file path. */ + JAVA_IO_TMPDIR("java.io.tmpdir"), + + /** Name of JIT compiler to use. */ + JAVA_COMPILER("java.compiler"), + + /** Path of extension directory or directories. */ + JAVA_EXT_DIRS("java.ext.dirs"), + + /** Operating system name. */ + OS_NAME("os.name"), + + /** Operating system architecture. */ + OS_ARCH("os.arch"), + + /** Operating system version. */ + OS_VERSION("os.version"), + + /** File separator ("/" on UNIX). */ + FILE_SEPARATOR("file.separator"), + + /** Path separator (":" on UNIX). */ + PATH_SEPARATOR("path.separator"), + + /** Line separator ("\n" on UNIX). */ + LINE_SEPARATOR("line.separator"), + + /** User's account name. */ + USER_NAME("user.name"), + + /** User's home directory. */ + USER_HOME("user.home"), + + /** User's current working directory. */ + USER_DIR("user.dir"); + + private final String key; + + StandardSystemProperty(String key) { + this.key = key; + } + + /** Returns the key used to lookup this system property. */ + public String key() { + return key; + } + + /** + * Returns the current value for this system property by delegating to {@link + * System#getProperty(String)}. + */ + public String value() { + return System.getProperty(key); + } + + /** Returns a string representation of this system property. */ + @Override + public String toString() { + return key() + "=" + value(); + } +} diff --git a/src/main/java/com/google/common/base/Stopwatch.java b/src/main/java/com/google/common/base/Stopwatch.java new file mode 100644 index 0000000..f69407b --- /dev/null +++ b/src/main/java/com/google/common/base/Stopwatch.java @@ -0,0 +1,265 @@ +/* + * Copyright (C) 2008 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.base; + +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.base.Preconditions.checkState; +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 com.google.common.annotations.GwtCompatible; +import com.google.common.annotations.GwtIncompatible; +import java.time.Duration; +import java.util.concurrent.TimeUnit; + +/** + * An object that measures elapsed time in nanoseconds. It is useful to measure elapsed time using + * this class instead of direct calls to {@link System#nanoTime} for a few reasons: + * + *

    + *
  • An alternate time source can be substituted, for testing or performance reasons. + *
  • As documented by {@code nanoTime}, the value returned has no absolute meaning, and can only + * be interpreted as relative to another timestamp returned by {@code nanoTime} at a different + * time. {@code Stopwatch} is a more effective abstraction because it exposes only these + * relative values, not the absolute ones. + *
+ * + *

Basic usage: + * + *

{@code
+ * Stopwatch stopwatch = Stopwatch.createStarted();
+ * doSomething();
+ * stopwatch.stop(); // optional
+ *
+ * Duration duration = stopwatch.elapsed();
+ *
+ * log.info("time: " + stopwatch); // formatted string like "12.3 ms"
+ * }
+ * + *

Stopwatch methods are not idempotent; it is an error to start or stop a stopwatch that is + * already in the desired state. + * + *

When testing code that uses this class, use {@link #createUnstarted(Ticker)} or {@link + * #createStarted(Ticker)} to supply a fake or mock ticker. This allows you to simulate any valid + * behavior of the stopwatch. + * + *

Note: This class is not thread-safe. + * + *

Warning for Android users: a stopwatch with default behavior may not continue to keep + * time while the device is asleep. Instead, create one like this: + * + *

{@code
+ * Stopwatch.createStarted(
+ *      new Ticker() {
+ *        public long read() {
+ *          return android.os.SystemClock.elapsedRealtimeNanos();
+ *        }
+ *      });
+ * }
+ * + * @author Kevin Bourrillion + * @since 10.0 + */ +@GwtCompatible(emulated = true) +@SuppressWarnings("GoodTime") // lots of violations +public final class Stopwatch { + private final Ticker ticker; + private boolean isRunning; + private long elapsedNanos; + private long startTick; + + /** + * Creates (but does not start) a new stopwatch using {@link System#nanoTime} as its time source. + * + * @since 15.0 + */ + public static Stopwatch createUnstarted() { + return new Stopwatch(); + } + + /** + * Creates (but does not start) a new stopwatch, using the specified time source. + * + * @since 15.0 + */ + public static Stopwatch createUnstarted(Ticker ticker) { + return new Stopwatch(ticker); + } + + /** + * Creates (and starts) a new stopwatch using {@link System#nanoTime} as its time source. + * + * @since 15.0 + */ + public static Stopwatch createStarted() { + return new Stopwatch().start(); + } + + /** + * Creates (and starts) a new stopwatch, using the specified time source. + * + * @since 15.0 + */ + public static Stopwatch createStarted(Ticker ticker) { + return new Stopwatch(ticker).start(); + } + + Stopwatch() { + this.ticker = Ticker.systemTicker(); + } + + Stopwatch(Ticker ticker) { + this.ticker = checkNotNull(ticker, "ticker"); + } + + /** + * Returns {@code true} if {@link #start()} has been called on this stopwatch, and {@link #stop()} + * has not been called since the last call to {@code start()}. + */ + public boolean isRunning() { + return isRunning; + } + + /** + * Starts the stopwatch. + * + * @return this {@code Stopwatch} instance + * @throws IllegalStateException if the stopwatch is already running. + */ + public Stopwatch start() { + checkState(!isRunning, "This stopwatch is already running."); + isRunning = true; + startTick = ticker.read(); + return this; + } + + /** + * Stops the stopwatch. Future reads will return the fixed duration that had elapsed up to this + * point. + * + * @return this {@code Stopwatch} instance + * @throws IllegalStateException if the stopwatch is already stopped. + */ + public Stopwatch stop() { + long tick = ticker.read(); + checkState(isRunning, "This stopwatch is already stopped."); + isRunning = false; + elapsedNanos += tick - startTick; + return this; + } + + /** + * Sets the elapsed time for this stopwatch to zero, and places it in a stopped state. + * + * @return this {@code Stopwatch} instance + */ + public Stopwatch reset() { + elapsedNanos = 0; + isRunning = false; + return this; + } + + private long elapsedNanos() { + return isRunning ? ticker.read() - startTick + elapsedNanos : elapsedNanos; + } + + /** + * Returns the current elapsed time shown on this stopwatch, expressed in the desired time unit, + * with any fraction rounded down. + * + *

Note: the overhead of measurement can be more than a microsecond, so it is generally + * not useful to specify {@link TimeUnit#NANOSECONDS} precision here. + * + *

It is generally not a good idea to use an ambiguous, unitless {@code long} to represent + * elapsed time. Therefore, we recommend using {@link #elapsed()} instead, which returns a + * strongly-typed {@link Duration} instance. + * + * @since 14.0 (since 10.0 as {@code elapsedTime()}) + */ + public long elapsed(TimeUnit desiredUnit) { + return desiredUnit.convert(elapsedNanos(), NANOSECONDS); + } + + /** + * Returns the current elapsed time shown on this stopwatch as a {@link Duration}. Unlike {@link + * #elapsed(TimeUnit)}, this method does not lose any precision due to rounding. + * + * @since 22.0 + */ + @GwtIncompatible + public Duration elapsed() { + return Duration.ofNanos(elapsedNanos()); + } + + /** Returns a string representation of the current elapsed time. */ + @Override + public String toString() { + long nanos = elapsedNanos(); + + TimeUnit unit = chooseUnit(nanos); + double value = (double) nanos / NANOSECONDS.convert(1, unit); + + // Too bad this functionality is not exposed as a regular method call + return Platform.formatCompact4Digits(value) + " " + abbreviate(unit); + } + + private static TimeUnit chooseUnit(long nanos) { + if (DAYS.convert(nanos, NANOSECONDS) > 0) { + return DAYS; + } + if (HOURS.convert(nanos, NANOSECONDS) > 0) { + return HOURS; + } + if (MINUTES.convert(nanos, NANOSECONDS) > 0) { + return MINUTES; + } + if (SECONDS.convert(nanos, NANOSECONDS) > 0) { + return SECONDS; + } + if (MILLISECONDS.convert(nanos, NANOSECONDS) > 0) { + return MILLISECONDS; + } + if (MICROSECONDS.convert(nanos, NANOSECONDS) > 0) { + return MICROSECONDS; + } + return NANOSECONDS; + } + + private static String abbreviate(TimeUnit unit) { + switch (unit) { + case NANOSECONDS: + return "ns"; + case MICROSECONDS: + return "\u03bcs"; // μs + case MILLISECONDS: + return "ms"; + case SECONDS: + return "s"; + case MINUTES: + return "min"; + case HOURS: + return "h"; + case DAYS: + return "d"; + default: + throw new AssertionError(); + } + } +} diff --git a/src/main/java/com/google/common/base/Strings.java b/src/main/java/com/google/common/base/Strings.java new file mode 100644 index 0000000..2f5ce48 --- /dev/null +++ b/src/main/java/com/google/common/base/Strings.java @@ -0,0 +1,311 @@ +/* + * Copyright (C) 2010 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.base; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; +import static java.util.logging.Level.WARNING; + +import com.google.common.annotations.GwtCompatible; +import com.google.common.annotations.VisibleForTesting; +import java.util.logging.Logger; + +/** + * Static utility methods pertaining to {@code String} or {@code CharSequence} instances. + * + * @author Kevin Bourrillion + * @since 3.0 + */ +@GwtCompatible +public final class Strings { + private Strings() {} + + /** + * Returns the given string if it is non-null; the empty string otherwise. + * + * @param string the string to test and possibly return + * @return {@code string} itself if it is non-null; {@code ""} if it is null + */ + public static String nullToEmpty(String string) { + return Platform.nullToEmpty(string); + } + + /** + * Returns the given string if it is nonempty; {@code null} otherwise. + * + * @param string the string to test and possibly return + * @return {@code string} itself if it is nonempty; {@code null} if it is empty or null + */ + public static String emptyToNull(String string) { + return Platform.emptyToNull(string); + } + + /** + * Returns {@code true} if the given string is null or is the empty string. + * + *

Consider normalizing your string references with {@link #nullToEmpty}. If you do, you can + * use {@link String#isEmpty()} instead of this method, and you won't need special null-safe forms + * of methods like {@link String#toUpperCase} either. Or, if you'd like to normalize "in the other + * direction," converting empty strings to {@code null}, you can use {@link #emptyToNull}. + * + * @param string a string reference to check + * @return {@code true} if the string is null or is the empty string + */ + public static boolean isNullOrEmpty(String string) { + return Platform.stringIsNullOrEmpty(string); + } + + /** + * Returns a string, of length at least {@code minLength}, consisting of {@code string} prepended + * with as many copies of {@code padChar} as are necessary to reach that length. For example, + * + *

    + *
  • {@code padStart("7", 3, '0')} returns {@code "007"} + *
  • {@code padStart("2010", 3, '0')} returns {@code "2010"} + *
+ * + *

See {@link java.util.Formatter} for a richer set of formatting capabilities. + * + * @param string the string which should appear at the end of the result + * @param minLength the minimum length the resulting string must have. Can be zero or negative, in + * which case the input string is always returned. + * @param padChar the character to insert at the beginning of the result until the minimum length + * is reached + * @return the padded string + */ + public static String padStart(String string, int minLength, char padChar) { + checkNotNull(string); // eager for GWT. + if (string.length() >= minLength) { + return string; + } + StringBuilder sb = new StringBuilder(minLength); + for (int i = string.length(); i < minLength; i++) { + sb.append(padChar); + } + sb.append(string); + return sb.toString(); + } + + /** + * Returns a string, of length at least {@code minLength}, consisting of {@code string} appended + * with as many copies of {@code padChar} as are necessary to reach that length. For example, + * + *

    + *
  • {@code padEnd("4.", 5, '0')} returns {@code "4.000"} + *
  • {@code padEnd("2010", 3, '!')} returns {@code "2010"} + *
+ * + *

See {@link java.util.Formatter} for a richer set of formatting capabilities. + * + * @param string the string which should appear at the beginning of the result + * @param minLength the minimum length the resulting string must have. Can be zero or negative, in + * which case the input string is always returned. + * @param padChar the character to append to the end of the result until the minimum length is + * reached + * @return the padded string + */ + public static String padEnd(String string, int minLength, char padChar) { + checkNotNull(string); // eager for GWT. + if (string.length() >= minLength) { + return string; + } + StringBuilder sb = new StringBuilder(minLength); + sb.append(string); + for (int i = string.length(); i < minLength; i++) { + sb.append(padChar); + } + return sb.toString(); + } + + /** + * Returns a string consisting of a specific number of concatenated copies of an input string. For + * example, {@code repeat("hey", 3)} returns the string {@code "heyheyhey"}. + * + * @param string any non-null string + * @param count the number of times to repeat it; a nonnegative integer + * @return a string containing {@code string} repeated {@code count} times (the empty string if + * {@code count} is zero) + * @throws IllegalArgumentException if {@code count} is negative + */ + public static String repeat(String string, int count) { + checkNotNull(string); // eager for GWT. + + if (count <= 1) { + checkArgument(count >= 0, "invalid count: %s", count); + return (count == 0) ? "" : string; + } + + // IF YOU MODIFY THE CODE HERE, you must update StringsRepeatBenchmark + final int len = string.length(); + final long longSize = (long) len * (long) count; + final int size = (int) longSize; + if (size != longSize) { + throw new ArrayIndexOutOfBoundsException("Required array size too large: " + longSize); + } + + final char[] array = new char[size]; + string.getChars(0, len, array, 0); + int n; + for (n = len; n < size - n; n <<= 1) { + System.arraycopy(array, 0, array, n, n); + } + System.arraycopy(array, 0, array, n, size - n); + return new String(array); + } + + /** + * Returns the longest string {@code prefix} such that {@code a.toString().startsWith(prefix) && + * b.toString().startsWith(prefix)}, taking care not to split surrogate pairs. If {@code a} and + * {@code b} have no common prefix, returns the empty string. + * + * @since 11.0 + */ + public static String commonPrefix(CharSequence a, CharSequence b) { + checkNotNull(a); + checkNotNull(b); + + int maxPrefixLength = Math.min(a.length(), b.length()); + int p = 0; + while (p < maxPrefixLength && a.charAt(p) == b.charAt(p)) { + p++; + } + if (validSurrogatePairAt(a, p - 1) || validSurrogatePairAt(b, p - 1)) { + p--; + } + return a.subSequence(0, p).toString(); + } + + /** + * Returns the longest string {@code suffix} such that {@code a.toString().endsWith(suffix) && + * b.toString().endsWith(suffix)}, taking care not to split surrogate pairs. If {@code a} and + * {@code b} have no common suffix, returns the empty string. + * + * @since 11.0 + */ + public static String commonSuffix(CharSequence a, CharSequence b) { + checkNotNull(a); + checkNotNull(b); + + int maxSuffixLength = Math.min(a.length(), b.length()); + int s = 0; + while (s < maxSuffixLength && a.charAt(a.length() - s - 1) == b.charAt(b.length() - s - 1)) { + s++; + } + if (validSurrogatePairAt(a, a.length() - s - 1) + || validSurrogatePairAt(b, b.length() - s - 1)) { + s--; + } + return a.subSequence(a.length() - s, a.length()).toString(); + } + + /** + * True when a valid surrogate pair starts at the given {@code index} in the given {@code string}. + * Out-of-range indexes return false. + */ + @VisibleForTesting + static boolean validSurrogatePairAt(CharSequence string, int index) { + return index >= 0 + && index <= (string.length() - 2) + && Character.isHighSurrogate(string.charAt(index)) + && Character.isLowSurrogate(string.charAt(index + 1)); + } + + /** + * Returns the given {@code template} string with each occurrence of {@code "%s"} replaced with + * the corresponding argument value from {@code args}; or, if the placeholder and argument counts + * do not match, returns a best-effort form of that string. Will not throw an exception under + * normal conditions. + * + *

Note: For most string-formatting needs, use {@link String#format String.format}, + * {@link java.io.PrintWriter#format PrintWriter.format}, and related methods. These support the + * full range of format + * specifiers, and alert you to usage errors by throwing {@link + * java.util.IllegalFormatException}. + * + *

In certain cases, such as outputting debugging information or constructing a message to be + * used for another unchecked exception, an exception during string formatting would serve little + * purpose except to supplant the real information you were trying to provide. These are the cases + * this method is made for; it instead generates a best-effort string with all supplied argument + * values present. This method is also useful in environments such as GWT where {@code + * String.format} is not available. As an example, method implementations of the {@link + * Preconditions} class use this formatter, for both of the reasons just discussed. + * + *

Warning: Only the exact two-character placeholder sequence {@code "%s"} is + * recognized. + * + * @param template a string containing zero or more {@code "%s"} placeholder sequences. {@code + * null} is treated as the four-character string {@code "null"}. + * @param args the arguments to be substituted into the message template. The first argument + * specified is substituted for the first occurrence of {@code "%s"} in the template, and so + * forth. A {@code null} argument is converted to the four-character string {@code "null"}; + * non-null values are converted to strings using {@link Object#toString()}. + * @since 25.1 + */ + // TODO(diamondm) consider using Arrays.toString() for array parameters + public static String lenientFormat(String template, Object ... args) { + template = String.valueOf(template); // null -> "null" + + if (args == null) { + args = new Object[] {"(Object[])null"}; + } else { + for (int i = 0; i < args.length; i++) { + args[i] = lenientToString(args[i]); + } + } + + // start substituting the arguments into the '%s' placeholders + StringBuilder builder = new StringBuilder(template.length() + 16 * args.length); + int templateStart = 0; + int i = 0; + while (i < args.length) { + int placeholderStart = template.indexOf("%s", templateStart); + if (placeholderStart == -1) { + break; + } + builder.append(template, templateStart, placeholderStart); + builder.append(args[i++]); + templateStart = placeholderStart + 2; + } + builder.append(template, templateStart, template.length()); + + // if we run out of placeholders, append the extra args in square braces + if (i < args.length) { + builder.append(" ["); + builder.append(args[i++]); + while (i < args.length) { + builder.append(", "); + builder.append(args[i++]); + } + builder.append(']'); + } + + return builder.toString(); + } + + private static String lenientToString(Object o) { + try { + return String.valueOf(o); + } catch (Exception e) { + // Default toString() behavior - see Object.toString() + String objectToString = + o.getClass().getName() + '@' + Integer.toHexString(System.identityHashCode(o)); + // Logger is created inline with fixed name to avoid forcing Proguard to create another class. + Logger.getLogger("com.google.common.base.Strings") + .log(WARNING, "Exception during lenientFormat for " + objectToString, e); + return "<" + objectToString + " threw " + e.getClass().getName() + ">"; + } + } +} diff --git a/src/main/java/com/google/common/base/Supplier.java b/src/main/java/com/google/common/base/Supplier.java new file mode 100644 index 0000000..d95aef4 --- /dev/null +++ b/src/main/java/com/google/common/base/Supplier.java @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.base; + +import com.google.common.annotations.GwtCompatible; + +/** + * Legacy version of {@link java.util.function.Supplier java.util.function.Supplier}. Semantically, + * this could be a factory, generator, builder, closure, or something else entirely. No guarantees + * are implied by this interface. + * + *

The {@link Suppliers} class provides common suppliers and related utilities. + * + *

As this interface extends {@code java.util.function.Supplier}, an instance of this type can be + * used as a {@code java.util.function.Supplier} directly. To use a {@code + * java.util.function.Supplier} in a context where a {@code com.google.common.base.Supplier} is + * needed, use {@code supplier::get}. + * + *

See the Guava User Guide article on the use of {@code Function}. + * + * @author Harry Heymann + * @since 2.0 + */ +@GwtCompatible +@FunctionalInterface +public interface Supplier extends java.util.function.Supplier { + /** + * Retrieves an instance of the appropriate type. The returned object may or may not be a new + * instance, depending on the implementation. + * + * @return an instance of the appropriate type + */ + @Override + T get(); +} diff --git a/src/main/java/com/google/common/base/Suppliers.java b/src/main/java/com/google/common/base/Suppliers.java new file mode 100644 index 0000000..9a23f95 --- /dev/null +++ b/src/main/java/com/google/common/base/Suppliers.java @@ -0,0 +1,364 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.base; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.annotations.GwtCompatible; +import com.google.common.annotations.VisibleForTesting; +import java.io.Serializable; +import java.util.concurrent.TimeUnit; + +/** + * Useful suppliers. + * + *

All methods return serializable suppliers as long as they're given serializable parameters. + * + * @author Laurence Gonsalves + * @author Harry Heymann + * @since 2.0 + */ +@GwtCompatible +public final class Suppliers { + private Suppliers() {} + + /** + * Returns a new supplier which is the composition of the provided function and supplier. In other + * words, the new supplier's value will be computed by retrieving the value from {@code supplier}, + * and then applying {@code function} to that value. Note that the resulting supplier will not + * call {@code supplier} or invoke {@code function} until it is called. + */ + public static Supplier compose(Function function, Supplier supplier) { + return new SupplierComposition<>(function, supplier); + } + + private static class SupplierComposition implements Supplier, Serializable { + final Function function; + final Supplier supplier; + + SupplierComposition(Function function, Supplier supplier) { + this.function = checkNotNull(function); + this.supplier = checkNotNull(supplier); + } + + @Override + public T get() { + return function.apply(supplier.get()); + } + + @Override + public boolean equals(Object obj) { + if (obj instanceof SupplierComposition) { + SupplierComposition that = (SupplierComposition) obj; + return function.equals(that.function) && supplier.equals(that.supplier); + } + return false; + } + + @Override + public int hashCode() { + return Objects.hashCode(function, supplier); + } + + @Override + public String toString() { + return "Suppliers.compose(" + function + ", " + supplier + ")"; + } + + private static final long serialVersionUID = 0; + } + + /** + * Returns a supplier which caches the instance retrieved during the first call to {@code get()} + * and returns that value on subsequent calls to {@code get()}. See: memoization + * + *

The returned supplier is thread-safe. The delegate's {@code get()} method will be invoked at + * most once unless the underlying {@code get()} throws an exception. The supplier's serialized + * form does not contain the cached value, which will be recalculated when {@code get()} is called + * on the reserialized instance. + * + *

When the underlying delegate throws an exception then this memoizing supplier will keep + * delegating calls until it returns valid data. + * + *

If {@code delegate} is an instance created by an earlier call to {@code memoize}, it is + * returned directly. + */ + public static Supplier memoize(Supplier delegate) { + if (delegate instanceof NonSerializableMemoizingSupplier + || delegate instanceof MemoizingSupplier) { + return delegate; + } + return delegate instanceof Serializable + ? new MemoizingSupplier(delegate) + : new NonSerializableMemoizingSupplier(delegate); + } + + @VisibleForTesting + static class MemoizingSupplier implements Supplier, Serializable { + final Supplier delegate; + transient volatile boolean initialized; + // "value" does not need to be volatile; visibility piggy-backs + // on volatile read of "initialized". + transient T value; + + MemoizingSupplier(Supplier delegate) { + this.delegate = checkNotNull(delegate); + } + + @Override + public T get() { + // A 2-field variant of Double Checked Locking. + if (!initialized) { + synchronized (this) { + if (!initialized) { + T t = delegate.get(); + value = t; + initialized = true; + return t; + } + } + } + return value; + } + + @Override + public String toString() { + return "Suppliers.memoize(" + + (initialized ? "" : delegate) + + ")"; + } + + private static final long serialVersionUID = 0; + } + + @VisibleForTesting + static class NonSerializableMemoizingSupplier implements Supplier { + volatile Supplier delegate; + volatile boolean initialized; + // "value" does not need to be volatile; visibility piggy-backs + // on volatile read of "initialized". + T value; + + NonSerializableMemoizingSupplier(Supplier delegate) { + this.delegate = checkNotNull(delegate); + } + + @Override + public T get() { + // A 2-field variant of Double Checked Locking. + if (!initialized) { + synchronized (this) { + if (!initialized) { + T t = delegate.get(); + value = t; + initialized = true; + // Release the delegate to GC. + delegate = null; + return t; + } + } + } + return value; + } + + @Override + public String toString() { + Supplier delegate = this.delegate; + return "Suppliers.memoize(" + + (delegate == null ? "" : delegate) + + ")"; + } + } + + /** + * Returns a supplier that caches the instance supplied by the delegate and removes the cached + * value after the specified time has passed. Subsequent calls to {@code get()} return the cached + * value if the expiration time has not passed. After the expiration time, a new value is + * retrieved, cached, and returned. See: memoization + * + *

The returned supplier is thread-safe. The supplier's serialized form does not contain the + * cached value, which will be recalculated when {@code get()} is called on the reserialized + * instance. The actual memoization does not happen when the underlying delegate throws an + * exception. + * + *

When the underlying delegate throws an exception then this memoizing supplier will keep + * delegating calls until it returns valid data. + * + * @param duration the length of time after a value is created that it should stop being returned + * by subsequent {@code get()} calls + * @param unit the unit that {@code duration} is expressed in + * @throws IllegalArgumentException if {@code duration} is not positive + * @since 2.0 + */ + @SuppressWarnings("GoodTime") // should accept a java.time.Duration + public static Supplier memoizeWithExpiration( + Supplier delegate, long duration, TimeUnit unit) { + return new ExpiringMemoizingSupplier(delegate, duration, unit); + } + + @VisibleForTesting + @SuppressWarnings("GoodTime") // lots of violations + static class ExpiringMemoizingSupplier implements Supplier, Serializable { + final Supplier delegate; + final long durationNanos; + transient volatile T value; + // The special value 0 means "not yet initialized". + transient volatile long expirationNanos; + + ExpiringMemoizingSupplier(Supplier delegate, long duration, TimeUnit unit) { + this.delegate = checkNotNull(delegate); + this.durationNanos = unit.toNanos(duration); + checkArgument(duration > 0, "duration (%s %s) must be > 0", duration, unit); + } + + @Override + public T get() { + // Another variant of Double Checked Locking. + // + // We use two volatile reads. We could reduce this to one by + // putting our fields into a holder class, but (at least on x86) + // the extra memory consumption and indirection are more + // expensive than the extra volatile reads. + long nanos = expirationNanos; + long now = Platform.systemNanoTime(); + if (nanos == 0 || now - nanos >= 0) { + synchronized (this) { + if (nanos == expirationNanos) { // recheck for lost race + T t = delegate.get(); + value = t; + nanos = now + durationNanos; + // In the very unlikely event that nanos is 0, set it to 1; + // no one will notice 1 ns of tardiness. + expirationNanos = (nanos == 0) ? 1 : nanos; + return t; + } + } + } + return value; + } + + @Override + public String toString() { + // This is a little strange if the unit the user provided was not NANOS, + // but we don't want to store the unit just for toString + return "Suppliers.memoizeWithExpiration(" + delegate + ", " + durationNanos + ", NANOS)"; + } + + private static final long serialVersionUID = 0; + } + + /** Returns a supplier that always supplies {@code instance}. */ + public static Supplier ofInstance(T instance) { + return new SupplierOfInstance(instance); + } + + private static class SupplierOfInstance implements Supplier, Serializable { + final T instance; + + SupplierOfInstance(T instance) { + this.instance = instance; + } + + @Override + public T get() { + return instance; + } + + @Override + public boolean equals(Object obj) { + if (obj instanceof SupplierOfInstance) { + SupplierOfInstance that = (SupplierOfInstance) obj; + return Objects.equal(instance, that.instance); + } + return false; + } + + @Override + public int hashCode() { + return Objects.hashCode(instance); + } + + @Override + public String toString() { + return "Suppliers.ofInstance(" + instance + ")"; + } + + private static final long serialVersionUID = 0; + } + + /** + * Returns a supplier whose {@code get()} method synchronizes on {@code delegate} before calling + * it, making it thread-safe. + */ + public static Supplier synchronizedSupplier(Supplier delegate) { + return new ThreadSafeSupplier(delegate); + } + + private static class ThreadSafeSupplier implements Supplier, Serializable { + final Supplier delegate; + + ThreadSafeSupplier(Supplier delegate) { + this.delegate = checkNotNull(delegate); + } + + @Override + public T get() { + synchronized (delegate) { + return delegate.get(); + } + } + + @Override + public String toString() { + return "Suppliers.synchronizedSupplier(" + delegate + ")"; + } + + private static final long serialVersionUID = 0; + } + + /** + * Returns a function that accepts a supplier and returns the result of invoking {@link + * Supplier#get} on that supplier. + * + *

Java 8 users: use the method reference {@code Supplier::get} instead. + * + * @since 8.0 + */ + public static Function, T> supplierFunction() { + @SuppressWarnings("unchecked") // implementation is "fully variant" + SupplierFunction sf = (SupplierFunction) SupplierFunctionImpl.INSTANCE; + return sf; + } + + private interface SupplierFunction extends Function, T> {} + + private enum SupplierFunctionImpl implements SupplierFunction { + INSTANCE; + + // Note: This makes T a "pass-through type" + @Override + public Object apply(Supplier input) { + return input.get(); + } + + @Override + public String toString() { + return "Suppliers.supplierFunction()"; + } + } +} diff --git a/src/main/java/com/google/common/base/Throwables.java b/src/main/java/com/google/common/base/Throwables.java new file mode 100644 index 0000000..1c33128 --- /dev/null +++ b/src/main/java/com/google/common/base/Throwables.java @@ -0,0 +1,536 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.base; + +import static com.google.common.base.Preconditions.checkNotNull; +import static java.util.Arrays.asList; +import static java.util.Collections.unmodifiableList; + +import com.google.common.annotations.Beta; +import com.google.common.annotations.GwtCompatible; +import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.VisibleForTesting; +import java.io.IOException; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.AbstractList; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * Static utility methods pertaining to instances of {@link Throwable}. + * + *

See the Guava User Guide entry on Throwables. + * + * @author Kevin Bourrillion + * @author Ben Yu + * @since 1.0 + */ +@GwtCompatible(emulated = true) +public final class Throwables { + private Throwables() {} + + /** + * Throws {@code throwable} if it is an instance of {@code declaredType}. Example usage: + * + *

+   * for (Foo foo : foos) {
+   *   try {
+   *     foo.bar();
+   *   } catch (BarException | RuntimeException | Error t) {
+   *     failure = t;
+   *   }
+   * }
+   * if (failure != null) {
+   *   throwIfInstanceOf(failure, BarException.class);
+   *   throwIfUnchecked(failure);
+   *   throw new AssertionError(failure);
+   * }
+   * 
+ * + * @since 20.0 + */ + @GwtIncompatible // Class.cast, Class.isInstance + public static void throwIfInstanceOf( + Throwable throwable, Class declaredType) throws X { + checkNotNull(throwable); + if (declaredType.isInstance(throwable)) { + throw declaredType.cast(throwable); + } + } + + /** + * Propagates {@code throwable} exactly as-is, if and only if it is an instance of {@code + * declaredType}. Example usage: + * + *
+   * try {
+   *   someMethodThatCouldThrowAnything();
+   * } catch (IKnowWhatToDoWithThisException e) {
+   *   handle(e);
+   * } catch (Throwable t) {
+   *   Throwables.propagateIfInstanceOf(t, IOException.class);
+   *   Throwables.propagateIfInstanceOf(t, SQLException.class);
+   *   throw Throwables.propagate(t);
+   * }
+   * 
+ * + * @deprecated Use {@link #throwIfInstanceOf}, which has the same behavior but rejects {@code + * null}. + */ + @Deprecated + @GwtIncompatible // throwIfInstanceOf + public static void propagateIfInstanceOf( + Throwable throwable, Class declaredType) throws X { + if (throwable != null) { + throwIfInstanceOf(throwable, declaredType); + } + } + + /** + * Throws {@code throwable} if it is a {@link RuntimeException} or {@link Error}. Example usage: + * + *
+   * for (Foo foo : foos) {
+   *   try {
+   *     foo.bar();
+   *   } catch (RuntimeException | Error t) {
+   *     failure = t;
+   *   }
+   * }
+   * if (failure != null) {
+   *   throwIfUnchecked(failure);
+   *   throw new AssertionError(failure);
+   * }
+   * 
+ * + * @since 20.0 + */ + public static void throwIfUnchecked(Throwable throwable) { + checkNotNull(throwable); + if (throwable instanceof RuntimeException) { + throw (RuntimeException) throwable; + } + if (throwable instanceof Error) { + throw (Error) throwable; + } + } + + /** + * Propagates {@code throwable} exactly as-is, if and only if it is an instance of {@link + * RuntimeException} or {@link Error}. Example usage: + * + *
+   * try {
+   *   someMethodThatCouldThrowAnything();
+   * } catch (IKnowWhatToDoWithThisException e) {
+   *   handle(e);
+   * } catch (Throwable t) {
+   *   Throwables.propagateIfPossible(t);
+   *   throw new RuntimeException("unexpected", t);
+   * }
+   * 
+ * + * @deprecated Use {@link #throwIfUnchecked}, which has the same behavior but rejects {@code + * null}. + */ + @Deprecated + @GwtIncompatible + public static void propagateIfPossible(Throwable throwable) { + if (throwable != null) { + throwIfUnchecked(throwable); + } + } + + /** + * Propagates {@code throwable} exactly as-is, if and only if it is an instance of {@link + * RuntimeException}, {@link Error}, or {@code declaredType}. Example usage: + * + *
+   * try {
+   *   someMethodThatCouldThrowAnything();
+   * } catch (IKnowWhatToDoWithThisException e) {
+   *   handle(e);
+   * } catch (Throwable t) {
+   *   Throwables.propagateIfPossible(t, OtherException.class);
+   *   throw new RuntimeException("unexpected", t);
+   * }
+   * 
+ * + * @param throwable the Throwable to possibly propagate + * @param declaredType the single checked exception type declared by the calling method + */ + @GwtIncompatible // propagateIfInstanceOf + public static void propagateIfPossible( + Throwable throwable, Class declaredType) throws X { + propagateIfInstanceOf(throwable, declaredType); + propagateIfPossible(throwable); + } + + /** + * Propagates {@code throwable} exactly as-is, if and only if it is an instance of {@link + * RuntimeException}, {@link Error}, {@code declaredType1}, or {@code declaredType2}. In the + * unlikely case that you have three or more declared checked exception types, you can handle them + * all by invoking these methods repeatedly. See usage example in {@link + * #propagateIfPossible(Throwable, Class)}. + * + * @param throwable the Throwable to possibly propagate + * @param declaredType1 any checked exception type declared by the calling method + * @param declaredType2 any other checked exception type declared by the calling method + */ + @GwtIncompatible // propagateIfInstanceOf + public static void propagateIfPossible( + Throwable throwable, Class declaredType1, Class declaredType2) + throws X1, X2 { + checkNotNull(declaredType2); + propagateIfInstanceOf(throwable, declaredType1); + propagateIfPossible(throwable, declaredType2); + } + + /** + * Propagates {@code throwable} as-is if it is an instance of {@link RuntimeException} or {@link + * Error}, or else as a last resort, wraps it in a {@code RuntimeException} and then propagates. + * + *

This method always throws an exception. The {@code RuntimeException} return type allows + * client code to signal to the compiler that statements after the call are unreachable. Example + * usage: + * + *

+   * T doSomething() {
+   *   try {
+   *     return someMethodThatCouldThrowAnything();
+   *   } catch (IKnowWhatToDoWithThisException e) {
+   *     return handle(e);
+   *   } catch (Throwable t) {
+   *     throw Throwables.propagate(t);
+   *   }
+   * }
+   * 
+ * + * @param throwable the Throwable to propagate + * @return nothing will ever be returned; this return type is only for your convenience, as + * illustrated in the example above + * @deprecated Use {@code throw e} or {@code throw new RuntimeException(e)} directly, or use a + * combination of {@link #throwIfUnchecked} and {@code throw new RuntimeException(e)}. For + * background on the deprecation, read Why we deprecated + * {@code Throwables.propagate}. + */ + @GwtIncompatible + @Deprecated + public static RuntimeException propagate(Throwable throwable) { + throwIfUnchecked(throwable); + throw new RuntimeException(throwable); + } + + /** + * Returns the innermost cause of {@code throwable}. The first throwable in a chain provides + * context from when the error or exception was initially detected. Example usage: + * + *
+   * assertEquals("Unable to assign a customer id", Throwables.getRootCause(e).getMessage());
+   * 
+ * + * @throws IllegalArgumentException if there is a loop in the causal chain + */ + public static Throwable getRootCause(Throwable throwable) { + // Keep a second pointer that slowly walks the causal chain. If the fast pointer ever catches + // the slower pointer, then there's a loop. + Throwable slowPointer = throwable; + boolean advanceSlowPointer = false; + + Throwable cause; + while ((cause = throwable.getCause()) != null) { + throwable = cause; + + if (throwable == slowPointer) { + throw new IllegalArgumentException("Loop in causal chain detected.", throwable); + } + if (advanceSlowPointer) { + slowPointer = slowPointer.getCause(); + } + advanceSlowPointer = !advanceSlowPointer; // only advance every other iteration + } + return throwable; + } + + /** + * Gets a {@code Throwable} cause chain as a list. The first entry in the list will be {@code + * throwable} followed by its cause hierarchy. Note that this is a snapshot of the cause chain and + * will not reflect any subsequent changes to the cause chain. + * + *

Here's an example of how it can be used to find specific types of exceptions in the cause + * chain: + * + *

+   * Iterables.filter(Throwables.getCausalChain(e), IOException.class));
+   * 
+ * + * @param throwable the non-null {@code Throwable} to extract causes from + * @return an unmodifiable list containing the cause chain starting with {@code throwable} + * @throws IllegalArgumentException if there is a loop in the causal chain + */ + @Beta // TODO(kevinb): decide best return type + public static List getCausalChain(Throwable throwable) { + checkNotNull(throwable); + List causes = new ArrayList<>(4); + causes.add(throwable); + + // Keep a second pointer that slowly walks the causal chain. If the fast pointer ever catches + // the slower pointer, then there's a loop. + Throwable slowPointer = throwable; + boolean advanceSlowPointer = false; + + Throwable cause; + while ((cause = throwable.getCause()) != null) { + throwable = cause; + causes.add(throwable); + + if (throwable == slowPointer) { + throw new IllegalArgumentException("Loop in causal chain detected.", throwable); + } + if (advanceSlowPointer) { + slowPointer = slowPointer.getCause(); + } + advanceSlowPointer = !advanceSlowPointer; // only advance every other iteration + } + return Collections.unmodifiableList(causes); + } + + /** + * Returns {@code throwable}'s cause, cast to {@code expectedCauseType}. + * + *

Prefer this method instead of manually casting an exception's cause. For example, {@code + * (IOException) e.getCause()} throws a {@link ClassCastException} that discards the original + * exception {@code e} if the cause is not an {@link IOException}, but {@code + * Throwables.getCauseAs(e, IOException.class)} keeps {@code e} as the {@link + * ClassCastException}'s cause. + * + * @throws ClassCastException if the cause cannot be cast to the expected type. The {@code + * ClassCastException}'s cause is {@code throwable}. + * @since 22.0 + */ + @Beta + @GwtIncompatible // Class.cast(Object) + public static X getCauseAs( + Throwable throwable, Class expectedCauseType) { + try { + return expectedCauseType.cast(throwable.getCause()); + } catch (ClassCastException e) { + e.initCause(throwable); + throw e; + } + } + + /** + * Returns a string containing the result of {@link Throwable#toString() toString()}, followed by + * the full, recursive stack trace of {@code throwable}. Note that you probably should not be + * parsing the resulting string; if you need programmatic access to the stack frames, you can call + * {@link Throwable#getStackTrace()}. + */ + @GwtIncompatible // java.io.PrintWriter, java.io.StringWriter + public static String getStackTraceAsString(Throwable throwable) { + StringWriter stringWriter = new StringWriter(); + throwable.printStackTrace(new PrintWriter(stringWriter)); + return stringWriter.toString(); + } + + /** + * Returns the stack trace of {@code throwable}, possibly providing slower iteration over the full + * trace but faster iteration over parts of the trace. Here, "slower" and "faster" are defined in + * comparison to the normal way to access the stack trace, {@link Throwable#getStackTrace() + * throwable.getStackTrace()}. Note, however, that this method's special implementation is not + * available for all platforms and configurations. If that implementation is unavailable, this + * method falls back to {@code getStackTrace}. Callers that require the special implementation can + * check its availability with {@link #lazyStackTraceIsLazy()}. + * + *

The expected (but not guaranteed) performance of the special implementation differs from + * {@code getStackTrace} in one main way: The {@code lazyStackTrace} call itself returns quickly + * by delaying the per-stack-frame work until each element is accessed. Roughly speaking: + * + *

    + *
  • {@code getStackTrace} takes {@code stackSize} time to return but then negligible time to + * retrieve each element of the returned list. + *
  • {@code lazyStackTrace} takes negligible time to return but then {@code 1/stackSize} time + * to retrieve each element of the returned list (probably slightly more than {@code + * 1/stackSize}). + *
+ * + *

Note: The special implementation does not respect calls to {@link Throwable#setStackTrace + * throwable.setStackTrace}. Instead, it always reflects the original stack trace from the + * exception's creation. + * + * @since 19.0 + */ + // TODO(cpovirk): Say something about the possibility that List access could fail at runtime? + @Beta + @GwtIncompatible // lazyStackTraceIsLazy, jlaStackTrace + // TODO(cpovirk): Consider making this available under GWT (slow implementation only). + public static List lazyStackTrace(Throwable throwable) { + return lazyStackTraceIsLazy() + ? jlaStackTrace(throwable) + : unmodifiableList(asList(throwable.getStackTrace())); + } + + /** + * Returns whether {@link #lazyStackTrace} will use the special implementation described in its + * documentation. + * + * @since 19.0 + */ + @Beta + @GwtIncompatible // getStackTraceElementMethod + public static boolean lazyStackTraceIsLazy() { + return getStackTraceElementMethod != null && getStackTraceDepthMethod != null; + } + + @GwtIncompatible // invokeAccessibleNonThrowingMethod + private static List jlaStackTrace(final Throwable t) { + checkNotNull(t); + /* + * TODO(cpovirk): Consider optimizing iterator() to catch IOOBE instead of doing bounds checks. + * + * TODO(cpovirk): Consider the UnsignedBytes pattern if it performs faster and doesn't cause + * AOSP grief. + */ + return new AbstractList() { + @Override + public StackTraceElement get(int n) { + return (StackTraceElement) + invokeAccessibleNonThrowingMethod(getStackTraceElementMethod, jla, t, n); + } + + @Override + public int size() { + return (Integer) invokeAccessibleNonThrowingMethod(getStackTraceDepthMethod, jla, t); + } + }; + } + + @GwtIncompatible // java.lang.reflect + private static Object invokeAccessibleNonThrowingMethod( + Method method, Object receiver, Object... params) { + try { + return method.invoke(receiver, params); + } catch (IllegalAccessException e) { + throw new RuntimeException(e); + } catch (InvocationTargetException e) { + throw propagate(e.getCause()); + } + } + + /** JavaLangAccess class name to load using reflection */ + @GwtIncompatible // not used by GWT emulation + private static final String JAVA_LANG_ACCESS_CLASSNAME = "sun.misc.JavaLangAccess"; + + /** SharedSecrets class name to load using reflection */ + @GwtIncompatible // not used by GWT emulation + @VisibleForTesting + static final String SHARED_SECRETS_CLASSNAME = "sun.misc.SharedSecrets"; + + /** Access to some fancy internal JVM internals. */ + @GwtIncompatible // java.lang.reflect + private static final Object jla = getJLA(); + + /** + * The "getStackTraceElementMethod" method, only available on some JDKs so we use reflection to + * find it when available. When this is null, use the slow way. + */ + @GwtIncompatible // java.lang.reflect + private static final Method getStackTraceElementMethod = + (jla == null) ? null : getGetMethod(); + + /** + * The "getStackTraceDepth" method, only available on some JDKs so we use reflection to find it + * when available. When this is null, use the slow way. + */ + @GwtIncompatible // java.lang.reflect + private static final Method getStackTraceDepthMethod = + (jla == null) ? null : getSizeMethod(); + + /** + * Returns the JavaLangAccess class that is present in all Sun JDKs. It is not allowed in + * AppEngine, and not present in non-Sun JDKs. + */ + @GwtIncompatible // java.lang.reflect + private static Object getJLA() { + try { + /* + * We load sun.misc.* classes using reflection since Android doesn't support these classes and + * would result in compilation failure if we directly refer to these classes. + */ + Class sharedSecrets = Class.forName(SHARED_SECRETS_CLASSNAME, false, null); + Method langAccess = sharedSecrets.getMethod("getJavaLangAccess"); + return langAccess.invoke(null); + } catch (ThreadDeath death) { + throw death; + } catch (Throwable t) { + /* + * This is not one of AppEngine's allowed classes, so even in Sun JDKs, this can fail with + * a NoClassDefFoundError. Other apps might deny access to sun.misc packages. + */ + return null; + } + } + + /** + * Returns the Method that can be used to resolve an individual StackTraceElement, or null if that + * method cannot be found (it is only to be found in fairly recent JDKs). + */ + @GwtIncompatible // java.lang.reflect + private static Method getGetMethod() { + return getJlaMethod("getStackTraceElement", Throwable.class, int.class); + } + + /** + * Returns the Method that can be used to return the size of a stack, or null if that method + * cannot be found (it is only to be found in fairly recent JDKs). + * + *

See Throwables#lazyStackTrace throws + * UnsupportedOperationException. + */ + @GwtIncompatible // java.lang.reflect + private static Method getSizeMethod() { + try { + Method getStackTraceDepth = getJlaMethod("getStackTraceDepth", Throwable.class); + if (getStackTraceDepth == null) { + return null; + } + getStackTraceDepth.invoke(getJLA(), new Throwable()); + return getStackTraceDepth; + } catch (UnsupportedOperationException | IllegalAccessException | InvocationTargetException e) { + return null; + } + } + + @GwtIncompatible // java.lang.reflect + private static Method getJlaMethod(String name, Class... parameterTypes) + throws ThreadDeath { + try { + return Class.forName(JAVA_LANG_ACCESS_CLASSNAME, false, null).getMethod(name, parameterTypes); + } catch (ThreadDeath death) { + throw death; + } catch (Throwable t) { + /* + * Either the JavaLangAccess class itself is not found, or the method is not supported on the + * JVM. + */ + return null; + } + } +} diff --git a/src/main/java/com/google/common/base/Ticker.java b/src/main/java/com/google/common/base/Ticker.java new file mode 100644 index 0000000..a53883b --- /dev/null +++ b/src/main/java/com/google/common/base/Ticker.java @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2011 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.base; + +import com.google.common.annotations.GwtCompatible; + +/** + * A time source; returns a time value representing the number of nanoseconds elapsed since some + * fixed but arbitrary point in time. Note that most users should use {@link Stopwatch} instead of + * interacting with this class directly. + * + *

Warning: this interface can only be used to measure elapsed time, not wall time. + * + * @author Kevin Bourrillion + * @since 10.0 (mostly + * source-compatible since 9.0) + */ +@GwtCompatible +public abstract class Ticker { + /** Constructor for use by subclasses. */ + protected Ticker() {} + + /** Returns the number of nanoseconds elapsed since this ticker's fixed point of reference. */ + public abstract long read(); + + /** + * A ticker that reads the current time using {@link System#nanoTime}. + * + * @since 10.0 + */ + public static Ticker systemTicker() { + return SYSTEM_TICKER; + } + + private static final Ticker SYSTEM_TICKER = + new Ticker() { + @Override + public long read() { + return Platform.systemNanoTime(); + } + }; +} diff --git a/src/main/java/com/google/common/base/Utf8.java b/src/main/java/com/google/common/base/Utf8.java new file mode 100644 index 0000000..8a2fbb7 --- /dev/null +++ b/src/main/java/com/google/common/base/Utf8.java @@ -0,0 +1,201 @@ +/* + * Copyright (C) 2013 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.base; + +import static com.google.common.base.Preconditions.checkPositionIndexes; +import static java.lang.Character.MAX_SURROGATE; +import static java.lang.Character.MIN_SURROGATE; + +import com.google.common.annotations.Beta; +import com.google.common.annotations.GwtCompatible; + +/** + * Low-level, high-performance utility methods related to the {@linkplain Charsets#UTF_8 UTF-8} + * character encoding. UTF-8 is defined in section D92 of The Unicode Standard Core + * Specification, Chapter 3. + * + *

The variant of UTF-8 implemented by this class is the restricted definition of UTF-8 + * introduced in Unicode 3.1. One implication of this is that it rejects "non-shortest form" byte sequences, + * even though the JDK decoder may accept them. + * + * @author Martin Buchholz + * @author Clément Roux + * @since 16.0 + */ +@Beta +@GwtCompatible(emulated = true) +public final class Utf8 { + /** + * Returns the number of bytes in the UTF-8-encoded form of {@code sequence}. For a string, this + * method is equivalent to {@code string.getBytes(UTF_8).length}, but is more efficient in both + * time and space. + * + * @throws IllegalArgumentException if {@code sequence} contains ill-formed UTF-16 (unpaired + * surrogates) + */ + public static int encodedLength(CharSequence sequence) { + // Warning to maintainers: this implementation is highly optimized. + int utf16Length = sequence.length(); + int utf8Length = utf16Length; + int i = 0; + + // This loop optimizes for pure ASCII. + while (i < utf16Length && sequence.charAt(i) < 0x80) { + i++; + } + + // This loop optimizes for chars less than 0x800. + for (; i < utf16Length; i++) { + char c = sequence.charAt(i); + if (c < 0x800) { + utf8Length += ((0x7f - c) >>> 31); // branch free! + } else { + utf8Length += encodedLengthGeneral(sequence, i); + break; + } + } + + if (utf8Length < utf16Length) { + // Necessary and sufficient condition for overflow because of maximum 3x expansion + throw new IllegalArgumentException( + "UTF-8 length does not fit in int: " + (utf8Length + (1L << 32))); + } + return utf8Length; + } + + private static int encodedLengthGeneral(CharSequence sequence, int start) { + int utf16Length = sequence.length(); + int utf8Length = 0; + for (int i = start; i < utf16Length; i++) { + char c = sequence.charAt(i); + if (c < 0x800) { + utf8Length += (0x7f - c) >>> 31; // branch free! + } else { + utf8Length += 2; + // jdk7+: if (Character.isSurrogate(c)) { + if (MIN_SURROGATE <= c && c <= MAX_SURROGATE) { + // Check that we have a well-formed surrogate pair. + if (Character.codePointAt(sequence, i) == c) { + throw new IllegalArgumentException(unpairedSurrogateMsg(i)); + } + i++; + } + } + } + return utf8Length; + } + + /** + * Returns {@code true} if {@code bytes} is a well-formed UTF-8 byte sequence according to + * Unicode 6.0. Note that this is a stronger criterion than simply whether the bytes can be + * decoded. For example, some versions of the JDK decoder will accept "non-shortest form" byte + * sequences, but encoding never reproduces these. Such byte sequences are not considered + * well-formed. + * + *

This method returns {@code true} if and only if {@code Arrays.equals(bytes, new + * String(bytes, UTF_8).getBytes(UTF_8))} does, but is more efficient in both time and space. + */ + public static boolean isWellFormed(byte[] bytes) { + return isWellFormed(bytes, 0, bytes.length); + } + + /** + * Returns whether the given byte array slice is a well-formed UTF-8 byte sequence, as defined by + * {@link #isWellFormed(byte[])}. Note that this can be false even when {@code + * isWellFormed(bytes)} is true. + * + * @param bytes the input buffer + * @param off the offset in the buffer of the first byte to read + * @param len the number of bytes to read from the buffer + */ + public static boolean isWellFormed(byte[] bytes, int off, int len) { + int end = off + len; + checkPositionIndexes(off, end, bytes.length); + // Look for the first non-ASCII character. + for (int i = off; i < end; i++) { + if (bytes[i] < 0) { + return isWellFormedSlowPath(bytes, i, end); + } + } + return true; + } + + private static boolean isWellFormedSlowPath(byte[] bytes, int off, int end) { + int index = off; + while (true) { + int byte1; + + // Optimize for interior runs of ASCII bytes. + do { + if (index >= end) { + return true; + } + } while ((byte1 = bytes[index++]) >= 0); + + if (byte1 < (byte) 0xE0) { + // Two-byte form. + if (index == end) { + return false; + } + // Simultaneously check for illegal trailing-byte in leading position + // and overlong 2-byte form. + if (byte1 < (byte) 0xC2 || bytes[index++] > (byte) 0xBF) { + return false; + } + } else if (byte1 < (byte) 0xF0) { + // Three-byte form. + if (index + 1 >= end) { + return false; + } + int byte2 = bytes[index++]; + if (byte2 > (byte) 0xBF + // Overlong? 5 most significant bits must not all be zero. + || (byte1 == (byte) 0xE0 && byte2 < (byte) 0xA0) + // Check for illegal surrogate codepoints. + || (byte1 == (byte) 0xED && (byte) 0xA0 <= byte2) + // Third byte trailing-byte test. + || bytes[index++] > (byte) 0xBF) { + return false; + } + } else { + // Four-byte form. + if (index + 2 >= end) { + return false; + } + int byte2 = bytes[index++]; + if (byte2 > (byte) 0xBF + // Check that 1 <= plane <= 16. Tricky optimized form of: + // if (byte1 > (byte) 0xF4 + // || byte1 == (byte) 0xF0 && byte2 < (byte) 0x90 + // || byte1 == (byte) 0xF4 && byte2 > (byte) 0x8F) + || (((byte1 << 28) + (byte2 - (byte) 0x90)) >> 30) != 0 + // Third byte trailing-byte test + || bytes[index++] > (byte) 0xBF + // Fourth byte trailing-byte test + || bytes[index++] > (byte) 0xBF) { + return false; + } + } + } + } + + private static String unpairedSurrogateMsg(int i) { + return "Unpaired surrogate at index " + i; + } + + private Utf8() {} +} diff --git a/src/main/java/com/google/common/base/Verify.java b/src/main/java/com/google/common/base/Verify.java new file mode 100644 index 0000000..09017b0 --- /dev/null +++ b/src/main/java/com/google/common/base/Verify.java @@ -0,0 +1,504 @@ +/* + * Copyright (C) 2013 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.base; + +import static com.google.common.base.Strings.lenientFormat; + +import com.google.common.annotations.GwtCompatible; + +/** + * Static convenience methods that serve the same purpose as Java language assertions, + * except that they are always enabled. These methods should be used instead of Java assertions + * whenever there is a chance the check may fail "in real life". Example: + * + *

{@code
+ * Bill bill = remoteService.getLastUnpaidBill();
+ *
+ * // In case bug 12345 happens again we'd rather just die
+ * Verify.verify(bill.status() == Status.UNPAID,
+ *     "Unexpected bill status: %s", bill.status());
+ * }
+ * + *

Comparison to alternatives

+ * + *

Note: In some cases the differences explained below can be subtle. When it's unclear + * which approach to use, don't worry too much about it; just pick something that seems + * reasonable and it will be fine. + * + *

    + *
  • If checking whether the caller has violated your method or constructor's contract + * (such as by passing an invalid argument), use the utilities of the {@link Preconditions} + * class instead. + *
  • If checking an impossible condition (which cannot happen unless your own + * class or its trusted dependencies is badly broken), this is what ordinary Java + * assertions are for. Note that assertions are not enabled by default; they are essentially + * considered "compiled comments." + *
  • An explicit {@code if/throw} (as illustrated below) is always acceptable; we still + * recommend using our {@link VerifyException} exception type. Throwing a plain {@link + * RuntimeException} is frowned upon. + *
  • Use of {@link java.util.Objects#requireNonNull(Object)} is generally discouraged, since + * {@link #verifyNotNull(Object)} and {@link Preconditions#checkNotNull(Object)} perform the + * same function with more clarity. + *
+ * + *

Warning about performance

+ * + *

Remember that parameter values for message construction must all be computed eagerly, and + * autoboxing and varargs array creation may happen as well, even when the verification succeeds and + * the message ends up unneeded. Performance-sensitive verification checks should continue to use + * usual form: + * + *

{@code
+ * Bill bill = remoteService.getLastUnpaidBill();
+ * if (bill.status() != Status.UNPAID) {
+ *   throw new VerifyException("Unexpected bill status: " + bill.status());
+ * }
+ * }
+ * + *

Only {@code %s} is supported

+ * + *

As with {@link Preconditions}, {@code Verify} uses {@link Strings#lenientFormat} to format + * error message template strings. This only supports the {@code "%s"} specifier, not the full range + * of {@link java.util.Formatter} specifiers. However, note that if the number of arguments does not + * match the number of occurrences of {@code "%s"} in the format string, {@code Verify} will still + * behave as expected, and will still include all argument values in the error message; the message + * will simply not be formatted exactly as intended. + * + *

More information

+ * + * See Conditional + * failures explained in the Guava User Guide for advice on when this class should be used. + * + * @since 17.0 + */ +@GwtCompatible +public final class Verify { + /** + * Ensures that {@code expression} is {@code true}, throwing a {@code VerifyException} with no + * message otherwise. + * + * @throws VerifyException if {@code expression} is {@code false} + * @see Preconditions#checkState Preconditions.checkState() + */ + public static void verify(boolean expression) { + if (!expression) { + throw new VerifyException(); + } + } + + /** + * Ensures that {@code expression} is {@code true}, throwing a {@code VerifyException} with a + * custom message otherwise. + * + * @param expression a boolean expression + * @param errorMessageTemplate a template for the exception message should the check fail. The + * message is formed by replacing each {@code %s} placeholder in the template with an + * argument. These are matched by position - the first {@code %s} gets {@code + * errorMessageArgs[0]}, etc. Unmatched arguments will be appended to the formatted message in + * square braces. Unmatched placeholders will be left as-is. + * @param errorMessageArgs the arguments to be substituted into the message template. Arguments + * are converted to strings using {@link String#valueOf(Object)}. + * @throws VerifyException if {@code expression} is {@code false} + * @see Preconditions#checkState Preconditions.checkState() + */ + public static void verify( + boolean expression, + String errorMessageTemplate, + Object ... errorMessageArgs) { + if (!expression) { + throw new VerifyException(lenientFormat(errorMessageTemplate, errorMessageArgs)); + } + } + + /** + * Ensures that {@code expression} is {@code true}, throwing a {@code VerifyException} with a + * custom message otherwise. + * + *

See {@link #verify(boolean, String, Object...)} for details. + * + * @since 23.1 (varargs overload since 17.0) + */ + public static void verify(boolean expression, String errorMessageTemplate, char p1) { + if (!expression) { + throw new VerifyException(lenientFormat(errorMessageTemplate, p1)); + } + } + + /** + * Ensures that {@code expression} is {@code true}, throwing a {@code VerifyException} with a + * custom message otherwise. + * + *

See {@link #verify(boolean, String, Object...)} for details. + * + * @since 23.1 (varargs overload since 17.0) + */ + public static void verify(boolean expression, String errorMessageTemplate, int p1) { + if (!expression) { + throw new VerifyException(lenientFormat(errorMessageTemplate, p1)); + } + } + + /** + * Ensures that {@code expression} is {@code true}, throwing a {@code VerifyException} with a + * custom message otherwise. + * + *

See {@link #verify(boolean, String, Object...)} for details. + * + * @since 23.1 (varargs overload since 17.0) + */ + public static void verify(boolean expression, String errorMessageTemplate, long p1) { + if (!expression) { + throw new VerifyException(lenientFormat(errorMessageTemplate, p1)); + } + } + + /** + * Ensures that {@code expression} is {@code true}, throwing a {@code VerifyException} with a + * custom message otherwise. + * + *

See {@link #verify(boolean, String, Object...)} for details. + * + * @since 23.1 (varargs overload since 17.0) + */ + public static void verify( + boolean expression, String errorMessageTemplate, Object p1) { + if (!expression) { + throw new VerifyException(lenientFormat(errorMessageTemplate, p1)); + } + } + + /** + * Ensures that {@code expression} is {@code true}, throwing a {@code VerifyException} with a + * custom message otherwise. + * + *

See {@link #verify(boolean, String, Object...)} for details. + * + * @since 23.1 (varargs overload since 17.0) + */ + public static void verify( + boolean expression, String errorMessageTemplate, char p1, char p2) { + if (!expression) { + throw new VerifyException(lenientFormat(errorMessageTemplate, p1, p2)); + } + } + + /** + * Ensures that {@code expression} is {@code true}, throwing a {@code VerifyException} with a + * custom message otherwise. + * + *

See {@link #verify(boolean, String, Object...)} for details. + * + * @since 23.1 (varargs overload since 17.0) + */ + public static void verify( + boolean expression, String errorMessageTemplate, int p1, char p2) { + if (!expression) { + throw new VerifyException(lenientFormat(errorMessageTemplate, p1, p2)); + } + } + + /** + * Ensures that {@code expression} is {@code true}, throwing a {@code VerifyException} with a + * custom message otherwise. + * + *

See {@link #verify(boolean, String, Object...)} for details. + * + * @since 23.1 (varargs overload since 17.0) + */ + public static void verify( + boolean expression, String errorMessageTemplate, long p1, char p2) { + if (!expression) { + throw new VerifyException(lenientFormat(errorMessageTemplate, p1, p2)); + } + } + + /** + * Ensures that {@code expression} is {@code true}, throwing a {@code VerifyException} with a + * custom message otherwise. + * + *

See {@link #verify(boolean, String, Object...)} for details. + * + * @since 23.1 (varargs overload since 17.0) + */ + public static void verify( + boolean expression, String errorMessageTemplate, Object p1, char p2) { + if (!expression) { + throw new VerifyException(lenientFormat(errorMessageTemplate, p1, p2)); + } + } + + /** + * Ensures that {@code expression} is {@code true}, throwing a {@code VerifyException} with a + * custom message otherwise. + * + *

See {@link #verify(boolean, String, Object...)} for details. + * + * @since 23.1 (varargs overload since 17.0) + */ + public static void verify( + boolean expression, String errorMessageTemplate, char p1, int p2) { + if (!expression) { + throw new VerifyException(lenientFormat(errorMessageTemplate, p1, p2)); + } + } + + /** + * Ensures that {@code expression} is {@code true}, throwing a {@code VerifyException} with a + * custom message otherwise. + * + *

See {@link #verify(boolean, String, Object...)} for details. + * + * @since 23.1 (varargs overload since 17.0) + */ + public static void verify( + boolean expression, String errorMessageTemplate, int p1, int p2) { + if (!expression) { + throw new VerifyException(lenientFormat(errorMessageTemplate, p1, p2)); + } + } + + /** + * Ensures that {@code expression} is {@code true}, throwing a {@code VerifyException} with a + * custom message otherwise. + * + *

See {@link #verify(boolean, String, Object...)} for details. + * + * @since 23.1 (varargs overload since 17.0) + */ + public static void verify( + boolean expression, String errorMessageTemplate, long p1, int p2) { + if (!expression) { + throw new VerifyException(lenientFormat(errorMessageTemplate, p1, p2)); + } + } + + /** + * Ensures that {@code expression} is {@code true}, throwing a {@code VerifyException} with a + * custom message otherwise. + * + *

See {@link #verify(boolean, String, Object...)} for details. + * + * @since 23.1 (varargs overload since 17.0) + */ + public static void verify( + boolean expression, String errorMessageTemplate, Object p1, int p2) { + if (!expression) { + throw new VerifyException(lenientFormat(errorMessageTemplate, p1, p2)); + } + } + + /** + * Ensures that {@code expression} is {@code true}, throwing a {@code VerifyException} with a + * custom message otherwise. + * + *

See {@link #verify(boolean, String, Object...)} for details. + * + * @since 23.1 (varargs overload since 17.0) + */ + public static void verify( + boolean expression, String errorMessageTemplate, char p1, long p2) { + if (!expression) { + throw new VerifyException(lenientFormat(errorMessageTemplate, p1, p2)); + } + } + + /** + * Ensures that {@code expression} is {@code true}, throwing a {@code VerifyException} with a + * custom message otherwise. + * + *

See {@link #verify(boolean, String, Object...)} for details. + * + * @since 23.1 (varargs overload since 17.0) + */ + public static void verify( + boolean expression, String errorMessageTemplate, int p1, long p2) { + if (!expression) { + throw new VerifyException(lenientFormat(errorMessageTemplate, p1, p2)); + } + } + + /** + * Ensures that {@code expression} is {@code true}, throwing a {@code VerifyException} with a + * custom message otherwise. + * + *

See {@link #verify(boolean, String, Object...)} for details. + * + * @since 23.1 (varargs overload since 17.0) + */ + public static void verify( + boolean expression, String errorMessageTemplate, long p1, long p2) { + if (!expression) { + throw new VerifyException(lenientFormat(errorMessageTemplate, p1, p2)); + } + } + + /** + * Ensures that {@code expression} is {@code true}, throwing a {@code VerifyException} with a + * custom message otherwise. + * + *

See {@link #verify(boolean, String, Object...)} for details. + * + * @since 23.1 (varargs overload since 17.0) + */ + public static void verify( + boolean expression, String errorMessageTemplate, Object p1, long p2) { + if (!expression) { + throw new VerifyException(lenientFormat(errorMessageTemplate, p1, p2)); + } + } + + /** + * Ensures that {@code expression} is {@code true}, throwing a {@code VerifyException} with a + * custom message otherwise. + * + *

See {@link #verify(boolean, String, Object...)} for details. + * + * @since 23.1 (varargs overload since 17.0) + */ + public static void verify( + boolean expression, String errorMessageTemplate, char p1, Object p2) { + if (!expression) { + throw new VerifyException(lenientFormat(errorMessageTemplate, p1, p2)); + } + } + + /** + * Ensures that {@code expression} is {@code true}, throwing a {@code VerifyException} with a + * custom message otherwise. + * + *

See {@link #verify(boolean, String, Object...)} for details. + * + * @since 23.1 (varargs overload since 17.0) + */ + public static void verify( + boolean expression, String errorMessageTemplate, int p1, Object p2) { + if (!expression) { + throw new VerifyException(lenientFormat(errorMessageTemplate, p1, p2)); + } + } + + /** + * Ensures that {@code expression} is {@code true}, throwing a {@code VerifyException} with a + * custom message otherwise. + * + *

See {@link #verify(boolean, String, Object...)} for details. + * + * @since 23.1 (varargs overload since 17.0) + */ + public static void verify( + boolean expression, String errorMessageTemplate, long p1, Object p2) { + if (!expression) { + throw new VerifyException(lenientFormat(errorMessageTemplate, p1, p2)); + } + } + + /** + * Ensures that {@code expression} is {@code true}, throwing a {@code VerifyException} with a + * custom message otherwise. + * + *

See {@link #verify(boolean, String, Object...)} for details. + * + * @since 23.1 (varargs overload since 17.0) + */ + public static void verify( + boolean expression, + String errorMessageTemplate, + Object p1, + Object p2) { + if (!expression) { + throw new VerifyException(lenientFormat(errorMessageTemplate, p1, p2)); + } + } + + /** + * Ensures that {@code expression} is {@code true}, throwing a {@code VerifyException} with a + * custom message otherwise. + * + *

See {@link #verify(boolean, String, Object...)} for details. + * + * @since 23.1 (varargs overload since 17.0) + */ + public static void verify( + boolean expression, + String errorMessageTemplate, + Object p1, + Object p2, + Object p3) { + if (!expression) { + throw new VerifyException(lenientFormat(errorMessageTemplate, p1, p2, p3)); + } + } + + /** + * Ensures that {@code expression} is {@code true}, throwing a {@code VerifyException} with a + * custom message otherwise. + * + *

See {@link #verify(boolean, String, Object...)} for details. + * + * @since 23.1 (varargs overload since 17.0) + */ + public static void verify( + boolean expression, + String errorMessageTemplate, + Object p1, + Object p2, + Object p3, + Object p4) { + if (!expression) { + throw new VerifyException(lenientFormat(errorMessageTemplate, p1, p2, p3, p4)); + } + } + + /** + * Ensures that {@code reference} is non-null, throwing a {@code VerifyException} with a default + * message otherwise. + * + * @return {@code reference}, guaranteed to be non-null, for convenience + * @throws VerifyException if {@code reference} is {@code null} + * @see Preconditions#checkNotNull Preconditions.checkNotNull() + */ + public static T verifyNotNull(T reference) { + return verifyNotNull(reference, "expected a non-null reference"); + } + + /** + * Ensures that {@code reference} is non-null, throwing a {@code VerifyException} with a custom + * message otherwise. + * + * @param errorMessageTemplate a template for the exception message should the check fail. The + * message is formed by replacing each {@code %s} placeholder in the template with an + * argument. These are matched by position - the first {@code %s} gets {@code + * errorMessageArgs[0]}, etc. Unmatched arguments will be appended to the formatted message in + * square braces. Unmatched placeholders will be left as-is. + * @param errorMessageArgs the arguments to be substituted into the message template. Arguments + * are converted to strings using {@link String#valueOf(Object)}. + * @return {@code reference}, guaranteed to be non-null, for convenience + * @throws VerifyException if {@code reference} is {@code null} + * @see Preconditions#checkNotNull Preconditions.checkNotNull() + */ + public static T verifyNotNull( + T reference, + String errorMessageTemplate, + Object ... errorMessageArgs) { + verify(reference != null, errorMessageTemplate, errorMessageArgs); + return reference; + } + + // TODO(kevinb): consider T verifySingleton(Iterable) to take over for + // Iterables.getOnlyElement() + + private Verify() {} +} diff --git a/src/main/java/com/google/common/base/VerifyException.java b/src/main/java/com/google/common/base/VerifyException.java new file mode 100644 index 0000000..5594b42 --- /dev/null +++ b/src/main/java/com/google/common/base/VerifyException.java @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2013 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.base; + +import com.google.common.annotations.GwtCompatible; + +/** + * Exception thrown upon the failure of a verification check, + * including those performed by the convenience methods of the {@link Verify} class. + * + * @since 17.0 + */ +@GwtCompatible +public class VerifyException extends RuntimeException { + /** Constructs a {@code VerifyException} with no message. */ + public VerifyException() {} + + /** Constructs a {@code VerifyException} with the message {@code message}. */ + public VerifyException(String message) { + super(message); + } + + /** + * Constructs a {@code VerifyException} with the cause {@code cause} and a message that is {@code + * null} if {@code cause} is null, and {@code cause.toString()} otherwise. + * + * @since 19.0 + */ + public VerifyException(Throwable cause) { + super(cause); + } + + /** + * Constructs a {@code VerifyException} with the message {@code message} and the cause {@code + * cause}. + * + * @since 19.0 + */ + public VerifyException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/src/main/java/com/google/common/base/internal/Finalizer.java b/src/main/java/com/google/common/base/internal/Finalizer.java new file mode 100644 index 0000000..d1e706a --- /dev/null +++ b/src/main/java/com/google/common/base/internal/Finalizer.java @@ -0,0 +1,234 @@ +/* + * Copyright (C) 2008 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.base.internal; + +import java.lang.ref.PhantomReference; +import java.lang.ref.Reference; +import java.lang.ref.ReferenceQueue; +import java.lang.ref.WeakReference; +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * Thread that finalizes referents. All references should implement {@code + * com.google.common.base.FinalizableReference}. + * + *

While this class is public, we consider it to be *internal* and not part of our published API. + * It is public so we can access it reflectively across class loaders in secure environments. + * + *

This class can't depend on other Guava code. If we were to load this class in the same class + * loader as the rest of Guava, this thread would keep an indirect strong reference to the class + * loader and prevent it from being garbage collected. This poses a problem for environments where + * you want to throw away the class loader. For example, dynamically reloading a web application or + * unloading an OSGi bundle. + * + *

{@code com.google.common.base.FinalizableReferenceQueue} loads this class in its own class + * loader. That way, this class doesn't prevent the main class loader from getting garbage + * collected, and this class can detect when the main class loader has been garbage collected and + * stop itself. + */ +public class Finalizer implements Runnable { + + private static final Logger logger = Logger.getLogger(Finalizer.class.getName()); + + /** Name of FinalizableReference.class. */ + private static final String FINALIZABLE_REFERENCE = "com.google.common.base.FinalizableReference"; + + /** + * Starts the Finalizer thread. FinalizableReferenceQueue calls this method reflectively. + * + * @param finalizableReferenceClass FinalizableReference.class. + * @param queue a reference queue that the thread will poll. + * @param frqReference a phantom reference to the FinalizableReferenceQueue, which will be queued + * either when the FinalizableReferenceQueue is no longer referenced anywhere, or when its + * close() method is called. + */ + public static void startFinalizer( + Class finalizableReferenceClass, + ReferenceQueue queue, + PhantomReference frqReference) { + /* + * We use FinalizableReference.class for two things: + * + * 1) To invoke FinalizableReference.finalizeReferent() + * + * 2) To detect when FinalizableReference's class loader has to be garbage collected, at which + * point, Finalizer can stop running + */ + if (!finalizableReferenceClass.getName().equals(FINALIZABLE_REFERENCE)) { + throw new IllegalArgumentException("Expected " + FINALIZABLE_REFERENCE + "."); + } + + Finalizer finalizer = new Finalizer(finalizableReferenceClass, queue, frqReference); + String threadName = Finalizer.class.getName(); + Thread thread = null; + if (bigThreadConstructor != null) { + try { + boolean inheritThreadLocals = false; + long defaultStackSize = 0; + thread = + bigThreadConstructor.newInstance( + (ThreadGroup) null, finalizer, threadName, defaultStackSize, inheritThreadLocals); + } catch (Throwable t) { + logger.log( + Level.INFO, "Failed to create a thread without inherited thread-local values", t); + } + } + if (thread == null) { + thread = new Thread((ThreadGroup) null, finalizer, threadName); + } + thread.setDaemon(true); + + try { + if (inheritableThreadLocals != null) { + inheritableThreadLocals.set(thread, null); + } + } catch (Throwable t) { + logger.log( + Level.INFO, + "Failed to clear thread local values inherited by reference finalizer thread.", + t); + } + + thread.start(); + } + + private final WeakReference> finalizableReferenceClassReference; + private final PhantomReference frqReference; + private final ReferenceQueue queue; + + // By preference, we will use the Thread constructor that has an `inheritThreadLocals` parameter. + // But before Java 9, our only way not to inherit ThreadLocals is to zap them after the thread + // is created, by accessing a private field. + private static final Constructor bigThreadConstructor = + getBigThreadConstructor(); + + private static final Field inheritableThreadLocals = + (bigThreadConstructor == null) ? getInheritableThreadLocalsField() : null; + + /** Constructs a new finalizer thread. */ + private Finalizer( + Class finalizableReferenceClass, + ReferenceQueue queue, + PhantomReference frqReference) { + this.queue = queue; + + this.finalizableReferenceClassReference = + new WeakReference>(finalizableReferenceClass); + + // Keep track of the FRQ that started us so we know when to stop. + this.frqReference = frqReference; + } + + /** Loops continuously, pulling references off the queue and cleaning them up. */ + @SuppressWarnings("InfiniteLoopStatement") + @Override + public void run() { + while (true) { + try { + if (!cleanUp(queue.remove())) { + break; + } + } catch (InterruptedException e) { + // ignore + } + } + } + + /** + * Cleans up a single reference. Catches and logs all throwables. + * + * @return true if the caller should continue, false if the associated FinalizableReferenceQueue + * is no longer referenced. + */ + private boolean cleanUp(Reference reference) { + Method finalizeReferentMethod = getFinalizeReferentMethod(); + if (finalizeReferentMethod == null) { + return false; + } + do { + /* + * This is for the benefit of phantom references. Weak and soft references will have already + * been cleared by this point. + */ + reference.clear(); + + if (reference == frqReference) { + /* + * The client no longer has a reference to the FinalizableReferenceQueue. We can stop. + */ + return false; + } + + try { + finalizeReferentMethod.invoke(reference); + } catch (Throwable t) { + logger.log(Level.SEVERE, "Error cleaning up after reference.", t); + } + + /* + * Loop as long as we have references available so as not to waste CPU looking up the Method + * over and over again. + */ + } while ((reference = queue.poll()) != null); + return true; + } + + /** Looks up FinalizableReference.finalizeReferent() method. */ + private Method getFinalizeReferentMethod() { + Class finalizableReferenceClass = finalizableReferenceClassReference.get(); + if (finalizableReferenceClass == null) { + /* + * FinalizableReference's class loader was reclaimed. While there's a chance that other + * finalizable references could be enqueued subsequently (at which point the class loader + * would be resurrected by virtue of us having a strong reference to it), we should pretty + * much just shut down and make sure we don't keep it alive any longer than necessary. + */ + return null; + } + try { + return finalizableReferenceClass.getMethod("finalizeReferent"); + } catch (NoSuchMethodException e) { + throw new AssertionError(e); + } + } + + private static Field getInheritableThreadLocalsField() { + try { + Field inheritableThreadLocals = Thread.class.getDeclaredField("inheritableThreadLocals"); + inheritableThreadLocals.setAccessible(true); + return inheritableThreadLocals; + } catch (Throwable t) { + logger.log( + Level.INFO, + "Couldn't access Thread.inheritableThreadLocals. Reference finalizer threads will " + + "inherit thread local values."); + return null; + } + } + + private static Constructor getBigThreadConstructor() { + try { + return Thread.class.getConstructor( + ThreadGroup.class, Runnable.class, String.class, long.class, boolean.class); + } catch (Throwable t) { + // Probably pre Java 9. We'll fall back to Thread.inheritableThreadLocals. + return null; + } + } +} diff --git a/src/main/java/com/google/common/cache/AbstractCache.java b/src/main/java/com/google/common/cache/AbstractCache.java new file mode 100644 index 0000000..d8ef032 --- /dev/null +++ b/src/main/java/com/google/common/cache/AbstractCache.java @@ -0,0 +1,265 @@ +/* + * Copyright (C) 2011 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.cache; + +import com.google.common.annotations.GwtCompatible; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Maps; +import java.util.Map; +import java.util.Map.Entry; +import java.util.concurrent.Callable; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.ExecutionException; + +/** + * This class provides a skeletal implementation of the {@code Cache} interface to minimize the + * effort required to implement this interface. + * + *

To implement a cache, the programmer needs only to extend this class and provide an + * implementation for the {@link #put} and {@link #getIfPresent} methods. {@link #getAllPresent} is + * implemented in terms of {@link #getIfPresent}; {@link #putAll} is implemented in terms of {@link + * #put}, {@link #invalidateAll(Iterable)} is implemented in terms of {@link #invalidate}. The + * method {@link #cleanUp} is a no-op. All other methods throw an {@link + * UnsupportedOperationException}. + * + * @author Charles Fry + * @since 10.0 + */ +@GwtCompatible +public abstract class AbstractCache implements Cache { + + /** Constructor for use by subclasses. */ + protected AbstractCache() {} + + /** @since 11.0 */ + @Override + public V get(K key, Callable valueLoader) throws ExecutionException { + throw new UnsupportedOperationException(); + } + + /** + * {@inheritDoc} + * + *

This implementation of {@code getAllPresent} lacks any insight into the internal cache data + * structure, and is thus forced to return the query keys instead of the cached keys. This is only + * possible with an unsafe cast which requires {@code keys} to actually be of type {@code K}. + * + * @since 11.0 + */ + @Override + public ImmutableMap getAllPresent(Iterable keys) { + Map result = Maps.newLinkedHashMap(); + for (Object key : keys) { + if (!result.containsKey(key)) { + @SuppressWarnings("unchecked") + K castKey = (K) key; + V value = getIfPresent(key); + if (value != null) { + result.put(castKey, value); + } + } + } + return ImmutableMap.copyOf(result); + } + + /** @since 11.0 */ + @Override + public void put(K key, V value) { + throw new UnsupportedOperationException(); + } + + /** @since 12.0 */ + @Override + public void putAll(Map m) { + for (Entry entry : m.entrySet()) { + put(entry.getKey(), entry.getValue()); + } + } + + @Override + public void cleanUp() {} + + @Override + public long size() { + throw new UnsupportedOperationException(); + } + + @Override + public void invalidate(Object key) { + throw new UnsupportedOperationException(); + } + + /** @since 11.0 */ + @Override + public void invalidateAll(Iterable keys) { + for (Object key : keys) { + invalidate(key); + } + } + + @Override + public void invalidateAll() { + throw new UnsupportedOperationException(); + } + + @Override + public CacheStats stats() { + throw new UnsupportedOperationException(); + } + + @Override + public ConcurrentMap asMap() { + throw new UnsupportedOperationException(); + } + + /** + * Accumulates statistics during the operation of a {@link Cache} for presentation by {@link + * Cache#stats}. This is solely intended for consumption by {@code Cache} implementors. + * + * @since 10.0 + */ + public interface StatsCounter { + /** + * Records cache hits. This should be called when a cache request returns a cached value. + * + * @param count the number of hits to record + * @since 11.0 + */ + void recordHits(int count); + + /** + * Records cache misses. This should be called when a cache request returns a value that was not + * found in the cache. This method should be called by the loading thread, as well as by threads + * blocking on the load. Multiple concurrent calls to {@link Cache} lookup methods with the same + * key on an absent value should result in a single call to either {@code recordLoadSuccess} or + * {@code recordLoadException} and multiple calls to this method, despite all being served by + * the results of a single load operation. + * + * @param count the number of misses to record + * @since 11.0 + */ + void recordMisses(int count); + + /** + * Records the successful load of a new entry. This should be called when a cache request causes + * an entry to be loaded, and the loading completes successfully. In contrast to {@link + * #recordMisses}, this method should only be called by the loading thread. + * + * @param loadTime the number of nanoseconds the cache spent computing or retrieving the new + * value + */ + @SuppressWarnings("GoodTime") // should accept a java.time.Duration + void recordLoadSuccess(long loadTime); + + /** + * Records the failed load of a new entry. This should be called when a cache request causes an + * entry to be loaded, but an exception is thrown while loading the entry. In contrast to {@link + * #recordMisses}, this method should only be called by the loading thread. + * + * @param loadTime the number of nanoseconds the cache spent computing or retrieving the new + * value prior to an exception being thrown + */ + @SuppressWarnings("GoodTime") // should accept a java.time.Duration + void recordLoadException(long loadTime); + + /** + * Records the eviction of an entry from the cache. This should only been called when an entry + * is evicted due to the cache's eviction strategy, and not as a result of manual {@linkplain + * Cache#invalidate invalidations}. + */ + void recordEviction(); + + /** + * Returns a snapshot of this counter's values. Note that this may be an inconsistent view, as + * it may be interleaved with update operations. + */ + CacheStats snapshot(); + } + + /** + * A thread-safe {@link StatsCounter} implementation for use by {@link Cache} implementors. + * + * @since 10.0 + */ + public static final class SimpleStatsCounter implements StatsCounter { + private final LongAddable hitCount = LongAddables.create(); + private final LongAddable missCount = LongAddables.create(); + private final LongAddable loadSuccessCount = LongAddables.create(); + private final LongAddable loadExceptionCount = LongAddables.create(); + private final LongAddable totalLoadTime = LongAddables.create(); + private final LongAddable evictionCount = LongAddables.create(); + + /** Constructs an instance with all counts initialized to zero. */ + public SimpleStatsCounter() {} + + /** @since 11.0 */ + @Override + public void recordHits(int count) { + hitCount.add(count); + } + + /** @since 11.0 */ + @Override + public void recordMisses(int count) { + missCount.add(count); + } + + @SuppressWarnings("GoodTime") // b/122668874 + @Override + public void recordLoadSuccess(long loadTime) { + loadSuccessCount.increment(); + totalLoadTime.add(loadTime); + } + + @SuppressWarnings("GoodTime") // b/122668874 + @Override + public void recordLoadException(long loadTime) { + loadExceptionCount.increment(); + totalLoadTime.add(loadTime); + } + + @Override + public void recordEviction() { + evictionCount.increment(); + } + + @Override + public CacheStats snapshot() { + return new CacheStats( + negativeToMaxValue(hitCount.sum()), + negativeToMaxValue(missCount.sum()), + negativeToMaxValue(loadSuccessCount.sum()), + negativeToMaxValue(loadExceptionCount.sum()), + negativeToMaxValue(totalLoadTime.sum()), + negativeToMaxValue(evictionCount.sum())); + } + + /** Returns {@code value}, if non-negative. Otherwise, returns {@link Long#MAX_VALUE}. */ + private static long negativeToMaxValue(long value) { + return (value >= 0) ? value : Long.MAX_VALUE; + } + + /** Increments all counters by the values in {@code other}. */ + public void incrementBy(StatsCounter other) { + CacheStats otherStats = other.snapshot(); + hitCount.add(otherStats.hitCount()); + missCount.add(otherStats.missCount()); + loadSuccessCount.add(otherStats.loadSuccessCount()); + loadExceptionCount.add(otherStats.loadExceptionCount()); + totalLoadTime.add(otherStats.totalLoadTime()); + evictionCount.add(otherStats.evictionCount()); + } + } +} diff --git a/src/main/java/com/google/common/cache/AbstractLoadingCache.java b/src/main/java/com/google/common/cache/AbstractLoadingCache.java new file mode 100644 index 0000000..38b9774 --- /dev/null +++ b/src/main/java/com/google/common/cache/AbstractLoadingCache.java @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2011 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.cache; + +import com.google.common.annotations.GwtIncompatible; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Maps; +import com.google.common.util.concurrent.UncheckedExecutionException; +import java.util.Map; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutionException; + +/** + * This class provides a skeletal implementation of the {@code Cache} interface to minimize the + * effort required to implement this interface. + * + *

To implement a cache, the programmer needs only to extend this class and provide an + * implementation for the {@link #get(Object)} and {@link #getIfPresent} methods. {@link + * #getUnchecked}, {@link #get(Object, Callable)}, and {@link #getAll} are implemented in terms of + * {@code get}; {@link #getAllPresent} is implemented in terms of {@code getIfPresent}; {@link + * #putAll} is implemented in terms of {@link #put}, {@link #invalidateAll(Iterable)} is implemented + * in terms of {@link #invalidate}. The method {@link #cleanUp} is a no-op. All other methods throw + * an {@link UnsupportedOperationException}. + * + * @author Charles Fry + * @since 11.0 + */ +@GwtIncompatible +public abstract class AbstractLoadingCache extends AbstractCache + implements LoadingCache { + + /** Constructor for use by subclasses. */ + protected AbstractLoadingCache() {} + + @Override + public V getUnchecked(K key) { + try { + return get(key); + } catch (ExecutionException e) { + throw new UncheckedExecutionException(e.getCause()); + } + } + + @Override + public ImmutableMap getAll(Iterable keys) throws ExecutionException { + Map result = Maps.newLinkedHashMap(); + for (K key : keys) { + if (!result.containsKey(key)) { + result.put(key, get(key)); + } + } + return ImmutableMap.copyOf(result); + } + + @Override + public final V apply(K key) { + return getUnchecked(key); + } + + @Override + public void refresh(K key) { + throw new UnsupportedOperationException(); + } +} diff --git a/src/main/java/com/google/common/cache/Cache.java b/src/main/java/com/google/common/cache/Cache.java new file mode 100644 index 0000000..601fc85 --- /dev/null +++ b/src/main/java/com/google/common/cache/Cache.java @@ -0,0 +1,169 @@ +/* + * Copyright (C) 2011 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.cache; + +import com.google.common.annotations.GwtCompatible; +import com.google.common.collect.ImmutableMap; +import com.google.common.util.concurrent.ExecutionError; +import com.google.common.util.concurrent.UncheckedExecutionException; +import java.util.Map; +import java.util.concurrent.Callable; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.ExecutionException; + +/** + * A semi-persistent mapping from keys to values. Cache entries are manually added using {@link + * #get(Object, Callable)} or {@link #put(Object, Object)}, and are stored in the cache until either + * evicted or manually invalidated. The common way to build instances is using {@link CacheBuilder}. + * + *

Implementations of this interface are expected to be thread-safe, and can be safely accessed + * by multiple concurrent threads. + * + * @author Charles Fry + * @since 10.0 + */ +@GwtCompatible +public interface Cache { + + /** + * Returns the value associated with {@code key} in this cache, or {@code null} if there is no + * cached value for {@code key}. + * + * @since 11.0 + */ + V getIfPresent(Object key); + + /** + * Returns the value associated with {@code key} in this cache, obtaining that value from {@code + * loader} if necessary. The method improves upon the conventional "if cached, return; otherwise + * create, cache and return" pattern. For further improvements, use {@link LoadingCache} and its + * {@link LoadingCache#get(Object) get(K)} method instead of this one. + * + *

Among the improvements that this method and {@code LoadingCache.get(K)} both provide are: + * + *

    + *
  • {@linkplain LoadingCache#get(Object) awaiting the result of a pending load} rather than + * starting a redundant one + *
  • eliminating the error-prone caching boilerplate + *
  • tracking load {@linkplain #stats statistics} + *
+ * + *

Among the further improvements that {@code LoadingCache} can provide but this method cannot: + * + *

    + *
  • consolidation of the loader logic to {@linkplain CacheBuilder#build(CacheLoader) a single + * authoritative location} + *
  • {@linkplain LoadingCache#refresh refreshing of entries}, including {@linkplain + * CacheBuilder#refreshAfterWrite automated refreshing} + *
  • {@linkplain LoadingCache#getAll bulk loading requests}, including {@linkplain + * CacheLoader#loadAll bulk loading implementations} + *
+ * + *

Warning: For any given key, every {@code loader} used with it should compute the same + * value. Otherwise, a call that passes one {@code loader} may return the result of another call + * with a differently behaving {@code loader}. For example, a call that requests a short timeout + * for an RPC may wait for a similar call that requests a long timeout, or a call by an + * unprivileged user may return a resource accessible only to a privileged user making a similar + * call. To prevent this problem, create a key object that includes all values that affect the + * result of the query. Or use {@code LoadingCache.get(K)}, which lacks the ability to refer to + * state other than that in the key. + * + *

Warning: as with {@link CacheLoader#load}, {@code loader} must not return + * {@code null}; it may either return a non-null value or throw an exception. + * + *

No observable state associated with this cache is modified until loading completes. + * + * @throws ExecutionException if a checked exception was thrown while loading the value + * @throws UncheckedExecutionException if an unchecked exception was thrown while loading the + * value + * @throws ExecutionError if an error was thrown while loading the value + * @since 11.0 + */ + V get(K key, Callable loader) throws ExecutionException; + + /** + * Returns a map of the values associated with {@code keys} in this cache. The returned map will + * only contain entries which are already present in the cache. + * + * @since 11.0 + */ + ImmutableMap getAllPresent(Iterable keys); + + /** + * Associates {@code value} with {@code key} in this cache. If the cache previously contained a + * value associated with {@code key}, the old value is replaced by {@code value}. + * + *

Prefer {@link #get(Object, Callable)} when using the conventional "if cached, return; + * otherwise create, cache and return" pattern. + * + * @since 11.0 + */ + void put(K key, V value); + + /** + * Copies all of the mappings from the specified map to the cache. The effect of this call is + * equivalent to that of calling {@code put(k, v)} on this map once for each mapping from key + * {@code k} to value {@code v} in the specified map. The behavior of this operation is undefined + * if the specified map is modified while the operation is in progress. + * + * @since 12.0 + */ + void putAll(Map m); + + /** Discards any cached value for key {@code key}. */ + void invalidate(Object key); + + /** + * Discards any cached values for keys {@code keys}. + * + * @since 11.0 + */ + void invalidateAll(Iterable keys); + + /** Discards all entries in the cache. */ + void invalidateAll(); + + /** Returns the approximate number of entries in this cache. */ + long size(); + + /** + * Returns a current snapshot of this cache's cumulative statistics, or a set of default values if + * the cache is not recording statistics. All statistics begin at zero and never decrease over the + * lifetime of the cache. + * + *

Warning: this cache may not be recording statistical data. For example, a cache + * created using {@link CacheBuilder} only does so if the {@link CacheBuilder#recordStats} method + * was called. If statistics are not being recorded, a {@code CacheStats} instance with zero for + * all values is returned. + * + */ + CacheStats stats(); + + /** + * Returns a view of the entries stored in this cache as a thread-safe map. Modifications made to + * the map directly affect the cache. + * + *

Iterators from the returned map are at least weakly consistent: they are safe for + * concurrent use, but if the cache is modified (including by eviction) after the iterator is + * created, it is undefined which of the changes (if any) will be reflected in that iterator. + */ + ConcurrentMap asMap(); + + /** + * Performs any pending maintenance operations needed by the cache. Exactly which activities are + * performed -- if any -- is implementation-dependent. + */ + void cleanUp(); +} diff --git a/src/main/java/com/google/common/cache/CacheBuilder.java b/src/main/java/com/google/common/cache/CacheBuilder.java new file mode 100644 index 0000000..a945293 --- /dev/null +++ b/src/main/java/com/google/common/cache/CacheBuilder.java @@ -0,0 +1,1055 @@ +/* + * Copyright (C) 2009 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.cache; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.base.Preconditions.checkState; + +import com.google.common.annotations.GwtCompatible; +import com.google.common.annotations.GwtIncompatible; +import com.google.common.base.Ascii; +import com.google.common.base.Equivalence; +import com.google.common.base.MoreObjects; +import com.google.common.base.Supplier; +import com.google.common.base.Suppliers; +import com.google.common.base.Ticker; +import com.google.common.cache.AbstractCache.SimpleStatsCounter; +import com.google.common.cache.AbstractCache.StatsCounter; +import com.google.common.cache.LocalCache.Strength; + + +import java.lang.ref.SoftReference; +import java.lang.ref.WeakReference; +import java.util.ConcurrentModificationException; +import java.util.IdentityHashMap; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.TimeUnit; +import java.util.logging.Level; +import java.util.logging.Logger; + + +/** + * A builder of {@link LoadingCache} and {@link Cache} instances having any combination of the + * following features: + * + *

    + *
  • automatic loading of entries into the cache + *
  • least-recently-used eviction when a maximum size is exceeded + *
  • time-based expiration of entries, measured since last access or last write + *
  • keys automatically wrapped in {@linkplain WeakReference weak} references + *
  • values automatically wrapped in {@linkplain WeakReference weak} or {@linkplain + * SoftReference soft} references + *
  • notification of evicted (or otherwise removed) entries + *
  • accumulation of cache access statistics + *
+ * + * + *

These features are all optional; caches can be created using all or none of them. By default + * cache instances created by {@code CacheBuilder} will not perform any type of eviction. + * + *

Usage example: + * + *

{@code
+ * LoadingCache graphs = CacheBuilder.newBuilder()
+ *     .maximumSize(10000)
+ *     .expireAfterWrite(Duration.ofMinutes(10))
+ *     .removalListener(MY_LISTENER)
+ *     .build(
+ *         new CacheLoader() {
+ *           public Graph load(Key key) throws AnyException {
+ *             return createExpensiveGraph(key);
+ *           }
+ *         });
+ * }
+ * + *

Or equivalently, + * + *

{@code
+ * // In real life this would come from a command-line flag or config file
+ * String spec = "maximumSize=10000,expireAfterWrite=10m";
+ *
+ * LoadingCache graphs = CacheBuilder.from(spec)
+ *     .removalListener(MY_LISTENER)
+ *     .build(
+ *         new CacheLoader() {
+ *           public Graph load(Key key) throws AnyException {
+ *             return createExpensiveGraph(key);
+ *           }
+ *         });
+ * }
+ * + *

The returned cache is implemented as a hash table with similar performance characteristics to + * {@link ConcurrentHashMap}. It implements all optional operations of the {@link LoadingCache} and + * {@link Cache} interfaces. The {@code asMap} view (and its collection views) have weakly + * consistent iterators. This means that they are safe for concurrent use, but if other threads + * modify the cache after the iterator is created, it is undefined which of these changes, if any, + * are reflected in that iterator. These iterators never throw {@link + * ConcurrentModificationException}. + * + *

Note: by default, the returned cache uses equality comparisons (the {@link + * Object#equals equals} method) to determine equality for keys or values. However, if {@link + * #weakKeys} was specified, the cache uses identity ({@code ==}) comparisons instead for keys. + * Likewise, if {@link #weakValues} or {@link #softValues} was specified, the cache uses identity + * comparisons for values. + * + *

Entries are automatically evicted from the cache when any of {@linkplain #maximumSize(long) + * maximumSize}, {@linkplain #maximumWeight(long) maximumWeight}, {@linkplain #expireAfterWrite + * expireAfterWrite}, {@linkplain #expireAfterAccess expireAfterAccess}, {@linkplain #weakKeys + * weakKeys}, {@linkplain #weakValues weakValues}, or {@linkplain #softValues softValues} are + * requested. + * + *

If {@linkplain #maximumSize(long) maximumSize} or {@linkplain #maximumWeight(long) + * maximumWeight} is requested entries may be evicted on each cache modification. + * + *

If {@linkplain #expireAfterWrite expireAfterWrite} or {@linkplain #expireAfterAccess + * expireAfterAccess} is requested entries may be evicted on each cache modification, on occasional + * cache accesses, or on calls to {@link Cache#cleanUp}. Expired entries may be counted by {@link + * Cache#size}, but will never be visible to read or write operations. + * + *

If {@linkplain #weakKeys weakKeys}, {@linkplain #weakValues weakValues}, or {@linkplain + * #softValues softValues} are requested, it is possible for a key or value present in the cache to + * be reclaimed by the garbage collector. Entries with reclaimed keys or values may be removed from + * the cache on each cache modification, on occasional cache accesses, or on calls to {@link + * Cache#cleanUp}; such entries may be counted in {@link Cache#size}, but will never be visible to + * read or write operations. + * + *

Certain cache configurations will result in the accrual of periodic maintenance tasks which + * will be performed during write operations, or during occasional read operations in the absence of + * writes. The {@link Cache#cleanUp} method of the returned cache will also perform maintenance, but + * calling it should not be necessary with a high throughput cache. Only caches built with + * {@linkplain #removalListener removalListener}, {@linkplain #expireAfterWrite expireAfterWrite}, + * {@linkplain #expireAfterAccess expireAfterAccess}, {@linkplain #weakKeys weakKeys}, {@linkplain + * #weakValues weakValues}, or {@linkplain #softValues softValues} perform periodic maintenance. + * + *

The caches produced by {@code CacheBuilder} are serializable, and the deserialized caches + * retain all the configuration properties of the original cache. Note that the serialized form does + * not include cache contents, but only configuration. + * + *

See the Guava User Guide article on caching for a higher-level + * explanation. + * + * @param the most general key type this builder will be able to create caches for. This is + * normally {@code Object} unless it is constrained by using a method like {@code + * #removalListener} + * @param the most general value type this builder will be able to create caches for. This is + * normally {@code Object} unless it is constrained by using a method like {@code + * #removalListener} + * @author Charles Fry + * @author Kevin Bourrillion + * @since 10.0 + */ +@GwtCompatible(emulated = true) +public final class CacheBuilder { + private static final int DEFAULT_INITIAL_CAPACITY = 16; + private static final int DEFAULT_CONCURRENCY_LEVEL = 4; + + @SuppressWarnings("GoodTime") // should be a java.time.Duration + private static final int DEFAULT_EXPIRATION_NANOS = 0; + + @SuppressWarnings("GoodTime") // should be a java.time.Duration + private static final int DEFAULT_REFRESH_NANOS = 0; + + static final Supplier NULL_STATS_COUNTER = + Suppliers.ofInstance( + new StatsCounter() { + @Override + public void recordHits(int count) {} + + @Override + public void recordMisses(int count) {} + + @SuppressWarnings("GoodTime") // b/122668874 + @Override + public void recordLoadSuccess(long loadTime) {} + + @SuppressWarnings("GoodTime") // b/122668874 + @Override + public void recordLoadException(long loadTime) {} + + @Override + public void recordEviction() {} + + @Override + public CacheStats snapshot() { + return EMPTY_STATS; + } + }); + static final CacheStats EMPTY_STATS = new CacheStats(0, 0, 0, 0, 0, 0); + + static final Supplier CACHE_STATS_COUNTER = + new Supplier() { + @Override + public StatsCounter get() { + return new SimpleStatsCounter(); + } + }; + + enum NullListener implements RemovalListener { + INSTANCE; + + @Override + public void onRemoval(RemovalNotification notification) {} + } + + enum OneWeigher implements Weigher { + INSTANCE; + + @Override + public int weigh(Object key, Object value) { + return 1; + } + } + + static final Ticker NULL_TICKER = + new Ticker() { + @Override + public long read() { + return 0; + } + }; + + private static final Logger logger = Logger.getLogger(CacheBuilder.class.getName()); + + static final int UNSET_INT = -1; + + boolean strictParsing = true; + + int initialCapacity = UNSET_INT; + int concurrencyLevel = UNSET_INT; + long maximumSize = UNSET_INT; + long maximumWeight = UNSET_INT; + Weigher weigher; + + Strength keyStrength; + Strength valueStrength; + + @SuppressWarnings("GoodTime") // should be a java.time.Duration + long expireAfterWriteNanos = UNSET_INT; + + @SuppressWarnings("GoodTime") // should be a java.time.Duration + long expireAfterAccessNanos = UNSET_INT; + + @SuppressWarnings("GoodTime") // should be a java.time.Duration + long refreshNanos = UNSET_INT; + + Equivalence keyEquivalence; + Equivalence valueEquivalence; + + RemovalListener removalListener; + Ticker ticker; + + Supplier statsCounterSupplier = NULL_STATS_COUNTER; + + private CacheBuilder() {} + + /** + * Constructs a new {@code CacheBuilder} instance with default settings, including strong keys, + * strong values, and no automatic eviction of any kind. + * + *

Note that while this return type is {@code CacheBuilder}, type parameters on + * the {@link #build} methods allow you to create a cache of any key and value type desired. + */ + public static CacheBuilder newBuilder() { + return new CacheBuilder<>(); + } + + /** + * Constructs a new {@code CacheBuilder} instance with the settings specified in {@code spec}. + * + * @since 12.0 + */ + @GwtIncompatible // To be supported + public static CacheBuilder from(CacheBuilderSpec spec) { + return spec.toCacheBuilder().lenientParsing(); + } + + /** + * Constructs a new {@code CacheBuilder} instance with the settings specified in {@code spec}. + * This is especially useful for command-line configuration of a {@code CacheBuilder}. + * + * @param spec a String in the format specified by {@link CacheBuilderSpec} + * @since 12.0 + */ + @GwtIncompatible // To be supported + public static CacheBuilder from(String spec) { + return from(CacheBuilderSpec.parse(spec)); + } + + /** + * Enables lenient parsing. Useful for tests and spec parsing. + * + * @return this {@code CacheBuilder} instance (for chaining) + */ + @GwtIncompatible // To be supported + CacheBuilder lenientParsing() { + strictParsing = false; + return this; + } + + /** + * Sets a custom {@code Equivalence} strategy for comparing keys. + * + *

By default, the cache uses {@link Equivalence#identity} to determine key equality when + * {@link #weakKeys} is specified, and {@link Equivalence#equals()} otherwise. + * + * @return this {@code CacheBuilder} instance (for chaining) + */ + @GwtIncompatible // To be supported + CacheBuilder keyEquivalence(Equivalence equivalence) { + checkState(keyEquivalence == null, "key equivalence was already set to %s", keyEquivalence); + keyEquivalence = checkNotNull(equivalence); + return this; + } + + Equivalence getKeyEquivalence() { + return MoreObjects.firstNonNull(keyEquivalence, getKeyStrength().defaultEquivalence()); + } + + /** + * Sets a custom {@code Equivalence} strategy for comparing values. + * + *

By default, the cache uses {@link Equivalence#identity} to determine value equality when + * {@link #weakValues} or {@link #softValues} is specified, and {@link Equivalence#equals()} + * otherwise. + * + * @return this {@code CacheBuilder} instance (for chaining) + */ + @GwtIncompatible // To be supported + CacheBuilder valueEquivalence(Equivalence equivalence) { + checkState( + valueEquivalence == null, "value equivalence was already set to %s", valueEquivalence); + this.valueEquivalence = checkNotNull(equivalence); + return this; + } + + Equivalence getValueEquivalence() { + return MoreObjects.firstNonNull(valueEquivalence, getValueStrength().defaultEquivalence()); + } + + /** + * Sets the minimum total size for the internal hash tables. For example, if the initial capacity + * is {@code 60}, and the concurrency level is {@code 8}, then eight segments are created, each + * having a hash table of size eight. Providing a large enough estimate at construction time + * avoids the need for expensive resizing operations later, but setting this value unnecessarily + * high wastes memory. + * + * @return this {@code CacheBuilder} instance (for chaining) + * @throws IllegalArgumentException if {@code initialCapacity} is negative + * @throws IllegalStateException if an initial capacity was already set + */ + public CacheBuilder initialCapacity(int initialCapacity) { + checkState( + this.initialCapacity == UNSET_INT, + "initial capacity was already set to %s", + this.initialCapacity); + checkArgument(initialCapacity >= 0); + this.initialCapacity = initialCapacity; + return this; + } + + int getInitialCapacity() { + return (initialCapacity == UNSET_INT) ? DEFAULT_INITIAL_CAPACITY : initialCapacity; + } + + /** + * Guides the allowed concurrency among update operations. Used as a hint for internal sizing. The + * table is internally partitioned to try to permit the indicated number of concurrent updates + * without contention. Because assignment of entries to these partitions is not necessarily + * uniform, the actual concurrency observed may vary. Ideally, you should choose a value to + * accommodate as many threads as will ever concurrently modify the table. Using a significantly + * higher value than you need can waste space and time, and a significantly lower value can lead + * to thread contention. But overestimates and underestimates within an order of magnitude do not + * usually have much noticeable impact. A value of one permits only one thread to modify the cache + * at a time, but since read operations and cache loading computations can proceed concurrently, + * this still yields higher concurrency than full synchronization. + * + *

Defaults to 4. Note:The default may change in the future. If you care about this + * value, you should always choose it explicitly. + * + *

The current implementation uses the concurrency level to create a fixed number of hashtable + * segments, each governed by its own write lock. The segment lock is taken once for each explicit + * write, and twice for each cache loading computation (once prior to loading the new value, and + * once after loading completes). Much internal cache management is performed at the segment + * granularity. For example, access queues and write queues are kept per segment when they are + * required by the selected eviction algorithm. As such, when writing unit tests it is not + * uncommon to specify {@code concurrencyLevel(1)} in order to achieve more deterministic eviction + * behavior. + * + *

Note that future implementations may abandon segment locking in favor of more advanced + * concurrency controls. + * + * @return this {@code CacheBuilder} instance (for chaining) + * @throws IllegalArgumentException if {@code concurrencyLevel} is nonpositive + * @throws IllegalStateException if a concurrency level was already set + */ + public CacheBuilder concurrencyLevel(int concurrencyLevel) { + checkState( + this.concurrencyLevel == UNSET_INT, + "concurrency level was already set to %s", + this.concurrencyLevel); + checkArgument(concurrencyLevel > 0); + this.concurrencyLevel = concurrencyLevel; + return this; + } + + int getConcurrencyLevel() { + return (concurrencyLevel == UNSET_INT) ? DEFAULT_CONCURRENCY_LEVEL : concurrencyLevel; + } + + /** + * Specifies the maximum number of entries the cache may contain. + * + *

Note that the cache may evict an entry before this limit is exceeded. For example, in + * the current implementation, when {@code concurrencyLevel} is greater than {@code 1}, each + * resulting segment inside the cache independently limits its own size to approximately + * {@code maximumSize / concurrencyLevel}. + * + *

When eviction is necessary, the cache evicts entries that are less likely to be used again. + * For example, the cache may evict an entry because it hasn't been used recently or very often. + * + *

If {@code maximumSize} is zero, elements will be evicted immediately after being loaded into + * cache. This can be useful in testing, or to disable caching temporarily. + * + *

This feature cannot be used in conjunction with {@link #maximumWeight}. + * + * @param maximumSize the maximum size of the cache + * @return this {@code CacheBuilder} instance (for chaining) + * @throws IllegalArgumentException if {@code maximumSize} is negative + * @throws IllegalStateException if a maximum size or weight was already set + */ + public CacheBuilder maximumSize(long maximumSize) { + checkState( + this.maximumSize == UNSET_INT, "maximum size was already set to %s", this.maximumSize); + checkState( + this.maximumWeight == UNSET_INT, + "maximum weight was already set to %s", + this.maximumWeight); + checkState(this.weigher == null, "maximum size can not be combined with weigher"); + checkArgument(maximumSize >= 0, "maximum size must not be negative"); + this.maximumSize = maximumSize; + return this; + } + + /** + * Specifies the maximum weight of entries the cache may contain. Weight is determined using the + * {@link Weigher} specified with {@link #weigher}, and use of this method requires a + * corresponding call to {@link #weigher} prior to calling {@link #build}. + * + *

Note that the cache may evict an entry before this limit is exceeded. For example, in + * the current implementation, when {@code concurrencyLevel} is greater than {@code 1}, each + * resulting segment inside the cache independently limits its own weight to approximately + * {@code maximumWeight / concurrencyLevel}. + * + *

When eviction is necessary, the cache evicts entries that are less likely to be used again. + * For example, the cache may evict an entry because it hasn't been used recently or very often. + * + *

If {@code maximumWeight} is zero, elements will be evicted immediately after being loaded + * into cache. This can be useful in testing, or to disable caching temporarily. + * + *

Note that weight is only used to determine whether the cache is over capacity; it has no + * effect on selecting which entry should be evicted next. + * + *

This feature cannot be used in conjunction with {@link #maximumSize}. + * + * @param maximumWeight the maximum total weight of entries the cache may contain + * @return this {@code CacheBuilder} instance (for chaining) + * @throws IllegalArgumentException if {@code maximumWeight} is negative + * @throws IllegalStateException if a maximum weight or size was already set + * @since 11.0 + */ + @GwtIncompatible // To be supported + public CacheBuilder maximumWeight(long maximumWeight) { + checkState( + this.maximumWeight == UNSET_INT, + "maximum weight was already set to %s", + this.maximumWeight); + checkState( + this.maximumSize == UNSET_INT, "maximum size was already set to %s", this.maximumSize); + this.maximumWeight = maximumWeight; + checkArgument(maximumWeight >= 0, "maximum weight must not be negative"); + return this; + } + + /** + * Specifies the weigher to use in determining the weight of entries. Entry weight is taken into + * consideration by {@link #maximumWeight(long)} when determining which entries to evict, and use + * of this method requires a corresponding call to {@link #maximumWeight(long)} prior to calling + * {@link #build}. Weights are measured and recorded when entries are inserted into the cache, and + * are thus effectively static during the lifetime of a cache entry. + * + *

When the weight of an entry is zero it will not be considered for size-based eviction + * (though it still may be evicted by other means). + * + *

Important note: Instead of returning this as a {@code CacheBuilder} + * instance, this method returns {@code CacheBuilder}. From this point on, either the + * original reference or the returned reference may be used to complete configuration and build + * the cache, but only the "generic" one is type-safe. That is, it will properly prevent you from + * building caches whose key or value types are incompatible with the types accepted by the + * weigher already provided; the {@code CacheBuilder} type cannot do this. For best results, + * simply use the standard method-chaining idiom, as illustrated in the documentation at top, + * configuring a {@code CacheBuilder} and building your {@link Cache} all in a single statement. + * + *

Warning: if you ignore the above advice, and use this {@code CacheBuilder} to build a + * cache whose key or value type is incompatible with the weigher, you will likely experience a + * {@link ClassCastException} at some undefined point in the future. + * + * @param weigher the weigher to use in calculating the weight of cache entries + * @return this {@code CacheBuilder} instance (for chaining) + * @throws IllegalArgumentException if {@code size} is negative + * @throws IllegalStateException if a maximum size was already set + * @since 11.0 + */ + @GwtIncompatible // To be supported + public CacheBuilder weigher( + Weigher weigher) { + checkState(this.weigher == null); + if (strictParsing) { + checkState( + this.maximumSize == UNSET_INT, + "weigher can not be combined with maximum size", + this.maximumSize); + } + + // safely limiting the kinds of caches this can produce + @SuppressWarnings("unchecked") + CacheBuilder me = (CacheBuilder) this; + me.weigher = checkNotNull(weigher); + return me; + } + + long getMaximumWeight() { + if (expireAfterWriteNanos == 0 || expireAfterAccessNanos == 0) { + return 0; + } + return (weigher == null) ? maximumSize : maximumWeight; + } + + // Make a safe contravariant cast now so we don't have to do it over and over. + @SuppressWarnings("unchecked") + Weigher getWeigher() { + return (Weigher) MoreObjects.firstNonNull(weigher, OneWeigher.INSTANCE); + } + + /** + * Specifies that each key (not value) stored in the cache should be wrapped in a {@link + * WeakReference} (by default, strong references are used). + * + *

Warning: when this method is used, the resulting cache will use identity ({@code ==}) + * comparison to determine equality of keys. Its {@link Cache#asMap} view will therefore + * technically violate the {@link Map} specification (in the same way that {@link IdentityHashMap} + * does). + * + *

Entries with keys that have been garbage collected may be counted in {@link Cache#size}, but + * will never be visible to read or write operations; such entries are cleaned up as part of the + * routine maintenance described in the class javadoc. + * + * @return this {@code CacheBuilder} instance (for chaining) + * @throws IllegalStateException if the key strength was already set + */ + @GwtIncompatible // java.lang.ref.WeakReference + public CacheBuilder weakKeys() { + return setKeyStrength(Strength.WEAK); + } + + CacheBuilder setKeyStrength(Strength strength) { + checkState(keyStrength == null, "Key strength was already set to %s", keyStrength); + keyStrength = checkNotNull(strength); + return this; + } + + Strength getKeyStrength() { + return MoreObjects.firstNonNull(keyStrength, Strength.STRONG); + } + + /** + * Specifies that each value (not key) stored in the cache should be wrapped in a {@link + * WeakReference} (by default, strong references are used). + * + *

Weak values will be garbage collected once they are weakly reachable. This makes them a poor + * candidate for caching; consider {@link #softValues} instead. + * + *

Note: when this method is used, the resulting cache will use identity ({@code ==}) + * comparison to determine equality of values. + * + *

Entries with values that have been garbage collected may be counted in {@link Cache#size}, + * but will never be visible to read or write operations; such entries are cleaned up as part of + * the routine maintenance described in the class javadoc. + * + * @return this {@code CacheBuilder} instance (for chaining) + * @throws IllegalStateException if the value strength was already set + */ + @GwtIncompatible // java.lang.ref.WeakReference + public CacheBuilder weakValues() { + return setValueStrength(Strength.WEAK); + } + + /** + * Specifies that each value (not key) stored in the cache should be wrapped in a {@link + * SoftReference} (by default, strong references are used). Softly-referenced objects will be + * garbage-collected in a globally least-recently-used manner, in response to memory + * demand. + * + *

Warning: in most circumstances it is better to set a per-cache {@linkplain + * #maximumSize(long) maximum size} instead of using soft references. You should only use this + * method if you are well familiar with the practical consequences of soft references. + * + *

Note: when this method is used, the resulting cache will use identity ({@code ==}) + * comparison to determine equality of values. + * + *

Entries with values that have been garbage collected may be counted in {@link Cache#size}, + * but will never be visible to read or write operations; such entries are cleaned up as part of + * the routine maintenance described in the class javadoc. + * + * @return this {@code CacheBuilder} instance (for chaining) + * @throws IllegalStateException if the value strength was already set + */ + @GwtIncompatible // java.lang.ref.SoftReference + public CacheBuilder softValues() { + return setValueStrength(Strength.SOFT); + } + + CacheBuilder setValueStrength(Strength strength) { + checkState(valueStrength == null, "Value strength was already set to %s", valueStrength); + valueStrength = checkNotNull(strength); + return this; + } + + Strength getValueStrength() { + return MoreObjects.firstNonNull(valueStrength, Strength.STRONG); + } + + /** + * Specifies that each entry should be automatically removed from the cache once a fixed duration + * has elapsed after the entry's creation, or the most recent replacement of its value. + * + *

When {@code duration} is zero, this method hands off to {@link #maximumSize(long) + * maximumSize}{@code (0)}, ignoring any otherwise-specified maximum size or weight. This can be + * useful in testing, or to disable caching temporarily without a code change. + * + *

Expired entries may be counted in {@link Cache#size}, but will never be visible to read or + * write operations. Expired entries are cleaned up as part of the routine maintenance described + * in the class javadoc. + * + * @param duration the length of time after an entry is created that it should be automatically + * removed + * @return this {@code CacheBuilder} instance (for chaining) + * @throws IllegalArgumentException if {@code duration} is negative + * @throws IllegalStateException if the time to live or time to idle was already set + * @throws ArithmeticException for durations greater than +/- approximately 292 years + * @since 25.0 + */ + + @GwtIncompatible // java.time.Duration + @SuppressWarnings("GoodTime") // java.time.Duration decomposition + public CacheBuilder expireAfterWrite(java.time.Duration duration) { + return expireAfterWrite(toNanosSaturated(duration), TimeUnit.NANOSECONDS); + } + + /** + * Specifies that each entry should be automatically removed from the cache once a fixed duration + * has elapsed after the entry's creation, or the most recent replacement of its value. + * + *

When {@code duration} is zero, this method hands off to {@link #maximumSize(long) + * maximumSize}{@code (0)}, ignoring any otherwise-specified maximum size or weight. This can be + * useful in testing, or to disable caching temporarily without a code change. + * + *

Expired entries may be counted in {@link Cache#size}, but will never be visible to read or + * write operations. Expired entries are cleaned up as part of the routine maintenance described + * in the class javadoc. + * + *

If you can represent the duration as a {@link java.time.Duration} (which should be preferred + * when feasible), use {@link #expireAfterWrite(Duration)} instead. + * + * @param duration the length of time after an entry is created that it should be automatically + * removed + * @param unit the unit that {@code duration} is expressed in + * @return this {@code CacheBuilder} instance (for chaining) + * @throws IllegalArgumentException if {@code duration} is negative + * @throws IllegalStateException if the time to live or time to idle was already set + */ + @SuppressWarnings("GoodTime") // should accept a java.time.Duration + public CacheBuilder expireAfterWrite(long duration, TimeUnit unit) { + checkState( + expireAfterWriteNanos == UNSET_INT, + "expireAfterWrite was already set to %s ns", + expireAfterWriteNanos); + checkArgument(duration >= 0, "duration cannot be negative: %s %s", duration, unit); + this.expireAfterWriteNanos = unit.toNanos(duration); + return this; + } + + @SuppressWarnings("GoodTime") // nanos internally, should be Duration + long getExpireAfterWriteNanos() { + return (expireAfterWriteNanos == UNSET_INT) ? DEFAULT_EXPIRATION_NANOS : expireAfterWriteNanos; + } + + /** + * Specifies that each entry should be automatically removed from the cache once a fixed duration + * has elapsed after the entry's creation, the most recent replacement of its value, or its last + * access. Access time is reset by all cache read and write operations (including {@code + * Cache.asMap().get(Object)} and {@code Cache.asMap().put(K, V)}), but not by {@code + * containsKey(Object)}, nor by operations on the collection-views of {@link Cache#asMap}}. So, + * for example, iterating through {@code Cache.asMap().entrySet()} does not reset access time for + * the entries you retrieve. + * + *

When {@code duration} is zero, this method hands off to {@link #maximumSize(long) + * maximumSize}{@code (0)}, ignoring any otherwise-specified maximum size or weight. This can be + * useful in testing, or to disable caching temporarily without a code change. + * + *

Expired entries may be counted in {@link Cache#size}, but will never be visible to read or + * write operations. Expired entries are cleaned up as part of the routine maintenance described + * in the class javadoc. + * + * @param duration the length of time after an entry is last accessed that it should be + * automatically removed + * @return this {@code CacheBuilder} instance (for chaining) + * @throws IllegalArgumentException if {@code duration} is negative + * @throws IllegalStateException if the time to idle or time to live was already set + * @throws ArithmeticException for durations greater than +/- approximately 292 years + * @since 25.0 + */ + + @GwtIncompatible // java.time.Duration + @SuppressWarnings("GoodTime") // java.time.Duration decomposition + public CacheBuilder expireAfterAccess(java.time.Duration duration) { + return expireAfterAccess(toNanosSaturated(duration), TimeUnit.NANOSECONDS); + } + + /** + * Specifies that each entry should be automatically removed from the cache once a fixed duration + * has elapsed after the entry's creation, the most recent replacement of its value, or its last + * access. Access time is reset by all cache read and write operations (including {@code + * Cache.asMap().get(Object)} and {@code Cache.asMap().put(K, V)}), but not by {@code + * containsKey(Object)}, nor by operations on the collection-views of {@link Cache#asMap}. So, for + * example, iterating through {@code Cache.asMap().entrySet()} does not reset access time for the + * entries you retrieve. + * + *

When {@code duration} is zero, this method hands off to {@link #maximumSize(long) + * maximumSize}{@code (0)}, ignoring any otherwise-specified maximum size or weight. This can be + * useful in testing, or to disable caching temporarily without a code change. + * + *

Expired entries may be counted in {@link Cache#size}, but will never be visible to read or + * write operations. Expired entries are cleaned up as part of the routine maintenance described + * in the class javadoc. + * + *

If you can represent the duration as a {@link java.time.Duration} (which should be preferred + * when feasible), use {@link #expireAfterAccess(Duration)} instead. + * + * @param duration the length of time after an entry is last accessed that it should be + * automatically removed + * @param unit the unit that {@code duration} is expressed in + * @return this {@code CacheBuilder} instance (for chaining) + * @throws IllegalArgumentException if {@code duration} is negative + * @throws IllegalStateException if the time to idle or time to live was already set + */ + @SuppressWarnings("GoodTime") // should accept a java.time.Duration + public CacheBuilder expireAfterAccess(long duration, TimeUnit unit) { + checkState( + expireAfterAccessNanos == UNSET_INT, + "expireAfterAccess was already set to %s ns", + expireAfterAccessNanos); + checkArgument(duration >= 0, "duration cannot be negative: %s %s", duration, unit); + this.expireAfterAccessNanos = unit.toNanos(duration); + return this; + } + + @SuppressWarnings("GoodTime") // nanos internally, should be Duration + long getExpireAfterAccessNanos() { + return (expireAfterAccessNanos == UNSET_INT) + ? DEFAULT_EXPIRATION_NANOS + : expireAfterAccessNanos; + } + + /** + * Specifies that active entries are eligible for automatic refresh once a fixed duration has + * elapsed after the entry's creation, or the most recent replacement of its value. The semantics + * of refreshes are specified in {@link LoadingCache#refresh}, and are performed by calling {@link + * CacheLoader#reload}. + * + *

As the default implementation of {@link CacheLoader#reload} is synchronous, it is + * recommended that users of this method override {@link CacheLoader#reload} with an asynchronous + * implementation; otherwise refreshes will be performed during unrelated cache read and write + * operations. + * + *

Currently automatic refreshes are performed when the first stale request for an entry + * occurs. The request triggering refresh will make a blocking call to {@link CacheLoader#reload} + * and immediately return the new value if the returned future is complete, and the old value + * otherwise. + * + *

Note: all exceptions thrown during refresh will be logged and then swallowed. + * + * @param duration the length of time after an entry is created that it should be considered + * stale, and thus eligible for refresh + * @return this {@code CacheBuilder} instance (for chaining) + * @throws IllegalArgumentException if {@code duration} is negative + * @throws IllegalStateException if the refresh interval was already set + * @throws ArithmeticException for durations greater than +/- approximately 292 years + * @since 25.0 + */ + + @GwtIncompatible // java.time.Duration + @SuppressWarnings("GoodTime") // java.time.Duration decomposition + public CacheBuilder refreshAfterWrite(java.time.Duration duration) { + return refreshAfterWrite(toNanosSaturated(duration), TimeUnit.NANOSECONDS); + } + + /** + * Specifies that active entries are eligible for automatic refresh once a fixed duration has + * elapsed after the entry's creation, or the most recent replacement of its value. The semantics + * of refreshes are specified in {@link LoadingCache#refresh}, and are performed by calling {@link + * CacheLoader#reload}. + * + *

As the default implementation of {@link CacheLoader#reload} is synchronous, it is + * recommended that users of this method override {@link CacheLoader#reload} with an asynchronous + * implementation; otherwise refreshes will be performed during unrelated cache read and write + * operations. + * + *

Currently automatic refreshes are performed when the first stale request for an entry + * occurs. The request triggering refresh will make a blocking call to {@link CacheLoader#reload} + * and immediately return the new value if the returned future is complete, and the old value + * otherwise. + * + *

Note: all exceptions thrown during refresh will be logged and then swallowed. + * + *

If you can represent the duration as a {@link java.time.Duration} (which should be preferred + * when feasible), use {@link #refreshAfterWrite(Duration)} instead. + * + * @param duration the length of time after an entry is created that it should be considered + * stale, and thus eligible for refresh + * @param unit the unit that {@code duration} is expressed in + * @return this {@code CacheBuilder} instance (for chaining) + * @throws IllegalArgumentException if {@code duration} is negative + * @throws IllegalStateException if the refresh interval was already set + * @since 11.0 + */ + @GwtIncompatible // To be supported (synchronously). + @SuppressWarnings("GoodTime") // should accept a java.time.Duration + public CacheBuilder refreshAfterWrite(long duration, TimeUnit unit) { + checkNotNull(unit); + checkState(refreshNanos == UNSET_INT, "refresh was already set to %s ns", refreshNanos); + checkArgument(duration > 0, "duration must be positive: %s %s", duration, unit); + this.refreshNanos = unit.toNanos(duration); + return this; + } + + @SuppressWarnings("GoodTime") // nanos internally, should be Duration + long getRefreshNanos() { + return (refreshNanos == UNSET_INT) ? DEFAULT_REFRESH_NANOS : refreshNanos; + } + + /** + * Specifies a nanosecond-precision time source for this cache. By default, {@link + * System#nanoTime} is used. + * + *

The primary intent of this method is to facilitate testing of caches with a fake or mock + * time source. + * + * @return this {@code CacheBuilder} instance (for chaining) + * @throws IllegalStateException if a ticker was already set + */ + public CacheBuilder ticker(Ticker ticker) { + checkState(this.ticker == null); + this.ticker = checkNotNull(ticker); + return this; + } + + Ticker getTicker(boolean recordsTime) { + if (ticker != null) { + return ticker; + } + return recordsTime ? Ticker.systemTicker() : NULL_TICKER; + } + + /** + * Specifies a listener instance that caches should notify each time an entry is removed for any + * {@linkplain RemovalCause reason}. Each cache created by this builder will invoke this listener + * as part of the routine maintenance described in the class documentation above. + * + *

Warning: after invoking this method, do not continue to use this cache builder + * reference; instead use the reference this method returns. At runtime, these point to the + * same instance, but only the returned reference has the correct generic type information so as + * to ensure type safety. For best results, use the standard method-chaining idiom illustrated in + * the class documentation above, configuring a builder and building your cache in a single + * statement. Failure to heed this advice can result in a {@link ClassCastException} being thrown + * by a cache operation at some undefined point in the future. + * + *

Warning: any exception thrown by {@code listener} will not be propagated to + * the {@code Cache} user, only logged via a {@link Logger}. + * + * @return the cache builder reference that should be used instead of {@code this} for any + * remaining configuration and cache building + * @return this {@code CacheBuilder} instance (for chaining) + * @throws IllegalStateException if a removal listener was already set + */ + + public CacheBuilder removalListener( + RemovalListener listener) { + checkState(this.removalListener == null); + + // safely limiting the kinds of caches this can produce + @SuppressWarnings("unchecked") + CacheBuilder me = (CacheBuilder) this; + me.removalListener = checkNotNull(listener); + return me; + } + + // Make a safe contravariant cast now so we don't have to do it over and over. + @SuppressWarnings("unchecked") + RemovalListener getRemovalListener() { + return (RemovalListener) + MoreObjects.firstNonNull(removalListener, NullListener.INSTANCE); + } + + /** + * Enable the accumulation of {@link CacheStats} during the operation of the cache. Without this + * {@link Cache#stats} will return zero for all statistics. Note that recording stats requires + * bookkeeping to be performed with each operation, and thus imposes a performance penalty on + * cache operation. + * + * @return this {@code CacheBuilder} instance (for chaining) + * @since 12.0 (previously, stats collection was automatic) + */ + public CacheBuilder recordStats() { + statsCounterSupplier = CACHE_STATS_COUNTER; + return this; + } + + boolean isRecordingStats() { + return statsCounterSupplier == CACHE_STATS_COUNTER; + } + + Supplier getStatsCounterSupplier() { + return statsCounterSupplier; + } + + /** + * Builds a cache, which either returns an already-loaded value for a given key or atomically + * computes or retrieves it using the supplied {@code CacheLoader}. If another thread is currently + * loading the value for this key, simply waits for that thread to finish and returns its loaded + * value. Note that multiple threads can concurrently load values for distinct keys. + * + *

This method does not alter the state of this {@code CacheBuilder} instance, so it can be + * invoked again to create multiple independent caches. + * + * @param loader the cache loader used to obtain new values + * @return a cache having the requested features + */ + public LoadingCache build( + CacheLoader loader) { + checkWeightWithWeigher(); + return new LocalCache.LocalLoadingCache<>(this, loader); + } + + /** + * Builds a cache which does not automatically load values when keys are requested. + * + *

Consider {@link #build(CacheLoader)} instead, if it is feasible to implement a {@code + * CacheLoader}. + * + *

This method does not alter the state of this {@code CacheBuilder} instance, so it can be + * invoked again to create multiple independent caches. + * + * @return a cache having the requested features + * @since 11.0 + */ + public Cache build() { + checkWeightWithWeigher(); + checkNonLoadingCache(); + return new LocalCache.LocalManualCache<>(this); + } + + private void checkNonLoadingCache() { + checkState(refreshNanos == UNSET_INT, "refreshAfterWrite requires a LoadingCache"); + } + + private void checkWeightWithWeigher() { + if (weigher == null) { + checkState(maximumWeight == UNSET_INT, "maximumWeight requires weigher"); + } else { + if (strictParsing) { + checkState(maximumWeight != UNSET_INT, "weigher requires maximumWeight"); + } else { + if (maximumWeight == UNSET_INT) { + logger.log(Level.WARNING, "ignoring weigher specified without maximumWeight"); + } + } + } + } + + /** + * Returns a string representation for this CacheBuilder instance. The exact form of the returned + * string is not specified. + */ + @Override + public String toString() { + MoreObjects.ToStringHelper s = MoreObjects.toStringHelper(this); + if (initialCapacity != UNSET_INT) { + s.add("initialCapacity", initialCapacity); + } + if (concurrencyLevel != UNSET_INT) { + s.add("concurrencyLevel", concurrencyLevel); + } + if (maximumSize != UNSET_INT) { + s.add("maximumSize", maximumSize); + } + if (maximumWeight != UNSET_INT) { + s.add("maximumWeight", maximumWeight); + } + if (expireAfterWriteNanos != UNSET_INT) { + s.add("expireAfterWrite", expireAfterWriteNanos + "ns"); + } + if (expireAfterAccessNanos != UNSET_INT) { + s.add("expireAfterAccess", expireAfterAccessNanos + "ns"); + } + if (keyStrength != null) { + s.add("keyStrength", Ascii.toLowerCase(keyStrength.toString())); + } + if (valueStrength != null) { + s.add("valueStrength", Ascii.toLowerCase(valueStrength.toString())); + } + if (keyEquivalence != null) { + s.addValue("keyEquivalence"); + } + if (valueEquivalence != null) { + s.addValue("valueEquivalence"); + } + if (removalListener != null) { + s.addValue("removalListener"); + } + return s.toString(); + } + + /** + * Returns the number of nanoseconds of the given duration without throwing or overflowing. + * + *

Instead of throwing {@link ArithmeticException}, this method silently saturates to either + * {@link Long#MAX_VALUE} or {@link Long#MIN_VALUE}. This behavior can be useful when decomposing + * a duration in order to call a legacy API which requires a {@code long, TimeUnit} pair. + */ + @GwtIncompatible // java.time.Duration + @SuppressWarnings("GoodTime") // duration decomposition + private static long toNanosSaturated(java.time.Duration duration) { + // Using a try/catch seems lazy, but the catch block will rarely get invoked (except for + // durations longer than approximately +/- 292 years). + try { + return duration.toNanos(); + } catch (ArithmeticException tooBig) { + return duration.isNegative() ? Long.MIN_VALUE : Long.MAX_VALUE; + } + } +} diff --git a/src/main/java/com/google/common/cache/CacheBuilderSpec.java b/src/main/java/com/google/common/cache/CacheBuilderSpec.java new file mode 100644 index 0000000..0a7b89a --- /dev/null +++ b/src/main/java/com/google/common/cache/CacheBuilderSpec.java @@ -0,0 +1,480 @@ +/* + * Copyright (C) 2011 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.cache; + +import static com.google.common.base.Preconditions.checkArgument; + +import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.MoreObjects; +import com.google.common.base.Objects; +import com.google.common.base.Splitter; +import com.google.common.cache.LocalCache.Strength; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import java.util.List; +import java.util.Locale; +import java.util.concurrent.TimeUnit; + + + +/** + * A specification of a {@link CacheBuilder} configuration. + * + *

{@code CacheBuilderSpec} supports parsing configuration off of a string, which makes it + * especially useful for command-line configuration of a {@code CacheBuilder}. + * + *

The string syntax is a series of comma-separated keys or key-value pairs, each corresponding + * to a {@code CacheBuilder} method. + * + *

    + *
  • {@code concurrencyLevel=[integer]}: sets {@link CacheBuilder#concurrencyLevel}. + *
  • {@code initialCapacity=[integer]}: sets {@link CacheBuilder#initialCapacity}. + *
  • {@code maximumSize=[long]}: sets {@link CacheBuilder#maximumSize}. + *
  • {@code maximumWeight=[long]}: sets {@link CacheBuilder#maximumWeight}. + *
  • {@code expireAfterAccess=[duration]}: sets {@link CacheBuilder#expireAfterAccess}. + *
  • {@code expireAfterWrite=[duration]}: sets {@link CacheBuilder#expireAfterWrite}. + *
  • {@code refreshAfterWrite=[duration]}: sets {@link CacheBuilder#refreshAfterWrite}. + *
  • {@code weakKeys}: sets {@link CacheBuilder#weakKeys}. + *
  • {@code softValues}: sets {@link CacheBuilder#softValues}. + *
  • {@code weakValues}: sets {@link CacheBuilder#weakValues}. + *
  • {@code recordStats}: sets {@link CacheBuilder#recordStats}. + *
+ * + *

The set of supported keys will grow as {@code CacheBuilder} evolves, but existing keys will + * never be removed. + * + *

Durations are represented by an integer, followed by one of "d", "h", "m", or "s", + * representing days, hours, minutes, or seconds respectively. (There is currently no syntax to + * request expiration in milliseconds, microseconds, or nanoseconds.) + * + *

Whitespace before and after commas and equal signs is ignored. Keys may not be repeated; it is + * also illegal to use the following pairs of keys in a single value: + * + *

    + *
  • {@code maximumSize} and {@code maximumWeight} + *
  • {@code softValues} and {@code weakValues} + *
+ * + *

{@code CacheBuilderSpec} does not support configuring {@code CacheBuilder} methods with + * non-value parameters. These must be configured in code. + * + *

A new {@code CacheBuilder} can be instantiated from a {@code CacheBuilderSpec} using {@link + * CacheBuilder#from(CacheBuilderSpec)} or {@link CacheBuilder#from(String)}. + * + * @author Adam Winer + * @since 12.0 + */ +@SuppressWarnings("GoodTime") // lots of violations (nanosecond math) +@GwtIncompatible +public final class CacheBuilderSpec { + /** Parses a single value. */ + private interface ValueParser { + void parse(CacheBuilderSpec spec, String key, String value); + } + + /** Splits each key-value pair. */ + private static final Splitter KEYS_SPLITTER = Splitter.on(',').trimResults(); + + /** Splits the key from the value. */ + private static final Splitter KEY_VALUE_SPLITTER = Splitter.on('=').trimResults(); + + /** Map of names to ValueParser. */ + private static final ImmutableMap VALUE_PARSERS = + ImmutableMap.builder() + .put("initialCapacity", new InitialCapacityParser()) + .put("maximumSize", new MaximumSizeParser()) + .put("maximumWeight", new MaximumWeightParser()) + .put("concurrencyLevel", new ConcurrencyLevelParser()) + .put("weakKeys", new KeyStrengthParser(Strength.WEAK)) + .put("softValues", new ValueStrengthParser(Strength.SOFT)) + .put("weakValues", new ValueStrengthParser(Strength.WEAK)) + .put("recordStats", new RecordStatsParser()) + .put("expireAfterAccess", new AccessDurationParser()) + .put("expireAfterWrite", new WriteDurationParser()) + .put("refreshAfterWrite", new RefreshDurationParser()) + .put("refreshInterval", new RefreshDurationParser()) + .build(); + + @VisibleForTesting Integer initialCapacity; + @VisibleForTesting Long maximumSize; + @VisibleForTesting Long maximumWeight; + @VisibleForTesting Integer concurrencyLevel; + @VisibleForTesting Strength keyStrength; + @VisibleForTesting Strength valueStrength; + @VisibleForTesting Boolean recordStats; + @VisibleForTesting long writeExpirationDuration; + @VisibleForTesting TimeUnit writeExpirationTimeUnit; + @VisibleForTesting long accessExpirationDuration; + @VisibleForTesting TimeUnit accessExpirationTimeUnit; + @VisibleForTesting long refreshDuration; + @VisibleForTesting TimeUnit refreshTimeUnit; + /** Specification; used for toParseableString(). */ + private final String specification; + + private CacheBuilderSpec(String specification) { + this.specification = specification; + } + + /** + * Creates a CacheBuilderSpec from a string. + * + * @param cacheBuilderSpecification the string form + */ + public static CacheBuilderSpec parse(String cacheBuilderSpecification) { + CacheBuilderSpec spec = new CacheBuilderSpec(cacheBuilderSpecification); + if (!cacheBuilderSpecification.isEmpty()) { + for (String keyValuePair : KEYS_SPLITTER.split(cacheBuilderSpecification)) { + List keyAndValue = ImmutableList.copyOf(KEY_VALUE_SPLITTER.split(keyValuePair)); + checkArgument(!keyAndValue.isEmpty(), "blank key-value pair"); + checkArgument( + keyAndValue.size() <= 2, + "key-value pair %s with more than one equals sign", + keyValuePair); + + // Find the ValueParser for the current key. + String key = keyAndValue.get(0); + ValueParser valueParser = VALUE_PARSERS.get(key); + checkArgument(valueParser != null, "unknown key %s", key); + + String value = keyAndValue.size() == 1 ? null : keyAndValue.get(1); + valueParser.parse(spec, key, value); + } + } + + return spec; + } + + /** Returns a CacheBuilderSpec that will prevent caching. */ + public static CacheBuilderSpec disableCaching() { + // Maximum size of zero is one way to block caching + return CacheBuilderSpec.parse("maximumSize=0"); + } + + /** Returns a CacheBuilder configured according to this instance's specification. */ + CacheBuilder toCacheBuilder() { + CacheBuilder builder = CacheBuilder.newBuilder(); + if (initialCapacity != null) { + builder.initialCapacity(initialCapacity); + } + if (maximumSize != null) { + builder.maximumSize(maximumSize); + } + if (maximumWeight != null) { + builder.maximumWeight(maximumWeight); + } + if (concurrencyLevel != null) { + builder.concurrencyLevel(concurrencyLevel); + } + if (keyStrength != null) { + switch (keyStrength) { + case WEAK: + builder.weakKeys(); + break; + default: + throw new AssertionError(); + } + } + if (valueStrength != null) { + switch (valueStrength) { + case SOFT: + builder.softValues(); + break; + case WEAK: + builder.weakValues(); + break; + default: + throw new AssertionError(); + } + } + if (recordStats != null && recordStats) { + builder.recordStats(); + } + if (writeExpirationTimeUnit != null) { + builder.expireAfterWrite(writeExpirationDuration, writeExpirationTimeUnit); + } + if (accessExpirationTimeUnit != null) { + builder.expireAfterAccess(accessExpirationDuration, accessExpirationTimeUnit); + } + if (refreshTimeUnit != null) { + builder.refreshAfterWrite(refreshDuration, refreshTimeUnit); + } + + return builder; + } + + /** + * Returns a string that can be used to parse an equivalent {@code CacheBuilderSpec}. The order + * and form of this representation is not guaranteed, except that reparsing its output will + * produce a {@code CacheBuilderSpec} equal to this instance. + */ + public String toParsableString() { + return specification; + } + + /** + * Returns a string representation for this CacheBuilderSpec instance. The form of this + * representation is not guaranteed. + */ + @Override + public String toString() { + return MoreObjects.toStringHelper(this).addValue(toParsableString()).toString(); + } + + @Override + public int hashCode() { + return Objects.hashCode( + initialCapacity, + maximumSize, + maximumWeight, + concurrencyLevel, + keyStrength, + valueStrength, + recordStats, + durationInNanos(writeExpirationDuration, writeExpirationTimeUnit), + durationInNanos(accessExpirationDuration, accessExpirationTimeUnit), + durationInNanos(refreshDuration, refreshTimeUnit)); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof CacheBuilderSpec)) { + return false; + } + CacheBuilderSpec that = (CacheBuilderSpec) obj; + return Objects.equal(initialCapacity, that.initialCapacity) + && Objects.equal(maximumSize, that.maximumSize) + && Objects.equal(maximumWeight, that.maximumWeight) + && Objects.equal(concurrencyLevel, that.concurrencyLevel) + && Objects.equal(keyStrength, that.keyStrength) + && Objects.equal(valueStrength, that.valueStrength) + && Objects.equal(recordStats, that.recordStats) + && Objects.equal( + durationInNanos(writeExpirationDuration, writeExpirationTimeUnit), + durationInNanos(that.writeExpirationDuration, that.writeExpirationTimeUnit)) + && Objects.equal( + durationInNanos(accessExpirationDuration, accessExpirationTimeUnit), + durationInNanos(that.accessExpirationDuration, that.accessExpirationTimeUnit)) + && Objects.equal( + durationInNanos(refreshDuration, refreshTimeUnit), + durationInNanos(that.refreshDuration, that.refreshTimeUnit)); + } + + /** + * Converts an expiration duration/unit pair into a single Long for hashing and equality. Uses + * nanos to match CacheBuilder implementation. + */ + private static Long durationInNanos(long duration, TimeUnit unit) { + return (unit == null) ? null : unit.toNanos(duration); + } + + /** Base class for parsing integers. */ + abstract static class IntegerParser implements ValueParser { + protected abstract void parseInteger(CacheBuilderSpec spec, int value); + + @Override + public void parse(CacheBuilderSpec spec, String key, String value) { + checkArgument(value != null && !value.isEmpty(), "value of key %s omitted", key); + try { + parseInteger(spec, Integer.parseInt(value)); + } catch (NumberFormatException e) { + throw new IllegalArgumentException( + format("key %s value set to %s, must be integer", key, value), e); + } + } + } + + /** Base class for parsing integers. */ + abstract static class LongParser implements ValueParser { + protected abstract void parseLong(CacheBuilderSpec spec, long value); + + @Override + public void parse(CacheBuilderSpec spec, String key, String value) { + checkArgument(value != null && !value.isEmpty(), "value of key %s omitted", key); + try { + parseLong(spec, Long.parseLong(value)); + } catch (NumberFormatException e) { + throw new IllegalArgumentException( + format("key %s value set to %s, must be integer", key, value), e); + } + } + } + + /** Parse initialCapacity */ + static class InitialCapacityParser extends IntegerParser { + @Override + protected void parseInteger(CacheBuilderSpec spec, int value) { + checkArgument( + spec.initialCapacity == null, + "initial capacity was already set to ", + spec.initialCapacity); + spec.initialCapacity = value; + } + } + + /** Parse maximumSize */ + static class MaximumSizeParser extends LongParser { + @Override + protected void parseLong(CacheBuilderSpec spec, long value) { + checkArgument(spec.maximumSize == null, "maximum size was already set to ", spec.maximumSize); + checkArgument( + spec.maximumWeight == null, "maximum weight was already set to ", spec.maximumWeight); + spec.maximumSize = value; + } + } + + /** Parse maximumWeight */ + static class MaximumWeightParser extends LongParser { + @Override + protected void parseLong(CacheBuilderSpec spec, long value) { + checkArgument( + spec.maximumWeight == null, "maximum weight was already set to ", spec.maximumWeight); + checkArgument(spec.maximumSize == null, "maximum size was already set to ", spec.maximumSize); + spec.maximumWeight = value; + } + } + + /** Parse concurrencyLevel */ + static class ConcurrencyLevelParser extends IntegerParser { + @Override + protected void parseInteger(CacheBuilderSpec spec, int value) { + checkArgument( + spec.concurrencyLevel == null, + "concurrency level was already set to ", + spec.concurrencyLevel); + spec.concurrencyLevel = value; + } + } + + /** Parse weakKeys */ + static class KeyStrengthParser implements ValueParser { + private final Strength strength; + + public KeyStrengthParser(Strength strength) { + this.strength = strength; + } + + @Override + public void parse(CacheBuilderSpec spec, String key, String value) { + checkArgument(value == null, "key %s does not take values", key); + checkArgument(spec.keyStrength == null, "%s was already set to %s", key, spec.keyStrength); + spec.keyStrength = strength; + } + } + + /** Parse weakValues and softValues */ + static class ValueStrengthParser implements ValueParser { + private final Strength strength; + + public ValueStrengthParser(Strength strength) { + this.strength = strength; + } + + @Override + public void parse(CacheBuilderSpec spec, String key, String value) { + checkArgument(value == null, "key %s does not take values", key); + checkArgument( + spec.valueStrength == null, "%s was already set to %s", key, spec.valueStrength); + + spec.valueStrength = strength; + } + } + + /** Parse recordStats */ + static class RecordStatsParser implements ValueParser { + + @Override + public void parse(CacheBuilderSpec spec, String key, String value) { + checkArgument(value == null, "recordStats does not take values"); + checkArgument(spec.recordStats == null, "recordStats already set"); + spec.recordStats = true; + } + } + + /** Base class for parsing times with durations */ + abstract static class DurationParser implements ValueParser { + protected abstract void parseDuration(CacheBuilderSpec spec, long duration, TimeUnit unit); + + @Override + public void parse(CacheBuilderSpec spec, String key, String value) { + checkArgument(value != null && !value.isEmpty(), "value of key %s omitted", key); + try { + char lastChar = value.charAt(value.length() - 1); + TimeUnit timeUnit; + switch (lastChar) { + case 'd': + timeUnit = TimeUnit.DAYS; + break; + case 'h': + timeUnit = TimeUnit.HOURS; + break; + case 'm': + timeUnit = TimeUnit.MINUTES; + break; + case 's': + timeUnit = TimeUnit.SECONDS; + break; + default: + throw new IllegalArgumentException( + format( + "key %s invalid format. was %s, must end with one of [dDhHmMsS]", key, value)); + } + + long duration = Long.parseLong(value.substring(0, value.length() - 1)); + parseDuration(spec, duration, timeUnit); + } catch (NumberFormatException e) { + throw new IllegalArgumentException( + format("key %s value set to %s, must be integer", key, value)); + } + } + } + + /** Parse expireAfterAccess */ + static class AccessDurationParser extends DurationParser { + @Override + protected void parseDuration(CacheBuilderSpec spec, long duration, TimeUnit unit) { + checkArgument(spec.accessExpirationTimeUnit == null, "expireAfterAccess already set"); + spec.accessExpirationDuration = duration; + spec.accessExpirationTimeUnit = unit; + } + } + + /** Parse expireAfterWrite */ + static class WriteDurationParser extends DurationParser { + @Override + protected void parseDuration(CacheBuilderSpec spec, long duration, TimeUnit unit) { + checkArgument(spec.writeExpirationTimeUnit == null, "expireAfterWrite already set"); + spec.writeExpirationDuration = duration; + spec.writeExpirationTimeUnit = unit; + } + } + + /** Parse refreshAfterWrite */ + static class RefreshDurationParser extends DurationParser { + @Override + protected void parseDuration(CacheBuilderSpec spec, long duration, TimeUnit unit) { + checkArgument(spec.refreshTimeUnit == null, "refreshAfterWrite already set"); + spec.refreshDuration = duration; + spec.refreshTimeUnit = unit; + } + } + + private static String format(String format, Object... args) { + return String.format(Locale.ROOT, format, args); + } +} diff --git a/src/main/java/com/google/common/cache/CacheLoader.java b/src/main/java/com/google/common/cache/CacheLoader.java new file mode 100644 index 0000000..36639fd --- /dev/null +++ b/src/main/java/com/google/common/cache/CacheLoader.java @@ -0,0 +1,251 @@ +/* + * Copyright (C) 2011 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.cache; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.annotations.GwtCompatible; +import com.google.common.annotations.GwtIncompatible; +import com.google.common.base.Function; +import com.google.common.base.Supplier; +import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.ListenableFuture; +import com.google.common.util.concurrent.ListenableFutureTask; +import java.io.Serializable; +import java.util.Map; +import java.util.concurrent.Callable; +import java.util.concurrent.Executor; + +/** + * Computes or retrieves values, based on a key, for use in populating a {@link LoadingCache}. + * + *

Most implementations will only need to implement {@link #load}. Other methods may be + * overridden as desired. + * + *

Usage example: + * + *

{@code
+ * CacheLoader loader = new CacheLoader() {
+ *   public Graph load(Key key) throws AnyException {
+ *     return createExpensiveGraph(key);
+ *   }
+ * };
+ * LoadingCache cache = CacheBuilder.newBuilder().build(loader);
+ * }
+ * + *

Since this example doesn't support reloading or bulk loading, it can also be specified much + * more simply: + * + *

{@code
+ * CacheLoader loader = CacheLoader.from(key -> createExpensiveGraph(key));
+ * }
+ * + * @author Charles Fry + * @since 10.0 + */ +@GwtCompatible(emulated = true) +public abstract class CacheLoader { + /** Constructor for use by subclasses. */ + protected CacheLoader() {} + + /** + * Computes or retrieves the value corresponding to {@code key}. + * + * @param key the non-null key whose value should be loaded + * @return the value associated with {@code key}; must not be null + * @throws Exception if unable to load the result + * @throws InterruptedException if this method is interrupted. {@code InterruptedException} is + * treated like any other {@code Exception} in all respects except that, when it is caught, + * the thread's interrupt status is set + */ + public abstract V load(K key) throws Exception; + + /** + * Computes or retrieves a replacement value corresponding to an already-cached {@code key}. This + * method is called when an existing cache entry is refreshed by {@link + * CacheBuilder#refreshAfterWrite}, or through a call to {@link LoadingCache#refresh}. + * + *

This implementation synchronously delegates to {@link #load}. It is recommended that it be + * overridden with an asynchronous implementation when using {@link + * CacheBuilder#refreshAfterWrite}. + * + *

Note: all exceptions thrown by this method will be logged and then swallowed. + * + * @param key the non-null key whose value should be loaded + * @param oldValue the non-null old value corresponding to {@code key} + * @return the future new value associated with {@code key}; must not be null, must not return + * null + * @throws Exception if unable to reload the result + * @throws InterruptedException if this method is interrupted. {@code InterruptedException} is + * treated like any other {@code Exception} in all respects except that, when it is caught, + * the thread's interrupt status is set + * @since 11.0 + */ + @GwtIncompatible // Futures + public ListenableFuture reload(K key, V oldValue) throws Exception { + checkNotNull(key); + checkNotNull(oldValue); + return Futures.immediateFuture(load(key)); + } + + /** + * Computes or retrieves the values corresponding to {@code keys}. This method is called by {@link + * LoadingCache#getAll}. + * + *

If the returned map doesn't contain all requested {@code keys} then the entries it does + * contain will be cached, but {@code getAll} will throw an exception. If the returned map + * contains extra keys not present in {@code keys} then all returned entries will be cached, but + * only the entries for {@code keys} will be returned from {@code getAll}. + * + *

This method should be overridden when bulk retrieval is significantly more efficient than + * many individual lookups. Note that {@link LoadingCache#getAll} will defer to individual calls + * to {@link LoadingCache#get} if this method is not overridden. + * + * @param keys the unique, non-null keys whose values should be loaded + * @return a map from each key in {@code keys} to the value associated with that key; may not + * contain null values + * @throws Exception if unable to load the result + * @throws InterruptedException if this method is interrupted. {@code InterruptedException} is + * treated like any other {@code Exception} in all respects except that, when it is caught, + * the thread's interrupt status is set + * @since 11.0 + */ + public Map loadAll(Iterable keys) throws Exception { + // This will be caught by getAll(), causing it to fall back to multiple calls to + // LoadingCache.get + throw new UnsupportedLoadingOperationException(); + } + + /** + * Returns a cache loader that uses {@code function} to load keys, without supporting either + * reloading or bulk loading. This allows creating a cache loader using a lambda expression. + * + * @param function the function to be used for loading values; must never return {@code null} + * @return a cache loader that loads values by passing each key to {@code function} + */ + public static CacheLoader from(Function function) { + return new FunctionToCacheLoader<>(function); + } + + /** + * Returns a cache loader based on an existing supplier instance. Note that there's no need + * to create a new supplier just to pass it in here; just subclass {@code CacheLoader} and + * implement {@link #load load} instead. + * + * @param supplier the supplier to be used for loading values; must never return {@code null} + * @return a cache loader that loads values by calling {@link Supplier#get}, irrespective of the + * key + */ + public static CacheLoader from(Supplier supplier) { + return new SupplierToCacheLoader(supplier); + } + + private static final class FunctionToCacheLoader extends CacheLoader + implements Serializable { + private final Function computingFunction; + + public FunctionToCacheLoader(Function computingFunction) { + this.computingFunction = checkNotNull(computingFunction); + } + + @Override + public V load(K key) { + return computingFunction.apply(checkNotNull(key)); + } + + private static final long serialVersionUID = 0; + } + + /** + * Returns a {@code CacheLoader} which wraps {@code loader}, executing calls to {@link + * CacheLoader#reload} using {@code executor}. + * + *

This method is useful only when {@code loader.reload} has a synchronous implementation, such + * as {@linkplain #reload the default implementation}. + * + * @since 17.0 + */ + @GwtIncompatible // Executor + Futures + public static CacheLoader asyncReloading( + final CacheLoader loader, final Executor executor) { + checkNotNull(loader); + checkNotNull(executor); + return new CacheLoader() { + @Override + public V load(K key) throws Exception { + return loader.load(key); + } + + @Override + public ListenableFuture reload(final K key, final V oldValue) throws Exception { + ListenableFutureTask task = + ListenableFutureTask.create( + new Callable() { + @Override + public V call() throws Exception { + return loader.reload(key, oldValue).get(); + } + }); + executor.execute(task); + return task; + } + + @Override + public Map loadAll(Iterable keys) throws Exception { + return loader.loadAll(keys); + } + }; + } + + private static final class SupplierToCacheLoader extends CacheLoader + implements Serializable { + private final Supplier computingSupplier; + + public SupplierToCacheLoader(Supplier computingSupplier) { + this.computingSupplier = checkNotNull(computingSupplier); + } + + @Override + public V load(Object key) { + checkNotNull(key); + return computingSupplier.get(); + } + + private static final long serialVersionUID = 0; + } + + /** + * Exception thrown by {@code loadAll()} to indicate that it is not supported. + * + * @since 19.0 + */ + public static final class UnsupportedLoadingOperationException + extends UnsupportedOperationException { + // Package-private because this should only be thrown by loadAll() when it is not overridden. + // Cache implementors may want to catch it but should not need to be able to throw it. + UnsupportedLoadingOperationException() {} + } + + /** + * Thrown to indicate that an invalid response was returned from a call to {@link CacheLoader}. + * + * @since 11.0 + */ + public static final class InvalidCacheLoadException extends RuntimeException { + public InvalidCacheLoadException(String message) { + super(message); + } + } +} diff --git a/src/main/java/com/google/common/cache/CacheStats.java b/src/main/java/com/google/common/cache/CacheStats.java new file mode 100644 index 0000000..a4c5163 --- /dev/null +++ b/src/main/java/com/google/common/cache/CacheStats.java @@ -0,0 +1,303 @@ +/* + * Copyright (C) 2011 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.cache; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.math.LongMath.saturatedAdd; +import static com.google.common.math.LongMath.saturatedSubtract; + +import com.google.common.annotations.GwtCompatible; +import com.google.common.base.MoreObjects; +import com.google.common.base.Objects; +import java.util.concurrent.Callable; + + +/** + * Statistics about the performance of a {@link Cache}. Instances of this class are immutable. + * + *

Cache statistics are incremented according to the following rules: + * + *

    + *
  • When a cache lookup encounters an existing cache entry {@code hitCount} is incremented. + *
  • When a cache lookup first encounters a missing cache entry, a new entry is loaded. + *
      + *
    • After successfully loading an entry {@code missCount} and {@code loadSuccessCount} + * are incremented, and the total loading time, in nanoseconds, is added to {@code + * totalLoadTime}. + *
    • When an exception is thrown while loading an entry, {@code missCount} and {@code + * loadExceptionCount} are incremented, and the total loading time, in nanoseconds, is + * added to {@code totalLoadTime}. + *
    • Cache lookups that encounter a missing cache entry that is still loading will wait + * for loading to complete (whether successful or not) and then increment {@code + * missCount}. + *
    + *
  • When an entry is evicted from the cache, {@code evictionCount} is incremented. + *
  • No stats are modified when a cache entry is invalidated or manually removed. + *
  • No stats are modified by operations invoked on the {@linkplain Cache#asMap asMap} view of + * the cache. + *
+ * + *

A lookup is specifically defined as an invocation of one of the methods {@link + * LoadingCache#get(Object)}, {@link LoadingCache#getUnchecked(Object)}, {@link Cache#get(Object, + * Callable)}, or {@link LoadingCache#getAll(Iterable)}. + * + * @author Charles Fry + * @since 10.0 + */ +@GwtCompatible +public final class CacheStats { + private final long hitCount; + private final long missCount; + private final long loadSuccessCount; + private final long loadExceptionCount; + + @SuppressWarnings("GoodTime") // should be a java.time.Duration + private final long totalLoadTime; + + private final long evictionCount; + + /** + * Constructs a new {@code CacheStats} instance. + * + *

Five parameters of the same type in a row is a bad thing, but this class is not constructed + * by end users and is too fine-grained for a builder. + */ + @SuppressWarnings("GoodTime") // should accept a java.time.Duration + public CacheStats( + long hitCount, + long missCount, + long loadSuccessCount, + long loadExceptionCount, + long totalLoadTime, + long evictionCount) { + checkArgument(hitCount >= 0); + checkArgument(missCount >= 0); + checkArgument(loadSuccessCount >= 0); + checkArgument(loadExceptionCount >= 0); + checkArgument(totalLoadTime >= 0); + checkArgument(evictionCount >= 0); + + this.hitCount = hitCount; + this.missCount = missCount; + this.loadSuccessCount = loadSuccessCount; + this.loadExceptionCount = loadExceptionCount; + this.totalLoadTime = totalLoadTime; + this.evictionCount = evictionCount; + } + + /** + * Returns the number of times {@link Cache} lookup methods have returned either a cached or + * uncached value. This is defined as {@code hitCount + missCount}. + * + *

Note: the values of the metrics are undefined in case of overflow (though it is + * guaranteed not to throw an exception). If you require specific handling, we recommend + * implementing your own stats collector. + */ + public long requestCount() { + return saturatedAdd(hitCount, missCount); + } + + /** Returns the number of times {@link Cache} lookup methods have returned a cached value. */ + public long hitCount() { + return hitCount; + } + + /** + * Returns the ratio of cache requests which were hits. This is defined as {@code hitCount / + * requestCount}, or {@code 1.0} when {@code requestCount == 0}. Note that {@code hitRate + + * missRate =~ 1.0}. + */ + public double hitRate() { + long requestCount = requestCount(); + return (requestCount == 0) ? 1.0 : (double) hitCount / requestCount; + } + + /** + * Returns the number of times {@link Cache} lookup methods have returned an uncached (newly + * loaded) value, or null. Multiple concurrent calls to {@link Cache} lookup methods on an absent + * value can result in multiple misses, all returning the results of a single cache load + * operation. + */ + public long missCount() { + return missCount; + } + + /** + * Returns the ratio of cache requests which were misses. This is defined as {@code missCount / + * requestCount}, or {@code 0.0} when {@code requestCount == 0}. Note that {@code hitRate + + * missRate =~ 1.0}. Cache misses include all requests which weren't cache hits, including + * requests which resulted in either successful or failed loading attempts, and requests which + * waited for other threads to finish loading. It is thus the case that {@code missCount >= + * loadSuccessCount + loadExceptionCount}. Multiple concurrent misses for the same key will result + * in a single load operation. + */ + public double missRate() { + long requestCount = requestCount(); + return (requestCount == 0) ? 0.0 : (double) missCount / requestCount; + } + + /** + * Returns the total number of times that {@link Cache} lookup methods attempted to load new + * values. This includes both successful load operations, as well as those that threw exceptions. + * This is defined as {@code loadSuccessCount + loadExceptionCount}. + * + *

Note: the values of the metrics are undefined in case of overflow (though it is + * guaranteed not to throw an exception). If you require specific handling, we recommend + * implementing your own stats collector. + */ + public long loadCount() { + return saturatedAdd(loadSuccessCount, loadExceptionCount); + } + + /** + * Returns the number of times {@link Cache} lookup methods have successfully loaded a new value. + * This is usually incremented in conjunction with {@link #missCount}, though {@code missCount} is + * also incremented when an exception is encountered during cache loading (see {@link + * #loadExceptionCount}). Multiple concurrent misses for the same key will result in a single load + * operation. This may be incremented not in conjunction with {@code missCount} if the load occurs + * as a result of a refresh or if the cache loader returned more items than was requested. {@code + * missCount} may also be incremented not in conjunction with this (nor {@link + * #loadExceptionCount}) on calls to {@code getIfPresent}. + */ + public long loadSuccessCount() { + return loadSuccessCount; + } + + /** + * Returns the number of times {@link Cache} lookup methods threw an exception while loading a new + * value. This is usually incremented in conjunction with {@code missCount}, though {@code + * missCount} is also incremented when cache loading completes successfully (see {@link + * #loadSuccessCount}). Multiple concurrent misses for the same key will result in a single load + * operation. This may be incremented not in conjunction with {@code missCount} if the load occurs + * as a result of a refresh or if the cache loader returned more items than was requested. {@code + * missCount} may also be incremented not in conjunction with this (nor {@link #loadSuccessCount}) + * on calls to {@code getIfPresent}. + */ + public long loadExceptionCount() { + return loadExceptionCount; + } + + /** + * Returns the ratio of cache loading attempts which threw exceptions. This is defined as {@code + * loadExceptionCount / (loadSuccessCount + loadExceptionCount)}, or {@code 0.0} when {@code + * loadSuccessCount + loadExceptionCount == 0}. + * + *

Note: the values of the metrics are undefined in case of overflow (though it is + * guaranteed not to throw an exception). If you require specific handling, we recommend + * implementing your own stats collector. + */ + public double loadExceptionRate() { + long totalLoadCount = saturatedAdd(loadSuccessCount, loadExceptionCount); + return (totalLoadCount == 0) ? 0.0 : (double) loadExceptionCount / totalLoadCount; + } + + /** + * Returns the total number of nanoseconds the cache has spent loading new values. This can be + * used to calculate the miss penalty. This value is increased every time {@code loadSuccessCount} + * or {@code loadExceptionCount} is incremented. + */ + @SuppressWarnings("GoodTime") // should return a java.time.Duration + public long totalLoadTime() { + return totalLoadTime; + } + + /** + * Returns the average time spent loading new values. This is defined as {@code totalLoadTime / + * (loadSuccessCount + loadExceptionCount)}. + * + *

Note: the values of the metrics are undefined in case of overflow (though it is + * guaranteed not to throw an exception). If you require specific handling, we recommend + * implementing your own stats collector. + */ + public double averageLoadPenalty() { + long totalLoadCount = saturatedAdd(loadSuccessCount, loadExceptionCount); + return (totalLoadCount == 0) ? 0.0 : (double) totalLoadTime / totalLoadCount; + } + + /** + * Returns the number of times an entry has been evicted. This count does not include manual + * {@linkplain Cache#invalidate invalidations}. + */ + public long evictionCount() { + return evictionCount; + } + + /** + * Returns a new {@code CacheStats} representing the difference between this {@code CacheStats} + * and {@code other}. Negative values, which aren't supported by {@code CacheStats} will be + * rounded up to zero. + */ + public CacheStats minus(CacheStats other) { + return new CacheStats( + Math.max(0, saturatedSubtract(hitCount, other.hitCount)), + Math.max(0, saturatedSubtract(missCount, other.missCount)), + Math.max(0, saturatedSubtract(loadSuccessCount, other.loadSuccessCount)), + Math.max(0, saturatedSubtract(loadExceptionCount, other.loadExceptionCount)), + Math.max(0, saturatedSubtract(totalLoadTime, other.totalLoadTime)), + Math.max(0, saturatedSubtract(evictionCount, other.evictionCount))); + } + + /** + * Returns a new {@code CacheStats} representing the sum of this {@code CacheStats} and {@code + * other}. + * + *

Note: the values of the metrics are undefined in case of overflow (though it is + * guaranteed not to throw an exception). If you require specific handling, we recommend + * implementing your own stats collector. + * + * @since 11.0 + */ + public CacheStats plus(CacheStats other) { + return new CacheStats( + saturatedAdd(hitCount, other.hitCount), + saturatedAdd(missCount, other.missCount), + saturatedAdd(loadSuccessCount, other.loadSuccessCount), + saturatedAdd(loadExceptionCount, other.loadExceptionCount), + saturatedAdd(totalLoadTime, other.totalLoadTime), + saturatedAdd(evictionCount, other.evictionCount)); + } + + @Override + public int hashCode() { + return Objects.hashCode( + hitCount, missCount, loadSuccessCount, loadExceptionCount, totalLoadTime, evictionCount); + } + + @Override + public boolean equals(Object object) { + if (object instanceof CacheStats) { + CacheStats other = (CacheStats) object; + return hitCount == other.hitCount + && missCount == other.missCount + && loadSuccessCount == other.loadSuccessCount + && loadExceptionCount == other.loadExceptionCount + && totalLoadTime == other.totalLoadTime + && evictionCount == other.evictionCount; + } + return false; + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this) + .add("hitCount", hitCount) + .add("missCount", missCount) + .add("loadSuccessCount", loadSuccessCount) + .add("loadExceptionCount", loadExceptionCount) + .add("totalLoadTime", totalLoadTime) + .add("evictionCount", evictionCount) + .toString(); + } +} diff --git a/src/main/java/com/google/common/cache/ForwardingCache.java b/src/main/java/com/google/common/cache/ForwardingCache.java new file mode 100644 index 0000000..f474ac7 --- /dev/null +++ b/src/main/java/com/google/common/cache/ForwardingCache.java @@ -0,0 +1,128 @@ +/* + * Copyright (C) 2011 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.cache; + +import com.google.common.annotations.GwtIncompatible; +import com.google.common.base.Preconditions; +import com.google.common.collect.ForwardingObject; +import com.google.common.collect.ImmutableMap; +import java.util.Map; +import java.util.concurrent.Callable; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.ExecutionException; + + +/** + * A cache which forwards all its method calls to another cache. Subclasses should override one or + * more methods to modify the behavior of the backing cache as desired per the decorator pattern. + * + * @author Charles Fry + * @since 10.0 + */ +@GwtIncompatible +public abstract class ForwardingCache extends ForwardingObject implements Cache { + + /** Constructor for use by subclasses. */ + protected ForwardingCache() {} + + @Override + protected abstract Cache delegate(); + + /** @since 11.0 */ + @Override + public V getIfPresent(Object key) { + return delegate().getIfPresent(key); + } + + /** @since 11.0 */ + @Override + public V get(K key, Callable valueLoader) throws ExecutionException { + return delegate().get(key, valueLoader); + } + + /** @since 11.0 */ + @Override + public ImmutableMap getAllPresent(Iterable keys) { + return delegate().getAllPresent(keys); + } + + /** @since 11.0 */ + @Override + public void put(K key, V value) { + delegate().put(key, value); + } + + /** @since 12.0 */ + @Override + public void putAll(Map m) { + delegate().putAll(m); + } + + @Override + public void invalidate(Object key) { + delegate().invalidate(key); + } + + /** @since 11.0 */ + @Override + public void invalidateAll(Iterable keys) { + delegate().invalidateAll(keys); + } + + @Override + public void invalidateAll() { + delegate().invalidateAll(); + } + + @Override + public long size() { + return delegate().size(); + } + + @Override + public CacheStats stats() { + return delegate().stats(); + } + + @Override + public ConcurrentMap asMap() { + return delegate().asMap(); + } + + @Override + public void cleanUp() { + delegate().cleanUp(); + } + + /** + * A simplified version of {@link ForwardingCache} where subclasses can pass in an already + * constructed {@link Cache} as the delegate. + * + * @since 10.0 + */ + public abstract static class SimpleForwardingCache extends ForwardingCache { + private final Cache delegate; + + protected SimpleForwardingCache(Cache delegate) { + this.delegate = Preconditions.checkNotNull(delegate); + } + + @Override + protected final Cache delegate() { + return delegate; + } + } +} diff --git a/src/main/java/com/google/common/cache/ForwardingLoadingCache.java b/src/main/java/com/google/common/cache/ForwardingLoadingCache.java new file mode 100644 index 0000000..ba88ded --- /dev/null +++ b/src/main/java/com/google/common/cache/ForwardingLoadingCache.java @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2011 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.cache; + +import com.google.common.annotations.GwtIncompatible; +import com.google.common.base.Preconditions; +import com.google.common.collect.ImmutableMap; +import java.util.concurrent.ExecutionException; + +/** + * A cache which forwards all its method calls to another cache. Subclasses should override one or + * more methods to modify the behavior of the backing cache as desired per the decorator pattern. + * + *

Note that {@link #get}, {@link #getUnchecked}, and {@link #apply} all expose the same + * underlying functionality, so should probably be overridden as a group. + * + * @author Charles Fry + * @since 11.0 + */ +@GwtIncompatible +public abstract class ForwardingLoadingCache extends ForwardingCache + implements LoadingCache { + + /** Constructor for use by subclasses. */ + protected ForwardingLoadingCache() {} + + @Override + protected abstract LoadingCache delegate(); + + @Override + public V get(K key) throws ExecutionException { + return delegate().get(key); + } + + @Override + public V getUnchecked(K key) { + return delegate().getUnchecked(key); + } + + @Override + public ImmutableMap getAll(Iterable keys) throws ExecutionException { + return delegate().getAll(keys); + } + + @Override + public V apply(K key) { + return delegate().apply(key); + } + + @Override + public void refresh(K key) { + delegate().refresh(key); + } + + /** + * A simplified version of {@link ForwardingLoadingCache} where subclasses can pass in an already + * constructed {@link LoadingCache} as the delegate. + * + * @since 10.0 + */ + public abstract static class SimpleForwardingLoadingCache + extends ForwardingLoadingCache { + private final LoadingCache delegate; + + protected SimpleForwardingLoadingCache(LoadingCache delegate) { + this.delegate = Preconditions.checkNotNull(delegate); + } + + @Override + protected final LoadingCache delegate() { + return delegate; + } + } +} diff --git a/src/main/java/com/google/common/cache/LoadingCache.java b/src/main/java/com/google/common/cache/LoadingCache.java new file mode 100644 index 0000000..6af1d3a --- /dev/null +++ b/src/main/java/com/google/common/cache/LoadingCache.java @@ -0,0 +1,160 @@ +/* + * Copyright (C) 2011 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.cache; + +import com.google.common.annotations.GwtCompatible; +import com.google.common.base.Function; +import com.google.common.collect.ImmutableMap; +import com.google.common.util.concurrent.ExecutionError; +import com.google.common.util.concurrent.UncheckedExecutionException; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.ExecutionException; + +/** + * A semi-persistent mapping from keys to values. Values are automatically loaded by the cache, and + * are stored in the cache until either evicted or manually invalidated. The common way to build + * instances is using {@link CacheBuilder}. + * + *

Implementations of this interface are expected to be thread-safe, and can be safely accessed + * by multiple concurrent threads. + * + *

When evaluated as a {@link Function}, a cache yields the same result as invoking {@link + * #getUnchecked}. + * + * @author Charles Fry + * @since 11.0 + */ +@GwtCompatible +public interface LoadingCache extends Cache, Function { + + /** + * Returns the value associated with {@code key} in this cache, first loading that value if + * necessary. No observable state associated with this cache is modified until loading completes. + * + *

If another call to {@link #get} or {@link #getUnchecked} is currently loading the value for + * {@code key}, simply waits for that thread to finish and returns its loaded value. Note that + * multiple threads can concurrently load values for distinct keys. + * + *

Caches loaded by a {@link CacheLoader} will call {@link CacheLoader#load} to load new values + * into the cache. Newly loaded values are added to the cache using {@code + * Cache.asMap().putIfAbsent} after loading has completed; if another value was associated with + * {@code key} while the new value was loading then a removal notification will be sent for the + * new value. + * + *

If the cache loader associated with this cache is known not to throw checked exceptions, + * then prefer {@link #getUnchecked} over this method. + * + * @throws ExecutionException if a checked exception was thrown while loading the value. ({@code + * ExecutionException} is thrown even if + * computation was interrupted by an {@code InterruptedException}.) + * @throws UncheckedExecutionException if an unchecked exception was thrown while loading the + * value + * @throws ExecutionError if an error was thrown while loading the value + */ + V get(K key) throws ExecutionException; + + /** + * Returns the value associated with {@code key} in this cache, first loading that value if + * necessary. No observable state associated with this cache is modified until loading completes. + * Unlike {@link #get}, this method does not throw a checked exception, and thus should only be + * used in situations where checked exceptions are not thrown by the cache loader. + * + *

If another call to {@link #get} or {@link #getUnchecked} is currently loading the value for + * {@code key}, simply waits for that thread to finish and returns its loaded value. Note that + * multiple threads can concurrently load values for distinct keys. + * + *

Caches loaded by a {@link CacheLoader} will call {@link CacheLoader#load} to load new values + * into the cache. Newly loaded values are added to the cache using {@code + * Cache.asMap().putIfAbsent} after loading has completed; if another value was associated with + * {@code key} while the new value was loading then a removal notification will be sent for the + * new value. + * + *

Warning: this method silently converts checked exceptions to unchecked exceptions, + * and should not be used with cache loaders which throw checked exceptions. In such cases use + * {@link #get} instead. + * + * @throws UncheckedExecutionException if an exception was thrown while loading the value. (As + * explained in the last paragraph above, this should be an unchecked exception only.) + * @throws ExecutionError if an error was thrown while loading the value + */ + V getUnchecked(K key); + + /** + * Returns a map of the values associated with {@code keys}, creating or retrieving those values + * if necessary. The returned map contains entries that were already cached, combined with newly + * loaded entries; it will never contain null keys or values. + * + *

Caches loaded by a {@link CacheLoader} will issue a single request to {@link + * CacheLoader#loadAll} for all keys which are not already present in the cache. All entries + * returned by {@link CacheLoader#loadAll} will be stored in the cache, over-writing any + * previously cached values. This method will throw an exception if {@link CacheLoader#loadAll} + * returns {@code null}, returns a map containing null keys or values, or fails to return an entry + * for each requested key. + * + *

Note that duplicate elements in {@code keys}, as determined by {@link Object#equals}, will + * be ignored. + * + * @throws ExecutionException if a checked exception was thrown while loading the value. ({@code + * ExecutionException} is thrown even if + * computation was interrupted by an {@code InterruptedException}.) + * @throws UncheckedExecutionException if an unchecked exception was thrown while loading the + * values + * @throws ExecutionError if an error was thrown while loading the values + * @since 11.0 + */ + ImmutableMap getAll(Iterable keys) throws ExecutionException; + + /** + * @deprecated Provided to satisfy the {@code Function} interface; use {@link #get} or {@link + * #getUnchecked} instead. + * @throws UncheckedExecutionException if an exception was thrown while loading the value. (As + * described in the documentation for {@link #getUnchecked}, {@code LoadingCache} should be + * used as a {@code Function} only with cache loaders that throw only unchecked exceptions.) + */ + @Deprecated + @Override + V apply(K key); + + /** + * Loads a new value for key {@code key}, possibly asynchronously. While the new value is loading + * the previous value (if any) will continue to be returned by {@code get(key)} unless it is + * evicted. If the new value is loaded successfully it will replace the previous value in the + * cache; if an exception is thrown while refreshing the previous value will remain, and the + * exception will be logged (using {@link java.util.logging.Logger}) and swallowed. + * + *

Caches loaded by a {@link CacheLoader} will call {@link CacheLoader#reload} if the cache + * currently contains a value for {@code key}, and {@link CacheLoader#load} otherwise. Loading is + * asynchronous only if {@link CacheLoader#reload} was overridden with an asynchronous + * implementation. + * + *

Returns without doing anything if another thread is currently loading the value for {@code + * key}. If the cache loader associated with this cache performs refresh asynchronously then this + * method may return before refresh completes. + * + * @since 11.0 + */ + void refresh(K key); + + /** + * {@inheritDoc} + * + *

Note that although the view is modifiable, no method on the returned map will ever + * cause entries to be automatically loaded. + */ + @Override + ConcurrentMap asMap(); +} diff --git a/src/main/java/com/google/common/cache/LocalCache.java b/src/main/java/com/google/common/cache/LocalCache.java new file mode 100644 index 0000000..a997ee1 --- /dev/null +++ b/src/main/java/com/google/common/cache/LocalCache.java @@ -0,0 +1,4994 @@ +/* + * Copyright (C) 2009 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.cache; + +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.base.Preconditions.checkState; +import static com.google.common.cache.CacheBuilder.NULL_TICKER; +import static com.google.common.cache.CacheBuilder.UNSET_INT; +import static com.google.common.util.concurrent.Futures.transform; +import static com.google.common.util.concurrent.MoreExecutors.directExecutor; +import static com.google.common.util.concurrent.Uninterruptibles.getUninterruptibly; +import static java.util.concurrent.TimeUnit.NANOSECONDS; + +import com.google.common.annotations.GwtCompatible; +import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.Equivalence; +import com.google.common.base.Stopwatch; +import com.google.common.base.Ticker; +import com.google.common.cache.AbstractCache.SimpleStatsCounter; +import com.google.common.cache.AbstractCache.StatsCounter; +import com.google.common.cache.CacheBuilder.NullListener; +import com.google.common.cache.CacheBuilder.OneWeigher; +import com.google.common.cache.CacheLoader.InvalidCacheLoadException; +import com.google.common.cache.CacheLoader.UnsupportedLoadingOperationException; +import com.google.common.cache.LocalCache.AbstractCacheSet; +import com.google.common.collect.AbstractSequentialIterator; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Iterators; +import com.google.common.collect.Maps; +import com.google.common.collect.Sets; +import com.google.common.primitives.Ints; +import com.google.common.util.concurrent.ExecutionError; +import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.ListenableFuture; +import com.google.common.util.concurrent.SettableFuture; +import com.google.common.util.concurrent.UncheckedExecutionException; +import com.google.common.util.concurrent.Uninterruptibles; + + + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.Serializable; +import java.lang.ref.Reference; +import java.lang.ref.ReferenceQueue; +import java.lang.ref.SoftReference; +import java.lang.ref.WeakReference; +import java.util.AbstractCollection; +import java.util.AbstractMap; +import java.util.AbstractQueue; +import java.util.AbstractSet; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; +import java.util.Map; +import java.util.Map.Entry; +import java.util.NoSuchElementException; +import java.util.Queue; +import java.util.Set; +import java.util.concurrent.Callable; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicReferenceArray; +import java.util.concurrent.locks.ReentrantLock; +import java.util.function.BiFunction; +import java.util.function.BiPredicate; +import java.util.function.Function; +import java.util.function.Predicate; +import java.util.logging.Level; +import java.util.logging.Logger; + + + +/** + * The concurrent hash map implementation built by {@link CacheBuilder}. + * + *

This implementation is heavily derived from revision 1.96 of ConcurrentHashMap.java. + * + * @author Charles Fry + * @author Bob Lee ({@code com.google.common.collect.MapMaker}) + * @author Doug Lea ({@code ConcurrentHashMap}) + */ +@SuppressWarnings("GoodTime") // lots of violations (nanosecond math) +@GwtCompatible(emulated = true) +class LocalCache extends AbstractMap implements ConcurrentMap { + + /* + * The basic strategy is to subdivide the table among Segments, each of which itself is a + * concurrently readable hash table. The map supports non-blocking reads and concurrent writes + * across different segments. + * + * If a maximum size is specified, a best-effort bounding is performed per segment, using a + * page-replacement algorithm to determine which entries to evict when the capacity has been + * exceeded. + * + * The page replacement algorithm's data structures are kept casually consistent with the map. The + * ordering of writes to a segment is sequentially consistent. An update to the map and recording + * of reads may not be immediately reflected on the algorithm's data structures. These structures + * are guarded by a lock and operations are applied in batches to avoid lock contention. The + * penalty of applying the batches is spread across threads so that the amortized cost is slightly + * higher than performing just the operation without enforcing the capacity constraint. + * + * This implementation uses a per-segment queue to record a memento of the additions, removals, + * and accesses that were performed on the map. The queue is drained on writes and when it exceeds + * its capacity threshold. + * + * The Least Recently Used page replacement algorithm was chosen due to its simplicity, high hit + * rate, and ability to be implemented with O(1) time complexity. The initial LRU implementation + * operates per-segment rather than globally for increased implementation simplicity. We expect + * the cache hit rate to be similar to that of a global LRU algorithm. + */ + + // Constants + + /** + * The maximum capacity, used if a higher value is implicitly specified by either of the + * constructors with arguments. MUST be a power of two {@code <= 1<<30} to ensure that entries are + * indexable using ints. + */ + static final int MAXIMUM_CAPACITY = 1 << 30; + + /** The maximum number of segments to allow; used to bound constructor arguments. */ + static final int MAX_SEGMENTS = 1 << 16; // slightly conservative + + /** Number of (unsynchronized) retries in the containsValue method. */ + static final int CONTAINS_VALUE_RETRIES = 3; + + /** + * Number of cache access operations that can be buffered per segment before the cache's recency + * ordering information is updated. This is used to avoid lock contention by recording a memento + * of reads and delaying a lock acquisition until the threshold is crossed or a mutation occurs. + * + *

This must be a (2^n)-1 as it is used as a mask. + */ + static final int DRAIN_THRESHOLD = 0x3F; + + /** + * Maximum number of entries to be drained in a single cleanup run. This applies independently to + * the cleanup queue and both reference queues. + */ + // TODO(fry): empirically optimize this + static final int DRAIN_MAX = 16; + + // Fields + + static final Logger logger = Logger.getLogger(LocalCache.class.getName()); + + /** + * Mask value for indexing into segments. The upper bits of a key's hash code are used to choose + * the segment. + */ + final int segmentMask; + + /** + * Shift value for indexing within segments. Helps prevent entries that end up in the same segment + * from also ending up in the same bucket. + */ + final int segmentShift; + + /** The segments, each of which is a specialized hash table. */ + final Segment[] segments; + + /** The concurrency level. */ + final int concurrencyLevel; + + /** Strategy for comparing keys. */ + final Equivalence keyEquivalence; + + /** Strategy for comparing values. */ + final Equivalence valueEquivalence; + + /** Strategy for referencing keys. */ + final Strength keyStrength; + + /** Strategy for referencing values. */ + final Strength valueStrength; + + /** The maximum weight of this map. UNSET_INT if there is no maximum. */ + final long maxWeight; + + /** Weigher to weigh cache entries. */ + final Weigher weigher; + + /** How long after the last access to an entry the map will retain that entry. */ + final long expireAfterAccessNanos; + + /** How long after the last write to an entry the map will retain that entry. */ + final long expireAfterWriteNanos; + + /** How long after the last write an entry becomes a candidate for refresh. */ + final long refreshNanos; + + /** Entries waiting to be consumed by the removal listener. */ + // TODO(fry): define a new type which creates event objects and automates the clear logic + final Queue> removalNotificationQueue; + + /** + * A listener that is invoked when an entry is removed due to expiration or garbage collection of + * soft/weak entries. + */ + final RemovalListener removalListener; + + /** Measures time in a testable way. */ + final Ticker ticker; + + /** Factory used to create new entries. */ + final EntryFactory entryFactory; + + /** + * Accumulates global cache statistics. Note that there are also per-segments stats counters which + * must be aggregated to obtain a global stats view. + */ + final StatsCounter globalStatsCounter; + + /** The default cache loader to use on loading operations. */ + final CacheLoader defaultLoader; + + /** + * Creates a new, empty map with the specified strategy, initial capacity and concurrency level. + */ + LocalCache( + CacheBuilder builder, CacheLoader loader) { + concurrencyLevel = Math.min(builder.getConcurrencyLevel(), MAX_SEGMENTS); + + keyStrength = builder.getKeyStrength(); + valueStrength = builder.getValueStrength(); + + keyEquivalence = builder.getKeyEquivalence(); + valueEquivalence = builder.getValueEquivalence(); + + maxWeight = builder.getMaximumWeight(); + weigher = builder.getWeigher(); + expireAfterAccessNanos = builder.getExpireAfterAccessNanos(); + expireAfterWriteNanos = builder.getExpireAfterWriteNanos(); + refreshNanos = builder.getRefreshNanos(); + + removalListener = builder.getRemovalListener(); + removalNotificationQueue = + (removalListener == NullListener.INSTANCE) + ? LocalCache.>discardingQueue() + : new ConcurrentLinkedQueue>(); + + ticker = builder.getTicker(recordsTime()); + entryFactory = EntryFactory.getFactory(keyStrength, usesAccessEntries(), usesWriteEntries()); + globalStatsCounter = builder.getStatsCounterSupplier().get(); + defaultLoader = loader; + + int initialCapacity = Math.min(builder.getInitialCapacity(), MAXIMUM_CAPACITY); + if (evictsBySize() && !customWeigher()) { + initialCapacity = (int) Math.min(initialCapacity, maxWeight); + } + + // Find the lowest power-of-two segmentCount that exceeds concurrencyLevel, unless + // maximumSize/Weight is specified in which case ensure that each segment gets at least 10 + // entries. The special casing for size-based eviction is only necessary because that eviction + // happens per segment instead of globally, so too many segments compared to the maximum size + // will result in random eviction behavior. + int segmentShift = 0; + int segmentCount = 1; + while (segmentCount < concurrencyLevel && (!evictsBySize() || segmentCount * 20 <= maxWeight)) { + ++segmentShift; + segmentCount <<= 1; + } + this.segmentShift = 32 - segmentShift; + segmentMask = segmentCount - 1; + + this.segments = newSegmentArray(segmentCount); + + int segmentCapacity = initialCapacity / segmentCount; + if (segmentCapacity * segmentCount < initialCapacity) { + ++segmentCapacity; + } + + int segmentSize = 1; + while (segmentSize < segmentCapacity) { + segmentSize <<= 1; + } + + if (evictsBySize()) { + // Ensure sum of segment max weights = overall max weights + long maxSegmentWeight = maxWeight / segmentCount + 1; + long remainder = maxWeight % segmentCount; + for (int i = 0; i < this.segments.length; ++i) { + if (i == remainder) { + maxSegmentWeight--; + } + this.segments[i] = + createSegment(segmentSize, maxSegmentWeight, builder.getStatsCounterSupplier().get()); + } + } else { + for (int i = 0; i < this.segments.length; ++i) { + this.segments[i] = + createSegment(segmentSize, UNSET_INT, builder.getStatsCounterSupplier().get()); + } + } + } + + boolean evictsBySize() { + return maxWeight >= 0; + } + + boolean customWeigher() { + return weigher != OneWeigher.INSTANCE; + } + + boolean expires() { + return expiresAfterWrite() || expiresAfterAccess(); + } + + boolean expiresAfterWrite() { + return expireAfterWriteNanos > 0; + } + + boolean expiresAfterAccess() { + return expireAfterAccessNanos > 0; + } + + boolean refreshes() { + return refreshNanos > 0; + } + + boolean usesAccessQueue() { + return expiresAfterAccess() || evictsBySize(); + } + + boolean usesWriteQueue() { + return expiresAfterWrite(); + } + + boolean recordsWrite() { + return expiresAfterWrite() || refreshes(); + } + + boolean recordsAccess() { + return expiresAfterAccess(); + } + + boolean recordsTime() { + return recordsWrite() || recordsAccess(); + } + + boolean usesWriteEntries() { + return usesWriteQueue() || recordsWrite(); + } + + boolean usesAccessEntries() { + return usesAccessQueue() || recordsAccess(); + } + + boolean usesKeyReferences() { + return keyStrength != Strength.STRONG; + } + + boolean usesValueReferences() { + return valueStrength != Strength.STRONG; + } + + enum Strength { + /* + * TODO(kevinb): If we strongly reference the value and aren't loading, we needn't wrap the + * value. This could save ~8 bytes per entry. + */ + + STRONG { + @Override + ValueReference referenceValue( + Segment segment, ReferenceEntry entry, V value, int weight) { + return (weight == 1) + ? new StrongValueReference(value) + : new WeightedStrongValueReference(value, weight); + } + + @Override + Equivalence defaultEquivalence() { + return Equivalence.equals(); + } + }, + SOFT { + @Override + ValueReference referenceValue( + Segment segment, ReferenceEntry entry, V value, int weight) { + return (weight == 1) + ? new SoftValueReference(segment.valueReferenceQueue, value, entry) + : new WeightedSoftValueReference( + segment.valueReferenceQueue, value, entry, weight); + } + + @Override + Equivalence defaultEquivalence() { + return Equivalence.identity(); + } + }, + WEAK { + @Override + ValueReference referenceValue( + Segment segment, ReferenceEntry entry, V value, int weight) { + return (weight == 1) + ? new WeakValueReference(segment.valueReferenceQueue, value, entry) + : new WeightedWeakValueReference( + segment.valueReferenceQueue, value, entry, weight); + } + + @Override + Equivalence defaultEquivalence() { + return Equivalence.identity(); + } + }; + + /** Creates a reference for the given value according to this value strength. */ + abstract ValueReference referenceValue( + Segment segment, ReferenceEntry entry, V value, int weight); + + /** + * Returns the default equivalence strategy used to compare and hash keys or values referenced + * at this strength. This strategy will be used unless the user explicitly specifies an + * alternate strategy. + */ + abstract Equivalence defaultEquivalence(); + } + + /** Creates new entries. */ + enum EntryFactory { + STRONG { + @Override + ReferenceEntry newEntry( + Segment segment, K key, int hash, ReferenceEntry next) { + return new StrongEntry<>(key, hash, next); + } + }, + STRONG_ACCESS { + @Override + ReferenceEntry newEntry( + Segment segment, K key, int hash, ReferenceEntry next) { + return new StrongAccessEntry<>(key, hash, next); + } + + @Override + ReferenceEntry copyEntry( + Segment segment, ReferenceEntry original, ReferenceEntry newNext) { + ReferenceEntry newEntry = super.copyEntry(segment, original, newNext); + copyAccessEntry(original, newEntry); + return newEntry; + } + }, + STRONG_WRITE { + @Override + ReferenceEntry newEntry( + Segment segment, K key, int hash, ReferenceEntry next) { + return new StrongWriteEntry<>(key, hash, next); + } + + @Override + ReferenceEntry copyEntry( + Segment segment, ReferenceEntry original, ReferenceEntry newNext) { + ReferenceEntry newEntry = super.copyEntry(segment, original, newNext); + copyWriteEntry(original, newEntry); + return newEntry; + } + }, + STRONG_ACCESS_WRITE { + @Override + ReferenceEntry newEntry( + Segment segment, K key, int hash, ReferenceEntry next) { + return new StrongAccessWriteEntry<>(key, hash, next); + } + + @Override + ReferenceEntry copyEntry( + Segment segment, ReferenceEntry original, ReferenceEntry newNext) { + ReferenceEntry newEntry = super.copyEntry(segment, original, newNext); + copyAccessEntry(original, newEntry); + copyWriteEntry(original, newEntry); + return newEntry; + } + }, + WEAK { + @Override + ReferenceEntry newEntry( + Segment segment, K key, int hash, ReferenceEntry next) { + return new WeakEntry<>(segment.keyReferenceQueue, key, hash, next); + } + }, + WEAK_ACCESS { + @Override + ReferenceEntry newEntry( + Segment segment, K key, int hash, ReferenceEntry next) { + return new WeakAccessEntry<>(segment.keyReferenceQueue, key, hash, next); + } + + @Override + ReferenceEntry copyEntry( + Segment segment, ReferenceEntry original, ReferenceEntry newNext) { + ReferenceEntry newEntry = super.copyEntry(segment, original, newNext); + copyAccessEntry(original, newEntry); + return newEntry; + } + }, + WEAK_WRITE { + @Override + ReferenceEntry newEntry( + Segment segment, K key, int hash, ReferenceEntry next) { + return new WeakWriteEntry<>(segment.keyReferenceQueue, key, hash, next); + } + + @Override + ReferenceEntry copyEntry( + Segment segment, ReferenceEntry original, ReferenceEntry newNext) { + ReferenceEntry newEntry = super.copyEntry(segment, original, newNext); + copyWriteEntry(original, newEntry); + return newEntry; + } + }, + WEAK_ACCESS_WRITE { + @Override + ReferenceEntry newEntry( + Segment segment, K key, int hash, ReferenceEntry next) { + return new WeakAccessWriteEntry<>(segment.keyReferenceQueue, key, hash, next); + } + + @Override + ReferenceEntry copyEntry( + Segment segment, ReferenceEntry original, ReferenceEntry newNext) { + ReferenceEntry newEntry = super.copyEntry(segment, original, newNext); + copyAccessEntry(original, newEntry); + copyWriteEntry(original, newEntry); + return newEntry; + } + }; + + // Masks used to compute indices in the following table. + + static final int ACCESS_MASK = 1; + static final int WRITE_MASK = 2; + static final int WEAK_MASK = 4; + + /** Look-up table for factories. */ + static final EntryFactory[] factories = { + STRONG, + STRONG_ACCESS, + STRONG_WRITE, + STRONG_ACCESS_WRITE, + WEAK, + WEAK_ACCESS, + WEAK_WRITE, + WEAK_ACCESS_WRITE, + }; + + static EntryFactory getFactory( + Strength keyStrength, boolean usesAccessQueue, boolean usesWriteQueue) { + int flags = + ((keyStrength == Strength.WEAK) ? WEAK_MASK : 0) + | (usesAccessQueue ? ACCESS_MASK : 0) + | (usesWriteQueue ? WRITE_MASK : 0); + return factories[flags]; + } + + /** + * Creates a new entry. + * + * @param segment to create the entry for + * @param key of the entry + * @param hash of the key + * @param next entry in the same bucket + */ + abstract ReferenceEntry newEntry( + Segment segment, K key, int hash, ReferenceEntry next); + + /** + * Copies an entry, assigning it a new {@code next} entry. + * + * @param original the entry to copy + * @param newNext entry in the same bucket + */ + // Guarded By Segment.this + ReferenceEntry copyEntry( + Segment segment, ReferenceEntry original, ReferenceEntry newNext) { + return newEntry(segment, original.getKey(), original.getHash(), newNext); + } + + // Guarded By Segment.this + void copyAccessEntry(ReferenceEntry original, ReferenceEntry newEntry) { + // TODO(fry): when we link values instead of entries this method can go + // away, as can connectAccessOrder, nullifyAccessOrder. + newEntry.setAccessTime(original.getAccessTime()); + + connectAccessOrder(original.getPreviousInAccessQueue(), newEntry); + connectAccessOrder(newEntry, original.getNextInAccessQueue()); + + nullifyAccessOrder(original); + } + + // Guarded By Segment.this + void copyWriteEntry(ReferenceEntry original, ReferenceEntry newEntry) { + // TODO(fry): when we link values instead of entries this method can go + // away, as can connectWriteOrder, nullifyWriteOrder. + newEntry.setWriteTime(original.getWriteTime()); + + connectWriteOrder(original.getPreviousInWriteQueue(), newEntry); + connectWriteOrder(newEntry, original.getNextInWriteQueue()); + + nullifyWriteOrder(original); + } + } + + /** A reference to a value. */ + interface ValueReference { + /** Returns the value. Does not block or throw exceptions. */ + + V get(); + + /** + * Waits for a value that may still be loading. Unlike get(), this method can block (in the case + * of FutureValueReference). + * + * @throws ExecutionException if the loading thread throws an exception + * @throws ExecutionError if the loading thread throws an error + */ + V waitForValue() throws ExecutionException; + + /** Returns the weight of this entry. This is assumed to be static between calls to setValue. */ + int getWeight(); + + /** + * Returns the entry associated with this value reference, or {@code null} if this value + * reference is independent of any entry. + */ + + ReferenceEntry getEntry(); + + /** + * Creates a copy of this reference for the given entry. + * + *

{@code value} may be null only for a loading reference. + */ + ValueReference copyFor( + ReferenceQueue queue, V value, ReferenceEntry entry); + + /** + * Notify pending loads that a new value was set. This is only relevant to loading value + * references. + */ + void notifyNewValue(V newValue); + + /** + * Returns true if a new value is currently loading, regardless of whether or not there is an + * existing value. It is assumed that the return value of this method is constant for any given + * ValueReference instance. + */ + boolean isLoading(); + + /** + * Returns true if this reference contains an active value, meaning one that is still considered + * present in the cache. Active values consist of live values, which are returned by cache + * lookups, and dead values, which have been evicted but awaiting removal. Non-active values + * consist strictly of loading values, though during refresh a value may be both active and + * loading. + */ + boolean isActive(); + } + + /** Placeholder. Indicates that the value hasn't been set yet. */ + static final ValueReference UNSET = + new ValueReference() { + @Override + public Object get() { + return null; + } + + @Override + public int getWeight() { + return 0; + } + + @Override + public ReferenceEntry getEntry() { + return null; + } + + @Override + public ValueReference copyFor( + ReferenceQueue queue, + Object value, + ReferenceEntry entry) { + return this; + } + + @Override + public boolean isLoading() { + return false; + } + + @Override + public boolean isActive() { + return false; + } + + @Override + public Object waitForValue() { + return null; + } + + @Override + public void notifyNewValue(Object newValue) {} + }; + + /** Singleton placeholder that indicates a value is being loaded. */ + @SuppressWarnings("unchecked") // impl never uses a parameter or returns any non-null value + static ValueReference unset() { + return (ValueReference) UNSET; + } + + private enum NullEntry implements ReferenceEntry { + INSTANCE; + + @Override + public ValueReference getValueReference() { + return null; + } + + @Override + public void setValueReference(ValueReference valueReference) {} + + @Override + public ReferenceEntry getNext() { + return null; + } + + @Override + public int getHash() { + return 0; + } + + @Override + public Object getKey() { + return null; + } + + @Override + public long getAccessTime() { + return 0; + } + + @Override + public void setAccessTime(long time) {} + + @Override + public ReferenceEntry getNextInAccessQueue() { + return this; + } + + @Override + public void setNextInAccessQueue(ReferenceEntry next) {} + + @Override + public ReferenceEntry getPreviousInAccessQueue() { + return this; + } + + @Override + public void setPreviousInAccessQueue(ReferenceEntry previous) {} + + @Override + public long getWriteTime() { + return 0; + } + + @Override + public void setWriteTime(long time) {} + + @Override + public ReferenceEntry getNextInWriteQueue() { + return this; + } + + @Override + public void setNextInWriteQueue(ReferenceEntry next) {} + + @Override + public ReferenceEntry getPreviousInWriteQueue() { + return this; + } + + @Override + public void setPreviousInWriteQueue(ReferenceEntry previous) {} + } + + abstract static class AbstractReferenceEntry implements ReferenceEntry { + @Override + public ValueReference getValueReference() { + throw new UnsupportedOperationException(); + } + + @Override + public void setValueReference(ValueReference valueReference) { + throw new UnsupportedOperationException(); + } + + @Override + public ReferenceEntry getNext() { + throw new UnsupportedOperationException(); + } + + @Override + public int getHash() { + throw new UnsupportedOperationException(); + } + + @Override + public K getKey() { + throw new UnsupportedOperationException(); + } + + @Override + public long getAccessTime() { + throw new UnsupportedOperationException(); + } + + @Override + public void setAccessTime(long time) { + throw new UnsupportedOperationException(); + } + + @Override + public ReferenceEntry getNextInAccessQueue() { + throw new UnsupportedOperationException(); + } + + @Override + public void setNextInAccessQueue(ReferenceEntry next) { + throw new UnsupportedOperationException(); + } + + @Override + public ReferenceEntry getPreviousInAccessQueue() { + throw new UnsupportedOperationException(); + } + + @Override + public void setPreviousInAccessQueue(ReferenceEntry previous) { + throw new UnsupportedOperationException(); + } + + @Override + public long getWriteTime() { + throw new UnsupportedOperationException(); + } + + @Override + public void setWriteTime(long time) { + throw new UnsupportedOperationException(); + } + + @Override + public ReferenceEntry getNextInWriteQueue() { + throw new UnsupportedOperationException(); + } + + @Override + public void setNextInWriteQueue(ReferenceEntry next) { + throw new UnsupportedOperationException(); + } + + @Override + public ReferenceEntry getPreviousInWriteQueue() { + throw new UnsupportedOperationException(); + } + + @Override + public void setPreviousInWriteQueue(ReferenceEntry previous) { + throw new UnsupportedOperationException(); + } + } + + @SuppressWarnings("unchecked") // impl never uses a parameter or returns any non-null value + static ReferenceEntry nullEntry() { + return (ReferenceEntry) NullEntry.INSTANCE; + } + + static final Queue DISCARDING_QUEUE = + new AbstractQueue() { + @Override + public boolean offer(Object o) { + return true; + } + + @Override + public Object peek() { + return null; + } + + @Override + public Object poll() { + return null; + } + + @Override + public int size() { + return 0; + } + + @Override + public Iterator iterator() { + return ImmutableSet.of().iterator(); + } + }; + + /** Queue that discards all elements. */ + @SuppressWarnings("unchecked") // impl never uses a parameter or returns any non-null value + static Queue discardingQueue() { + return (Queue) DISCARDING_QUEUE; + } + + /* + * Note: All of this duplicate code sucks, but it saves a lot of memory. If only Java had mixins! + * To maintain this code, make a change for the strong reference type. Then, cut and paste, and + * replace "Strong" with "Soft" or "Weak" within the pasted text. The primary difference is that + * strong entries store the key reference directly while soft and weak entries delegate to their + * respective superclasses. + */ + + /** Used for strongly-referenced keys. */ + static class StrongEntry extends AbstractReferenceEntry { + final K key; + + StrongEntry(K key, int hash, ReferenceEntry next) { + this.key = key; + this.hash = hash; + this.next = next; + } + + @Override + public K getKey() { + return this.key; + } + + // The code below is exactly the same for each entry type. + + final int hash; + final ReferenceEntry next; + volatile ValueReference valueReference = unset(); + + @Override + public ValueReference getValueReference() { + return valueReference; + } + + @Override + public void setValueReference(ValueReference valueReference) { + this.valueReference = valueReference; + } + + @Override + public int getHash() { + return hash; + } + + @Override + public ReferenceEntry getNext() { + return next; + } + } + + static final class StrongAccessEntry extends StrongEntry { + StrongAccessEntry(K key, int hash, ReferenceEntry next) { + super(key, hash, next); + } + + // The code below is exactly the same for each access entry type. + + volatile long accessTime = Long.MAX_VALUE; + + @Override + public long getAccessTime() { + return accessTime; + } + + @Override + public void setAccessTime(long time) { + this.accessTime = time; + } + + // Guarded By Segment.this + ReferenceEntry nextAccess = nullEntry(); + + @Override + public ReferenceEntry getNextInAccessQueue() { + return nextAccess; + } + + @Override + public void setNextInAccessQueue(ReferenceEntry next) { + this.nextAccess = next; + } + + // Guarded By Segment.this + ReferenceEntry previousAccess = nullEntry(); + + @Override + public ReferenceEntry getPreviousInAccessQueue() { + return previousAccess; + } + + @Override + public void setPreviousInAccessQueue(ReferenceEntry previous) { + this.previousAccess = previous; + } + } + + static final class StrongWriteEntry extends StrongEntry { + StrongWriteEntry(K key, int hash, ReferenceEntry next) { + super(key, hash, next); + } + + // The code below is exactly the same for each write entry type. + + volatile long writeTime = Long.MAX_VALUE; + + @Override + public long getWriteTime() { + return writeTime; + } + + @Override + public void setWriteTime(long time) { + this.writeTime = time; + } + + // Guarded By Segment.this + ReferenceEntry nextWrite = nullEntry(); + + @Override + public ReferenceEntry getNextInWriteQueue() { + return nextWrite; + } + + @Override + public void setNextInWriteQueue(ReferenceEntry next) { + this.nextWrite = next; + } + + // Guarded By Segment.this + ReferenceEntry previousWrite = nullEntry(); + + @Override + public ReferenceEntry getPreviousInWriteQueue() { + return previousWrite; + } + + @Override + public void setPreviousInWriteQueue(ReferenceEntry previous) { + this.previousWrite = previous; + } + } + + static final class StrongAccessWriteEntry extends StrongEntry { + StrongAccessWriteEntry(K key, int hash, ReferenceEntry next) { + super(key, hash, next); + } + + // The code below is exactly the same for each access entry type. + + volatile long accessTime = Long.MAX_VALUE; + + @Override + public long getAccessTime() { + return accessTime; + } + + @Override + public void setAccessTime(long time) { + this.accessTime = time; + } + + // Guarded By Segment.this + ReferenceEntry nextAccess = nullEntry(); + + @Override + public ReferenceEntry getNextInAccessQueue() { + return nextAccess; + } + + @Override + public void setNextInAccessQueue(ReferenceEntry next) { + this.nextAccess = next; + } + + // Guarded By Segment.this + ReferenceEntry previousAccess = nullEntry(); + + @Override + public ReferenceEntry getPreviousInAccessQueue() { + return previousAccess; + } + + @Override + public void setPreviousInAccessQueue(ReferenceEntry previous) { + this.previousAccess = previous; + } + + // The code below is exactly the same for each write entry type. + + volatile long writeTime = Long.MAX_VALUE; + + @Override + public long getWriteTime() { + return writeTime; + } + + @Override + public void setWriteTime(long time) { + this.writeTime = time; + } + + // Guarded By Segment.this + ReferenceEntry nextWrite = nullEntry(); + + @Override + public ReferenceEntry getNextInWriteQueue() { + return nextWrite; + } + + @Override + public void setNextInWriteQueue(ReferenceEntry next) { + this.nextWrite = next; + } + + // Guarded By Segment.this + ReferenceEntry previousWrite = nullEntry(); + + @Override + public ReferenceEntry getPreviousInWriteQueue() { + return previousWrite; + } + + @Override + public void setPreviousInWriteQueue(ReferenceEntry previous) { + this.previousWrite = previous; + } + } + + /** Used for weakly-referenced keys. */ + static class WeakEntry extends WeakReference implements ReferenceEntry { + WeakEntry(ReferenceQueue queue, K key, int hash, ReferenceEntry next) { + super(key, queue); + this.hash = hash; + this.next = next; + } + + @Override + public K getKey() { + return get(); + } + + /* + * It'd be nice to get these for free from AbstractReferenceEntry, but we're already extending + * WeakReference. + */ + + // null access + + @Override + public long getAccessTime() { + throw new UnsupportedOperationException(); + } + + @Override + public void setAccessTime(long time) { + throw new UnsupportedOperationException(); + } + + @Override + public ReferenceEntry getNextInAccessQueue() { + throw new UnsupportedOperationException(); + } + + @Override + public void setNextInAccessQueue(ReferenceEntry next) { + throw new UnsupportedOperationException(); + } + + @Override + public ReferenceEntry getPreviousInAccessQueue() { + throw new UnsupportedOperationException(); + } + + @Override + public void setPreviousInAccessQueue(ReferenceEntry previous) { + throw new UnsupportedOperationException(); + } + + // null write + + @Override + public long getWriteTime() { + throw new UnsupportedOperationException(); + } + + @Override + public void setWriteTime(long time) { + throw new UnsupportedOperationException(); + } + + @Override + public ReferenceEntry getNextInWriteQueue() { + throw new UnsupportedOperationException(); + } + + @Override + public void setNextInWriteQueue(ReferenceEntry next) { + throw new UnsupportedOperationException(); + } + + @Override + public ReferenceEntry getPreviousInWriteQueue() { + throw new UnsupportedOperationException(); + } + + @Override + public void setPreviousInWriteQueue(ReferenceEntry previous) { + throw new UnsupportedOperationException(); + } + + // The code below is exactly the same for each entry type. + + final int hash; + final ReferenceEntry next; + volatile ValueReference valueReference = unset(); + + @Override + public ValueReference getValueReference() { + return valueReference; + } + + @Override + public void setValueReference(ValueReference valueReference) { + this.valueReference = valueReference; + } + + @Override + public int getHash() { + return hash; + } + + @Override + public ReferenceEntry getNext() { + return next; + } + } + + static final class WeakAccessEntry extends WeakEntry { + WeakAccessEntry(ReferenceQueue queue, K key, int hash, ReferenceEntry next) { + super(queue, key, hash, next); + } + + // The code below is exactly the same for each access entry type. + + volatile long accessTime = Long.MAX_VALUE; + + @Override + public long getAccessTime() { + return accessTime; + } + + @Override + public void setAccessTime(long time) { + this.accessTime = time; + } + + // Guarded By Segment.this + ReferenceEntry nextAccess = nullEntry(); + + @Override + public ReferenceEntry getNextInAccessQueue() { + return nextAccess; + } + + @Override + public void setNextInAccessQueue(ReferenceEntry next) { + this.nextAccess = next; + } + + // Guarded By Segment.this + ReferenceEntry previousAccess = nullEntry(); + + @Override + public ReferenceEntry getPreviousInAccessQueue() { + return previousAccess; + } + + @Override + public void setPreviousInAccessQueue(ReferenceEntry previous) { + this.previousAccess = previous; + } + } + + static final class WeakWriteEntry extends WeakEntry { + WeakWriteEntry(ReferenceQueue queue, K key, int hash, ReferenceEntry next) { + super(queue, key, hash, next); + } + + // The code below is exactly the same for each write entry type. + + volatile long writeTime = Long.MAX_VALUE; + + @Override + public long getWriteTime() { + return writeTime; + } + + @Override + public void setWriteTime(long time) { + this.writeTime = time; + } + + // Guarded By Segment.this + ReferenceEntry nextWrite = nullEntry(); + + @Override + public ReferenceEntry getNextInWriteQueue() { + return nextWrite; + } + + @Override + public void setNextInWriteQueue(ReferenceEntry next) { + this.nextWrite = next; + } + + // Guarded By Segment.this + ReferenceEntry previousWrite = nullEntry(); + + @Override + public ReferenceEntry getPreviousInWriteQueue() { + return previousWrite; + } + + @Override + public void setPreviousInWriteQueue(ReferenceEntry previous) { + this.previousWrite = previous; + } + } + + static final class WeakAccessWriteEntry extends WeakEntry { + WeakAccessWriteEntry( + ReferenceQueue queue, K key, int hash, ReferenceEntry next) { + super(queue, key, hash, next); + } + + // The code below is exactly the same for each access entry type. + + volatile long accessTime = Long.MAX_VALUE; + + @Override + public long getAccessTime() { + return accessTime; + } + + @Override + public void setAccessTime(long time) { + this.accessTime = time; + } + + // Guarded By Segment.this + ReferenceEntry nextAccess = nullEntry(); + + @Override + public ReferenceEntry getNextInAccessQueue() { + return nextAccess; + } + + @Override + public void setNextInAccessQueue(ReferenceEntry next) { + this.nextAccess = next; + } + + // Guarded By Segment.this + ReferenceEntry previousAccess = nullEntry(); + + @Override + public ReferenceEntry getPreviousInAccessQueue() { + return previousAccess; + } + + @Override + public void setPreviousInAccessQueue(ReferenceEntry previous) { + this.previousAccess = previous; + } + + // The code below is exactly the same for each write entry type. + + volatile long writeTime = Long.MAX_VALUE; + + @Override + public long getWriteTime() { + return writeTime; + } + + @Override + public void setWriteTime(long time) { + this.writeTime = time; + } + + // Guarded By Segment.this + ReferenceEntry nextWrite = nullEntry(); + + @Override + public ReferenceEntry getNextInWriteQueue() { + return nextWrite; + } + + @Override + public void setNextInWriteQueue(ReferenceEntry next) { + this.nextWrite = next; + } + + // Guarded By Segment.this + ReferenceEntry previousWrite = nullEntry(); + + @Override + public ReferenceEntry getPreviousInWriteQueue() { + return previousWrite; + } + + @Override + public void setPreviousInWriteQueue(ReferenceEntry previous) { + this.previousWrite = previous; + } + } + + /** References a weak value. */ + static class WeakValueReference extends WeakReference implements ValueReference { + final ReferenceEntry entry; + + WeakValueReference(ReferenceQueue queue, V referent, ReferenceEntry entry) { + super(referent, queue); + this.entry = entry; + } + + @Override + public int getWeight() { + return 1; + } + + @Override + public ReferenceEntry getEntry() { + return entry; + } + + @Override + public void notifyNewValue(V newValue) {} + + @Override + public ValueReference copyFor( + ReferenceQueue queue, V value, ReferenceEntry entry) { + return new WeakValueReference<>(queue, value, entry); + } + + @Override + public boolean isLoading() { + return false; + } + + @Override + public boolean isActive() { + return true; + } + + @Override + public V waitForValue() { + return get(); + } + } + + /** References a soft value. */ + static class SoftValueReference extends SoftReference implements ValueReference { + final ReferenceEntry entry; + + SoftValueReference(ReferenceQueue queue, V referent, ReferenceEntry entry) { + super(referent, queue); + this.entry = entry; + } + + @Override + public int getWeight() { + return 1; + } + + @Override + public ReferenceEntry getEntry() { + return entry; + } + + @Override + public void notifyNewValue(V newValue) {} + + @Override + public ValueReference copyFor( + ReferenceQueue queue, V value, ReferenceEntry entry) { + return new SoftValueReference<>(queue, value, entry); + } + + @Override + public boolean isLoading() { + return false; + } + + @Override + public boolean isActive() { + return true; + } + + @Override + public V waitForValue() { + return get(); + } + } + + /** References a strong value. */ + static class StrongValueReference implements ValueReference { + final V referent; + + StrongValueReference(V referent) { + this.referent = referent; + } + + @Override + public V get() { + return referent; + } + + @Override + public int getWeight() { + return 1; + } + + @Override + public ReferenceEntry getEntry() { + return null; + } + + @Override + public ValueReference copyFor( + ReferenceQueue queue, V value, ReferenceEntry entry) { + return this; + } + + @Override + public boolean isLoading() { + return false; + } + + @Override + public boolean isActive() { + return true; + } + + @Override + public V waitForValue() { + return get(); + } + + @Override + public void notifyNewValue(V newValue) {} + } + + /** References a weak value. */ + static final class WeightedWeakValueReference extends WeakValueReference { + final int weight; + + WeightedWeakValueReference( + ReferenceQueue queue, V referent, ReferenceEntry entry, int weight) { + super(queue, referent, entry); + this.weight = weight; + } + + @Override + public int getWeight() { + return weight; + } + + @Override + public ValueReference copyFor( + ReferenceQueue queue, V value, ReferenceEntry entry) { + return new WeightedWeakValueReference<>(queue, value, entry, weight); + } + } + + /** References a soft value. */ + static final class WeightedSoftValueReference extends SoftValueReference { + final int weight; + + WeightedSoftValueReference( + ReferenceQueue queue, V referent, ReferenceEntry entry, int weight) { + super(queue, referent, entry); + this.weight = weight; + } + + @Override + public int getWeight() { + return weight; + } + + @Override + public ValueReference copyFor( + ReferenceQueue queue, V value, ReferenceEntry entry) { + return new WeightedSoftValueReference<>(queue, value, entry, weight); + } + } + + /** References a strong value. */ + static final class WeightedStrongValueReference extends StrongValueReference { + final int weight; + + WeightedStrongValueReference(V referent, int weight) { + super(referent); + this.weight = weight; + } + + @Override + public int getWeight() { + return weight; + } + } + + /** + * Applies a supplemental hash function to a given hash code, which defends against poor quality + * hash functions. This is critical when the concurrent hash map uses power-of-two length hash + * tables, that otherwise encounter collisions for hash codes that do not differ in lower or upper + * bits. + * + * @param h hash code + */ + static int rehash(int h) { + // Spread bits to regularize both segment and index locations, + // using variant of single-word Wang/Jenkins hash. + // TODO(kevinb): use Hashing/move this to Hashing? + h += (h << 15) ^ 0xffffcd7d; + h ^= (h >>> 10); + h += (h << 3); + h ^= (h >>> 6); + h += (h << 2) + (h << 14); + return h ^ (h >>> 16); + } + + /** + * This method is a convenience for testing. Code should call {@link Segment#newEntry} directly. + */ + @VisibleForTesting + ReferenceEntry newEntry(K key, int hash, ReferenceEntry next) { + Segment segment = segmentFor(hash); + segment.lock(); + try { + return segment.newEntry(key, hash, next); + } finally { + segment.unlock(); + } + } + + /** + * This method is a convenience for testing. Code should call {@link Segment#copyEntry} directly. + */ + // Guarded By Segment.this + @VisibleForTesting + ReferenceEntry copyEntry(ReferenceEntry original, ReferenceEntry newNext) { + int hash = original.getHash(); + return segmentFor(hash).copyEntry(original, newNext); + } + + /** + * This method is a convenience for testing. Code should call {@link Segment#setValue} instead. + */ + // Guarded By Segment.this + @VisibleForTesting + ValueReference newValueReference(ReferenceEntry entry, V value, int weight) { + int hash = entry.getHash(); + return valueStrength.referenceValue(segmentFor(hash), entry, checkNotNull(value), weight); + } + + int hash(Object key) { + int h = keyEquivalence.hash(key); + return rehash(h); + } + + void reclaimValue(ValueReference valueReference) { + ReferenceEntry entry = valueReference.getEntry(); + int hash = entry.getHash(); + segmentFor(hash).reclaimValue(entry.getKey(), hash, valueReference); + } + + void reclaimKey(ReferenceEntry entry) { + int hash = entry.getHash(); + segmentFor(hash).reclaimKey(entry, hash); + } + + /** + * This method is a convenience for testing. Code should call {@link Segment#getLiveValue} + * instead. + */ + @VisibleForTesting + boolean isLive(ReferenceEntry entry, long now) { + return segmentFor(entry.getHash()).getLiveValue(entry, now) != null; + } + + /** + * Returns the segment that should be used for a key with the given hash. + * + * @param hash the hash code for the key + * @return the segment + */ + Segment segmentFor(int hash) { + // TODO(fry): Lazily create segments? + return segments[(hash >>> segmentShift) & segmentMask]; + } + + Segment createSegment( + int initialCapacity, long maxSegmentWeight, StatsCounter statsCounter) { + return new Segment<>(this, initialCapacity, maxSegmentWeight, statsCounter); + } + + /** + * Gets the value from an entry. Returns null if the entry is invalid, partially-collected, + * loading, or expired. Unlike {@link Segment#getLiveValue} this method does not attempt to + * cleanup stale entries. As such it should only be called outside of a segment context, such as + * during iteration. + */ + + V getLiveValue(ReferenceEntry entry, long now) { + if (entry.getKey() == null) { + return null; + } + V value = entry.getValueReference().get(); + if (value == null) { + return null; + } + + if (isExpired(entry, now)) { + return null; + } + return value; + } + + // expiration + + /** Returns true if the entry has expired. */ + boolean isExpired(ReferenceEntry entry, long now) { + checkNotNull(entry); + if (expiresAfterAccess() && (now - entry.getAccessTime() >= expireAfterAccessNanos)) { + return true; + } + if (expiresAfterWrite() && (now - entry.getWriteTime() >= expireAfterWriteNanos)) { + return true; + } + return false; + } + + // queues + + // Guarded By Segment.this + static void connectAccessOrder(ReferenceEntry previous, ReferenceEntry next) { + previous.setNextInAccessQueue(next); + next.setPreviousInAccessQueue(previous); + } + + // Guarded By Segment.this + static void nullifyAccessOrder(ReferenceEntry nulled) { + ReferenceEntry nullEntry = nullEntry(); + nulled.setNextInAccessQueue(nullEntry); + nulled.setPreviousInAccessQueue(nullEntry); + } + + // Guarded By Segment.this + static void connectWriteOrder(ReferenceEntry previous, ReferenceEntry next) { + previous.setNextInWriteQueue(next); + next.setPreviousInWriteQueue(previous); + } + + // Guarded By Segment.this + static void nullifyWriteOrder(ReferenceEntry nulled) { + ReferenceEntry nullEntry = nullEntry(); + nulled.setNextInWriteQueue(nullEntry); + nulled.setPreviousInWriteQueue(nullEntry); + } + + /** + * Notifies listeners that an entry has been automatically removed due to expiration, eviction, or + * eligibility for garbage collection. This should be called every time expireEntries or + * evictEntry is called (once the lock is released). + */ + void processPendingNotifications() { + RemovalNotification notification; + while ((notification = removalNotificationQueue.poll()) != null) { + try { + removalListener.onRemoval(notification); + } catch (Throwable e) { + logger.log(Level.WARNING, "Exception thrown by removal listener", e); + } + } + } + + @SuppressWarnings("unchecked") + final Segment[] newSegmentArray(int ssize) { + return new Segment[ssize]; + } + + // Inner Classes + + /** + * Segments are specialized versions of hash tables. This subclass inherits from ReentrantLock + * opportunistically, just to simplify some locking and avoid separate construction. + */ + @SuppressWarnings("serial") // This class is never serialized. + static class Segment extends ReentrantLock { + + /* + * TODO(fry): Consider copying variables (like evictsBySize) from outer class into this class. + * It will require more memory but will reduce indirection. + */ + + /* + * Segments maintain a table of entry lists that are ALWAYS kept in a consistent state, so can + * be read without locking. Next fields of nodes are immutable (final). All list additions are + * performed at the front of each bin. This makes it easy to check changes, and also fast to + * traverse. When nodes would otherwise be changed, new nodes are created to replace them. This + * works well for hash tables since the bin lists tend to be short. (The average length is less + * than two.) + * + * Read operations can thus proceed without locking, but rely on selected uses of volatiles to + * ensure that completed write operations performed by other threads are noticed. For most + * purposes, the "count" field, tracking the number of elements, serves as that volatile + * variable ensuring visibility. This is convenient because this field needs to be read in many + * read operations anyway: + * + * - All (unsynchronized) read operations must first read the "count" field, and should not look + * at table entries if it is 0. + * + * - All (synchronized) write operations should write to the "count" field after structurally + * changing any bin. The operations must not take any action that could even momentarily cause a + * concurrent read operation to see inconsistent data. This is made easier by the nature of the + * read operations in Map. For example, no operation can reveal that the table has grown but the + * threshold has not yet been updated, so there are no atomicity requirements for this with + * respect to reads. + * + * As a guide, all critical volatile reads and writes to the count field are marked in code + * comments. + */ + + final LocalCache map; + + /** The number of live elements in this segment's region. */ + volatile int count; + + /** The weight of the live elements in this segment's region. */ + + long totalWeight; + + /** + * Number of updates that alter the size of the table. This is used during bulk-read methods to + * make sure they see a consistent snapshot: If modCounts change during a traversal of segments + * loading size or checking containsValue, then we might have an inconsistent view of state so + * (usually) must retry. + */ + int modCount; + + /** + * The table is expanded when its size exceeds this threshold. (The value of this field is + * always {@code (int) (capacity * 0.75)}.) + */ + int threshold; + + /** The per-segment table. */ + volatile AtomicReferenceArray> table; + + /** The maximum weight of this segment. UNSET_INT if there is no maximum. */ + final long maxSegmentWeight; + + /** + * The key reference queue contains entries whose keys have been garbage collected, and which + * need to be cleaned up internally. + */ + final ReferenceQueue keyReferenceQueue; + + /** + * The value reference queue contains value references whose values have been garbage collected, + * and which need to be cleaned up internally. + */ + final ReferenceQueue valueReferenceQueue; + + /** + * The recency queue is used to record which entries were accessed for updating the access + * list's ordering. It is drained as a batch operation when either the DRAIN_THRESHOLD is + * crossed or a write occurs on the segment. + */ + final Queue> recencyQueue; + + /** + * A counter of the number of reads since the last write, used to drain queues on a small + * fraction of read operations. + */ + final AtomicInteger readCount = new AtomicInteger(); + + /** + * A queue of elements currently in the map, ordered by write time. Elements are added to the + * tail of the queue on write. + */ + + final Queue> writeQueue; + + /** + * A queue of elements currently in the map, ordered by access time. Elements are added to the + * tail of the queue on access (note that writes count as accesses). + */ + + final Queue> accessQueue; + + /** Accumulates cache statistics. */ + final StatsCounter statsCounter; + + Segment( + LocalCache map, + int initialCapacity, + long maxSegmentWeight, + StatsCounter statsCounter) { + this.map = map; + this.maxSegmentWeight = maxSegmentWeight; + this.statsCounter = checkNotNull(statsCounter); + initTable(newEntryArray(initialCapacity)); + + keyReferenceQueue = map.usesKeyReferences() ? new ReferenceQueue() : null; + + valueReferenceQueue = map.usesValueReferences() ? new ReferenceQueue() : null; + + recencyQueue = + map.usesAccessQueue() + ? new ConcurrentLinkedQueue>() + : LocalCache.>discardingQueue(); + + writeQueue = + map.usesWriteQueue() + ? new WriteQueue() + : LocalCache.>discardingQueue(); + + accessQueue = + map.usesAccessQueue() + ? new AccessQueue() + : LocalCache.>discardingQueue(); + } + + AtomicReferenceArray> newEntryArray(int size) { + return new AtomicReferenceArray<>(size); + } + + void initTable(AtomicReferenceArray> newTable) { + this.threshold = newTable.length() * 3 / 4; // 0.75 + if (!map.customWeigher() && this.threshold == maxSegmentWeight) { + // prevent spurious expansion before eviction + this.threshold++; + } + this.table = newTable; + } + + + ReferenceEntry newEntry(K key, int hash, ReferenceEntry next) { + return map.entryFactory.newEntry(this, checkNotNull(key), hash, next); + } + + /** + * Copies {@code original} into a new entry chained to {@code newNext}. Returns the new entry, + * or {@code null} if {@code original} was already garbage collected. + */ + + ReferenceEntry copyEntry(ReferenceEntry original, ReferenceEntry newNext) { + if (original.getKey() == null) { + // key collected + return null; + } + + ValueReference valueReference = original.getValueReference(); + V value = valueReference.get(); + if ((value == null) && valueReference.isActive()) { + // value collected + return null; + } + + ReferenceEntry newEntry = map.entryFactory.copyEntry(this, original, newNext); + newEntry.setValueReference(valueReference.copyFor(this.valueReferenceQueue, value, newEntry)); + return newEntry; + } + + /** Sets a new value of an entry. Adds newly created entries at the end of the access queue. */ + + void setValue(ReferenceEntry entry, K key, V value, long now) { + ValueReference previous = entry.getValueReference(); + int weight = map.weigher.weigh(key, value); + checkState(weight >= 0, "Weights must be non-negative"); + + ValueReference valueReference = + map.valueStrength.referenceValue(this, entry, value, weight); + entry.setValueReference(valueReference); + recordWrite(entry, weight, now); + previous.notifyNewValue(value); + } + + // loading + + V get(K key, int hash, CacheLoader loader) throws ExecutionException { + checkNotNull(key); + checkNotNull(loader); + try { + if (count != 0) { // read-volatile + // don't call getLiveEntry, which would ignore loading values + ReferenceEntry e = getEntry(key, hash); + if (e != null) { + long now = map.ticker.read(); + V value = getLiveValue(e, now); + if (value != null) { + recordRead(e, now); + statsCounter.recordHits(1); + return scheduleRefresh(e, key, hash, value, now, loader); + } + ValueReference valueReference = e.getValueReference(); + if (valueReference.isLoading()) { + return waitForLoadingValue(e, key, valueReference); + } + } + } + + // at this point e is either null or expired; + return lockedGetOrLoad(key, hash, loader); + } catch (ExecutionException ee) { + Throwable cause = ee.getCause(); + if (cause instanceof Error) { + throw new ExecutionError((Error) cause); + } else if (cause instanceof RuntimeException) { + throw new UncheckedExecutionException(cause); + } + throw ee; + } finally { + postReadCleanup(); + } + } + + + V get(Object key, int hash) { + try { + if (count != 0) { // read-volatile + long now = map.ticker.read(); + ReferenceEntry e = getLiveEntry(key, hash, now); + if (e == null) { + return null; + } + + V value = e.getValueReference().get(); + if (value != null) { + recordRead(e, now); + return scheduleRefresh(e, e.getKey(), hash, value, now, map.defaultLoader); + } + tryDrainReferenceQueues(); + } + return null; + } finally { + postReadCleanup(); + } + } + + V lockedGetOrLoad(K key, int hash, CacheLoader loader) throws ExecutionException { + ReferenceEntry e; + ValueReference valueReference = null; + LoadingValueReference loadingValueReference = null; + boolean createNewEntry = true; + + lock(); + try { + // re-read ticker once inside the lock + long now = map.ticker.read(); + preWriteCleanup(now); + + int newCount = this.count - 1; + AtomicReferenceArray> table = this.table; + int index = hash & (table.length() - 1); + ReferenceEntry first = table.get(index); + + for (e = first; e != null; e = e.getNext()) { + K entryKey = e.getKey(); + if (e.getHash() == hash + && entryKey != null + && map.keyEquivalence.equivalent(key, entryKey)) { + valueReference = e.getValueReference(); + if (valueReference.isLoading()) { + createNewEntry = false; + } else { + V value = valueReference.get(); + if (value == null) { + enqueueNotification( + entryKey, hash, value, valueReference.getWeight(), RemovalCause.COLLECTED); + } else if (map.isExpired(e, now)) { + // This is a duplicate check, as preWriteCleanup already purged expired + // entries, but let's accommodate an incorrect expiration queue. + enqueueNotification( + entryKey, hash, value, valueReference.getWeight(), RemovalCause.EXPIRED); + } else { + recordLockedRead(e, now); + statsCounter.recordHits(1); + // we were concurrent with loading; don't consider refresh + return value; + } + + // immediately reuse invalid entries + writeQueue.remove(e); + accessQueue.remove(e); + this.count = newCount; // write-volatile + } + break; + } + } + + if (createNewEntry) { + loadingValueReference = new LoadingValueReference<>(); + + if (e == null) { + e = newEntry(key, hash, first); + e.setValueReference(loadingValueReference); + table.set(index, e); + } else { + e.setValueReference(loadingValueReference); + } + } + } finally { + unlock(); + postWriteCleanup(); + } + + if (createNewEntry) { + try { + // Synchronizes on the entry to allow failing fast when a recursive load is + // detected. This may be circumvented when an entry is copied, but will fail fast most + // of the time. + synchronized (e) { + return loadSync(key, hash, loadingValueReference, loader); + } + } finally { + statsCounter.recordMisses(1); + } + } else { + // The entry already exists. Wait for loading. + return waitForLoadingValue(e, key, valueReference); + } + } + + V waitForLoadingValue(ReferenceEntry e, K key, ValueReference valueReference) + throws ExecutionException { + if (!valueReference.isLoading()) { + throw new AssertionError(); + } + + checkState(!Thread.holdsLock(e), "Recursive load of: %s", key); + // don't consider expiration as we're concurrent with loading + try { + V value = valueReference.waitForValue(); + if (value == null) { + throw new InvalidCacheLoadException("CacheLoader returned null for key " + key + "."); + } + // re-read ticker now that loading has completed + long now = map.ticker.read(); + recordRead(e, now); + return value; + } finally { + statsCounter.recordMisses(1); + } + } + + V compute(K key, int hash, BiFunction function) { + ReferenceEntry e; + ValueReference valueReference = null; + LoadingValueReference loadingValueReference = null; + boolean createNewEntry = true; + V newValue; + + lock(); + try { + // re-read ticker once inside the lock + long now = map.ticker.read(); + preWriteCleanup(now); + + AtomicReferenceArray> table = this.table; + int index = hash & (table.length() - 1); + ReferenceEntry first = table.get(index); + + for (e = first; e != null; e = e.getNext()) { + K entryKey = e.getKey(); + if (e.getHash() == hash + && entryKey != null + && map.keyEquivalence.equivalent(key, entryKey)) { + valueReference = e.getValueReference(); + if (map.isExpired(e, now)) { + // This is a duplicate check, as preWriteCleanup already purged expired + // entries, but let's accomodate an incorrect expiration queue. + enqueueNotification( + entryKey, + hash, + valueReference.get(), + valueReference.getWeight(), + RemovalCause.EXPIRED); + } + + // immediately reuse invalid entries + writeQueue.remove(e); + accessQueue.remove(e); + createNewEntry = false; + break; + } + } + + // note valueReference can be an existing value or even itself another loading value if + // the value for the key is already being computed. + loadingValueReference = new LoadingValueReference<>(valueReference); + + if (e == null) { + createNewEntry = true; + e = newEntry(key, hash, first); + e.setValueReference(loadingValueReference); + table.set(index, e); + } else { + e.setValueReference(loadingValueReference); + } + + newValue = loadingValueReference.compute(key, function); + if (newValue != null) { + if (valueReference != null && newValue == valueReference.get()) { + loadingValueReference.set(newValue); + e.setValueReference(valueReference); + recordWrite(e, 0, now); // no change in weight + return newValue; + } + try { + return getAndRecordStats( + key, hash, loadingValueReference, Futures.immediateFuture(newValue)); + } catch (ExecutionException exception) { + throw new AssertionError("impossible; Futures.immediateFuture can't throw"); + } + } else if (createNewEntry) { + removeLoadingValue(key, hash, loadingValueReference); + return null; + } else { + removeEntry(e, hash, RemovalCause.EXPLICIT); + return null; + } + } finally { + unlock(); + postWriteCleanup(); + } + } + + // at most one of loadSync/loadAsync may be called for any given LoadingValueReference + + V loadSync( + K key, + int hash, + LoadingValueReference loadingValueReference, + CacheLoader loader) + throws ExecutionException { + ListenableFuture loadingFuture = loadingValueReference.loadFuture(key, loader); + return getAndRecordStats(key, hash, loadingValueReference, loadingFuture); + } + + ListenableFuture loadAsync( + final K key, + final int hash, + final LoadingValueReference loadingValueReference, + CacheLoader loader) { + final ListenableFuture loadingFuture = loadingValueReference.loadFuture(key, loader); + loadingFuture.addListener( + new Runnable() { + @Override + public void run() { + try { + getAndRecordStats(key, hash, loadingValueReference, loadingFuture); + } catch (Throwable t) { + logger.log(Level.WARNING, "Exception thrown during refresh", t); + loadingValueReference.setException(t); + } + } + }, + directExecutor()); + return loadingFuture; + } + + /** Waits uninterruptibly for {@code newValue} to be loaded, and then records loading stats. */ + V getAndRecordStats( + K key, + int hash, + LoadingValueReference loadingValueReference, + ListenableFuture newValue) + throws ExecutionException { + V value = null; + try { + value = getUninterruptibly(newValue); + if (value == null) { + throw new InvalidCacheLoadException("CacheLoader returned null for key " + key + "."); + } + statsCounter.recordLoadSuccess(loadingValueReference.elapsedNanos()); + storeLoadedValue(key, hash, loadingValueReference, value); + return value; + } finally { + if (value == null) { + statsCounter.recordLoadException(loadingValueReference.elapsedNanos()); + removeLoadingValue(key, hash, loadingValueReference); + } + } + } + + V scheduleRefresh( + ReferenceEntry entry, + K key, + int hash, + V oldValue, + long now, + CacheLoader loader) { + if (map.refreshes() + && (now - entry.getWriteTime() > map.refreshNanos) + && !entry.getValueReference().isLoading()) { + V newValue = refresh(key, hash, loader, true); + if (newValue != null) { + return newValue; + } + } + return oldValue; + } + + /** + * Refreshes the value associated with {@code key}, unless another thread is already doing so. + * Returns the newly refreshed value associated with {@code key} if it was refreshed inline, or + * {@code null} if another thread is performing the refresh or if an error occurs during + * refresh. + */ + + V refresh(K key, int hash, CacheLoader loader, boolean checkTime) { + final LoadingValueReference loadingValueReference = + insertLoadingValueReference(key, hash, checkTime); + if (loadingValueReference == null) { + return null; + } + + ListenableFuture result = loadAsync(key, hash, loadingValueReference, loader); + if (result.isDone()) { + try { + return Uninterruptibles.getUninterruptibly(result); + } catch (Throwable t) { + // don't let refresh exceptions propagate; error was already logged + } + } + return null; + } + + /** + * Returns a newly inserted {@code LoadingValueReference}, or null if the live value reference + * is already loading. + */ + + LoadingValueReference insertLoadingValueReference( + final K key, final int hash, boolean checkTime) { + ReferenceEntry e = null; + lock(); + try { + long now = map.ticker.read(); + preWriteCleanup(now); + + AtomicReferenceArray> table = this.table; + int index = hash & (table.length() - 1); + ReferenceEntry first = table.get(index); + + // Look for an existing entry. + for (e = first; e != null; e = e.getNext()) { + K entryKey = e.getKey(); + if (e.getHash() == hash + && entryKey != null + && map.keyEquivalence.equivalent(key, entryKey)) { + // We found an existing entry. + + ValueReference valueReference = e.getValueReference(); + if (valueReference.isLoading() + || (checkTime && (now - e.getWriteTime() < map.refreshNanos))) { + // refresh is a no-op if loading is pending + // if checkTime, we want to check *after* acquiring the lock if refresh still needs + // to be scheduled + return null; + } + + // continue returning old value while loading + ++modCount; + LoadingValueReference loadingValueReference = + new LoadingValueReference<>(valueReference); + e.setValueReference(loadingValueReference); + return loadingValueReference; + } + } + + ++modCount; + LoadingValueReference loadingValueReference = new LoadingValueReference<>(); + e = newEntry(key, hash, first); + e.setValueReference(loadingValueReference); + table.set(index, e); + return loadingValueReference; + } finally { + unlock(); + postWriteCleanup(); + } + } + + // reference queues, for garbage collection cleanup + + /** Cleanup collected entries when the lock is available. */ + void tryDrainReferenceQueues() { + if (tryLock()) { + try { + drainReferenceQueues(); + } finally { + unlock(); + } + } + } + + /** + * Drain the key and value reference queues, cleaning up internal entries containing garbage + * collected keys or values. + */ + + void drainReferenceQueues() { + if (map.usesKeyReferences()) { + drainKeyReferenceQueue(); + } + if (map.usesValueReferences()) { + drainValueReferenceQueue(); + } + } + + + void drainKeyReferenceQueue() { + Reference ref; + int i = 0; + while ((ref = keyReferenceQueue.poll()) != null) { + @SuppressWarnings("unchecked") + ReferenceEntry entry = (ReferenceEntry) ref; + map.reclaimKey(entry); + if (++i == DRAIN_MAX) { + break; + } + } + } + + + void drainValueReferenceQueue() { + Reference ref; + int i = 0; + while ((ref = valueReferenceQueue.poll()) != null) { + @SuppressWarnings("unchecked") + ValueReference valueReference = (ValueReference) ref; + map.reclaimValue(valueReference); + if (++i == DRAIN_MAX) { + break; + } + } + } + + /** Clears all entries from the key and value reference queues. */ + void clearReferenceQueues() { + if (map.usesKeyReferences()) { + clearKeyReferenceQueue(); + } + if (map.usesValueReferences()) { + clearValueReferenceQueue(); + } + } + + void clearKeyReferenceQueue() { + while (keyReferenceQueue.poll() != null) {} + } + + void clearValueReferenceQueue() { + while (valueReferenceQueue.poll() != null) {} + } + + // recency queue, shared by expiration and eviction + + /** + * Records the relative order in which this read was performed by adding {@code entry} to the + * recency queue. At write-time, or when the queue is full past the threshold, the queue will be + * drained and the entries therein processed. + * + *

Note: locked reads should use {@link #recordLockedRead}. + */ + void recordRead(ReferenceEntry entry, long now) { + if (map.recordsAccess()) { + entry.setAccessTime(now); + } + recencyQueue.add(entry); + } + + /** + * Updates the eviction metadata that {@code entry} was just read. This currently amounts to + * adding {@code entry} to relevant eviction lists. + * + *

Note: this method should only be called under lock, as it directly manipulates the + * eviction queues. Unlocked reads should use {@link #recordRead}. + */ + + void recordLockedRead(ReferenceEntry entry, long now) { + if (map.recordsAccess()) { + entry.setAccessTime(now); + } + accessQueue.add(entry); + } + + /** + * Updates eviction metadata that {@code entry} was just written. This currently amounts to + * adding {@code entry} to relevant eviction lists. + */ + + void recordWrite(ReferenceEntry entry, int weight, long now) { + // we are already under lock, so drain the recency queue immediately + drainRecencyQueue(); + totalWeight += weight; + + if (map.recordsAccess()) { + entry.setAccessTime(now); + } + if (map.recordsWrite()) { + entry.setWriteTime(now); + } + accessQueue.add(entry); + writeQueue.add(entry); + } + + /** + * Drains the recency queue, updating eviction metadata that the entries therein were read in + * the specified relative order. This currently amounts to adding them to relevant eviction + * lists (accounting for the fact that they could have been removed from the map since being + * added to the recency queue). + */ + + void drainRecencyQueue() { + ReferenceEntry e; + while ((e = recencyQueue.poll()) != null) { + // An entry may be in the recency queue despite it being removed from + // the map . This can occur when the entry was concurrently read while a + // writer is removing it from the segment or after a clear has removed + // all of the segment's entries. + if (accessQueue.contains(e)) { + accessQueue.add(e); + } + } + } + + // expiration + + /** Cleanup expired entries when the lock is available. */ + void tryExpireEntries(long now) { + if (tryLock()) { + try { + expireEntries(now); + } finally { + unlock(); + // don't call postWriteCleanup as we're in a read + } + } + } + + + void expireEntries(long now) { + drainRecencyQueue(); + + ReferenceEntry e; + while ((e = writeQueue.peek()) != null && map.isExpired(e, now)) { + if (!removeEntry(e, e.getHash(), RemovalCause.EXPIRED)) { + throw new AssertionError(); + } + } + while ((e = accessQueue.peek()) != null && map.isExpired(e, now)) { + if (!removeEntry(e, e.getHash(), RemovalCause.EXPIRED)) { + throw new AssertionError(); + } + } + } + + // eviction + + + void enqueueNotification( + K key, int hash, V value, int weight, RemovalCause cause) { + totalWeight -= weight; + if (cause.wasEvicted()) { + statsCounter.recordEviction(); + } + if (map.removalNotificationQueue != DISCARDING_QUEUE) { + RemovalNotification notification = RemovalNotification.create(key, value, cause); + map.removalNotificationQueue.offer(notification); + } + } + + /** + * Performs eviction if the segment is over capacity. Avoids flushing the entire cache if the + * newest entry exceeds the maximum weight all on its own. + * + * @param newest the most recently added entry + */ + + void evictEntries(ReferenceEntry newest) { + if (!map.evictsBySize()) { + return; + } + + drainRecencyQueue(); + + // If the newest entry by itself is too heavy for the segment, don't bother evicting + // anything else, just that + if (newest.getValueReference().getWeight() > maxSegmentWeight) { + if (!removeEntry(newest, newest.getHash(), RemovalCause.SIZE)) { + throw new AssertionError(); + } + } + + while (totalWeight > maxSegmentWeight) { + ReferenceEntry e = getNextEvictable(); + if (!removeEntry(e, e.getHash(), RemovalCause.SIZE)) { + throw new AssertionError(); + } + } + } + + // TODO(fry): instead implement this with an eviction head + + ReferenceEntry getNextEvictable() { + for (ReferenceEntry e : accessQueue) { + int weight = e.getValueReference().getWeight(); + if (weight > 0) { + return e; + } + } + throw new AssertionError(); + } + + /** Returns first entry of bin for given hash. */ + ReferenceEntry getFirst(int hash) { + // read this volatile field only once + AtomicReferenceArray> table = this.table; + return table.get(hash & (table.length() - 1)); + } + + // Specialized implementations of map methods + + + ReferenceEntry getEntry(Object key, int hash) { + for (ReferenceEntry e = getFirst(hash); e != null; e = e.getNext()) { + if (e.getHash() != hash) { + continue; + } + + K entryKey = e.getKey(); + if (entryKey == null) { + tryDrainReferenceQueues(); + continue; + } + + if (map.keyEquivalence.equivalent(key, entryKey)) { + return e; + } + } + + return null; + } + + + ReferenceEntry getLiveEntry(Object key, int hash, long now) { + ReferenceEntry e = getEntry(key, hash); + if (e == null) { + return null; + } else if (map.isExpired(e, now)) { + tryExpireEntries(now); + return null; + } + return e; + } + + /** + * Gets the value from an entry. Returns null if the entry is invalid, partially-collected, + * loading, or expired. + */ + V getLiveValue(ReferenceEntry entry, long now) { + if (entry.getKey() == null) { + tryDrainReferenceQueues(); + return null; + } + V value = entry.getValueReference().get(); + if (value == null) { + tryDrainReferenceQueues(); + return null; + } + + if (map.isExpired(entry, now)) { + tryExpireEntries(now); + return null; + } + return value; + } + + boolean containsKey(Object key, int hash) { + try { + if (count != 0) { // read-volatile + long now = map.ticker.read(); + ReferenceEntry e = getLiveEntry(key, hash, now); + if (e == null) { + return false; + } + return e.getValueReference().get() != null; + } + + return false; + } finally { + postReadCleanup(); + } + } + + /** + * This method is a convenience for testing. Code should call {@link LocalCache#containsValue} + * directly. + */ + @VisibleForTesting + boolean containsValue(Object value) { + try { + if (count != 0) { // read-volatile + long now = map.ticker.read(); + AtomicReferenceArray> table = this.table; + int length = table.length(); + for (int i = 0; i < length; ++i) { + for (ReferenceEntry e = table.get(i); e != null; e = e.getNext()) { + V entryValue = getLiveValue(e, now); + if (entryValue == null) { + continue; + } + if (map.valueEquivalence.equivalent(value, entryValue)) { + return true; + } + } + } + } + + return false; + } finally { + postReadCleanup(); + } + } + + + V put(K key, int hash, V value, boolean onlyIfAbsent) { + lock(); + try { + long now = map.ticker.read(); + preWriteCleanup(now); + + int newCount = this.count + 1; + if (newCount > this.threshold) { // ensure capacity + expand(); + newCount = this.count + 1; + } + + AtomicReferenceArray> table = this.table; + int index = hash & (table.length() - 1); + ReferenceEntry first = table.get(index); + + // Look for an existing entry. + for (ReferenceEntry e = first; e != null; e = e.getNext()) { + K entryKey = e.getKey(); + if (e.getHash() == hash + && entryKey != null + && map.keyEquivalence.equivalent(key, entryKey)) { + // We found an existing entry. + + ValueReference valueReference = e.getValueReference(); + V entryValue = valueReference.get(); + + if (entryValue == null) { + ++modCount; + if (valueReference.isActive()) { + enqueueNotification( + key, hash, entryValue, valueReference.getWeight(), RemovalCause.COLLECTED); + setValue(e, key, value, now); + newCount = this.count; // count remains unchanged + } else { + setValue(e, key, value, now); + newCount = this.count + 1; + } + this.count = newCount; // write-volatile + evictEntries(e); + return null; + } else if (onlyIfAbsent) { + // Mimic + // "if (!map.containsKey(key)) ... + // else return map.get(key); + recordLockedRead(e, now); + return entryValue; + } else { + // clobber existing entry, count remains unchanged + ++modCount; + enqueueNotification( + key, hash, entryValue, valueReference.getWeight(), RemovalCause.REPLACED); + setValue(e, key, value, now); + evictEntries(e); + return entryValue; + } + } + } + + // Create a new entry. + ++modCount; + ReferenceEntry newEntry = newEntry(key, hash, first); + setValue(newEntry, key, value, now); + table.set(index, newEntry); + newCount = this.count + 1; + this.count = newCount; // write-volatile + evictEntries(newEntry); + return null; + } finally { + unlock(); + postWriteCleanup(); + } + } + + /** Expands the table if possible. */ + + void expand() { + AtomicReferenceArray> oldTable = table; + int oldCapacity = oldTable.length(); + if (oldCapacity >= MAXIMUM_CAPACITY) { + return; + } + + /* + * Reclassify nodes in each list to new Map. Because we are using power-of-two expansion, the + * elements from each bin must either stay at same index, or move with a power of two offset. + * We eliminate unnecessary node creation by catching cases where old nodes can be reused + * because their next fields won't change. Statistically, at the default threshold, only about + * one-sixth of them need cloning when a table doubles. The nodes they replace will be garbage + * collectable as soon as they are no longer referenced by any reader thread that may be in + * the midst of traversing table right now. + */ + + int newCount = count; + AtomicReferenceArray> newTable = newEntryArray(oldCapacity << 1); + threshold = newTable.length() * 3 / 4; + int newMask = newTable.length() - 1; + for (int oldIndex = 0; oldIndex < oldCapacity; ++oldIndex) { + // We need to guarantee that any existing reads of old Map can + // proceed. So we cannot yet null out each bin. + ReferenceEntry head = oldTable.get(oldIndex); + + if (head != null) { + ReferenceEntry next = head.getNext(); + int headIndex = head.getHash() & newMask; + + // Single node on list + if (next == null) { + newTable.set(headIndex, head); + } else { + // Reuse the consecutive sequence of nodes with the same target + // index from the end of the list. tail points to the first + // entry in the reusable list. + ReferenceEntry tail = head; + int tailIndex = headIndex; + for (ReferenceEntry e = next; e != null; e = e.getNext()) { + int newIndex = e.getHash() & newMask; + if (newIndex != tailIndex) { + // The index changed. We'll need to copy the previous entry. + tailIndex = newIndex; + tail = e; + } + } + newTable.set(tailIndex, tail); + + // Clone nodes leading up to the tail. + for (ReferenceEntry e = head; e != tail; e = e.getNext()) { + int newIndex = e.getHash() & newMask; + ReferenceEntry newNext = newTable.get(newIndex); + ReferenceEntry newFirst = copyEntry(e, newNext); + if (newFirst != null) { + newTable.set(newIndex, newFirst); + } else { + removeCollectedEntry(e); + newCount--; + } + } + } + } + } + table = newTable; + this.count = newCount; + } + + boolean replace(K key, int hash, V oldValue, V newValue) { + lock(); + try { + long now = map.ticker.read(); + preWriteCleanup(now); + + AtomicReferenceArray> table = this.table; + int index = hash & (table.length() - 1); + ReferenceEntry first = table.get(index); + + for (ReferenceEntry e = first; e != null; e = e.getNext()) { + K entryKey = e.getKey(); + if (e.getHash() == hash + && entryKey != null + && map.keyEquivalence.equivalent(key, entryKey)) { + ValueReference valueReference = e.getValueReference(); + V entryValue = valueReference.get(); + if (entryValue == null) { + if (valueReference.isActive()) { + // If the value disappeared, this entry is partially collected. + int newCount = this.count - 1; + ++modCount; + ReferenceEntry newFirst = + removeValueFromChain( + first, + e, + entryKey, + hash, + entryValue, + valueReference, + RemovalCause.COLLECTED); + newCount = this.count - 1; + table.set(index, newFirst); + this.count = newCount; // write-volatile + } + return false; + } + + if (map.valueEquivalence.equivalent(oldValue, entryValue)) { + ++modCount; + enqueueNotification( + key, hash, entryValue, valueReference.getWeight(), RemovalCause.REPLACED); + setValue(e, key, newValue, now); + evictEntries(e); + return true; + } else { + // Mimic + // "if (map.containsKey(key) && map.get(key).equals(oldValue))..." + recordLockedRead(e, now); + return false; + } + } + } + + return false; + } finally { + unlock(); + postWriteCleanup(); + } + } + + + V replace(K key, int hash, V newValue) { + lock(); + try { + long now = map.ticker.read(); + preWriteCleanup(now); + + AtomicReferenceArray> table = this.table; + int index = hash & (table.length() - 1); + ReferenceEntry first = table.get(index); + + for (ReferenceEntry e = first; e != null; e = e.getNext()) { + K entryKey = e.getKey(); + if (e.getHash() == hash + && entryKey != null + && map.keyEquivalence.equivalent(key, entryKey)) { + ValueReference valueReference = e.getValueReference(); + V entryValue = valueReference.get(); + if (entryValue == null) { + if (valueReference.isActive()) { + // If the value disappeared, this entry is partially collected. + int newCount = this.count - 1; + ++modCount; + ReferenceEntry newFirst = + removeValueFromChain( + first, + e, + entryKey, + hash, + entryValue, + valueReference, + RemovalCause.COLLECTED); + newCount = this.count - 1; + table.set(index, newFirst); + this.count = newCount; // write-volatile + } + return null; + } + + ++modCount; + enqueueNotification( + key, hash, entryValue, valueReference.getWeight(), RemovalCause.REPLACED); + setValue(e, key, newValue, now); + evictEntries(e); + return entryValue; + } + } + + return null; + } finally { + unlock(); + postWriteCleanup(); + } + } + + + V remove(Object key, int hash) { + lock(); + try { + long now = map.ticker.read(); + preWriteCleanup(now); + + int newCount = this.count - 1; + AtomicReferenceArray> table = this.table; + int index = hash & (table.length() - 1); + ReferenceEntry first = table.get(index); + + for (ReferenceEntry e = first; e != null; e = e.getNext()) { + K entryKey = e.getKey(); + if (e.getHash() == hash + && entryKey != null + && map.keyEquivalence.equivalent(key, entryKey)) { + ValueReference valueReference = e.getValueReference(); + V entryValue = valueReference.get(); + + RemovalCause cause; + if (entryValue != null) { + cause = RemovalCause.EXPLICIT; + } else if (valueReference.isActive()) { + cause = RemovalCause.COLLECTED; + } else { + // currently loading + return null; + } + + ++modCount; + ReferenceEntry newFirst = + removeValueFromChain(first, e, entryKey, hash, entryValue, valueReference, cause); + newCount = this.count - 1; + table.set(index, newFirst); + this.count = newCount; // write-volatile + return entryValue; + } + } + + return null; + } finally { + unlock(); + postWriteCleanup(); + } + } + + boolean remove(Object key, int hash, Object value) { + lock(); + try { + long now = map.ticker.read(); + preWriteCleanup(now); + + int newCount = this.count - 1; + AtomicReferenceArray> table = this.table; + int index = hash & (table.length() - 1); + ReferenceEntry first = table.get(index); + + for (ReferenceEntry e = first; e != null; e = e.getNext()) { + K entryKey = e.getKey(); + if (e.getHash() == hash + && entryKey != null + && map.keyEquivalence.equivalent(key, entryKey)) { + ValueReference valueReference = e.getValueReference(); + V entryValue = valueReference.get(); + + RemovalCause cause; + if (map.valueEquivalence.equivalent(value, entryValue)) { + cause = RemovalCause.EXPLICIT; + } else if (entryValue == null && valueReference.isActive()) { + cause = RemovalCause.COLLECTED; + } else { + // currently loading + return false; + } + + ++modCount; + ReferenceEntry newFirst = + removeValueFromChain(first, e, entryKey, hash, entryValue, valueReference, cause); + newCount = this.count - 1; + table.set(index, newFirst); + this.count = newCount; // write-volatile + return (cause == RemovalCause.EXPLICIT); + } + } + + return false; + } finally { + unlock(); + postWriteCleanup(); + } + } + + boolean storeLoadedValue( + K key, int hash, LoadingValueReference oldValueReference, V newValue) { + lock(); + try { + long now = map.ticker.read(); + preWriteCleanup(now); + + int newCount = this.count + 1; + if (newCount > this.threshold) { // ensure capacity + expand(); + newCount = this.count + 1; + } + + AtomicReferenceArray> table = this.table; + int index = hash & (table.length() - 1); + ReferenceEntry first = table.get(index); + + for (ReferenceEntry e = first; e != null; e = e.getNext()) { + K entryKey = e.getKey(); + if (e.getHash() == hash + && entryKey != null + && map.keyEquivalence.equivalent(key, entryKey)) { + ValueReference valueReference = e.getValueReference(); + V entryValue = valueReference.get(); + // replace the old LoadingValueReference if it's live, otherwise + // perform a putIfAbsent + if (oldValueReference == valueReference + || (entryValue == null && valueReference != UNSET)) { + ++modCount; + if (oldValueReference.isActive()) { + RemovalCause cause = + (entryValue == null) ? RemovalCause.COLLECTED : RemovalCause.REPLACED; + enqueueNotification(key, hash, entryValue, oldValueReference.getWeight(), cause); + newCount--; + } + setValue(e, key, newValue, now); + this.count = newCount; // write-volatile + evictEntries(e); + return true; + } + + // the loaded value was already clobbered + enqueueNotification(key, hash, newValue, 0, RemovalCause.REPLACED); + return false; + } + } + + ++modCount; + ReferenceEntry newEntry = newEntry(key, hash, first); + setValue(newEntry, key, newValue, now); + table.set(index, newEntry); + this.count = newCount; // write-volatile + evictEntries(newEntry); + return true; + } finally { + unlock(); + postWriteCleanup(); + } + } + + void clear() { + if (count != 0) { // read-volatile + lock(); + try { + long now = map.ticker.read(); + preWriteCleanup(now); + + AtomicReferenceArray> table = this.table; + for (int i = 0; i < table.length(); ++i) { + for (ReferenceEntry e = table.get(i); e != null; e = e.getNext()) { + // Loading references aren't actually in the map yet. + if (e.getValueReference().isActive()) { + K key = e.getKey(); + V value = e.getValueReference().get(); + RemovalCause cause = + (key == null || value == null) ? RemovalCause.COLLECTED : RemovalCause.EXPLICIT; + enqueueNotification( + key, e.getHash(), value, e.getValueReference().getWeight(), cause); + } + } + } + for (int i = 0; i < table.length(); ++i) { + table.set(i, null); + } + clearReferenceQueues(); + writeQueue.clear(); + accessQueue.clear(); + readCount.set(0); + + ++modCount; + count = 0; // write-volatile + } finally { + unlock(); + postWriteCleanup(); + } + } + } + + + + ReferenceEntry removeValueFromChain( + ReferenceEntry first, + ReferenceEntry entry, + K key, + int hash, + V value, + ValueReference valueReference, + RemovalCause cause) { + enqueueNotification(key, hash, value, valueReference.getWeight(), cause); + writeQueue.remove(entry); + accessQueue.remove(entry); + + if (valueReference.isLoading()) { + valueReference.notifyNewValue(null); + return first; + } else { + return removeEntryFromChain(first, entry); + } + } + + + + ReferenceEntry removeEntryFromChain( + ReferenceEntry first, ReferenceEntry entry) { + int newCount = count; + ReferenceEntry newFirst = entry.getNext(); + for (ReferenceEntry e = first; e != entry; e = e.getNext()) { + ReferenceEntry next = copyEntry(e, newFirst); + if (next != null) { + newFirst = next; + } else { + removeCollectedEntry(e); + newCount--; + } + } + this.count = newCount; + return newFirst; + } + + + void removeCollectedEntry(ReferenceEntry entry) { + enqueueNotification( + entry.getKey(), + entry.getHash(), + entry.getValueReference().get(), + entry.getValueReference().getWeight(), + RemovalCause.COLLECTED); + writeQueue.remove(entry); + accessQueue.remove(entry); + } + + /** Removes an entry whose key has been garbage collected. */ + boolean reclaimKey(ReferenceEntry entry, int hash) { + lock(); + try { + int newCount = count - 1; + AtomicReferenceArray> table = this.table; + int index = hash & (table.length() - 1); + ReferenceEntry first = table.get(index); + + for (ReferenceEntry e = first; e != null; e = e.getNext()) { + if (e == entry) { + ++modCount; + ReferenceEntry newFirst = + removeValueFromChain( + first, + e, + e.getKey(), + hash, + e.getValueReference().get(), + e.getValueReference(), + RemovalCause.COLLECTED); + newCount = this.count - 1; + table.set(index, newFirst); + this.count = newCount; // write-volatile + return true; + } + } + + return false; + } finally { + unlock(); + postWriteCleanup(); + } + } + + /** Removes an entry whose value has been garbage collected. */ + boolean reclaimValue(K key, int hash, ValueReference valueReference) { + lock(); + try { + int newCount = this.count - 1; + AtomicReferenceArray> table = this.table; + int index = hash & (table.length() - 1); + ReferenceEntry first = table.get(index); + + for (ReferenceEntry e = first; e != null; e = e.getNext()) { + K entryKey = e.getKey(); + if (e.getHash() == hash + && entryKey != null + && map.keyEquivalence.equivalent(key, entryKey)) { + ValueReference v = e.getValueReference(); + if (v == valueReference) { + ++modCount; + ReferenceEntry newFirst = + removeValueFromChain( + first, + e, + entryKey, + hash, + valueReference.get(), + valueReference, + RemovalCause.COLLECTED); + newCount = this.count - 1; + table.set(index, newFirst); + this.count = newCount; // write-volatile + return true; + } + return false; + } + } + + return false; + } finally { + unlock(); + if (!isHeldByCurrentThread()) { // don't cleanup inside of put + postWriteCleanup(); + } + } + } + + boolean removeLoadingValue(K key, int hash, LoadingValueReference valueReference) { + lock(); + try { + AtomicReferenceArray> table = this.table; + int index = hash & (table.length() - 1); + ReferenceEntry first = table.get(index); + + for (ReferenceEntry e = first; e != null; e = e.getNext()) { + K entryKey = e.getKey(); + if (e.getHash() == hash + && entryKey != null + && map.keyEquivalence.equivalent(key, entryKey)) { + ValueReference v = e.getValueReference(); + if (v == valueReference) { + if (valueReference.isActive()) { + e.setValueReference(valueReference.getOldValue()); + } else { + ReferenceEntry newFirst = removeEntryFromChain(first, e); + table.set(index, newFirst); + } + return true; + } + return false; + } + } + + return false; + } finally { + unlock(); + postWriteCleanup(); + } + } + + @VisibleForTesting + + boolean removeEntry(ReferenceEntry entry, int hash, RemovalCause cause) { + int newCount = this.count - 1; + AtomicReferenceArray> table = this.table; + int index = hash & (table.length() - 1); + ReferenceEntry first = table.get(index); + + for (ReferenceEntry e = first; e != null; e = e.getNext()) { + if (e == entry) { + ++modCount; + ReferenceEntry newFirst = + removeValueFromChain( + first, + e, + e.getKey(), + hash, + e.getValueReference().get(), + e.getValueReference(), + cause); + newCount = this.count - 1; + table.set(index, newFirst); + this.count = newCount; // write-volatile + return true; + } + } + + return false; + } + + /** + * Performs routine cleanup following a read. Normally cleanup happens during writes. If cleanup + * is not observed after a sufficient number of reads, try cleaning up from the read thread. + */ + void postReadCleanup() { + if ((readCount.incrementAndGet() & DRAIN_THRESHOLD) == 0) { + cleanUp(); + } + } + + /** + * Performs routine cleanup prior to executing a write. This should be called every time a write + * thread acquires the segment lock, immediately after acquiring the lock. + * + *

Post-condition: expireEntries has been run. + */ + + void preWriteCleanup(long now) { + runLockedCleanup(now); + } + + /** Performs routine cleanup following a write. */ + void postWriteCleanup() { + runUnlockedCleanup(); + } + + void cleanUp() { + long now = map.ticker.read(); + runLockedCleanup(now); + runUnlockedCleanup(); + } + + void runLockedCleanup(long now) { + if (tryLock()) { + try { + drainReferenceQueues(); + expireEntries(now); // calls drainRecencyQueue + readCount.set(0); + } finally { + unlock(); + } + } + } + + void runUnlockedCleanup() { + // locked cleanup may generate notifications we can send unlocked + if (!isHeldByCurrentThread()) { + map.processPendingNotifications(); + } + } + } + + static class LoadingValueReference implements ValueReference { + volatile ValueReference oldValue; + + // TODO(fry): rename get, then extend AbstractFuture instead of containing SettableFuture + final SettableFuture futureValue = SettableFuture.create(); + final Stopwatch stopwatch = Stopwatch.createUnstarted(); + + public LoadingValueReference() { + this(null); + } + + public LoadingValueReference(ValueReference oldValue) { + this.oldValue = (oldValue == null) ? LocalCache.unset() : oldValue; + } + + @Override + public boolean isLoading() { + return true; + } + + @Override + public boolean isActive() { + return oldValue.isActive(); + } + + @Override + public int getWeight() { + return oldValue.getWeight(); + } + + public boolean set(V newValue) { + return futureValue.set(newValue); + } + + public boolean setException(Throwable t) { + return futureValue.setException(t); + } + + private ListenableFuture fullyFailedFuture(Throwable t) { + return Futures.immediateFailedFuture(t); + } + + @Override + public void notifyNewValue(V newValue) { + if (newValue != null) { + // The pending load was clobbered by a manual write. + // Unblock all pending gets, and have them return the new value. + set(newValue); + } else { + // The pending load was removed. Delay notifications until loading completes. + oldValue = unset(); + } + + // TODO(fry): could also cancel loading if we had a handle on its future + } + + public ListenableFuture loadFuture(K key, CacheLoader loader) { + try { + stopwatch.start(); + V previousValue = oldValue.get(); + if (previousValue == null) { + V newValue = loader.load(key); + return set(newValue) ? futureValue : Futures.immediateFuture(newValue); + } + ListenableFuture newValue = loader.reload(key, previousValue); + if (newValue == null) { + return Futures.immediateFuture(null); + } + // To avoid a race, make sure the refreshed value is set into loadingValueReference + // *before* returning newValue from the cache query. + return transform( + newValue, + new com.google.common.base.Function() { + @Override + public V apply(V newValue) { + LoadingValueReference.this.set(newValue); + return newValue; + } + }, + directExecutor()); + } catch (Throwable t) { + ListenableFuture result = setException(t) ? futureValue : fullyFailedFuture(t); + if (t instanceof InterruptedException) { + Thread.currentThread().interrupt(); + } + return result; + } + } + + public V compute(K key, BiFunction function) { + stopwatch.start(); + V previousValue; + try { + previousValue = oldValue.waitForValue(); + } catch (ExecutionException e) { + previousValue = null; + } + V newValue; + try { + newValue = function.apply(key, previousValue); + } catch (Throwable th) { + this.setException(th); + throw th; + } + this.set(newValue); + return newValue; + } + + public long elapsedNanos() { + return stopwatch.elapsed(NANOSECONDS); + } + + @Override + public V waitForValue() throws ExecutionException { + return getUninterruptibly(futureValue); + } + + @Override + public V get() { + return oldValue.get(); + } + + public ValueReference getOldValue() { + return oldValue; + } + + @Override + public ReferenceEntry getEntry() { + return null; + } + + @Override + public ValueReference copyFor( + ReferenceQueue queue, V value, ReferenceEntry entry) { + return this; + } + } + + // Queues + + /** + * A custom queue for managing eviction order. Note that this is tightly integrated with {@code + * ReferenceEntry}, upon which it relies to perform its linking. + * + *

Note that this entire implementation makes the assumption that all elements which are in the + * map are also in this queue, and that all elements not in the queue are not in the map. + * + *

The benefits of creating our own queue are that (1) we can replace elements in the middle of + * the queue as part of copyWriteEntry, and (2) the contains method is highly optimized for the + * current model. + */ + static final class WriteQueue extends AbstractQueue> { + final ReferenceEntry head = + new AbstractReferenceEntry() { + + @Override + public long getWriteTime() { + return Long.MAX_VALUE; + } + + @Override + public void setWriteTime(long time) {} + + ReferenceEntry nextWrite = this; + + @Override + public ReferenceEntry getNextInWriteQueue() { + return nextWrite; + } + + @Override + public void setNextInWriteQueue(ReferenceEntry next) { + this.nextWrite = next; + } + + ReferenceEntry previousWrite = this; + + @Override + public ReferenceEntry getPreviousInWriteQueue() { + return previousWrite; + } + + @Override + public void setPreviousInWriteQueue(ReferenceEntry previous) { + this.previousWrite = previous; + } + }; + + // implements Queue + + @Override + public boolean offer(ReferenceEntry entry) { + // unlink + connectWriteOrder(entry.getPreviousInWriteQueue(), entry.getNextInWriteQueue()); + + // add to tail + connectWriteOrder(head.getPreviousInWriteQueue(), entry); + connectWriteOrder(entry, head); + + return true; + } + + @Override + public ReferenceEntry peek() { + ReferenceEntry next = head.getNextInWriteQueue(); + return (next == head) ? null : next; + } + + @Override + public ReferenceEntry poll() { + ReferenceEntry next = head.getNextInWriteQueue(); + if (next == head) { + return null; + } + + remove(next); + return next; + } + + @Override + @SuppressWarnings("unchecked") + public boolean remove(Object o) { + ReferenceEntry e = (ReferenceEntry) o; + ReferenceEntry previous = e.getPreviousInWriteQueue(); + ReferenceEntry next = e.getNextInWriteQueue(); + connectWriteOrder(previous, next); + nullifyWriteOrder(e); + + return next != NullEntry.INSTANCE; + } + + @Override + @SuppressWarnings("unchecked") + public boolean contains(Object o) { + ReferenceEntry e = (ReferenceEntry) o; + return e.getNextInWriteQueue() != NullEntry.INSTANCE; + } + + @Override + public boolean isEmpty() { + return head.getNextInWriteQueue() == head; + } + + @Override + public int size() { + int size = 0; + for (ReferenceEntry e = head.getNextInWriteQueue(); + e != head; + e = e.getNextInWriteQueue()) { + size++; + } + return size; + } + + @Override + public void clear() { + ReferenceEntry e = head.getNextInWriteQueue(); + while (e != head) { + ReferenceEntry next = e.getNextInWriteQueue(); + nullifyWriteOrder(e); + e = next; + } + + head.setNextInWriteQueue(head); + head.setPreviousInWriteQueue(head); + } + + @Override + public Iterator> iterator() { + return new AbstractSequentialIterator>(peek()) { + @Override + protected ReferenceEntry computeNext(ReferenceEntry previous) { + ReferenceEntry next = previous.getNextInWriteQueue(); + return (next == head) ? null : next; + } + }; + } + } + + /** + * A custom queue for managing access order. Note that this is tightly integrated with {@code + * ReferenceEntry}, upon which it relies to perform its linking. + * + *

Note that this entire implementation makes the assumption that all elements which are in the + * map are also in this queue, and that all elements not in the queue are not in the map. + * + *

The benefits of creating our own queue are that (1) we can replace elements in the middle of + * the queue as part of copyWriteEntry, and (2) the contains method is highly optimized for the + * current model. + */ + static final class AccessQueue extends AbstractQueue> { + final ReferenceEntry head = + new AbstractReferenceEntry() { + + @Override + public long getAccessTime() { + return Long.MAX_VALUE; + } + + @Override + public void setAccessTime(long time) {} + + ReferenceEntry nextAccess = this; + + @Override + public ReferenceEntry getNextInAccessQueue() { + return nextAccess; + } + + @Override + public void setNextInAccessQueue(ReferenceEntry next) { + this.nextAccess = next; + } + + ReferenceEntry previousAccess = this; + + @Override + public ReferenceEntry getPreviousInAccessQueue() { + return previousAccess; + } + + @Override + public void setPreviousInAccessQueue(ReferenceEntry previous) { + this.previousAccess = previous; + } + }; + + // implements Queue + + @Override + public boolean offer(ReferenceEntry entry) { + // unlink + connectAccessOrder(entry.getPreviousInAccessQueue(), entry.getNextInAccessQueue()); + + // add to tail + connectAccessOrder(head.getPreviousInAccessQueue(), entry); + connectAccessOrder(entry, head); + + return true; + } + + @Override + public ReferenceEntry peek() { + ReferenceEntry next = head.getNextInAccessQueue(); + return (next == head) ? null : next; + } + + @Override + public ReferenceEntry poll() { + ReferenceEntry next = head.getNextInAccessQueue(); + if (next == head) { + return null; + } + + remove(next); + return next; + } + + @Override + @SuppressWarnings("unchecked") + public boolean remove(Object o) { + ReferenceEntry e = (ReferenceEntry) o; + ReferenceEntry previous = e.getPreviousInAccessQueue(); + ReferenceEntry next = e.getNextInAccessQueue(); + connectAccessOrder(previous, next); + nullifyAccessOrder(e); + + return next != NullEntry.INSTANCE; + } + + @Override + @SuppressWarnings("unchecked") + public boolean contains(Object o) { + ReferenceEntry e = (ReferenceEntry) o; + return e.getNextInAccessQueue() != NullEntry.INSTANCE; + } + + @Override + public boolean isEmpty() { + return head.getNextInAccessQueue() == head; + } + + @Override + public int size() { + int size = 0; + for (ReferenceEntry e = head.getNextInAccessQueue(); + e != head; + e = e.getNextInAccessQueue()) { + size++; + } + return size; + } + + @Override + public void clear() { + ReferenceEntry e = head.getNextInAccessQueue(); + while (e != head) { + ReferenceEntry next = e.getNextInAccessQueue(); + nullifyAccessOrder(e); + e = next; + } + + head.setNextInAccessQueue(head); + head.setPreviousInAccessQueue(head); + } + + @Override + public Iterator> iterator() { + return new AbstractSequentialIterator>(peek()) { + @Override + protected ReferenceEntry computeNext(ReferenceEntry previous) { + ReferenceEntry next = previous.getNextInAccessQueue(); + return (next == head) ? null : next; + } + }; + } + } + + // Cache support + + public void cleanUp() { + for (Segment segment : segments) { + segment.cleanUp(); + } + } + + // ConcurrentMap methods + + @Override + public boolean isEmpty() { + /* + * Sum per-segment modCounts to avoid mis-reporting when elements are concurrently added and + * removed in one segment while checking another, in which case the table was never actually + * empty at any point. (The sum ensures accuracy up through at least 1<<31 per-segment + * modifications before recheck.) Method containsValue() uses similar constructions for + * stability checks. + */ + long sum = 0L; + Segment[] segments = this.segments; + for (int i = 0; i < segments.length; ++i) { + if (segments[i].count != 0) { + return false; + } + sum += segments[i].modCount; + } + + if (sum != 0L) { // recheck unless no modifications + for (int i = 0; i < segments.length; ++i) { + if (segments[i].count != 0) { + return false; + } + sum -= segments[i].modCount; + } + return sum == 0L; + } + return true; + } + + long longSize() { + Segment[] segments = this.segments; + long sum = 0; + for (int i = 0; i < segments.length; ++i) { + sum += Math.max(0, segments[i].count); // see https://github.com/google/guava/issues/2108 + } + return sum; + } + + @Override + public int size() { + return Ints.saturatedCast(longSize()); + } + + @Override + public V get(Object key) { + if (key == null) { + return null; + } + int hash = hash(key); + return segmentFor(hash).get(key, hash); + } + + V get(K key, CacheLoader loader) throws ExecutionException { + int hash = hash(checkNotNull(key)); + return segmentFor(hash).get(key, hash, loader); + } + + public V getIfPresent(Object key) { + int hash = hash(checkNotNull(key)); + V value = segmentFor(hash).get(key, hash); + if (value == null) { + globalStatsCounter.recordMisses(1); + } else { + globalStatsCounter.recordHits(1); + } + return value; + } + + // Only becomes available in Java 8 when it's on the interface. + // @Override + @Override + public V getOrDefault(Object key, V defaultValue) { + V result = get(key); + return (result != null) ? result : defaultValue; + } + + V getOrLoad(K key) throws ExecutionException { + return get(key, defaultLoader); + } + + ImmutableMap getAllPresent(Iterable keys) { + int hits = 0; + int misses = 0; + + Map result = Maps.newLinkedHashMap(); + for (Object key : keys) { + V value = get(key); + if (value == null) { + misses++; + } else { + // TODO(fry): store entry key instead of query key + @SuppressWarnings("unchecked") + K castKey = (K) key; + result.put(castKey, value); + hits++; + } + } + globalStatsCounter.recordHits(hits); + globalStatsCounter.recordMisses(misses); + return ImmutableMap.copyOf(result); + } + + ImmutableMap getAll(Iterable keys) throws ExecutionException { + int hits = 0; + int misses = 0; + + Map result = Maps.newLinkedHashMap(); + Set keysToLoad = Sets.newLinkedHashSet(); + for (K key : keys) { + V value = get(key); + if (!result.containsKey(key)) { + result.put(key, value); + if (value == null) { + misses++; + keysToLoad.add(key); + } else { + hits++; + } + } + } + + try { + if (!keysToLoad.isEmpty()) { + try { + Map newEntries = loadAll(keysToLoad, defaultLoader); + for (K key : keysToLoad) { + V value = newEntries.get(key); + if (value == null) { + throw new InvalidCacheLoadException("loadAll failed to return a value for " + key); + } + result.put(key, value); + } + } catch (UnsupportedLoadingOperationException e) { + // loadAll not implemented, fallback to load + for (K key : keysToLoad) { + misses--; // get will count this miss + result.put(key, get(key, defaultLoader)); + } + } + } + return ImmutableMap.copyOf(result); + } finally { + globalStatsCounter.recordHits(hits); + globalStatsCounter.recordMisses(misses); + } + } + + /** + * Returns the result of calling {@link CacheLoader#loadAll}, or null if {@code loader} doesn't + * implement {@code loadAll}. + */ + + Map loadAll(Set keys, CacheLoader loader) + throws ExecutionException { + checkNotNull(loader); + checkNotNull(keys); + Stopwatch stopwatch = Stopwatch.createStarted(); + Map result; + boolean success = false; + try { + @SuppressWarnings("unchecked") // safe since all keys extend K + Map map = (Map) loader.loadAll(keys); + result = map; + success = true; + } catch (UnsupportedLoadingOperationException e) { + success = true; + throw e; + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + throw new ExecutionException(e); + } catch (RuntimeException e) { + throw new UncheckedExecutionException(e); + } catch (Exception e) { + throw new ExecutionException(e); + } catch (Error e) { + throw new ExecutionError(e); + } finally { + if (!success) { + globalStatsCounter.recordLoadException(stopwatch.elapsed(NANOSECONDS)); + } + } + + if (result == null) { + globalStatsCounter.recordLoadException(stopwatch.elapsed(NANOSECONDS)); + throw new InvalidCacheLoadException(loader + " returned null map from loadAll"); + } + + stopwatch.stop(); + // TODO(fry): batch by segment + boolean nullsPresent = false; + for (Entry entry : result.entrySet()) { + K key = entry.getKey(); + V value = entry.getValue(); + if (key == null || value == null) { + // delay failure until non-null entries are stored + nullsPresent = true; + } else { + put(key, value); + } + } + + if (nullsPresent) { + globalStatsCounter.recordLoadException(stopwatch.elapsed(NANOSECONDS)); + throw new InvalidCacheLoadException(loader + " returned null keys or values from loadAll"); + } + + // TODO(fry): record count of loaded entries + globalStatsCounter.recordLoadSuccess(stopwatch.elapsed(NANOSECONDS)); + return result; + } + + /** + * Returns the internal entry for the specified key. The entry may be loading, expired, or + * partially collected. + */ + ReferenceEntry getEntry(Object key) { + // does not impact recency ordering + if (key == null) { + return null; + } + int hash = hash(key); + return segmentFor(hash).getEntry(key, hash); + } + + void refresh(K key) { + int hash = hash(checkNotNull(key)); + segmentFor(hash).refresh(key, hash, defaultLoader, false); + } + + @Override + public boolean containsKey(Object key) { + // does not impact recency ordering + if (key == null) { + return false; + } + int hash = hash(key); + return segmentFor(hash).containsKey(key, hash); + } + + @Override + public boolean containsValue(Object value) { + // does not impact recency ordering + if (value == null) { + return false; + } + + // This implementation is patterned after ConcurrentHashMap, but without the locking. The only + // way for it to return a false negative would be for the target value to jump around in the map + // such that none of the subsequent iterations observed it, despite the fact that at every point + // in time it was present somewhere int the map. This becomes increasingly unlikely as + // CONTAINS_VALUE_RETRIES increases, though without locking it is theoretically possible. + long now = ticker.read(); + final Segment[] segments = this.segments; + long last = -1L; + for (int i = 0; i < CONTAINS_VALUE_RETRIES; i++) { + long sum = 0L; + for (Segment segment : segments) { + // ensure visibility of most recent completed write + int unused = segment.count; // read-volatile + + AtomicReferenceArray> table = segment.table; + for (int j = 0; j < table.length(); j++) { + for (ReferenceEntry e = table.get(j); e != null; e = e.getNext()) { + V v = segment.getLiveValue(e, now); + if (v != null && valueEquivalence.equivalent(value, v)) { + return true; + } + } + } + sum += segment.modCount; + } + if (sum == last) { + break; + } + last = sum; + } + return false; + } + + @Override + public V put(K key, V value) { + checkNotNull(key); + checkNotNull(value); + int hash = hash(key); + return segmentFor(hash).put(key, hash, value, false); + } + + @Override + public V putIfAbsent(K key, V value) { + checkNotNull(key); + checkNotNull(value); + int hash = hash(key); + return segmentFor(hash).put(key, hash, value, true); + } + + @Override + public V compute(K key, BiFunction function) { + checkNotNull(key); + checkNotNull(function); + int hash = hash(key); + return segmentFor(hash).compute(key, hash, function); + } + + @Override + public V computeIfAbsent(K key, Function function) { + checkNotNull(key); + checkNotNull(function); + return compute(key, (k, oldValue) -> (oldValue == null) ? function.apply(key) : oldValue); + } + + @Override + public V computeIfPresent(K key, BiFunction function) { + checkNotNull(key); + checkNotNull(function); + return compute(key, (k, oldValue) -> (oldValue == null) ? null : function.apply(k, oldValue)); + } + + @Override + public V merge(K key, V newValue, BiFunction function) { + checkNotNull(key); + checkNotNull(newValue); + checkNotNull(function); + return compute( + key, (k, oldValue) -> (oldValue == null) ? newValue : function.apply(oldValue, newValue)); + } + + @Override + public void putAll(Map m) { + for (Entry e : m.entrySet()) { + put(e.getKey(), e.getValue()); + } + } + + @Override + public V remove(Object key) { + if (key == null) { + return null; + } + int hash = hash(key); + return segmentFor(hash).remove(key, hash); + } + + @Override + public boolean remove(Object key, Object value) { + if (key == null || value == null) { + return false; + } + int hash = hash(key); + return segmentFor(hash).remove(key, hash, value); + } + + @Override + public boolean replace(K key, V oldValue, V newValue) { + checkNotNull(key); + checkNotNull(newValue); + if (oldValue == null) { + return false; + } + int hash = hash(key); + return segmentFor(hash).replace(key, hash, oldValue, newValue); + } + + @Override + public V replace(K key, V value) { + checkNotNull(key); + checkNotNull(value); + int hash = hash(key); + return segmentFor(hash).replace(key, hash, value); + } + + @Override + public void clear() { + for (Segment segment : segments) { + segment.clear(); + } + } + + void invalidateAll(Iterable keys) { + // TODO(fry): batch by segment + for (Object key : keys) { + remove(key); + } + } + + Set keySet; + + @Override + public Set keySet() { + // does not impact recency ordering + Set ks = keySet; + return (ks != null) ? ks : (keySet = new KeySet(this)); + } + + Collection values; + + @Override + public Collection values() { + // does not impact recency ordering + Collection vs = values; + return (vs != null) ? vs : (values = new Values(this)); + } + + Set> entrySet; + + @Override + @GwtIncompatible // Not supported. + public Set> entrySet() { + // does not impact recency ordering + Set> es = entrySet; + return (es != null) ? es : (entrySet = new EntrySet(this)); + } + + // Iterator Support + + abstract class HashIterator implements Iterator { + + int nextSegmentIndex; + int nextTableIndex; + Segment currentSegment; + AtomicReferenceArray> currentTable; + ReferenceEntry nextEntry; + WriteThroughEntry nextExternal; + WriteThroughEntry lastReturned; + + HashIterator() { + nextSegmentIndex = segments.length - 1; + nextTableIndex = -1; + advance(); + } + + @Override + public abstract T next(); + + final void advance() { + nextExternal = null; + + if (nextInChain()) { + return; + } + + if (nextInTable()) { + return; + } + + while (nextSegmentIndex >= 0) { + currentSegment = segments[nextSegmentIndex--]; + if (currentSegment.count != 0) { + currentTable = currentSegment.table; + nextTableIndex = currentTable.length() - 1; + if (nextInTable()) { + return; + } + } + } + } + + /** Finds the next entry in the current chain. Returns true if an entry was found. */ + boolean nextInChain() { + if (nextEntry != null) { + for (nextEntry = nextEntry.getNext(); nextEntry != null; nextEntry = nextEntry.getNext()) { + if (advanceTo(nextEntry)) { + return true; + } + } + } + return false; + } + + /** Finds the next entry in the current table. Returns true if an entry was found. */ + boolean nextInTable() { + while (nextTableIndex >= 0) { + if ((nextEntry = currentTable.get(nextTableIndex--)) != null) { + if (advanceTo(nextEntry) || nextInChain()) { + return true; + } + } + } + return false; + } + + /** + * Advances to the given entry. Returns true if the entry was valid, false if it should be + * skipped. + */ + boolean advanceTo(ReferenceEntry entry) { + try { + long now = ticker.read(); + K key = entry.getKey(); + V value = getLiveValue(entry, now); + if (value != null) { + nextExternal = new WriteThroughEntry(key, value); + return true; + } else { + // Skip stale entry. + return false; + } + } finally { + currentSegment.postReadCleanup(); + } + } + + @Override + public boolean hasNext() { + return nextExternal != null; + } + + WriteThroughEntry nextEntry() { + if (nextExternal == null) { + throw new NoSuchElementException(); + } + lastReturned = nextExternal; + advance(); + return lastReturned; + } + + @Override + public void remove() { + checkState(lastReturned != null); + LocalCache.this.remove(lastReturned.getKey()); + lastReturned = null; + } + } + + final class KeyIterator extends HashIterator { + + @Override + public K next() { + return nextEntry().getKey(); + } + } + + final class ValueIterator extends HashIterator { + + @Override + public V next() { + return nextEntry().getValue(); + } + } + + /** + * Custom Entry class used by EntryIterator.next(), that relays setValue changes to the underlying + * map. + */ + final class WriteThroughEntry implements Entry { + final K key; // non-null + V value; // non-null + + WriteThroughEntry(K key, V value) { + this.key = key; + this.value = value; + } + + @Override + public K getKey() { + return key; + } + + @Override + public V getValue() { + return value; + } + + @Override + public boolean equals(Object object) { + // Cannot use key and value equivalence + if (object instanceof Entry) { + Entry that = (Entry) object; + return key.equals(that.getKey()) && value.equals(that.getValue()); + } + return false; + } + + @Override + public int hashCode() { + // Cannot use key and value equivalence + return key.hashCode() ^ value.hashCode(); + } + + @Override + public V setValue(V newValue) { + V oldValue = put(key, newValue); + value = newValue; // only if put succeeds + return oldValue; + } + + @Override + public String toString() { + return getKey() + "=" + getValue(); + } + } + + final class EntryIterator extends HashIterator> { + + @Override + public Entry next() { + return nextEntry(); + } + } + + abstract class AbstractCacheSet extends AbstractSet { + final ConcurrentMap map; + + AbstractCacheSet(ConcurrentMap map) { + this.map = map; + } + + @Override + public int size() { + return map.size(); + } + + @Override + public boolean isEmpty() { + return map.isEmpty(); + } + + @Override + public void clear() { + map.clear(); + } + + // super.toArray() may misbehave if size() is inaccurate, at least on old versions of Android. + // https://code.google.com/p/android/issues/detail?id=36519 / http://r.android.com/47508 + + @Override + public Object[] toArray() { + return toArrayList(this).toArray(); + } + + @Override + public E[] toArray(E[] a) { + return toArrayList(this).toArray(a); + } + } + + private static ArrayList toArrayList(Collection c) { + // Avoid calling ArrayList(Collection), which may call back into toArray. + ArrayList result = new ArrayList(c.size()); + Iterators.addAll(result, c.iterator()); + return result; + } + + boolean removeIf(BiPredicate filter) { + checkNotNull(filter); + boolean changed = false; + for (K key : keySet()) { + while (true) { + V value = get(key); + if (value == null || !filter.test(key, value)) { + break; + } else if (LocalCache.this.remove(key, value)) { + changed = true; + break; + } + } + } + return changed; + } + + + final class KeySet extends AbstractCacheSet { + + KeySet(ConcurrentMap map) { + super(map); + } + + @Override + public Iterator iterator() { + return new KeyIterator(); + } + + @Override + public boolean contains(Object o) { + return map.containsKey(o); + } + + @Override + public boolean remove(Object o) { + return map.remove(o) != null; + } + } + + + final class Values extends AbstractCollection { + private final ConcurrentMap map; + + Values(ConcurrentMap map) { + this.map = map; + } + + @Override + public int size() { + return map.size(); + } + + @Override + public boolean isEmpty() { + return map.isEmpty(); + } + + @Override + public void clear() { + map.clear(); + } + + @Override + public Iterator iterator() { + return new ValueIterator(); + } + + @Override + public boolean removeIf(Predicate filter) { + checkNotNull(filter); + return LocalCache.this.removeIf((k, v) -> filter.test(v)); + } + + @Override + public boolean contains(Object o) { + return map.containsValue(o); + } + + // super.toArray() may misbehave if size() is inaccurate, at least on old versions of Android. + // https://code.google.com/p/android/issues/detail?id=36519 / http://r.android.com/47508 + + @Override + public Object[] toArray() { + return toArrayList(this).toArray(); + } + + @Override + public E[] toArray(E[] a) { + return toArrayList(this).toArray(a); + } + } + + + final class EntrySet extends AbstractCacheSet> { + + EntrySet(ConcurrentMap map) { + super(map); + } + + @Override + public Iterator> iterator() { + return new EntryIterator(); + } + + @Override + public boolean removeIf(Predicate> filter) { + checkNotNull(filter); + return LocalCache.this.removeIf((k, v) -> filter.test(Maps.immutableEntry(k, v))); + } + + @Override + public boolean contains(Object o) { + if (!(o instanceof Entry)) { + return false; + } + Entry e = (Entry) o; + Object key = e.getKey(); + if (key == null) { + return false; + } + V v = LocalCache.this.get(key); + + return v != null && valueEquivalence.equivalent(e.getValue(), v); + } + + @Override + public boolean remove(Object o) { + if (!(o instanceof Entry)) { + return false; + } + Entry e = (Entry) o; + Object key = e.getKey(); + return key != null && LocalCache.this.remove(key, e.getValue()); + } + } + + // Serialization Support + + /** + * Serializes the configuration of a LocalCache, reconstituting it as a Cache using CacheBuilder + * upon deserialization. An instance of this class is fit for use by the writeReplace of + * LocalManualCache. + * + *

Unfortunately, readResolve() doesn't get called when a circular dependency is present, so + * the proxy must be able to behave as the cache itself. + */ + static class ManualSerializationProxy extends ForwardingCache + implements Serializable { + private static final long serialVersionUID = 1; + + final Strength keyStrength; + final Strength valueStrength; + final Equivalence keyEquivalence; + final Equivalence valueEquivalence; + final long expireAfterWriteNanos; + final long expireAfterAccessNanos; + final long maxWeight; + final Weigher weigher; + final int concurrencyLevel; + final RemovalListener removalListener; + final Ticker ticker; + final CacheLoader loader; + + transient Cache delegate; + + ManualSerializationProxy(LocalCache cache) { + this( + cache.keyStrength, + cache.valueStrength, + cache.keyEquivalence, + cache.valueEquivalence, + cache.expireAfterWriteNanos, + cache.expireAfterAccessNanos, + cache.maxWeight, + cache.weigher, + cache.concurrencyLevel, + cache.removalListener, + cache.ticker, + cache.defaultLoader); + } + + private ManualSerializationProxy( + Strength keyStrength, + Strength valueStrength, + Equivalence keyEquivalence, + Equivalence valueEquivalence, + long expireAfterWriteNanos, + long expireAfterAccessNanos, + long maxWeight, + Weigher weigher, + int concurrencyLevel, + RemovalListener removalListener, + Ticker ticker, + CacheLoader loader) { + this.keyStrength = keyStrength; + this.valueStrength = valueStrength; + this.keyEquivalence = keyEquivalence; + this.valueEquivalence = valueEquivalence; + this.expireAfterWriteNanos = expireAfterWriteNanos; + this.expireAfterAccessNanos = expireAfterAccessNanos; + this.maxWeight = maxWeight; + this.weigher = weigher; + this.concurrencyLevel = concurrencyLevel; + this.removalListener = removalListener; + this.ticker = (ticker == Ticker.systemTicker() || ticker == NULL_TICKER) ? null : ticker; + this.loader = loader; + } + + CacheBuilder recreateCacheBuilder() { + CacheBuilder builder = + CacheBuilder.newBuilder() + .setKeyStrength(keyStrength) + .setValueStrength(valueStrength) + .keyEquivalence(keyEquivalence) + .valueEquivalence(valueEquivalence) + .concurrencyLevel(concurrencyLevel) + .removalListener(removalListener); + builder.strictParsing = false; + if (expireAfterWriteNanos > 0) { + builder.expireAfterWrite(expireAfterWriteNanos, TimeUnit.NANOSECONDS); + } + if (expireAfterAccessNanos > 0) { + builder.expireAfterAccess(expireAfterAccessNanos, TimeUnit.NANOSECONDS); + } + if (weigher != OneWeigher.INSTANCE) { + builder.weigher(weigher); + if (maxWeight != UNSET_INT) { + builder.maximumWeight(maxWeight); + } + } else { + if (maxWeight != UNSET_INT) { + builder.maximumSize(maxWeight); + } + } + if (ticker != null) { + builder.ticker(ticker); + } + return builder; + } + + private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { + in.defaultReadObject(); + CacheBuilder builder = recreateCacheBuilder(); + this.delegate = builder.build(); + } + + private Object readResolve() { + return delegate; + } + + @Override + protected Cache delegate() { + return delegate; + } + } + + /** + * Serializes the configuration of a LocalCache, reconstituting it as an LoadingCache using + * CacheBuilder upon deserialization. An instance of this class is fit for use by the writeReplace + * of LocalLoadingCache. + * + *

Unfortunately, readResolve() doesn't get called when a circular dependency is present, so + * the proxy must be able to behave as the cache itself. + */ + static final class LoadingSerializationProxy extends ManualSerializationProxy + implements LoadingCache, Serializable { + private static final long serialVersionUID = 1; + + transient LoadingCache autoDelegate; + + LoadingSerializationProxy(LocalCache cache) { + super(cache); + } + + private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { + in.defaultReadObject(); + CacheBuilder builder = recreateCacheBuilder(); + this.autoDelegate = builder.build(loader); + } + + @Override + public V get(K key) throws ExecutionException { + return autoDelegate.get(key); + } + + @Override + public V getUnchecked(K key) { + return autoDelegate.getUnchecked(key); + } + + @Override + public ImmutableMap getAll(Iterable keys) throws ExecutionException { + return autoDelegate.getAll(keys); + } + + @Override + public final V apply(K key) { + return autoDelegate.apply(key); + } + + @Override + public void refresh(K key) { + autoDelegate.refresh(key); + } + + private Object readResolve() { + return autoDelegate; + } + } + + static class LocalManualCache implements Cache, Serializable { + final LocalCache localCache; + + LocalManualCache(CacheBuilder builder) { + this(new LocalCache(builder, null)); + } + + private LocalManualCache(LocalCache localCache) { + this.localCache = localCache; + } + + // Cache methods + + @Override + public V getIfPresent(Object key) { + return localCache.getIfPresent(key); + } + + @Override + public V get(K key, final Callable valueLoader) throws ExecutionException { + checkNotNull(valueLoader); + return localCache.get( + key, + new CacheLoader() { + @Override + public V load(Object key) throws Exception { + return valueLoader.call(); + } + }); + } + + @Override + public ImmutableMap getAllPresent(Iterable keys) { + return localCache.getAllPresent(keys); + } + + @Override + public void put(K key, V value) { + localCache.put(key, value); + } + + @Override + public void putAll(Map m) { + localCache.putAll(m); + } + + @Override + public void invalidate(Object key) { + checkNotNull(key); + localCache.remove(key); + } + + @Override + public void invalidateAll(Iterable keys) { + localCache.invalidateAll(keys); + } + + @Override + public void invalidateAll() { + localCache.clear(); + } + + @Override + public long size() { + return localCache.longSize(); + } + + @Override + public ConcurrentMap asMap() { + return localCache; + } + + @Override + public CacheStats stats() { + SimpleStatsCounter aggregator = new SimpleStatsCounter(); + aggregator.incrementBy(localCache.globalStatsCounter); + for (Segment segment : localCache.segments) { + aggregator.incrementBy(segment.statsCounter); + } + return aggregator.snapshot(); + } + + @Override + public void cleanUp() { + localCache.cleanUp(); + } + + // Serialization Support + + private static final long serialVersionUID = 1; + + Object writeReplace() { + return new ManualSerializationProxy<>(localCache); + } + } + + static class LocalLoadingCache extends LocalManualCache + implements LoadingCache { + + LocalLoadingCache( + CacheBuilder builder, CacheLoader loader) { + super(new LocalCache(builder, checkNotNull(loader))); + } + + // LoadingCache methods + + @Override + public V get(K key) throws ExecutionException { + return localCache.getOrLoad(key); + } + + @Override + public V getUnchecked(K key) { + try { + return get(key); + } catch (ExecutionException e) { + throw new UncheckedExecutionException(e.getCause()); + } + } + + @Override + public ImmutableMap getAll(Iterable keys) throws ExecutionException { + return localCache.getAll(keys); + } + + @Override + public void refresh(K key) { + localCache.refresh(key); + } + + @Override + public final V apply(K key) { + return getUnchecked(key); + } + + // Serialization Support + + private static final long serialVersionUID = 1; + + @Override + Object writeReplace() { + return new LoadingSerializationProxy<>(localCache); + } + } +} diff --git a/src/main/java/com/google/common/cache/LongAddable.java b/src/main/java/com/google/common/cache/LongAddable.java new file mode 100644 index 0000000..eaa6414 --- /dev/null +++ b/src/main/java/com/google/common/cache/LongAddable.java @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2012 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.cache; + +import com.google.common.annotations.GwtCompatible; + +/** + * Abstract interface for objects that can concurrently add longs. + * + * @author Louis Wasserman + */ +@GwtCompatible +interface LongAddable { + void increment(); + + void add(long x); + + long sum(); +} diff --git a/src/main/java/com/google/common/cache/LongAddables.java b/src/main/java/com/google/common/cache/LongAddables.java new file mode 100644 index 0000000..d1b7157 --- /dev/null +++ b/src/main/java/com/google/common/cache/LongAddables.java @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2012 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.cache; + +import com.google.common.annotations.GwtCompatible; +import com.google.common.base.Supplier; +import java.util.concurrent.atomic.AtomicLong; + +/** + * Source of {@link LongAddable} objects that deals with GWT, Unsafe, and all that. + * + * @author Louis Wasserman + */ +@GwtCompatible(emulated = true) +final class LongAddables { + private static final Supplier SUPPLIER; + + static { + SUPPLIER = PureJavaLongAddable::new; + } + + public static LongAddable create() { + return SUPPLIER.get(); + } + + private static final class PureJavaLongAddable extends AtomicLong implements LongAddable { + @Override + public void increment() { + getAndIncrement(); + } + + @Override + public void add(long x) { + getAndAdd(x); + } + + @Override + public long sum() { + return get(); + } + } +} diff --git a/src/main/java/com/google/common/cache/ReferenceEntry.java b/src/main/java/com/google/common/cache/ReferenceEntry.java new file mode 100644 index 0000000..98e8c57 --- /dev/null +++ b/src/main/java/com/google/common/cache/ReferenceEntry.java @@ -0,0 +1,111 @@ +/* + * Copyright (C) 2009 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.cache; + +import com.google.common.annotations.GwtIncompatible; +import com.google.common.cache.LocalCache.ValueReference; + + +/** + * An entry in a reference map. + * + *

Entries in the map can be in the following states: + * + *

Valid: + * + *

    + *
  • Live: valid key/value are set + *
  • Loading: loading is pending + *
+ * + *

Invalid: + * + *

    + *
  • Expired: time expired (key/value may still be set) + *
  • Collected: key/value was partially collected, but not yet cleaned up + *
  • Unset: marked as unset, awaiting cleanup or reuse + *
+ */ +@GwtIncompatible +interface ReferenceEntry { + /** Returns the value reference from this entry. */ + ValueReference getValueReference(); + + /** Sets the value reference for this entry. */ + void setValueReference(ValueReference valueReference); + + /** Returns the next entry in the chain. */ + + ReferenceEntry getNext(); + + /** Returns the entry's hash. */ + int getHash(); + + /** Returns the key for this entry. */ + + K getKey(); + + /* + * Used by entries that use access order. Access entries are maintained in a doubly-linked list. + * New entries are added at the tail of the list at write time; stale entries are expired from + * the head of the list. + */ + + /** Returns the time that this entry was last accessed, in ns. */ + @SuppressWarnings("GoodTime") + long getAccessTime(); + + /** Sets the entry access time in ns. */ + @SuppressWarnings("GoodTime") // b/122668874 + void setAccessTime(long time); + + /** Returns the next entry in the access queue. */ + ReferenceEntry getNextInAccessQueue(); + + /** Sets the next entry in the access queue. */ + void setNextInAccessQueue(ReferenceEntry next); + + /** Returns the previous entry in the access queue. */ + ReferenceEntry getPreviousInAccessQueue(); + + /** Sets the previous entry in the access queue. */ + void setPreviousInAccessQueue(ReferenceEntry previous); + + /* + * Implemented by entries that use write order. Write entries are maintained in a doubly-linked + * list. New entries are added at the tail of the list at write time and stale entries are + * expired from the head of the list. + */ + + @SuppressWarnings("GoodTime") + /** Returns the time that this entry was last written, in ns. */ + long getWriteTime(); + + /** Sets the entry write time in ns. */ + @SuppressWarnings("GoodTime") // b/122668874 + void setWriteTime(long time); + + /** Returns the next entry in the write queue. */ + ReferenceEntry getNextInWriteQueue(); + + /** Sets the next entry in the write queue. */ + void setNextInWriteQueue(ReferenceEntry next); + + /** Returns the previous entry in the write queue. */ + ReferenceEntry getPreviousInWriteQueue(); + + /** Sets the previous entry in the write queue. */ + void setPreviousInWriteQueue(ReferenceEntry previous); +} diff --git a/src/main/java/com/google/common/cache/RemovalCause.java b/src/main/java/com/google/common/cache/RemovalCause.java new file mode 100644 index 0000000..8ecc1d6 --- /dev/null +++ b/src/main/java/com/google/common/cache/RemovalCause.java @@ -0,0 +1,94 @@ +/* + * Copyright (C) 2011 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.cache; + +import com.google.common.annotations.GwtCompatible; +import java.util.Iterator; +import java.util.Map; +import java.util.concurrent.ConcurrentMap; + +/** + * The reason why a cached entry was removed. + * + * @author Charles Fry + * @since 10.0 + */ +@GwtCompatible +public enum RemovalCause { + /** + * The entry was manually removed by the user. This can result from the user invoking {@link + * Cache#invalidate}, {@link Cache#invalidateAll(Iterable)}, {@link Cache#invalidateAll()}, {@link + * Map#remove}, {@link ConcurrentMap#remove}, or {@link Iterator#remove}. + */ + EXPLICIT { + @Override + boolean wasEvicted() { + return false; + } + }, + + /** + * The entry itself was not actually removed, but its value was replaced by the user. This can + * result from the user invoking {@link Cache#put}, {@link LoadingCache#refresh}, {@link Map#put}, + * {@link Map#putAll}, {@link ConcurrentMap#replace(Object, Object)}, or {@link + * ConcurrentMap#replace(Object, Object, Object)}. + */ + REPLACED { + @Override + boolean wasEvicted() { + return false; + } + }, + + /** + * The entry was removed automatically because its key or value was garbage-collected. This can + * occur when using {@link CacheBuilder#weakKeys}, {@link CacheBuilder#weakValues}, or {@link + * CacheBuilder#softValues}. + */ + COLLECTED { + @Override + boolean wasEvicted() { + return true; + } + }, + + /** + * The entry's expiration timestamp has passed. This can occur when using {@link + * CacheBuilder#expireAfterWrite} or {@link CacheBuilder#expireAfterAccess}. + */ + EXPIRED { + @Override + boolean wasEvicted() { + return true; + } + }, + + /** + * The entry was evicted due to size constraints. This can occur when using {@link + * CacheBuilder#maximumSize} or {@link CacheBuilder#maximumWeight}. + */ + SIZE { + @Override + boolean wasEvicted() { + return true; + } + }; + + /** + * Returns {@code true} if there was an automatic removal due to eviction (the cause is neither + * {@link #EXPLICIT} nor {@link #REPLACED}). + */ + abstract boolean wasEvicted(); +} diff --git a/src/main/java/com/google/common/cache/RemovalListener.java b/src/main/java/com/google/common/cache/RemovalListener.java new file mode 100644 index 0000000..c9a343c --- /dev/null +++ b/src/main/java/com/google/common/cache/RemovalListener.java @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2011 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.cache; + +import com.google.common.annotations.GwtCompatible; + +/** + * An object that can receive a notification when an entry is removed from a cache. The removal + * resulting in notification could have occurred to an entry being manually removed or replaced, or + * due to eviction resulting from timed expiration, exceeding a maximum size, or garbage collection. + * + *

An instance may be called concurrently by multiple threads to process different entries. + * Implementations of this interface should avoid performing blocking calls or synchronizing on + * shared resources. + * + * @param the most general type of keys this listener can listen for; for example {@code Object} + * if any key is acceptable + * @param the most general type of values this listener can listen for; for example {@code + * Object} if any key is acceptable + * @author Charles Fry + * @since 10.0 + */ +@GwtCompatible +@FunctionalInterface +public interface RemovalListener { + /** + * Notifies the listener that a removal occurred at some point in the past. + * + *

This does not always signify that the key is now absent from the cache, as it may have + * already been re-added. + */ + // Technically should accept RemovalNotification, but because + // RemovalNotification is guaranteed covariant, let's make users' lives simpler. + void onRemoval(RemovalNotification notification); +} diff --git a/src/main/java/com/google/common/cache/RemovalListeners.java b/src/main/java/com/google/common/cache/RemovalListeners.java new file mode 100644 index 0000000..c82b094 --- /dev/null +++ b/src/main/java/com/google/common/cache/RemovalListeners.java @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2011 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.cache; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.annotations.GwtIncompatible; +import java.util.concurrent.Executor; + +/** + * A collection of common removal listeners. + * + * @author Charles Fry + * @since 10.0 + */ +@GwtIncompatible +public final class RemovalListeners { + + private RemovalListeners() {} + + /** + * Returns a {@code RemovalListener} which processes all eviction notifications using {@code + * executor}. + * + * @param listener the backing listener + * @param executor the executor with which removal notifications are asynchronously executed + */ + public static RemovalListener asynchronous( + final RemovalListener listener, final Executor executor) { + checkNotNull(listener); + checkNotNull(executor); + return new RemovalListener() { + @Override + public void onRemoval(final RemovalNotification notification) { + executor.execute( + new Runnable() { + @Override + public void run() { + listener.onRemoval(notification); + } + }); + } + }; + } +} diff --git a/src/main/java/com/google/common/cache/RemovalNotification.java b/src/main/java/com/google/common/cache/RemovalNotification.java new file mode 100644 index 0000000..2406f93 --- /dev/null +++ b/src/main/java/com/google/common/cache/RemovalNotification.java @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2011 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.cache; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.annotations.GwtCompatible; +import java.util.AbstractMap.SimpleImmutableEntry; + + +/** + * A notification of the removal of a single entry. The key and/or value may be null if they were + * already garbage collected. + * + *

Like other {@code Entry} instances associated with {@code CacheBuilder}, this class holds + * strong references to the key and value, regardless of the type of references the cache may be + * using. + * + * @author Charles Fry + * @since 10.0 + */ +@GwtCompatible +public final class RemovalNotification extends SimpleImmutableEntry { + private final RemovalCause cause; + + /** + * Creates a new {@code RemovalNotification} for the given {@code key}/{@code value} pair, with + * the given {@code cause} for the removal. The {@code key} and/or {@code value} may be {@code + * null} if they were already garbage collected. + * + * @since 19.0 + */ + public static RemovalNotification create( + K key, V value, RemovalCause cause) { + return new RemovalNotification(key, value, cause); + } + + private RemovalNotification(K key, V value, RemovalCause cause) { + super(key, value); + this.cause = checkNotNull(cause); + } + + /** Returns the cause for which the entry was removed. */ + public RemovalCause getCause() { + return cause; + } + + /** + * Returns {@code true} if there was an automatic removal due to eviction (the cause is neither + * {@link RemovalCause#EXPLICIT} nor {@link RemovalCause#REPLACED}). + */ + public boolean wasEvicted() { + return cause.wasEvicted(); + } + + private static final long serialVersionUID = 0; +} diff --git a/src/main/java/com/google/common/cache/Weigher.java b/src/main/java/com/google/common/cache/Weigher.java new file mode 100644 index 0000000..7372a46 --- /dev/null +++ b/src/main/java/com/google/common/cache/Weigher.java @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2011 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.cache; + +import com.google.common.annotations.GwtCompatible; + +/** + * Calculates the weights of cache entries. + * + * @author Charles Fry + * @since 11.0 + */ +@GwtCompatible +@FunctionalInterface +public interface Weigher { + + /** + * Returns the weight of a cache entry. There is no unit for entry weights; rather they are simply + * relative to each other. + * + * @return the weight of the entry; must be non-negative + */ + int weigh(K key, V value); +} diff --git a/src/main/java/com/google/common/collect/AbstractBiMap.java b/src/main/java/com/google/common/collect/AbstractBiMap.java new file mode 100644 index 0000000..3a1486d --- /dev/null +++ b/src/main/java/com/google/common/collect/AbstractBiMap.java @@ -0,0 +1,485 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkState; +import static com.google.common.collect.CollectPreconditions.checkRemove; + +import com.google.common.annotations.GwtCompatible; +import com.google.common.annotations.GwtIncompatible; +import com.google.common.base.Objects; + + + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.Serializable; +import java.util.Collection; +import java.util.Iterator; +import java.util.Map; +import java.util.Set; +import java.util.function.BiFunction; + + + +/** + * A general-purpose bimap implementation using any two backing {@code Map} instances. + * + *

Note that this class contains {@code equals()} calls that keep it from supporting {@code + * IdentityHashMap} backing maps. + * + * @author Kevin Bourrillion + * @author Mike Bostock + */ +@GwtCompatible(emulated = true) +abstract class AbstractBiMap extends ForwardingMap + implements BiMap, Serializable { + + private transient Map delegate; + transient AbstractBiMap inverse; + + /** Package-private constructor for creating a map-backed bimap. */ + AbstractBiMap(Map forward, Map backward) { + setDelegates(forward, backward); + } + + /** Private constructor for inverse bimap. */ + private AbstractBiMap(Map backward, AbstractBiMap forward) { + delegate = backward; + inverse = forward; + } + + @Override + protected Map delegate() { + return delegate; + } + + /** Returns its input, or throws an exception if this is not a valid key. */ + + K checkKey(K key) { + return key; + } + + /** Returns its input, or throws an exception if this is not a valid value. */ + + V checkValue(V value) { + return value; + } + + /** + * Specifies the delegate maps going in each direction. Called by the constructor and by + * subclasses during deserialization. + */ + void setDelegates(Map forward, Map backward) { + checkState(delegate == null); + checkState(inverse == null); + checkArgument(forward.isEmpty()); + checkArgument(backward.isEmpty()); + checkArgument(forward != backward); + delegate = forward; + inverse = makeInverse(backward); + } + + AbstractBiMap makeInverse(Map backward) { + return new Inverse<>(backward, this); + } + + void setInverse(AbstractBiMap inverse) { + this.inverse = inverse; + } + + // Query Operations (optimizations) + + @Override + public boolean containsValue(Object value) { + return inverse.containsKey(value); + } + + // Modification Operations + + + @Override + public V put(K key, V value) { + return putInBothMaps(key, value, false); + } + + + @Override + public V forcePut(K key, V value) { + return putInBothMaps(key, value, true); + } + + private V putInBothMaps(K key, V value, boolean force) { + checkKey(key); + checkValue(value); + boolean containedKey = containsKey(key); + if (containedKey && Objects.equal(value, get(key))) { + return value; + } + if (force) { + inverse().remove(value); + } else { + checkArgument(!containsValue(value), "value already present: %s", value); + } + V oldValue = delegate.put(key, value); + updateInverseMap(key, containedKey, oldValue, value); + return oldValue; + } + + private void updateInverseMap(K key, boolean containedKey, V oldValue, V newValue) { + if (containedKey) { + removeFromInverseMap(oldValue); + } + inverse.delegate.put(newValue, key); + } + + + @Override + public V remove(Object key) { + return containsKey(key) ? removeFromBothMaps(key) : null; + } + + + private V removeFromBothMaps(Object key) { + V oldValue = delegate.remove(key); + removeFromInverseMap(oldValue); + return oldValue; + } + + private void removeFromInverseMap(V oldValue) { + inverse.delegate.remove(oldValue); + } + + // Bulk Operations + + @Override + public void putAll(Map map) { + for (Entry entry : map.entrySet()) { + put(entry.getKey(), entry.getValue()); + } + } + + @Override + public void replaceAll(BiFunction function) { + this.delegate.replaceAll(function); + inverse.delegate.clear(); + Entry broken = null; + Iterator> itr = this.delegate.entrySet().iterator(); + while (itr.hasNext()) { + Entry entry = itr.next(); + K k = entry.getKey(); + V v = entry.getValue(); + K conflict = inverse.delegate.putIfAbsent(v, k); + if (conflict != null) { + broken = entry; + // We're definitely going to throw, but we'll try to keep the BiMap in an internally + // consistent state by removing the bad entry. + itr.remove(); + } + } + if (broken != null) { + throw new IllegalArgumentException("value already present: " + broken.getValue()); + } + } + + @Override + public void clear() { + delegate.clear(); + inverse.delegate.clear(); + } + + // Views + + @Override + public BiMap inverse() { + return inverse; + } + + private transient Set keySet; + + @Override + public Set keySet() { + Set result = keySet; + return (result == null) ? keySet = new KeySet() : result; + } + + + private class KeySet extends ForwardingSet { + @Override + protected Set delegate() { + return delegate.keySet(); + } + + @Override + public void clear() { + AbstractBiMap.this.clear(); + } + + @Override + public boolean remove(Object key) { + if (!contains(key)) { + return false; + } + removeFromBothMaps(key); + return true; + } + + @Override + public boolean removeAll(Collection keysToRemove) { + return standardRemoveAll(keysToRemove); + } + + @Override + public boolean retainAll(Collection keysToRetain) { + return standardRetainAll(keysToRetain); + } + + @Override + public Iterator iterator() { + return Maps.keyIterator(entrySet().iterator()); + } + } + + private transient Set valueSet; + + @Override + public Set values() { + /* + * We can almost reuse the inverse's keySet, except we have to fix the + * iteration order so that it is consistent with the forward map. + */ + Set result = valueSet; + return (result == null) ? valueSet = new ValueSet() : result; + } + + + private class ValueSet extends ForwardingSet { + final Set valuesDelegate = inverse.keySet(); + + @Override + protected Set delegate() { + return valuesDelegate; + } + + @Override + public Iterator iterator() { + return Maps.valueIterator(entrySet().iterator()); + } + + @Override + public Object[] toArray() { + return standardToArray(); + } + + @Override + public T[] toArray(T[] array) { + return standardToArray(array); + } + + @Override + public String toString() { + return standardToString(); + } + } + + private transient Set> entrySet; + + @Override + public Set> entrySet() { + Set> result = entrySet; + return (result == null) ? entrySet = new EntrySet() : result; + } + + class BiMapEntry extends ForwardingMapEntry { + private final Entry delegate; + + BiMapEntry(Entry delegate) { + this.delegate = delegate; + } + + @Override + protected Entry delegate() { + return delegate; + } + + @Override + public V setValue(V value) { + checkValue(value); + // Preconditions keep the map and inverse consistent. + checkState(entrySet().contains(this), "entry no longer in map"); + // similar to putInBothMaps, but set via entry + if (Objects.equal(value, getValue())) { + return value; + } + checkArgument(!containsValue(value), "value already present: %s", value); + V oldValue = delegate.setValue(value); + checkState(Objects.equal(value, get(getKey())), "entry no longer in map"); + updateInverseMap(getKey(), true, oldValue, value); + return oldValue; + } + } + + Iterator> entrySetIterator() { + final Iterator> iterator = delegate.entrySet().iterator(); + return new Iterator>() { + Entry entry; + + @Override + public boolean hasNext() { + return iterator.hasNext(); + } + + @Override + public Entry next() { + entry = iterator.next(); + return new BiMapEntry(entry); + } + + @Override + public void remove() { + checkRemove(entry != null); + V value = entry.getValue(); + iterator.remove(); + removeFromInverseMap(value); + entry = null; + } + }; + } + + + private class EntrySet extends ForwardingSet> { + final Set> esDelegate = delegate.entrySet(); + + @Override + protected Set> delegate() { + return esDelegate; + } + + @Override + public void clear() { + AbstractBiMap.this.clear(); + } + + @Override + public boolean remove(Object object) { + if (!esDelegate.contains(object)) { + return false; + } + + // safe because esDelegate.contains(object). + Entry entry = (Entry) object; + inverse.delegate.remove(entry.getValue()); + /* + * Remove the mapping in inverse before removing from esDelegate because + * if entry is part of esDelegate, entry might be invalidated after the + * mapping is removed from esDelegate. + */ + esDelegate.remove(entry); + return true; + } + + @Override + public Iterator> iterator() { + return entrySetIterator(); + } + + // See java.util.Collections.CheckedEntrySet for details on attacks. + + @Override + public Object[] toArray() { + return standardToArray(); + } + + @Override + public T[] toArray(T[] array) { + return standardToArray(array); + } + + @Override + public boolean contains(Object o) { + return Maps.containsEntryImpl(delegate(), o); + } + + @Override + public boolean containsAll(Collection c) { + return standardContainsAll(c); + } + + @Override + public boolean removeAll(Collection c) { + return standardRemoveAll(c); + } + + @Override + public boolean retainAll(Collection c) { + return standardRetainAll(c); + } + } + + /** The inverse of any other {@code AbstractBiMap} subclass. */ + static class Inverse extends AbstractBiMap { + Inverse(Map backward, AbstractBiMap forward) { + super(backward, forward); + } + + /* + * Serialization stores the forward bimap, the inverse of this inverse. + * Deserialization calls inverse() on the forward bimap and returns that + * inverse. + * + * If a bimap and its inverse are serialized together, the deserialized + * instances have inverse() methods that return the other. + */ + + @Override + K checkKey(K key) { + return inverse.checkValue(key); + } + + @Override + V checkValue(V value) { + return inverse.checkKey(value); + } + + /** @serialData the forward bimap */ + @GwtIncompatible // java.io.ObjectOutputStream + private void writeObject(ObjectOutputStream stream) throws IOException { + stream.defaultWriteObject(); + stream.writeObject(inverse()); + } + + @GwtIncompatible // java.io.ObjectInputStream + @SuppressWarnings("unchecked") // reading data stored by writeObject + private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException { + stream.defaultReadObject(); + setInverse((AbstractBiMap) stream.readObject()); + } + + @GwtIncompatible // Not needed in the emulated source. + Object readResolve() { + return inverse().inverse(); + } + + @GwtIncompatible // Not needed in emulated source. + private static final long serialVersionUID = 0; + } + + @GwtIncompatible // Not needed in emulated source. + private static final long serialVersionUID = 0; +} diff --git a/src/main/java/com/google/common/collect/AbstractIndexedListIterator.java b/src/main/java/com/google/common/collect/AbstractIndexedListIterator.java new file mode 100644 index 0000000..855fb1c --- /dev/null +++ b/src/main/java/com/google/common/collect/AbstractIndexedListIterator.java @@ -0,0 +1,102 @@ +/* + * Copyright (C) 2009 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import static com.google.common.base.Preconditions.checkPositionIndex; + +import com.google.common.annotations.GwtCompatible; +import java.util.ListIterator; +import java.util.NoSuchElementException; + +/** + * This class provides a skeletal implementation of the {@link ListIterator} interface across a + * fixed number of elements that may be retrieved by position. It does not support {@link #remove}, + * {@link #set}, or {@link #add}. + * + * @author Jared Levy + */ +@GwtCompatible +abstract class AbstractIndexedListIterator extends UnmodifiableListIterator { + private final int size; + private int position; + + /** Returns the element with the specified index. This method is called by {@link #next()}. */ + protected abstract E get(int index); + + /** + * Constructs an iterator across a sequence of the given size whose initial position is 0. That + * is, the first call to {@link #next()} will return the first element (or throw {@link + * NoSuchElementException} if {@code size} is zero). + * + * @throws IllegalArgumentException if {@code size} is negative + */ + protected AbstractIndexedListIterator(int size) { + this(size, 0); + } + + /** + * Constructs an iterator across a sequence of the given size with the given initial position. + * That is, the first call to {@link #nextIndex()} will return {@code position}, and the first + * call to {@link #next()} will return the element at that index, if available. Calls to {@link + * #previous()} can retrieve the preceding {@code position} elements. + * + * @throws IndexOutOfBoundsException if {@code position} is negative or is greater than {@code + * size} + * @throws IllegalArgumentException if {@code size} is negative + */ + protected AbstractIndexedListIterator(int size, int position) { + checkPositionIndex(position, size); + this.size = size; + this.position = position; + } + + @Override + public final boolean hasNext() { + return position < size; + } + + @Override + public final E next() { + if (!hasNext()) { + throw new NoSuchElementException(); + } + return get(position++); + } + + @Override + public final int nextIndex() { + return position; + } + + @Override + public final boolean hasPrevious() { + return position > 0; + } + + @Override + public final E previous() { + if (!hasPrevious()) { + throw new NoSuchElementException(); + } + return get(--position); + } + + @Override + public final int previousIndex() { + return position - 1; + } +} diff --git a/src/main/java/com/google/common/collect/AbstractIterator.java b/src/main/java/com/google/common/collect/AbstractIterator.java new file mode 100644 index 0000000..9fdab6d --- /dev/null +++ b/src/main/java/com/google/common/collect/AbstractIterator.java @@ -0,0 +1,174 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import static com.google.common.base.Preconditions.checkState; + +import com.google.common.annotations.GwtCompatible; + +import java.util.NoSuchElementException; + + +/** + * This class provides a skeletal implementation of the {@code Iterator} interface, to make this + * interface easier to implement for certain types of data sources. + * + *

{@code Iterator} requires its implementations to support querying the end-of-data status + * without changing the iterator's state, using the {@link #hasNext} method. But many data sources, + * such as {@link java.io.Reader#read()}, do not expose this information; the only way to discover + * whether there is any data left is by trying to retrieve it. These types of data sources are + * ordinarily difficult to write iterators for. But using this class, one must implement only the + * {@link #computeNext} method, and invoke the {@link #endOfData} method when appropriate. + * + *

Another example is an iterator that skips over null elements in a backing iterator. This could + * be implemented as: + * + *

{@code
+ * public static Iterator skipNulls(final Iterator in) {
+ *   return new AbstractIterator() {
+ *     protected String computeNext() {
+ *       while (in.hasNext()) {
+ *         String s = in.next();
+ *         if (s != null) {
+ *           return s;
+ *         }
+ *       }
+ *       return endOfData();
+ *     }
+ *   };
+ * }
+ * }
+ * + *

This class supports iterators that include null elements. + * + * @author Kevin Bourrillion + * @since 2.0 + */ +// When making changes to this class, please also update the copy at +// com.google.common.base.AbstractIterator +@GwtCompatible +public abstract class AbstractIterator extends UnmodifiableIterator { + private State state = State.NOT_READY; + + /** Constructor for use by subclasses. */ + protected AbstractIterator() {} + + private enum State { + /** We have computed the next element and haven't returned it yet. */ + READY, + + /** We haven't yet computed or have already returned the element. */ + NOT_READY, + + /** We have reached the end of the data and are finished. */ + DONE, + + /** We've suffered an exception and are kaput. */ + FAILED, + } + + private T next; + + /** + * Returns the next element. Note: the implementation must call {@link #endOfData()} when + * there are no elements left in the iteration. Failure to do so could result in an infinite loop. + * + *

The initial invocation of {@link #hasNext()} or {@link #next()} calls this method, as does + * the first invocation of {@code hasNext} or {@code next} following each successful call to + * {@code next}. Once the implementation either invokes {@code endOfData} or throws an exception, + * {@code computeNext} is guaranteed to never be called again. + * + *

If this method throws an exception, it will propagate outward to the {@code hasNext} or + * {@code next} invocation that invoked this method. Any further attempts to use the iterator will + * result in an {@link IllegalStateException}. + * + *

The implementation of this method may not invoke the {@code hasNext}, {@code next}, or + * {@link #peek()} methods on this instance; if it does, an {@code IllegalStateException} will + * result. + * + * @return the next element if there was one. If {@code endOfData} was called during execution, + * the return value will be ignored. + * @throws RuntimeException if any unrecoverable error happens. This exception will propagate + * outward to the {@code hasNext()}, {@code next()}, or {@code peek()} invocation that invoked + * this method. Any further attempts to use the iterator will result in an {@link + * IllegalStateException}. + */ + protected abstract T computeNext(); + + /** + * Implementations of {@link #computeNext} must invoke this method when there are no + * elements left in the iteration. + * + * @return {@code null}; a convenience so your {@code computeNext} implementation can use the + * simple statement {@code return endOfData();} + */ + + protected final T endOfData() { + state = State.DONE; + return null; + } + + // TODO(kak): Should we remove this? Some people are using it to prefetch? + @Override + public final boolean hasNext() { + checkState(state != State.FAILED); + switch (state) { + case DONE: + return false; + case READY: + return true; + default: + } + return tryToComputeNext(); + } + + private boolean tryToComputeNext() { + state = State.FAILED; // temporary pessimism + next = computeNext(); + if (state != State.DONE) { + state = State.READY; + return true; + } + return false; + } + + // TODO(kak): Should we remove this? + @Override + public final T next() { + if (!hasNext()) { + throw new NoSuchElementException(); + } + state = State.NOT_READY; + T result = next; + next = null; + return result; + } + + /** + * Returns the next element in the iteration without advancing the iteration, according to the + * contract of {@link PeekingIterator#peek()}. + * + *

Implementations of {@code AbstractIterator} that wish to expose this functionality should + * implement {@code PeekingIterator}. + */ + public final T peek() { + if (!hasNext()) { + throw new NoSuchElementException(); + } + return next; + } +} diff --git a/src/main/java/com/google/common/collect/AbstractListMultimap.java b/src/main/java/com/google/common/collect/AbstractListMultimap.java new file mode 100644 index 0000000..4c2f05e --- /dev/null +++ b/src/main/java/com/google/common/collect/AbstractListMultimap.java @@ -0,0 +1,141 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import com.google.common.annotations.GwtCompatible; + +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Map; + + +/** + * Basic implementation of the {@link ListMultimap} interface. It's a wrapper around {@link + * AbstractMapBasedMultimap} that converts the returned collections into {@code Lists}. The {@link + * #createCollection} method must return a {@code List}. + * + * @author Jared Levy + * @since 2.0 + */ +@GwtCompatible +abstract class AbstractListMultimap extends AbstractMapBasedMultimap + implements ListMultimap { + /** + * Creates a new multimap that uses the provided map. + * + * @param map place to store the mapping from each key to its corresponding values + */ + protected AbstractListMultimap(Map> map) { + super(map); + } + + @Override + abstract List createCollection(); + + @Override + List createUnmodifiableEmptyCollection() { + return Collections.emptyList(); + } + + @Override + Collection unmodifiableCollectionSubclass(Collection collection) { + return Collections.unmodifiableList((List) collection); + } + + @Override + Collection wrapCollection(K key, Collection collection) { + return wrapList(key, (List) collection, null); + } + + // Following Javadoc copied from ListMultimap. + + /** + * {@inheritDoc} + * + *

Because the values for a given key may have duplicates and follow the insertion ordering, + * this method returns a {@link List}, instead of the {@link Collection} specified in the {@link + * Multimap} interface. + */ + @Override + public List get(K key) { + return (List) super.get(key); + } + + /** + * {@inheritDoc} + * + *

Because the values for a given key may have duplicates and follow the insertion ordering, + * this method returns a {@link List}, instead of the {@link Collection} specified in the {@link + * Multimap} interface. + */ + + @Override + public List removeAll(Object key) { + return (List) super.removeAll(key); + } + + /** + * {@inheritDoc} + * + *

Because the values for a given key may have duplicates and follow the insertion ordering, + * this method returns a {@link List}, instead of the {@link Collection} specified in the {@link + * Multimap} interface. + */ + + @Override + public List replaceValues(K key, Iterable values) { + return (List) super.replaceValues(key, values); + } + + /** + * Stores a key-value pair in the multimap. + * + * @param key key to store in the multimap + * @param value value to store in the multimap + * @return {@code true} always + */ + + @Override + public boolean put(K key, V value) { + return super.put(key, value); + } + + /** + * {@inheritDoc} + * + *

Though the method signature doesn't say so explicitly, the returned map has {@link List} + * values. + */ + @Override + public Map> asMap() { + return super.asMap(); + } + + /** + * Compares the specified object to this multimap for equality. + * + *

Two {@code ListMultimap} instances are equal if, for each key, they contain the same values + * in the same order. If the value orderings disagree, the multimaps will not be considered equal. + */ + @Override + public boolean equals(Object object) { + return super.equals(object); + } + + private static final long serialVersionUID = 6588350623831699109L; +} diff --git a/src/main/java/com/google/common/collect/AbstractMapBasedMultimap.java b/src/main/java/com/google/common/collect/AbstractMapBasedMultimap.java new file mode 100644 index 0000000..889a7c1 --- /dev/null +++ b/src/main/java/com/google/common/collect/AbstractMapBasedMultimap.java @@ -0,0 +1,1637 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.collect.CollectPreconditions.checkRemove; + +import com.google.common.annotations.GwtCompatible; +import com.google.common.collect.Maps.ViewCachingAbstractMap; + +import java.io.Serializable; +import java.util.AbstractCollection; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.ConcurrentModificationException; +import java.util.Iterator; +import java.util.List; +import java.util.ListIterator; +import java.util.Map; +import java.util.Map.Entry; +import java.util.NavigableMap; +import java.util.NavigableSet; +import java.util.RandomAccess; +import java.util.Set; +import java.util.SortedMap; +import java.util.SortedSet; +import java.util.Spliterator; +import java.util.function.BiConsumer; + + + +/** + * Basic implementation of the {@link Multimap} interface. This class represents a multimap as a map + * that associates each key with a collection of values. All methods of {@link Multimap} are + * supported, including those specified as optional in the interface. + * + *

To implement a multimap, a subclass must define the method {@link #createCollection()}, which + * creates an empty collection of values for a key. + * + *

The multimap constructor takes a map that has a single entry for each distinct key. When you + * insert a key-value pair with a key that isn't already in the multimap, {@code + * AbstractMapBasedMultimap} calls {@link #createCollection()} to create the collection of values + * for that key. The subclass should not call {@link #createCollection()} directly, and a new + * instance should be created every time the method is called. + * + *

For example, the subclass could pass a {@link java.util.TreeMap} during construction, and + * {@link #createCollection()} could return a {@link java.util.TreeSet}, in which case the + * multimap's iterators would propagate through the keys and values in sorted order. + * + *

Keys and values may be null, as long as the underlying collection classes support null + * elements. + * + *

The collections created by {@link #createCollection()} may or may not allow duplicates. If the + * collection, such as a {@link Set}, does not support duplicates, an added key-value pair will + * replace an existing pair with the same key and value, if such a pair is present. With collections + * like {@link List} that allow duplicates, the collection will keep the existing key-value pairs + * while adding a new pair. + * + *

This class is not threadsafe when any concurrent operations update the multimap, even if the + * underlying map and {@link #createCollection()} method return threadsafe classes. Concurrent read + * operations will work correctly. To allow concurrent update operations, wrap your multimap with a + * call to {@link Multimaps#synchronizedMultimap}. + * + *

For serialization to work, the subclass must specify explicit {@code readObject} and {@code + * writeObject} methods. + * + * @author Jared Levy + * @author Louis Wasserman + */ +@GwtCompatible +abstract class AbstractMapBasedMultimap extends AbstractMultimap + implements Serializable { + /* + * Here's an outline of the overall design. + * + * The map variable contains the collection of values associated with each + * key. When a key-value pair is added to a multimap that didn't previously + * contain any values for that key, a new collection generated by + * createCollection is added to the map. That same collection instance + * remains in the map as long as the multimap has any values for the key. If + * all values for the key are removed, the key and collection are removed + * from the map. + * + * The get method returns a WrappedCollection, which decorates the collection + * in the map (if the key is present) or an empty collection (if the key is + * not present). When the collection delegate in the WrappedCollection is + * empty, the multimap may contain subsequently added values for that key. To + * handle that situation, the WrappedCollection checks whether map contains + * an entry for the provided key, and if so replaces the delegate. + */ + + private transient Map> map; + private transient int totalSize; + + /** + * Creates a new multimap that uses the provided map. + * + * @param map place to store the mapping from each key to its corresponding values + * @throws IllegalArgumentException if {@code map} is not empty + */ + protected AbstractMapBasedMultimap(Map> map) { + checkArgument(map.isEmpty()); + this.map = map; + } + + /** Used during deserialization only. */ + final void setMap(Map> map) { + this.map = map; + totalSize = 0; + for (Collection values : map.values()) { + checkArgument(!values.isEmpty()); + totalSize += values.size(); + } + } + + /** + * Creates an unmodifiable, empty collection of values. + * + *

This is used in {@link #removeAll} on an empty key. + */ + Collection createUnmodifiableEmptyCollection() { + return unmodifiableCollectionSubclass(createCollection()); + } + + /** + * Creates the collection of values for a single key. + * + *

Collections with weak, soft, or phantom references are not supported. Each call to {@code + * createCollection} should create a new instance. + * + *

The returned collection class determines whether duplicate key-value pairs are allowed. + * + * @return an empty collection of values + */ + abstract Collection createCollection(); + + /** + * Creates the collection of values for an explicitly provided key. By default, it simply calls + * {@link #createCollection()}, which is the correct behavior for most implementations. The {@link + * LinkedHashMultimap} class overrides it. + * + * @param key key to associate with values in the collection + * @return an empty collection of values + */ + Collection createCollection(K key) { + return createCollection(); + } + + Map> backingMap() { + return map; + } + + // Query Operations + + @Override + public int size() { + return totalSize; + } + + @Override + public boolean containsKey(Object key) { + return map.containsKey(key); + } + + // Modification Operations + + @Override + public boolean put(K key, V value) { + Collection collection = map.get(key); + if (collection == null) { + collection = createCollection(key); + if (collection.add(value)) { + totalSize++; + map.put(key, collection); + return true; + } else { + throw new AssertionError("New Collection violated the Collection spec"); + } + } else if (collection.add(value)) { + totalSize++; + return true; + } else { + return false; + } + } + + private Collection getOrCreateCollection(K key) { + Collection collection = map.get(key); + if (collection == null) { + collection = createCollection(key); + map.put(key, collection); + } + return collection; + } + + // Bulk Operations + + /** + * {@inheritDoc} + * + *

The returned collection is immutable. + */ + @Override + public Collection replaceValues(K key, Iterable values) { + Iterator iterator = values.iterator(); + if (!iterator.hasNext()) { + return removeAll(key); + } + + // TODO(lowasser): investigate atomic failure? + Collection collection = getOrCreateCollection(key); + Collection oldValues = createCollection(); + oldValues.addAll(collection); + + totalSize -= collection.size(); + collection.clear(); + + while (iterator.hasNext()) { + if (collection.add(iterator.next())) { + totalSize++; + } + } + + return unmodifiableCollectionSubclass(oldValues); + } + + /** + * {@inheritDoc} + * + *

The returned collection is immutable. + */ + @Override + public Collection removeAll(Object key) { + Collection collection = map.remove(key); + + if (collection == null) { + return createUnmodifiableEmptyCollection(); + } + + Collection output = createCollection(); + output.addAll(collection); + totalSize -= collection.size(); + collection.clear(); + + return unmodifiableCollectionSubclass(output); + } + + Collection unmodifiableCollectionSubclass(Collection collection) { + return Collections.unmodifiableCollection(collection); + } + + @Override + public void clear() { + // Clear each collection, to make previously returned collections empty. + for (Collection collection : map.values()) { + collection.clear(); + } + map.clear(); + totalSize = 0; + } + + // Views + + /** + * {@inheritDoc} + * + *

The returned collection is not serializable. + */ + @Override + public Collection get(K key) { + Collection collection = map.get(key); + if (collection == null) { + collection = createCollection(key); + } + return wrapCollection(key, collection); + } + + /** + * Generates a decorated collection that remains consistent with the values in the multimap for + * the provided key. Changes to the multimap may alter the returned collection, and vice versa. + */ + Collection wrapCollection(K key, Collection collection) { + return new WrappedCollection(key, collection, null); + } + + final List wrapList(K key, List list, WrappedCollection ancestor) { + return (list instanceof RandomAccess) + ? new RandomAccessWrappedList(key, list, ancestor) + : new WrappedList(key, list, ancestor); + } + + /** + * Collection decorator that stays in sync with the multimap values for a key. There are two kinds + * of wrapped collections: full and subcollections. Both have a delegate pointing to the + * underlying collection class. + * + *

Full collections, identified by a null ancestor field, contain all multimap values for a + * given key. Its delegate is a value in {@link AbstractMapBasedMultimap#map} whenever the + * delegate is non-empty. The {@code refreshIfEmpty}, {@code removeIfEmpty}, and {@code addToMap} + * methods ensure that the {@code WrappedCollection} and map remain consistent. + * + *

A subcollection, such as a sublist, contains some of the values for a given key. Its + * ancestor field points to the full wrapped collection with all values for the key. The + * subcollection {@code refreshIfEmpty}, {@code removeIfEmpty}, and {@code addToMap} methods call + * the corresponding methods of the full wrapped collection. + */ + + class WrappedCollection extends AbstractCollection { + final K key; + Collection delegate; + final WrappedCollection ancestor; + final Collection ancestorDelegate; + + WrappedCollection( + K key, Collection delegate, WrappedCollection ancestor) { + this.key = key; + this.delegate = delegate; + this.ancestor = ancestor; + this.ancestorDelegate = (ancestor == null) ? null : ancestor.getDelegate(); + } + + /** + * If the delegate collection is empty, but the multimap has values for the key, replace the + * delegate with the new collection for the key. + * + *

For a subcollection, refresh its ancestor and validate that the ancestor delegate hasn't + * changed. + */ + void refreshIfEmpty() { + if (ancestor != null) { + ancestor.refreshIfEmpty(); + if (ancestor.getDelegate() != ancestorDelegate) { + throw new ConcurrentModificationException(); + } + } else if (delegate.isEmpty()) { + Collection newDelegate = map.get(key); + if (newDelegate != null) { + delegate = newDelegate; + } + } + } + + /** + * If collection is empty, remove it from {@code AbstractMapBasedMultimap.this.map}. For + * subcollections, check whether the ancestor collection is empty. + */ + void removeIfEmpty() { + if (ancestor != null) { + ancestor.removeIfEmpty(); + } else if (delegate.isEmpty()) { + map.remove(key); + } + } + + K getKey() { + return key; + } + + /** + * Add the delegate to the map. Other {@code WrappedCollection} methods should call this method + * after adding elements to a previously empty collection. + * + *

Subcollection add the ancestor's delegate instead. + */ + void addToMap() { + if (ancestor != null) { + ancestor.addToMap(); + } else { + map.put(key, delegate); + } + } + + @Override + public int size() { + refreshIfEmpty(); + return delegate.size(); + } + + @Override + public boolean equals(Object object) { + if (object == this) { + return true; + } + refreshIfEmpty(); + return delegate.equals(object); + } + + @Override + public int hashCode() { + refreshIfEmpty(); + return delegate.hashCode(); + } + + @Override + public String toString() { + refreshIfEmpty(); + return delegate.toString(); + } + + Collection getDelegate() { + return delegate; + } + + @Override + public Iterator iterator() { + refreshIfEmpty(); + return new WrappedIterator(); + } + + @Override + public Spliterator spliterator() { + refreshIfEmpty(); + return delegate.spliterator(); + } + + /** Collection iterator for {@code WrappedCollection}. */ + class WrappedIterator implements Iterator { + final Iterator delegateIterator; + final Collection originalDelegate = delegate; + + WrappedIterator() { + delegateIterator = iteratorOrListIterator(delegate); + } + + WrappedIterator(Iterator delegateIterator) { + this.delegateIterator = delegateIterator; + } + + /** + * If the delegate changed since the iterator was created, the iterator is no longer valid. + */ + void validateIterator() { + refreshIfEmpty(); + if (delegate != originalDelegate) { + throw new ConcurrentModificationException(); + } + } + + @Override + public boolean hasNext() { + validateIterator(); + return delegateIterator.hasNext(); + } + + @Override + public V next() { + validateIterator(); + return delegateIterator.next(); + } + + @Override + public void remove() { + delegateIterator.remove(); + totalSize--; + removeIfEmpty(); + } + + Iterator getDelegateIterator() { + validateIterator(); + return delegateIterator; + } + } + + @Override + public boolean add(V value) { + refreshIfEmpty(); + boolean wasEmpty = delegate.isEmpty(); + boolean changed = delegate.add(value); + if (changed) { + totalSize++; + if (wasEmpty) { + addToMap(); + } + } + return changed; + } + + WrappedCollection getAncestor() { + return ancestor; + } + + // The following methods are provided for better performance. + + @Override + public boolean addAll(Collection collection) { + if (collection.isEmpty()) { + return false; + } + int oldSize = size(); // calls refreshIfEmpty + boolean changed = delegate.addAll(collection); + if (changed) { + int newSize = delegate.size(); + totalSize += (newSize - oldSize); + if (oldSize == 0) { + addToMap(); + } + } + return changed; + } + + @Override + public boolean contains(Object o) { + refreshIfEmpty(); + return delegate.contains(o); + } + + @Override + public boolean containsAll(Collection c) { + refreshIfEmpty(); + return delegate.containsAll(c); + } + + @Override + public void clear() { + int oldSize = size(); // calls refreshIfEmpty + if (oldSize == 0) { + return; + } + delegate.clear(); + totalSize -= oldSize; + removeIfEmpty(); // maybe shouldn't be removed if this is a sublist + } + + @Override + public boolean remove(Object o) { + refreshIfEmpty(); + boolean changed = delegate.remove(o); + if (changed) { + totalSize--; + removeIfEmpty(); + } + return changed; + } + + @Override + public boolean removeAll(Collection c) { + if (c.isEmpty()) { + return false; + } + int oldSize = size(); // calls refreshIfEmpty + boolean changed = delegate.removeAll(c); + if (changed) { + int newSize = delegate.size(); + totalSize += (newSize - oldSize); + removeIfEmpty(); + } + return changed; + } + + @Override + public boolean retainAll(Collection c) { + checkNotNull(c); + int oldSize = size(); // calls refreshIfEmpty + boolean changed = delegate.retainAll(c); + if (changed) { + int newSize = delegate.size(); + totalSize += (newSize - oldSize); + removeIfEmpty(); + } + return changed; + } + } + + private static Iterator iteratorOrListIterator(Collection collection) { + return (collection instanceof List) + ? ((List) collection).listIterator() + : collection.iterator(); + } + + /** Set decorator that stays in sync with the multimap values for a key. */ + + class WrappedSet extends WrappedCollection implements Set { + WrappedSet(K key, Set delegate) { + super(key, delegate, null); + } + + @Override + public boolean removeAll(Collection c) { + if (c.isEmpty()) { + return false; + } + int oldSize = size(); // calls refreshIfEmpty + + // Guava issue 1013: AbstractSet and most JDK set implementations are + // susceptible to quadratic removeAll performance on lists; + // use a slightly smarter implementation here + boolean changed = Sets.removeAllImpl((Set) delegate, c); + if (changed) { + int newSize = delegate.size(); + totalSize += (newSize - oldSize); + removeIfEmpty(); + } + return changed; + } + } + + /** SortedSet decorator that stays in sync with the multimap values for a key. */ + + class WrappedSortedSet extends WrappedCollection implements SortedSet { + WrappedSortedSet(K key, SortedSet delegate, WrappedCollection ancestor) { + super(key, delegate, ancestor); + } + + SortedSet getSortedSetDelegate() { + return (SortedSet) getDelegate(); + } + + @Override + public Comparator comparator() { + return getSortedSetDelegate().comparator(); + } + + @Override + public V first() { + refreshIfEmpty(); + return getSortedSetDelegate().first(); + } + + @Override + public V last() { + refreshIfEmpty(); + return getSortedSetDelegate().last(); + } + + @Override + public SortedSet headSet(V toElement) { + refreshIfEmpty(); + return new WrappedSortedSet( + getKey(), + getSortedSetDelegate().headSet(toElement), + (getAncestor() == null) ? this : getAncestor()); + } + + @Override + public SortedSet subSet(V fromElement, V toElement) { + refreshIfEmpty(); + return new WrappedSortedSet( + getKey(), + getSortedSetDelegate().subSet(fromElement, toElement), + (getAncestor() == null) ? this : getAncestor()); + } + + @Override + public SortedSet tailSet(V fromElement) { + refreshIfEmpty(); + return new WrappedSortedSet( + getKey(), + getSortedSetDelegate().tailSet(fromElement), + (getAncestor() == null) ? this : getAncestor()); + } + } + + + class WrappedNavigableSet extends WrappedSortedSet implements NavigableSet { + WrappedNavigableSet( + K key, NavigableSet delegate, WrappedCollection ancestor) { + super(key, delegate, ancestor); + } + + @Override + NavigableSet getSortedSetDelegate() { + return (NavigableSet) super.getSortedSetDelegate(); + } + + @Override + public V lower(V v) { + return getSortedSetDelegate().lower(v); + } + + @Override + public V floor(V v) { + return getSortedSetDelegate().floor(v); + } + + @Override + public V ceiling(V v) { + return getSortedSetDelegate().ceiling(v); + } + + @Override + public V higher(V v) { + return getSortedSetDelegate().higher(v); + } + + @Override + public V pollFirst() { + return Iterators.pollNext(iterator()); + } + + @Override + public V pollLast() { + return Iterators.pollNext(descendingIterator()); + } + + private NavigableSet wrap(NavigableSet wrapped) { + return new WrappedNavigableSet(key, wrapped, (getAncestor() == null) ? this : getAncestor()); + } + + @Override + public NavigableSet descendingSet() { + return wrap(getSortedSetDelegate().descendingSet()); + } + + @Override + public Iterator descendingIterator() { + return new WrappedIterator(getSortedSetDelegate().descendingIterator()); + } + + @Override + public NavigableSet subSet( + V fromElement, boolean fromInclusive, V toElement, boolean toInclusive) { + return wrap( + getSortedSetDelegate().subSet(fromElement, fromInclusive, toElement, toInclusive)); + } + + @Override + public NavigableSet headSet(V toElement, boolean inclusive) { + return wrap(getSortedSetDelegate().headSet(toElement, inclusive)); + } + + @Override + public NavigableSet tailSet(V fromElement, boolean inclusive) { + return wrap(getSortedSetDelegate().tailSet(fromElement, inclusive)); + } + } + + /** List decorator that stays in sync with the multimap values for a key. */ + + class WrappedList extends WrappedCollection implements List { + WrappedList(K key, List delegate, WrappedCollection ancestor) { + super(key, delegate, ancestor); + } + + List getListDelegate() { + return (List) getDelegate(); + } + + @Override + public boolean addAll(int index, Collection c) { + if (c.isEmpty()) { + return false; + } + int oldSize = size(); // calls refreshIfEmpty + boolean changed = getListDelegate().addAll(index, c); + if (changed) { + int newSize = getDelegate().size(); + totalSize += (newSize - oldSize); + if (oldSize == 0) { + addToMap(); + } + } + return changed; + } + + @Override + public V get(int index) { + refreshIfEmpty(); + return getListDelegate().get(index); + } + + @Override + public V set(int index, V element) { + refreshIfEmpty(); + return getListDelegate().set(index, element); + } + + @Override + public void add(int index, V element) { + refreshIfEmpty(); + boolean wasEmpty = getDelegate().isEmpty(); + getListDelegate().add(index, element); + totalSize++; + if (wasEmpty) { + addToMap(); + } + } + + @Override + public V remove(int index) { + refreshIfEmpty(); + V value = getListDelegate().remove(index); + totalSize--; + removeIfEmpty(); + return value; + } + + @Override + public int indexOf(Object o) { + refreshIfEmpty(); + return getListDelegate().indexOf(o); + } + + @Override + public int lastIndexOf(Object o) { + refreshIfEmpty(); + return getListDelegate().lastIndexOf(o); + } + + @Override + public ListIterator listIterator() { + refreshIfEmpty(); + return new WrappedListIterator(); + } + + @Override + public ListIterator listIterator(int index) { + refreshIfEmpty(); + return new WrappedListIterator(index); + } + + @Override + public List subList(int fromIndex, int toIndex) { + refreshIfEmpty(); + return wrapList( + getKey(), + getListDelegate().subList(fromIndex, toIndex), + (getAncestor() == null) ? this : getAncestor()); + } + + /** ListIterator decorator. */ + private class WrappedListIterator extends WrappedIterator implements ListIterator { + WrappedListIterator() {} + + public WrappedListIterator(int index) { + super(getListDelegate().listIterator(index)); + } + + private ListIterator getDelegateListIterator() { + return (ListIterator) getDelegateIterator(); + } + + @Override + public boolean hasPrevious() { + return getDelegateListIterator().hasPrevious(); + } + + @Override + public V previous() { + return getDelegateListIterator().previous(); + } + + @Override + public int nextIndex() { + return getDelegateListIterator().nextIndex(); + } + + @Override + public int previousIndex() { + return getDelegateListIterator().previousIndex(); + } + + @Override + public void set(V value) { + getDelegateListIterator().set(value); + } + + @Override + public void add(V value) { + boolean wasEmpty = isEmpty(); + getDelegateListIterator().add(value); + totalSize++; + if (wasEmpty) { + addToMap(); + } + } + } + } + + /** + * List decorator that stays in sync with the multimap values for a key and supports rapid random + * access. + */ + private class RandomAccessWrappedList extends WrappedList implements RandomAccess { + RandomAccessWrappedList( + K key, List delegate, WrappedCollection ancestor) { + super(key, delegate, ancestor); + } + } + + @Override + Set createKeySet() { + return new KeySet(map); + } + + final Set createMaybeNavigableKeySet() { + if (map instanceof NavigableMap) { + return new NavigableKeySet((NavigableMap>) map); + } else if (map instanceof SortedMap) { + return new SortedKeySet((SortedMap>) map); + } else { + return new KeySet(map); + } + } + + + private class KeySet extends Maps.KeySet> { + KeySet(final Map> subMap) { + super(subMap); + } + + @Override + public Iterator iterator() { + final Iterator>> entryIterator = map().entrySet().iterator(); + return new Iterator() { + Entry> entry; + + @Override + public boolean hasNext() { + return entryIterator.hasNext(); + } + + @Override + public K next() { + entry = entryIterator.next(); + return entry.getKey(); + } + + @Override + public void remove() { + checkRemove(entry != null); + Collection collection = entry.getValue(); + entryIterator.remove(); + totalSize -= collection.size(); + collection.clear(); + entry = null; + } + }; + } + + // The following methods are included for better performance. + + @Override + public Spliterator spliterator() { + return map().keySet().spliterator(); + } + + @Override + public boolean remove(Object key) { + int count = 0; + Collection collection = map().remove(key); + if (collection != null) { + count = collection.size(); + collection.clear(); + totalSize -= count; + } + return count > 0; + } + + @Override + public void clear() { + Iterators.clear(iterator()); + } + + @Override + public boolean containsAll(Collection c) { + return map().keySet().containsAll(c); + } + + @Override + public boolean equals(Object object) { + return this == object || this.map().keySet().equals(object); + } + + @Override + public int hashCode() { + return map().keySet().hashCode(); + } + } + + + private class SortedKeySet extends KeySet implements SortedSet { + + SortedKeySet(SortedMap> subMap) { + super(subMap); + } + + SortedMap> sortedMap() { + return (SortedMap>) super.map(); + } + + @Override + public Comparator comparator() { + return sortedMap().comparator(); + } + + @Override + public K first() { + return sortedMap().firstKey(); + } + + @Override + public SortedSet headSet(K toElement) { + return new SortedKeySet(sortedMap().headMap(toElement)); + } + + @Override + public K last() { + return sortedMap().lastKey(); + } + + @Override + public SortedSet subSet(K fromElement, K toElement) { + return new SortedKeySet(sortedMap().subMap(fromElement, toElement)); + } + + @Override + public SortedSet tailSet(K fromElement) { + return new SortedKeySet(sortedMap().tailMap(fromElement)); + } + } + + + class NavigableKeySet extends SortedKeySet implements NavigableSet { + NavigableKeySet(NavigableMap> subMap) { + super(subMap); + } + + @Override + NavigableMap> sortedMap() { + return (NavigableMap>) super.sortedMap(); + } + + @Override + public K lower(K k) { + return sortedMap().lowerKey(k); + } + + @Override + public K floor(K k) { + return sortedMap().floorKey(k); + } + + @Override + public K ceiling(K k) { + return sortedMap().ceilingKey(k); + } + + @Override + public K higher(K k) { + return sortedMap().higherKey(k); + } + + @Override + public K pollFirst() { + return Iterators.pollNext(iterator()); + } + + @Override + public K pollLast() { + return Iterators.pollNext(descendingIterator()); + } + + @Override + public NavigableSet descendingSet() { + return new NavigableKeySet(sortedMap().descendingMap()); + } + + @Override + public Iterator descendingIterator() { + return descendingSet().iterator(); + } + + @Override + public NavigableSet headSet(K toElement) { + return headSet(toElement, false); + } + + @Override + public NavigableSet headSet(K toElement, boolean inclusive) { + return new NavigableKeySet(sortedMap().headMap(toElement, inclusive)); + } + + @Override + public NavigableSet subSet(K fromElement, K toElement) { + return subSet(fromElement, true, toElement, false); + } + + @Override + public NavigableSet subSet( + K fromElement, boolean fromInclusive, K toElement, boolean toInclusive) { + return new NavigableKeySet( + sortedMap().subMap(fromElement, fromInclusive, toElement, toInclusive)); + } + + @Override + public NavigableSet tailSet(K fromElement) { + return tailSet(fromElement, true); + } + + @Override + public NavigableSet tailSet(K fromElement, boolean inclusive) { + return new NavigableKeySet(sortedMap().tailMap(fromElement, inclusive)); + } + } + + /** Removes all values for the provided key. */ + private void removeValuesForKey(Object key) { + Collection collection = Maps.safeRemove(map, key); + + if (collection != null) { + int count = collection.size(); + collection.clear(); + totalSize -= count; + } + } + + private abstract class Itr implements Iterator { + final Iterator>> keyIterator; + K key; + Collection collection; + Iterator valueIterator; + + Itr() { + keyIterator = map.entrySet().iterator(); + key = null; + collection = null; + valueIterator = Iterators.emptyModifiableIterator(); + } + + abstract T output(K key, V value); + + @Override + public boolean hasNext() { + return keyIterator.hasNext() || valueIterator.hasNext(); + } + + @Override + public T next() { + if (!valueIterator.hasNext()) { + Entry> mapEntry = keyIterator.next(); + key = mapEntry.getKey(); + collection = mapEntry.getValue(); + valueIterator = collection.iterator(); + } + return output(key, valueIterator.next()); + } + + @Override + public void remove() { + valueIterator.remove(); + if (collection.isEmpty()) { + keyIterator.remove(); + } + totalSize--; + } + } + + /** + * {@inheritDoc} + * + *

The iterator generated by the returned collection traverses the values for one key, followed + * by the values of a second key, and so on. + */ + @Override + public Collection values() { + return super.values(); + } + + @Override + Collection createValues() { + return new Values(); + } + + @Override + Iterator valueIterator() { + return new Itr() { + @Override + V output(K key, V value) { + return value; + } + }; + } + + @Override + Spliterator valueSpliterator() { + return CollectSpliterators.flatMap( + map.values().spliterator(), Collection::spliterator, Spliterator.SIZED, size()); + } + + /* + * TODO(kevinb): should we copy this javadoc to each concrete class, so that + * classes like LinkedHashMultimap that need to say something different are + * still able to {@inheritDoc} all the way from Multimap? + */ + + @Override + Multiset createKeys() { + return new Multimaps.Keys(this); + } + + /** + * {@inheritDoc} + * + *

The iterator generated by the returned collection traverses the values for one key, followed + * by the values of a second key, and so on. + * + *

Each entry is an immutable snapshot of a key-value mapping in the multimap, taken at the + * time the entry is returned by a method call to the collection or its iterator. + */ + @Override + public Collection> entries() { + return super.entries(); + } + + @Override + Collection> createEntries() { + if (this instanceof SetMultimap) { + return new EntrySet(); + } else { + return new Entries(); + } + } + + /** + * Returns an iterator across all key-value map entries, used by {@code entries().iterator()} and + * {@code values().iterator()}. The default behavior, which traverses the values for one key, the + * values for a second key, and so on, suffices for most {@code AbstractMapBasedMultimap} + * implementations. + * + * @return an iterator across map entries + */ + @Override + Iterator> entryIterator() { + return new Itr>() { + @Override + Entry output(K key, V value) { + return Maps.immutableEntry(key, value); + } + }; + } + + @Override + Spliterator> entrySpliterator() { + return CollectSpliterators.flatMap( + map.entrySet().spliterator(), + keyToValueCollectionEntry -> { + K key = keyToValueCollectionEntry.getKey(); + Collection valueCollection = keyToValueCollectionEntry.getValue(); + return CollectSpliterators.map( + valueCollection.spliterator(), (V value) -> Maps.immutableEntry(key, value)); + }, + Spliterator.SIZED, + size()); + } + + @Override + public void forEach(BiConsumer action) { + checkNotNull(action); + map.forEach( + (key, valueCollection) -> valueCollection.forEach(value -> action.accept(key, value))); + } + + @Override + Map> createAsMap() { + return new AsMap(map); + } + + final Map> createMaybeNavigableAsMap() { + if (map instanceof NavigableMap) { + return new NavigableAsMap((NavigableMap>) map); + } else if (map instanceof SortedMap) { + return new SortedAsMap((SortedMap>) map); + } else { + return new AsMap(map); + } + } + + + private class AsMap extends ViewCachingAbstractMap> { + /** + * Usually the same as map, but smaller for the headMap(), tailMap(), or subMap() of a + * SortedAsMap. + */ + final transient Map> submap; + + AsMap(Map> submap) { + this.submap = submap; + } + + @Override + protected Set>> createEntrySet() { + return new AsMapEntries(); + } + + // The following methods are included for performance. + + @Override + public boolean containsKey(Object key) { + return Maps.safeContainsKey(submap, key); + } + + @Override + public Collection get(Object key) { + Collection collection = Maps.safeGet(submap, key); + if (collection == null) { + return null; + } + @SuppressWarnings("unchecked") + K k = (K) key; + return wrapCollection(k, collection); + } + + @Override + public Set keySet() { + return AbstractMapBasedMultimap.this.keySet(); + } + + @Override + public int size() { + return submap.size(); + } + + @Override + public Collection remove(Object key) { + Collection collection = submap.remove(key); + if (collection == null) { + return null; + } + + Collection output = createCollection(); + output.addAll(collection); + totalSize -= collection.size(); + collection.clear(); + return output; + } + + @Override + public boolean equals(Object object) { + return this == object || submap.equals(object); + } + + @Override + public int hashCode() { + return submap.hashCode(); + } + + @Override + public String toString() { + return submap.toString(); + } + + @Override + public void clear() { + if (submap == map) { + AbstractMapBasedMultimap.this.clear(); + } else { + Iterators.clear(new AsMapIterator()); + } + } + + Entry> wrapEntry(Entry> entry) { + K key = entry.getKey(); + return Maps.immutableEntry(key, wrapCollection(key, entry.getValue())); + } + + + class AsMapEntries extends Maps.EntrySet> { + @Override + Map> map() { + return AsMap.this; + } + + @Override + public Iterator>> iterator() { + return new AsMapIterator(); + } + + @Override + public Spliterator>> spliterator() { + return CollectSpliterators.map(submap.entrySet().spliterator(), AsMap.this::wrapEntry); + } + + // The following methods are included for performance. + + @Override + public boolean contains(Object o) { + return Collections2.safeContains(submap.entrySet(), o); + } + + @Override + public boolean remove(Object o) { + if (!contains(o)) { + return false; + } + Entry entry = (Entry) o; + removeValuesForKey(entry.getKey()); + return true; + } + } + + /** Iterator across all keys and value collections. */ + class AsMapIterator implements Iterator>> { + final Iterator>> delegateIterator = submap.entrySet().iterator(); + Collection collection; + + @Override + public boolean hasNext() { + return delegateIterator.hasNext(); + } + + @Override + public Entry> next() { + Entry> entry = delegateIterator.next(); + collection = entry.getValue(); + return wrapEntry(entry); + } + + @Override + public void remove() { + checkRemove(collection != null); + delegateIterator.remove(); + totalSize -= collection.size(); + collection.clear(); + collection = null; + } + } + } + + + private class SortedAsMap extends AsMap implements SortedMap> { + SortedAsMap(SortedMap> submap) { + super(submap); + } + + SortedMap> sortedMap() { + return (SortedMap>) submap; + } + + @Override + public Comparator comparator() { + return sortedMap().comparator(); + } + + @Override + public K firstKey() { + return sortedMap().firstKey(); + } + + @Override + public K lastKey() { + return sortedMap().lastKey(); + } + + @Override + public SortedMap> headMap(K toKey) { + return new SortedAsMap(sortedMap().headMap(toKey)); + } + + @Override + public SortedMap> subMap(K fromKey, K toKey) { + return new SortedAsMap(sortedMap().subMap(fromKey, toKey)); + } + + @Override + public SortedMap> tailMap(K fromKey) { + return new SortedAsMap(sortedMap().tailMap(fromKey)); + } + + SortedSet sortedKeySet; + + // returns a SortedSet, even though returning a Set would be sufficient to + // satisfy the SortedMap.keySet() interface + @Override + public SortedSet keySet() { + SortedSet result = sortedKeySet; + return (result == null) ? sortedKeySet = createKeySet() : result; + } + + @Override + SortedSet createKeySet() { + return new SortedKeySet(sortedMap()); + } + } + + class NavigableAsMap extends SortedAsMap implements NavigableMap> { + + NavigableAsMap(NavigableMap> submap) { + super(submap); + } + + @Override + NavigableMap> sortedMap() { + return (NavigableMap>) super.sortedMap(); + } + + @Override + public Entry> lowerEntry(K key) { + Entry> entry = sortedMap().lowerEntry(key); + return (entry == null) ? null : wrapEntry(entry); + } + + @Override + public K lowerKey(K key) { + return sortedMap().lowerKey(key); + } + + @Override + public Entry> floorEntry(K key) { + Entry> entry = sortedMap().floorEntry(key); + return (entry == null) ? null : wrapEntry(entry); + } + + @Override + public K floorKey(K key) { + return sortedMap().floorKey(key); + } + + @Override + public Entry> ceilingEntry(K key) { + Entry> entry = sortedMap().ceilingEntry(key); + return (entry == null) ? null : wrapEntry(entry); + } + + @Override + public K ceilingKey(K key) { + return sortedMap().ceilingKey(key); + } + + @Override + public Entry> higherEntry(K key) { + Entry> entry = sortedMap().higherEntry(key); + return (entry == null) ? null : wrapEntry(entry); + } + + @Override + public K higherKey(K key) { + return sortedMap().higherKey(key); + } + + @Override + public Entry> firstEntry() { + Entry> entry = sortedMap().firstEntry(); + return (entry == null) ? null : wrapEntry(entry); + } + + @Override + public Entry> lastEntry() { + Entry> entry = sortedMap().lastEntry(); + return (entry == null) ? null : wrapEntry(entry); + } + + @Override + public Entry> pollFirstEntry() { + return pollAsMapEntry(entrySet().iterator()); + } + + @Override + public Entry> pollLastEntry() { + return pollAsMapEntry(descendingMap().entrySet().iterator()); + } + + Entry> pollAsMapEntry(Iterator>> entryIterator) { + if (!entryIterator.hasNext()) { + return null; + } + Entry> entry = entryIterator.next(); + Collection output = createCollection(); + output.addAll(entry.getValue()); + entryIterator.remove(); + return Maps.immutableEntry(entry.getKey(), unmodifiableCollectionSubclass(output)); + } + + @Override + public NavigableMap> descendingMap() { + return new NavigableAsMap(sortedMap().descendingMap()); + } + + @Override + public NavigableSet keySet() { + return (NavigableSet) super.keySet(); + } + + @Override + NavigableSet createKeySet() { + return new NavigableKeySet(sortedMap()); + } + + @Override + public NavigableSet navigableKeySet() { + return keySet(); + } + + @Override + public NavigableSet descendingKeySet() { + return descendingMap().navigableKeySet(); + } + + @Override + public NavigableMap> subMap(K fromKey, K toKey) { + return subMap(fromKey, true, toKey, false); + } + + @Override + public NavigableMap> subMap( + K fromKey, boolean fromInclusive, K toKey, boolean toInclusive) { + return new NavigableAsMap(sortedMap().subMap(fromKey, fromInclusive, toKey, toInclusive)); + } + + @Override + public NavigableMap> headMap(K toKey) { + return headMap(toKey, false); + } + + @Override + public NavigableMap> headMap(K toKey, boolean inclusive) { + return new NavigableAsMap(sortedMap().headMap(toKey, inclusive)); + } + + @Override + public NavigableMap> tailMap(K fromKey) { + return tailMap(fromKey, true); + } + + @Override + public NavigableMap> tailMap(K fromKey, boolean inclusive) { + return new NavigableAsMap(sortedMap().tailMap(fromKey, inclusive)); + } + } + + private static final long serialVersionUID = 2447537837011683357L; +} diff --git a/src/main/java/com/google/common/collect/AbstractMapBasedMultiset.java b/src/main/java/com/google/common/collect/AbstractMapBasedMultiset.java new file mode 100644 index 0000000..b2a3968 --- /dev/null +++ b/src/main/java/com/google/common/collect/AbstractMapBasedMultiset.java @@ -0,0 +1,338 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.collect.CollectPreconditions.checkNonnegative; +import static com.google.common.collect.CollectPreconditions.checkRemove; + +import com.google.common.annotations.GwtCompatible; +import com.google.common.annotations.GwtIncompatible; +import com.google.common.primitives.Ints; + +import java.io.InvalidObjectException; +import java.io.ObjectStreamException; +import java.io.Serializable; +import java.util.ConcurrentModificationException; +import java.util.Iterator; +import java.util.Map; +import java.util.Set; +import java.util.function.ObjIntConsumer; + + + +/** + * Basic implementation of {@code Multiset} backed by an instance of {@code Map}. + * + *

For serialization to work, the subclass must specify explicit {@code readObject} and {@code + * writeObject} methods. + * + * @author Kevin Bourrillion + */ +@GwtCompatible(emulated = true) +abstract class AbstractMapBasedMultiset extends AbstractMultiset implements Serializable { + // TODO(lowasser): consider overhauling this back to Map + private transient Map backingMap; + + /* + * Cache the size for efficiency. Using a long lets us avoid the need for + * overflow checking and ensures that size() will function correctly even if + * the multiset had once been larger than Integer.MAX_VALUE. + */ + private transient long size; + + /** Standard constructor. */ + protected AbstractMapBasedMultiset(Map backingMap) { + checkArgument(backingMap.isEmpty()); + this.backingMap = backingMap; + } + + /** Used during deserialization only. The backing map must be empty. */ + void setBackingMap(Map backingMap) { + this.backingMap = backingMap; + } + + // Required Implementations + + /** + * {@inheritDoc} + * + *

Invoking {@link Multiset.Entry#getCount} on an entry in the returned set always returns the + * current count of that element in the multiset, as opposed to the count at the time the entry + * was retrieved. + */ + @Override + public Set> entrySet() { + return super.entrySet(); + } + + @Override + Iterator elementIterator() { + final Iterator> backingEntries = backingMap.entrySet().iterator(); + return new Iterator() { + Map.Entry toRemove; + + @Override + public boolean hasNext() { + return backingEntries.hasNext(); + } + + @Override + public E next() { + final Map.Entry mapEntry = backingEntries.next(); + toRemove = mapEntry; + return mapEntry.getKey(); + } + + @Override + public void remove() { + checkRemove(toRemove != null); + size -= toRemove.getValue().getAndSet(0); + backingEntries.remove(); + toRemove = null; + } + }; + } + + @Override + Iterator> entryIterator() { + final Iterator> backingEntries = backingMap.entrySet().iterator(); + return new Iterator>() { + Map.Entry toRemove; + + @Override + public boolean hasNext() { + return backingEntries.hasNext(); + } + + @Override + public Multiset.Entry next() { + final Map.Entry mapEntry = backingEntries.next(); + toRemove = mapEntry; + return new Multisets.AbstractEntry() { + @Override + public E getElement() { + return mapEntry.getKey(); + } + + @Override + public int getCount() { + Count count = mapEntry.getValue(); + if (count == null || count.get() == 0) { + Count frequency = backingMap.get(getElement()); + if (frequency != null) { + return frequency.get(); + } + } + return (count == null) ? 0 : count.get(); + } + }; + } + + @Override + public void remove() { + checkRemove(toRemove != null); + size -= toRemove.getValue().getAndSet(0); + backingEntries.remove(); + toRemove = null; + } + }; + } + + @Override + public void forEachEntry(ObjIntConsumer action) { + checkNotNull(action); + backingMap.forEach((element, count) -> action.accept(element, count.get())); + } + + @Override + public void clear() { + for (Count frequency : backingMap.values()) { + frequency.set(0); + } + backingMap.clear(); + size = 0L; + } + + @Override + int distinctElements() { + return backingMap.size(); + } + + // Optimizations - Query Operations + + @Override + public int size() { + return Ints.saturatedCast(size); + } + + @Override + public Iterator iterator() { + return new MapBasedMultisetIterator(); + } + + /* + * Not subclassing AbstractMultiset$MultisetIterator because next() needs to + * retrieve the Map.Entry entry, which can then be used for + * a more efficient remove() call. + */ + private class MapBasedMultisetIterator implements Iterator { + final Iterator> entryIterator; + Map.Entry currentEntry; + int occurrencesLeft; + boolean canRemove; + + MapBasedMultisetIterator() { + this.entryIterator = backingMap.entrySet().iterator(); + } + + @Override + public boolean hasNext() { + return occurrencesLeft > 0 || entryIterator.hasNext(); + } + + @Override + public E next() { + if (occurrencesLeft == 0) { + currentEntry = entryIterator.next(); + occurrencesLeft = currentEntry.getValue().get(); + } + occurrencesLeft--; + canRemove = true; + return currentEntry.getKey(); + } + + @Override + public void remove() { + checkRemove(canRemove); + int frequency = currentEntry.getValue().get(); + if (frequency <= 0) { + throw new ConcurrentModificationException(); + } + if (currentEntry.getValue().addAndGet(-1) == 0) { + entryIterator.remove(); + } + size--; + canRemove = false; + } + } + + @Override + public int count(Object element) { + Count frequency = Maps.safeGet(backingMap, element); + return (frequency == null) ? 0 : frequency.get(); + } + + // Optional Operations - Modification Operations + + /** + * {@inheritDoc} + * + * @throws IllegalArgumentException if the call would result in more than {@link + * Integer#MAX_VALUE} occurrences of {@code element} in this multiset. + */ + + @Override + public int add(E element, int occurrences) { + if (occurrences == 0) { + return count(element); + } + checkArgument(occurrences > 0, "occurrences cannot be negative: %s", occurrences); + Count frequency = backingMap.get(element); + int oldCount; + if (frequency == null) { + oldCount = 0; + backingMap.put(element, new Count(occurrences)); + } else { + oldCount = frequency.get(); + long newCount = (long) oldCount + (long) occurrences; + checkArgument(newCount <= Integer.MAX_VALUE, "too many occurrences: %s", newCount); + frequency.add(occurrences); + } + size += occurrences; + return oldCount; + } + + + @Override + public int remove(Object element, int occurrences) { + if (occurrences == 0) { + return count(element); + } + checkArgument(occurrences > 0, "occurrences cannot be negative: %s", occurrences); + Count frequency = backingMap.get(element); + if (frequency == null) { + return 0; + } + + int oldCount = frequency.get(); + + int numberRemoved; + if (oldCount > occurrences) { + numberRemoved = occurrences; + } else { + numberRemoved = oldCount; + backingMap.remove(element); + } + + frequency.add(-numberRemoved); + size -= numberRemoved; + return oldCount; + } + + // Roughly a 33% performance improvement over AbstractMultiset.setCount(). + + @Override + public int setCount(E element, int count) { + checkNonnegative(count, "count"); + + Count existingCounter; + int oldCount; + if (count == 0) { + existingCounter = backingMap.remove(element); + oldCount = getAndSet(existingCounter, count); + } else { + existingCounter = backingMap.get(element); + oldCount = getAndSet(existingCounter, count); + + if (existingCounter == null) { + backingMap.put(element, new Count(count)); + } + } + + size += (count - oldCount); + return oldCount; + } + + private static int getAndSet(Count i, int count) { + if (i == null) { + return 0; + } + + return i.getAndSet(count); + } + + // Don't allow default serialization. + @GwtIncompatible // java.io.ObjectStreamException + private void readObjectNoData() throws ObjectStreamException { + throw new InvalidObjectException("Stream data required"); + } + + @GwtIncompatible // not needed in emulated source. + private static final long serialVersionUID = -2250766705698539974L; +} diff --git a/src/main/java/com/google/common/collect/AbstractMapEntry.java b/src/main/java/com/google/common/collect/AbstractMapEntry.java new file mode 100644 index 0000000..d3b55ed --- /dev/null +++ b/src/main/java/com/google/common/collect/AbstractMapEntry.java @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import com.google.common.annotations.GwtCompatible; +import com.google.common.base.Objects; +import java.util.Map.Entry; + + +/** + * Implementation of the {@code equals}, {@code hashCode}, and {@code toString} methods of {@code + * Entry}. + * + * @author Jared Levy + */ +@GwtCompatible +abstract class AbstractMapEntry implements Entry { + + @Override + public abstract K getKey(); + + @Override + public abstract V getValue(); + + @Override + public V setValue(V value) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean equals(Object object) { + if (object instanceof Entry) { + Entry that = (Entry) object; + return Objects.equal(this.getKey(), that.getKey()) + && Objects.equal(this.getValue(), that.getValue()); + } + return false; + } + + @Override + public int hashCode() { + K k = getKey(); + V v = getValue(); + return ((k == null) ? 0 : k.hashCode()) ^ ((v == null) ? 0 : v.hashCode()); + } + + /** Returns a string representation of the form {@code {key}={value}}. */ + @Override + public String toString() { + return getKey() + "=" + getValue(); + } +} diff --git a/src/main/java/com/google/common/collect/AbstractMultimap.java b/src/main/java/com/google/common/collect/AbstractMultimap.java new file mode 100644 index 0000000..983dc50 --- /dev/null +++ b/src/main/java/com/google/common/collect/AbstractMultimap.java @@ -0,0 +1,265 @@ +/* + * Copyright (C) 2012 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.annotations.GwtCompatible; + + +import java.util.AbstractCollection; +import java.util.Collection; +import java.util.Iterator; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; +import java.util.Spliterator; +import java.util.Spliterators; + + + +/** + * A skeleton {@code Multimap} implementation, not necessarily in terms of a {@code Map}. + * + * @author Louis Wasserman + */ +@GwtCompatible +abstract class AbstractMultimap implements Multimap { + @Override + public boolean isEmpty() { + return size() == 0; + } + + @Override + public boolean containsValue(Object value) { + for (Collection collection : asMap().values()) { + if (collection.contains(value)) { + return true; + } + } + + return false; + } + + @Override + public boolean containsEntry(Object key, Object value) { + Collection collection = asMap().get(key); + return collection != null && collection.contains(value); + } + + + @Override + public boolean remove(Object key, Object value) { + Collection collection = asMap().get(key); + return collection != null && collection.remove(value); + } + + + @Override + public boolean put(K key, V value) { + return get(key).add(value); + } + + + @Override + public boolean putAll(K key, Iterable values) { + checkNotNull(values); + // make sure we only call values.iterator() once + // and we only call get(key) if values is nonempty + if (values instanceof Collection) { + Collection valueCollection = (Collection) values; + return !valueCollection.isEmpty() && get(key).addAll(valueCollection); + } else { + Iterator valueItr = values.iterator(); + return valueItr.hasNext() && Iterators.addAll(get(key), valueItr); + } + } + + + @Override + public boolean putAll(Multimap multimap) { + boolean changed = false; + for (Entry entry : multimap.entries()) { + changed |= put(entry.getKey(), entry.getValue()); + } + return changed; + } + + + @Override + public Collection replaceValues(K key, Iterable values) { + checkNotNull(values); + Collection result = removeAll(key); + putAll(key, values); + return result; + } + + private transient Collection> entries; + + @Override + public Collection> entries() { + Collection> result = entries; + return (result == null) ? entries = createEntries() : result; + } + + abstract Collection> createEntries(); + + + class Entries extends Multimaps.Entries { + @Override + Multimap multimap() { + return AbstractMultimap.this; + } + + @Override + public Iterator> iterator() { + return entryIterator(); + } + + @Override + public Spliterator> spliterator() { + return entrySpliterator(); + } + } + + + class EntrySet extends Entries implements Set> { + @Override + public int hashCode() { + return Sets.hashCodeImpl(this); + } + + @Override + public boolean equals(Object obj) { + return Sets.equalsImpl(this, obj); + } + } + + abstract Iterator> entryIterator(); + + Spliterator> entrySpliterator() { + return Spliterators.spliterator( + entryIterator(), size(), (this instanceof SetMultimap) ? Spliterator.DISTINCT : 0); + } + + private transient Set keySet; + + @Override + public Set keySet() { + Set result = keySet; + return (result == null) ? keySet = createKeySet() : result; + } + + abstract Set createKeySet(); + + private transient Multiset keys; + + @Override + public Multiset keys() { + Multiset result = keys; + return (result == null) ? keys = createKeys() : result; + } + + abstract Multiset createKeys(); + + private transient Collection values; + + @Override + public Collection values() { + Collection result = values; + return (result == null) ? values = createValues() : result; + } + + abstract Collection createValues(); + + + class Values extends AbstractCollection { + @Override + public Iterator iterator() { + return valueIterator(); + } + + @Override + public Spliterator spliterator() { + return valueSpliterator(); + } + + @Override + public int size() { + return AbstractMultimap.this.size(); + } + + @Override + public boolean contains(Object o) { + return AbstractMultimap.this.containsValue(o); + } + + @Override + public void clear() { + AbstractMultimap.this.clear(); + } + } + + Iterator valueIterator() { + return Maps.valueIterator(entries().iterator()); + } + + Spliterator valueSpliterator() { + return Spliterators.spliterator(valueIterator(), size(), 0); + } + + private transient Map> asMap; + + @Override + public Map> asMap() { + Map> result = asMap; + return (result == null) ? asMap = createAsMap() : result; + } + + abstract Map> createAsMap(); + + // Comparison and hashing + + @Override + public boolean equals(Object object) { + return Multimaps.equalsImpl(this, object); + } + + /** + * Returns the hash code for this multimap. + * + *

The hash code of a multimap is defined as the hash code of the map view, as returned by + * {@link Multimap#asMap}. + * + * @see Map#hashCode + */ + @Override + public int hashCode() { + return asMap().hashCode(); + } + + /** + * Returns a string representation of the multimap, generated by calling {@code toString} on the + * map returned by {@link Multimap#asMap}. + * + * @return a string representation of the multimap + */ + @Override + public String toString() { + return asMap().toString(); + } +} diff --git a/src/main/java/com/google/common/collect/AbstractMultiset.java b/src/main/java/com/google/common/collect/AbstractMultiset.java new file mode 100644 index 0000000..8f5e780 --- /dev/null +++ b/src/main/java/com/google/common/collect/AbstractMultiset.java @@ -0,0 +1,232 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import static com.google.common.collect.Multisets.setCountImpl; + +import com.google.common.annotations.GwtCompatible; + + + +import java.util.AbstractCollection; +import java.util.Collection; +import java.util.Iterator; +import java.util.Set; + + + +/** + * This class provides a skeletal implementation of the {@link Multiset} interface. A new multiset + * implementation can be created easily by extending this class and implementing the {@link + * Multiset#entrySet()} method, plus optionally overriding {@link #add(Object, int)} and {@link + * #remove(Object, int)} to enable modifications to the multiset. + * + *

The {@link #count} and {@link #size} implementations all iterate across the set returned by + * {@link Multiset#entrySet()}, as do many methods acting on the set returned by {@link + * #elementSet()}. Override those methods for better performance. + * + * @author Kevin Bourrillion + * @author Louis Wasserman + */ +@GwtCompatible +abstract class AbstractMultiset extends AbstractCollection implements Multiset { + // Query Operations + + @Override + public boolean isEmpty() { + return entrySet().isEmpty(); + } + + @Override + public boolean contains(Object element) { + return count(element) > 0; + } + + // Modification Operations + + @Override + public final boolean add(E element) { + add(element, 1); + return true; + } + + + @Override + public int add(E element, int occurrences) { + throw new UnsupportedOperationException(); + } + + + @Override + public final boolean remove(Object element) { + return remove(element, 1) > 0; + } + + + @Override + public int remove(Object element, int occurrences) { + throw new UnsupportedOperationException(); + } + + + @Override + public int setCount(E element, int count) { + return setCountImpl(this, element, count); + } + + + @Override + public boolean setCount(E element, int oldCount, int newCount) { + return setCountImpl(this, element, oldCount, newCount); + } + + // Bulk Operations + + /** + * {@inheritDoc} + * + *

This implementation is highly efficient when {@code elementsToAdd} is itself a {@link + * Multiset}. + */ + + @Override + public final boolean addAll(Collection elementsToAdd) { + return Multisets.addAllImpl(this, elementsToAdd); + } + + + @Override + public final boolean removeAll(Collection elementsToRemove) { + return Multisets.removeAllImpl(this, elementsToRemove); + } + + + @Override + public final boolean retainAll(Collection elementsToRetain) { + return Multisets.retainAllImpl(this, elementsToRetain); + } + + @Override + public abstract void clear(); + + // Views + + private transient Set elementSet; + + @Override + public Set elementSet() { + Set result = elementSet; + if (result == null) { + elementSet = result = createElementSet(); + } + return result; + } + + /** + * Creates a new instance of this multiset's element set, which will be returned by {@link + * #elementSet()}. + */ + Set createElementSet() { + return new ElementSet(); + } + + + class ElementSet extends Multisets.ElementSet { + @Override + Multiset multiset() { + return AbstractMultiset.this; + } + + @Override + public Iterator iterator() { + return elementIterator(); + } + } + + abstract Iterator elementIterator(); + + private transient Set> entrySet; + + @Override + public Set> entrySet() { + Set> result = entrySet; + if (result == null) { + entrySet = result = createEntrySet(); + } + return result; + } + + + class EntrySet extends Multisets.EntrySet { + @Override + Multiset multiset() { + return AbstractMultiset.this; + } + + @Override + public Iterator> iterator() { + return entryIterator(); + } + + @Override + public int size() { + return distinctElements(); + } + } + + Set> createEntrySet() { + return new EntrySet(); + } + + abstract Iterator> entryIterator(); + + abstract int distinctElements(); + + // Object methods + + /** + * {@inheritDoc} + * + *

This implementation returns {@code true} if {@code object} is a multiset of the same size + * and if, for each element, the two multisets have the same count. + */ + @Override + public final boolean equals(Object object) { + return Multisets.equalsImpl(this, object); + } + + /** + * {@inheritDoc} + * + *

This implementation returns the hash code of {@link Multiset#entrySet()}. + */ + @Override + public final int hashCode() { + return entrySet().hashCode(); + } + + /** + * {@inheritDoc} + * + *

This implementation returns the result of invoking {@code toString} on {@link + * Multiset#entrySet()}. + */ + @Override + public final String toString() { + return entrySet().toString(); + } +} diff --git a/src/main/java/com/google/common/collect/AbstractNavigableMap.java b/src/main/java/com/google/common/collect/AbstractNavigableMap.java new file mode 100644 index 0000000..03fbaf3 --- /dev/null +++ b/src/main/java/com/google/common/collect/AbstractNavigableMap.java @@ -0,0 +1,169 @@ +/* + * Copyright (C) 2012 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import com.google.common.annotations.GwtIncompatible; +import com.google.common.collect.Maps.IteratorBasedAbstractMap; +import java.util.Iterator; +import java.util.NavigableMap; +import java.util.NavigableSet; +import java.util.NoSuchElementException; +import java.util.Set; +import java.util.SortedMap; + + +/** + * Skeletal implementation of {@link NavigableMap}. + * + * @author Louis Wasserman + */ +@GwtIncompatible +abstract class AbstractNavigableMap extends IteratorBasedAbstractMap + implements NavigableMap { + + @Override + public abstract V get(Object key); + + @Override + public Entry firstEntry() { + return Iterators.getNext(entryIterator(), null); + } + + @Override + public Entry lastEntry() { + return Iterators.getNext(descendingEntryIterator(), null); + } + + @Override + public Entry pollFirstEntry() { + return Iterators.pollNext(entryIterator()); + } + + @Override + public Entry pollLastEntry() { + return Iterators.pollNext(descendingEntryIterator()); + } + + @Override + public K firstKey() { + Entry entry = firstEntry(); + if (entry == null) { + throw new NoSuchElementException(); + } else { + return entry.getKey(); + } + } + + @Override + public K lastKey() { + Entry entry = lastEntry(); + if (entry == null) { + throw new NoSuchElementException(); + } else { + return entry.getKey(); + } + } + + @Override + public Entry lowerEntry(K key) { + return headMap(key, false).lastEntry(); + } + + @Override + public Entry floorEntry(K key) { + return headMap(key, true).lastEntry(); + } + + @Override + public Entry ceilingEntry(K key) { + return tailMap(key, true).firstEntry(); + } + + @Override + public Entry higherEntry(K key) { + return tailMap(key, false).firstEntry(); + } + + @Override + public K lowerKey(K key) { + return Maps.keyOrNull(lowerEntry(key)); + } + + @Override + public K floorKey(K key) { + return Maps.keyOrNull(floorEntry(key)); + } + + @Override + public K ceilingKey(K key) { + return Maps.keyOrNull(ceilingEntry(key)); + } + + @Override + public K higherKey(K key) { + return Maps.keyOrNull(higherEntry(key)); + } + + abstract Iterator> descendingEntryIterator(); + + @Override + public SortedMap subMap(K fromKey, K toKey) { + return subMap(fromKey, true, toKey, false); + } + + @Override + public SortedMap headMap(K toKey) { + return headMap(toKey, false); + } + + @Override + public SortedMap tailMap(K fromKey) { + return tailMap(fromKey, true); + } + + @Override + public NavigableSet navigableKeySet() { + return new Maps.NavigableKeySet<>(this); + } + + @Override + public Set keySet() { + return navigableKeySet(); + } + + @Override + public NavigableSet descendingKeySet() { + return descendingMap().navigableKeySet(); + } + + @Override + public NavigableMap descendingMap() { + return new DescendingMap(); + } + + private final class DescendingMap extends Maps.DescendingMap { + @Override + NavigableMap forward() { + return AbstractNavigableMap.this; + } + + @Override + Iterator> entryIterator() { + return descendingEntryIterator(); + } + } +} diff --git a/src/main/java/com/google/common/collect/AbstractRangeSet.java b/src/main/java/com/google/common/collect/AbstractRangeSet.java new file mode 100644 index 0000000..3634525 --- /dev/null +++ b/src/main/java/com/google/common/collect/AbstractRangeSet.java @@ -0,0 +1,100 @@ +/* + * Copyright (C) 2011 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the + * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import com.google.common.annotations.GwtIncompatible; + + +/** + * A skeletal implementation of {@code RangeSet}. + * + * @author Louis Wasserman + */ +@GwtIncompatible +abstract class AbstractRangeSet implements RangeSet { + AbstractRangeSet() {} + + @Override + public boolean contains(C value) { + return rangeContaining(value) != null; + } + + @Override + public abstract Range rangeContaining(C value); + + @Override + public boolean isEmpty() { + return asRanges().isEmpty(); + } + + @Override + public void add(Range range) { + throw new UnsupportedOperationException(); + } + + @Override + public void remove(Range range) { + throw new UnsupportedOperationException(); + } + + @Override + public void clear() { + remove(Range.all()); + } + + @Override + public boolean enclosesAll(RangeSet other) { + return enclosesAll(other.asRanges()); + } + + @Override + public void addAll(RangeSet other) { + addAll(other.asRanges()); + } + + @Override + public void removeAll(RangeSet other) { + removeAll(other.asRanges()); + } + + @Override + public boolean intersects(Range otherRange) { + return !subRangeSet(otherRange).isEmpty(); + } + + @Override + public abstract boolean encloses(Range otherRange); + + @Override + public boolean equals(Object obj) { + if (obj == this) { + return true; + } else if (obj instanceof RangeSet) { + RangeSet other = (RangeSet) obj; + return this.asRanges().equals(other.asRanges()); + } + return false; + } + + @Override + public final int hashCode() { + return asRanges().hashCode(); + } + + @Override + public final String toString() { + return asRanges().toString(); + } +} diff --git a/src/main/java/com/google/common/collect/AbstractSequentialIterator.java b/src/main/java/com/google/common/collect/AbstractSequentialIterator.java new file mode 100644 index 0000000..4da6c49 --- /dev/null +++ b/src/main/java/com/google/common/collect/AbstractSequentialIterator.java @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2010 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import com.google.common.annotations.GwtCompatible; +import java.util.NoSuchElementException; + + +/** + * This class provides a skeletal implementation of the {@code Iterator} interface for sequences + * whose next element can always be derived from the previous element. Null elements are not + * supported, nor is the {@link #remove()} method. + * + *

Example: + * + *

{@code
+ * Iterator powersOfTwo =
+ *     new AbstractSequentialIterator(1) {
+ *       protected Integer computeNext(Integer previous) {
+ *         return (previous == 1 << 30) ? null : previous * 2;
+ *       }
+ *     };
+ * }
+ * + * @author Chris Povirk + * @since 12.0 (in Guava as {@code AbstractLinkedIterator} since 8.0) + */ +@GwtCompatible +public abstract class AbstractSequentialIterator extends UnmodifiableIterator { + private T nextOrNull; + + /** + * Creates a new iterator with the given first element, or, if {@code firstOrNull} is null, + * creates a new empty iterator. + */ + protected AbstractSequentialIterator(T firstOrNull) { + this.nextOrNull = firstOrNull; + } + + /** + * Returns the element that follows {@code previous}, or returns {@code null} if no elements + * remain. This method is invoked during each call to {@link #next()} in order to compute the + * result of a future call to {@code next()}. + */ + + protected abstract T computeNext(T previous); + + @Override + public final boolean hasNext() { + return nextOrNull != null; + } + + @Override + public final T next() { + if (!hasNext()) { + throw new NoSuchElementException(); + } + try { + return nextOrNull; + } finally { + nextOrNull = computeNext(nextOrNull); + } + } +} diff --git a/src/main/java/com/google/common/collect/AbstractSetMultimap.java b/src/main/java/com/google/common/collect/AbstractSetMultimap.java new file mode 100644 index 0000000..aeb4f9e --- /dev/null +++ b/src/main/java/com/google/common/collect/AbstractSetMultimap.java @@ -0,0 +1,152 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import com.google.common.annotations.GwtCompatible; + +import java.util.Collection; +import java.util.Collections; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; + + +/** + * Basic implementation of the {@link SetMultimap} interface. It's a wrapper around {@link + * AbstractMapBasedMultimap} that converts the returned collections into {@code Sets}. The {@link + * #createCollection} method must return a {@code Set}. + * + * @author Jared Levy + */ +@GwtCompatible +abstract class AbstractSetMultimap extends AbstractMapBasedMultimap + implements SetMultimap { + /** + * Creates a new multimap that uses the provided map. + * + * @param map place to store the mapping from each key to its corresponding values + */ + protected AbstractSetMultimap(Map> map) { + super(map); + } + + @Override + abstract Set createCollection(); + + @Override + Set createUnmodifiableEmptyCollection() { + return Collections.emptySet(); + } + + @Override + Collection unmodifiableCollectionSubclass(Collection collection) { + return Collections.unmodifiableSet((Set) collection); + } + + @Override + Collection wrapCollection(K key, Collection collection) { + return new WrappedSet(key, (Set) collection); + } + + // Following Javadoc copied from SetMultimap. + + /** + * {@inheritDoc} + * + *

Because a {@code SetMultimap} has unique values for a given key, this method returns a + * {@link Set}, instead of the {@link Collection} specified in the {@link Multimap} interface. + */ + @Override + public Set get(K key) { + return (Set) super.get(key); + } + + /** + * {@inheritDoc} + * + *

Because a {@code SetMultimap} has unique values for a given key, this method returns a + * {@link Set}, instead of the {@link Collection} specified in the {@link Multimap} interface. + */ + @Override + public Set> entries() { + return (Set>) super.entries(); + } + + /** + * {@inheritDoc} + * + *

Because a {@code SetMultimap} has unique values for a given key, this method returns a + * {@link Set}, instead of the {@link Collection} specified in the {@link Multimap} interface. + */ + + @Override + public Set removeAll(Object key) { + return (Set) super.removeAll(key); + } + + /** + * {@inheritDoc} + * + *

Because a {@code SetMultimap} has unique values for a given key, this method returns a + * {@link Set}, instead of the {@link Collection} specified in the {@link Multimap} interface. + * + *

Any duplicates in {@code values} will be stored in the multimap once. + */ + + @Override + public Set replaceValues(K key, Iterable values) { + return (Set) super.replaceValues(key, values); + } + + /** + * {@inheritDoc} + * + *

Though the method signature doesn't say so explicitly, the returned map has {@link Set} + * values. + */ + @Override + public Map> asMap() { + return super.asMap(); + } + + /** + * Stores a key-value pair in the multimap. + * + * @param key key to store in the multimap + * @param value value to store in the multimap + * @return {@code true} if the method increased the size of the multimap, or {@code false} if the + * multimap already contained the key-value pair + */ + + @Override + public boolean put(K key, V value) { + return super.put(key, value); + } + + /** + * Compares the specified object to this multimap for equality. + * + *

Two {@code SetMultimap} instances are equal if, for each key, they contain the same values. + * Equality does not depend on the ordering of keys or values. + */ + @Override + public boolean equals(Object object) { + return super.equals(object); + } + + private static final long serialVersionUID = 7431625294878419160L; +} diff --git a/src/main/java/com/google/common/collect/AbstractSortedKeySortedSetMultimap.java b/src/main/java/com/google/common/collect/AbstractSortedKeySortedSetMultimap.java new file mode 100644 index 0000000..0ee6edb --- /dev/null +++ b/src/main/java/com/google/common/collect/AbstractSortedKeySortedSetMultimap.java @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2012 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import com.google.common.annotations.GwtCompatible; +import java.util.Collection; +import java.util.Set; +import java.util.SortedMap; +import java.util.SortedSet; + +/** + * Basic implementation of a {@link SortedSetMultimap} with a sorted key set. + * + *

This superclass allows {@code TreeMultimap} to override methods to return navigable set and + * map types in non-GWT only, while GWT code will inherit the SortedMap/SortedSet overrides. + * + * @author Louis Wasserman + */ +@GwtCompatible +abstract class AbstractSortedKeySortedSetMultimap extends AbstractSortedSetMultimap { + + AbstractSortedKeySortedSetMultimap(SortedMap> map) { + super(map); + } + + @Override + public SortedMap> asMap() { + return (SortedMap>) super.asMap(); + } + + @Override + SortedMap> backingMap() { + return (SortedMap>) super.backingMap(); + } + + @Override + public SortedSet keySet() { + return (SortedSet) super.keySet(); + } + + @Override + Set createKeySet() { + return createMaybeNavigableKeySet(); + } +} diff --git a/src/main/java/com/google/common/collect/AbstractSortedMultiset.java b/src/main/java/com/google/common/collect/AbstractSortedMultiset.java new file mode 100644 index 0000000..b28f8e2 --- /dev/null +++ b/src/main/java/com/google/common/collect/AbstractSortedMultiset.java @@ -0,0 +1,147 @@ +/* + * Copyright (C) 2011 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.collect; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.annotations.GwtCompatible; + +import java.util.Comparator; +import java.util.Iterator; +import java.util.NavigableSet; + + + +/** + * This class provides a skeletal implementation of the {@link SortedMultiset} interface. + * + *

The {@link #count} and {@link #size} implementations all iterate across the set returned by + * {@link Multiset#entrySet()}, as do many methods acting on the set returned by {@link + * #elementSet()}. Override those methods for better performance. + * + * @author Louis Wasserman + */ +@GwtCompatible(emulated = true) +abstract class AbstractSortedMultiset extends AbstractMultiset implements SortedMultiset { + @GwtTransient final Comparator comparator; + + // needed for serialization + @SuppressWarnings("unchecked") + AbstractSortedMultiset() { + this((Comparator) Ordering.natural()); + } + + AbstractSortedMultiset(Comparator comparator) { + this.comparator = checkNotNull(comparator); + } + + @Override + public NavigableSet elementSet() { + return (NavigableSet) super.elementSet(); + } + + @Override + NavigableSet createElementSet() { + return new SortedMultisets.NavigableElementSet(this); + } + + @Override + public Comparator comparator() { + return comparator; + } + + @Override + public Entry firstEntry() { + Iterator> entryIterator = entryIterator(); + return entryIterator.hasNext() ? entryIterator.next() : null; + } + + @Override + public Entry lastEntry() { + Iterator> entryIterator = descendingEntryIterator(); + return entryIterator.hasNext() ? entryIterator.next() : null; + } + + @Override + public Entry pollFirstEntry() { + Iterator> entryIterator = entryIterator(); + if (entryIterator.hasNext()) { + Entry result = entryIterator.next(); + result = Multisets.immutableEntry(result.getElement(), result.getCount()); + entryIterator.remove(); + return result; + } + return null; + } + + @Override + public Entry pollLastEntry() { + Iterator> entryIterator = descendingEntryIterator(); + if (entryIterator.hasNext()) { + Entry result = entryIterator.next(); + result = Multisets.immutableEntry(result.getElement(), result.getCount()); + entryIterator.remove(); + return result; + } + return null; + } + + @Override + public SortedMultiset subMultiset( + E fromElement, + BoundType fromBoundType, + E toElement, + BoundType toBoundType) { + // These are checked elsewhere, but NullPointerTester wants them checked eagerly. + checkNotNull(fromBoundType); + checkNotNull(toBoundType); + return tailMultiset(fromElement, fromBoundType).headMultiset(toElement, toBoundType); + } + + abstract Iterator> descendingEntryIterator(); + + Iterator descendingIterator() { + return Multisets.iteratorImpl(descendingMultiset()); + } + + private transient SortedMultiset descendingMultiset; + + @Override + public SortedMultiset descendingMultiset() { + SortedMultiset result = descendingMultiset; + return (result == null) ? descendingMultiset = createDescendingMultiset() : result; + } + + SortedMultiset createDescendingMultiset() { + + class DescendingMultisetImpl extends DescendingMultiset { + @Override + SortedMultiset forwardMultiset() { + return AbstractSortedMultiset.this; + } + + @Override + Iterator> entryIterator() { + return descendingEntryIterator(); + } + + @Override + public Iterator iterator() { + return descendingIterator(); + } + } + return new DescendingMultisetImpl(); + } +} diff --git a/src/main/java/com/google/common/collect/AbstractSortedSetMultimap.java b/src/main/java/com/google/common/collect/AbstractSortedSetMultimap.java new file mode 100644 index 0000000..9239414 --- /dev/null +++ b/src/main/java/com/google/common/collect/AbstractSortedSetMultimap.java @@ -0,0 +1,148 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import com.google.common.annotations.GwtCompatible; + +import java.util.Collection; +import java.util.Collections; +import java.util.Map; +import java.util.NavigableSet; +import java.util.SortedSet; + + +/** + * Basic implementation of the {@link SortedSetMultimap} interface. It's a wrapper around {@link + * AbstractMapBasedMultimap} that converts the returned collections into sorted sets. The {@link + * #createCollection} method must return a {@code SortedSet}. + * + * @author Jared Levy + */ +@GwtCompatible +abstract class AbstractSortedSetMultimap extends AbstractSetMultimap + implements SortedSetMultimap { + /** + * Creates a new multimap that uses the provided map. + * + * @param map place to store the mapping from each key to its corresponding values + */ + protected AbstractSortedSetMultimap(Map> map) { + super(map); + } + + @Override + abstract SortedSet createCollection(); + + @Override + SortedSet createUnmodifiableEmptyCollection() { + return unmodifiableCollectionSubclass(createCollection()); + } + + @Override + SortedSet unmodifiableCollectionSubclass(Collection collection) { + if (collection instanceof NavigableSet) { + return Sets.unmodifiableNavigableSet((NavigableSet) collection); + } else { + return Collections.unmodifiableSortedSet((SortedSet) collection); + } + } + + @Override + Collection wrapCollection(K key, Collection collection) { + if (collection instanceof NavigableSet) { + return new WrappedNavigableSet(key, (NavigableSet) collection, null); + } else { + return new WrappedSortedSet(key, (SortedSet) collection, null); + } + } + + // Following Javadoc copied from Multimap and SortedSetMultimap. + + /** + * Returns a collection view of all values associated with a key. If no mappings in the multimap + * have the provided key, an empty collection is returned. + * + *

Changes to the returned collection will update the underlying multimap, and vice versa. + * + *

Because a {@code SortedSetMultimap} has unique sorted values for a given key, this method + * returns a {@link SortedSet}, instead of the {@link Collection} specified in the {@link + * Multimap} interface. + */ + @Override + public SortedSet get(K key) { + return (SortedSet) super.get(key); + } + + /** + * Removes all values associated with a given key. The returned collection is immutable. + * + *

Because a {@code SortedSetMultimap} has unique sorted values for a given key, this method + * returns a {@link SortedSet}, instead of the {@link Collection} specified in the {@link + * Multimap} interface. + */ + + @Override + public SortedSet removeAll(Object key) { + return (SortedSet) super.removeAll(key); + } + + /** + * Stores a collection of values with the same key, replacing any existing values for that key. + * The returned collection is immutable. + * + *

Because a {@code SortedSetMultimap} has unique sorted values for a given key, this method + * returns a {@link SortedSet}, instead of the {@link Collection} specified in the {@link + * Multimap} interface. + * + *

Any duplicates in {@code values} will be stored in the multimap once. + */ + + @Override + public SortedSet replaceValues(K key, Iterable values) { + return (SortedSet) super.replaceValues(key, values); + } + + /** + * Returns a map view that associates each key with the corresponding values in the multimap. + * Changes to the returned map, such as element removal, will update the underlying multimap. The + * map does not support {@code setValue} on its entries, {@code put}, or {@code putAll}. + * + *

When passed a key that is present in the map, {@code asMap().get(Object)} has the same + * behavior as {@link #get}, returning a live collection. When passed a key that is not present, + * however, {@code asMap().get(Object)} returns {@code null} instead of an empty collection. + * + *

Though the method signature doesn't say so explicitly, the returned map has {@link + * SortedSet} values. + */ + @Override + public Map> asMap() { + return super.asMap(); + } + + /** + * {@inheritDoc} + * + *

Consequently, the values do not follow their natural ordering or the ordering of the value + * comparator. + */ + @Override + public Collection values() { + return super.values(); + } + + private static final long serialVersionUID = 430848587173315748L; +} diff --git a/src/main/java/com/google/common/collect/AbstractTable.java b/src/main/java/com/google/common/collect/AbstractTable.java new file mode 100644 index 0000000..36cd5e7 --- /dev/null +++ b/src/main/java/com/google/common/collect/AbstractTable.java @@ -0,0 +1,241 @@ +/* + * Copyright (C) 2013 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.collect; + +import com.google.common.annotations.GwtCompatible; + + +import java.util.AbstractCollection; +import java.util.AbstractSet; +import java.util.Collection; +import java.util.Iterator; +import java.util.Map; +import java.util.Set; +import java.util.Spliterator; + + + +/** + * Skeletal, implementation-agnostic implementation of the {@link Table} interface. + * + * @author Louis Wasserman + */ +@GwtCompatible +abstract class AbstractTable implements Table { + + @Override + public boolean containsRow(Object rowKey) { + return Maps.safeContainsKey(rowMap(), rowKey); + } + + @Override + public boolean containsColumn(Object columnKey) { + return Maps.safeContainsKey(columnMap(), columnKey); + } + + @Override + public Set rowKeySet() { + return rowMap().keySet(); + } + + @Override + public Set columnKeySet() { + return columnMap().keySet(); + } + + @Override + public boolean containsValue(Object value) { + for (Map row : rowMap().values()) { + if (row.containsValue(value)) { + return true; + } + } + return false; + } + + @Override + public boolean contains(Object rowKey, Object columnKey) { + Map row = Maps.safeGet(rowMap(), rowKey); + return row != null && Maps.safeContainsKey(row, columnKey); + } + + @Override + public V get(Object rowKey, Object columnKey) { + Map row = Maps.safeGet(rowMap(), rowKey); + return (row == null) ? null : Maps.safeGet(row, columnKey); + } + + @Override + public boolean isEmpty() { + return size() == 0; + } + + @Override + public void clear() { + Iterators.clear(cellSet().iterator()); + } + + + @Override + public V remove(Object rowKey, Object columnKey) { + Map row = Maps.safeGet(rowMap(), rowKey); + return (row == null) ? null : Maps.safeRemove(row, columnKey); + } + + + @Override + public V put(R rowKey, C columnKey, V value) { + return row(rowKey).put(columnKey, value); + } + + @Override + public void putAll(Table table) { + for (Table.Cell cell : table.cellSet()) { + put(cell.getRowKey(), cell.getColumnKey(), cell.getValue()); + } + } + + private transient Set> cellSet; + + @Override + public Set> cellSet() { + Set> result = cellSet; + return (result == null) ? cellSet = createCellSet() : result; + } + + Set> createCellSet() { + return new CellSet(); + } + + abstract Iterator> cellIterator(); + + abstract Spliterator> cellSpliterator(); + + + class CellSet extends AbstractSet> { + @Override + public boolean contains(Object o) { + if (o instanceof Cell) { + Cell cell = (Cell) o; + Map row = Maps.safeGet(rowMap(), cell.getRowKey()); + return row != null + && Collections2.safeContains( + row.entrySet(), Maps.immutableEntry(cell.getColumnKey(), cell.getValue())); + } + return false; + } + + @Override + public boolean remove(Object o) { + if (o instanceof Cell) { + Cell cell = (Cell) o; + Map row = Maps.safeGet(rowMap(), cell.getRowKey()); + return row != null + && Collections2.safeRemove( + row.entrySet(), Maps.immutableEntry(cell.getColumnKey(), cell.getValue())); + } + return false; + } + + @Override + public void clear() { + AbstractTable.this.clear(); + } + + @Override + public Iterator> iterator() { + return cellIterator(); + } + + @Override + public Spliterator> spliterator() { + return cellSpliterator(); + } + + @Override + public int size() { + return AbstractTable.this.size(); + } + } + + private transient Collection values; + + @Override + public Collection values() { + Collection result = values; + return (result == null) ? values = createValues() : result; + } + + Collection createValues() { + return new Values(); + } + + Iterator valuesIterator() { + return new TransformedIterator, V>(cellSet().iterator()) { + @Override + V transform(Cell cell) { + return cell.getValue(); + } + }; + } + + Spliterator valuesSpliterator() { + return CollectSpliterators.map(cellSpliterator(), Table.Cell::getValue); + } + + + class Values extends AbstractCollection { + @Override + public Iterator iterator() { + return valuesIterator(); + } + + @Override + public Spliterator spliterator() { + return valuesSpliterator(); + } + + @Override + public boolean contains(Object o) { + return containsValue(o); + } + + @Override + public void clear() { + AbstractTable.this.clear(); + } + + @Override + public int size() { + return AbstractTable.this.size(); + } + } + + @Override + public boolean equals(Object obj) { + return Tables.equalsImpl(this, obj); + } + + @Override + public int hashCode() { + return cellSet().hashCode(); + } + + /** Returns the string representation {@code rowMap().toString()}. */ + @Override + public String toString() { + return rowMap().toString(); + } +} diff --git a/src/main/java/com/google/common/collect/AllEqualOrdering.java b/src/main/java/com/google/common/collect/AllEqualOrdering.java new file mode 100644 index 0000000..b8546a5 --- /dev/null +++ b/src/main/java/com/google/common/collect/AllEqualOrdering.java @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2012 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import com.google.common.annotations.GwtCompatible; +import java.io.Serializable; +import java.util.List; + + +/** + * An ordering that treats all references as equals, even nulls. + * + * @author Emily Soldal + */ +@GwtCompatible(serializable = true) +final class AllEqualOrdering extends Ordering implements Serializable { + static final AllEqualOrdering INSTANCE = new AllEqualOrdering(); + + @Override + public int compare(Object left, Object right) { + return 0; + } + + @Override + public List sortedCopy(Iterable iterable) { + return Lists.newArrayList(iterable); + } + + @Override + public ImmutableList immutableSortedCopy(Iterable iterable) { + return ImmutableList.copyOf(iterable); + } + + @SuppressWarnings("unchecked") + @Override + public Ordering reverse() { + return (Ordering) this; + } + + private Object readResolve() { + return INSTANCE; + } + + @Override + public String toString() { + return "Ordering.allEqual()"; + } + + private static final long serialVersionUID = 0; +} diff --git a/src/main/java/com/google/common/collect/ArrayListMultimap.java b/src/main/java/com/google/common/collect/ArrayListMultimap.java new file mode 100644 index 0000000..bebdae6 --- /dev/null +++ b/src/main/java/com/google/common/collect/ArrayListMultimap.java @@ -0,0 +1,171 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import static com.google.common.collect.CollectPreconditions.checkNonnegative; + +import com.google.common.annotations.GwtCompatible; +import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.VisibleForTesting; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * Implementation of {@code Multimap} that uses an {@code ArrayList} to store the values for a given + * key. A {@link HashMap} associates each key with an {@link ArrayList} of values. + * + *

When iterating through the collections supplied by this class, the ordering of values for a + * given key agrees with the order in which the values were added. + * + *

This multimap allows duplicate key-value pairs. After adding a new key-value pair equal to an + * existing key-value pair, the {@code ArrayListMultimap} will contain entries for both the new + * value and the old value. + * + *

Keys and values may be null. All optional multimap methods are supported, and all returned + * views are modifiable. + * + *

The lists returned by {@link #get}, {@link #removeAll}, and {@link #replaceValues} all + * implement {@link java.util.RandomAccess}. + * + *

This class is not threadsafe when any concurrent operations update the multimap. Concurrent + * read operations will work correctly. To allow concurrent update operations, wrap your multimap + * with a call to {@link Multimaps#synchronizedListMultimap}. + * + *

See the Guava User Guide article on {@code + * Multimap}. + * + * @author Jared Levy + * @since 2.0 + */ +@GwtCompatible(serializable = true, emulated = true) +public final class ArrayListMultimap + extends ArrayListMultimapGwtSerializationDependencies { + // Default from ArrayList + private static final int DEFAULT_VALUES_PER_KEY = 3; + + @VisibleForTesting transient int expectedValuesPerKey; + + /** + * Creates a new, empty {@code ArrayListMultimap} with the default initial capacities. + * + *

This method will soon be deprecated in favor of {@code + * MultimapBuilder.hashKeys().arrayListValues().build()}. + */ + public static ArrayListMultimap create() { + return new ArrayListMultimap<>(); + } + + /** + * Constructs an empty {@code ArrayListMultimap} with enough capacity to hold the specified + * numbers of keys and values without resizing. + * + *

This method will soon be deprecated in favor of {@code + * MultimapBuilder.hashKeys(expectedKeys).arrayListValues(expectedValuesPerKey).build()}. + * + * @param expectedKeys the expected number of distinct keys + * @param expectedValuesPerKey the expected average number of values per key + * @throws IllegalArgumentException if {@code expectedKeys} or {@code expectedValuesPerKey} is + * negative + */ + public static ArrayListMultimap create(int expectedKeys, int expectedValuesPerKey) { + return new ArrayListMultimap<>(expectedKeys, expectedValuesPerKey); + } + + /** + * Constructs an {@code ArrayListMultimap} with the same mappings as the specified multimap. + * + *

This method will soon be deprecated in favor of {@code + * MultimapBuilder.hashKeys().arrayListValues().build(multimap)}. + * + * @param multimap the multimap whose contents are copied to this multimap + */ + public static ArrayListMultimap create(Multimap multimap) { + return new ArrayListMultimap<>(multimap); + } + + private ArrayListMultimap() { + this(12, DEFAULT_VALUES_PER_KEY); + } + + private ArrayListMultimap(int expectedKeys, int expectedValuesPerKey) { + super(Platform.>newHashMapWithExpectedSize(expectedKeys)); + checkNonnegative(expectedValuesPerKey, "expectedValuesPerKey"); + this.expectedValuesPerKey = expectedValuesPerKey; + } + + private ArrayListMultimap(Multimap multimap) { + this( + multimap.keySet().size(), + (multimap instanceof ArrayListMultimap) + ? ((ArrayListMultimap) multimap).expectedValuesPerKey + : DEFAULT_VALUES_PER_KEY); + putAll(multimap); + } + + /** + * Creates a new, empty {@code ArrayList} to hold the collection of values for an arbitrary key. + */ + @Override + List createCollection() { + return new ArrayList(expectedValuesPerKey); + } + + /** + * Reduces the memory used by this {@code ArrayListMultimap}, if feasible. + * + * @deprecated For a {@link ListMultimap} that automatically trims to size, use {@link + * ImmutableListMultimap}. If you need a mutable collection, remove the {@code trimToSize} + * call, or switch to a {@code HashMap>}. + */ + @Deprecated + public void trimToSize() { + for (Collection collection : backingMap().values()) { + ArrayList arrayList = (ArrayList) collection; + arrayList.trimToSize(); + } + } + + /** + * @serialData expectedValuesPerKey, number of distinct keys, and then for each distinct key: the + * key, number of values for that key, and the key's values + */ + @GwtIncompatible // java.io.ObjectOutputStream + private void writeObject(ObjectOutputStream stream) throws IOException { + stream.defaultWriteObject(); + Serialization.writeMultimap(this, stream); + } + + @GwtIncompatible // java.io.ObjectOutputStream + private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException { + stream.defaultReadObject(); + expectedValuesPerKey = DEFAULT_VALUES_PER_KEY; + int distinctKeys = Serialization.readCount(stream); + Map> map = Maps.newHashMap(); + setMap(map); + Serialization.populateMultimap(this, stream, distinctKeys); + } + + @GwtIncompatible // Not needed in emulated source. + private static final long serialVersionUID = 0; +} diff --git a/src/main/java/com/google/common/collect/ArrayListMultimapGwtSerializationDependencies.java b/src/main/java/com/google/common/collect/ArrayListMultimapGwtSerializationDependencies.java new file mode 100644 index 0000000..9a8cdfb --- /dev/null +++ b/src/main/java/com/google/common/collect/ArrayListMultimapGwtSerializationDependencies.java @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2016 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import com.google.common.annotations.GwtCompatible; +import java.util.Collection; +import java.util.Map; + +/** + * A dummy superclass to support GWT serialization of the element types of an {@link + * ArrayListMultimap}. The GWT supersource for this class contains a field for each type. + * + *

For details about this hack, see {@code GwtSerializationDependencies}, which takes the same + * approach but with a subclass rather than a superclass. + * + *

TODO(cpovirk): Consider applying this subclass approach to our other types. + */ +@GwtCompatible(emulated = true) +abstract class ArrayListMultimapGwtSerializationDependencies + extends AbstractListMultimap { + ArrayListMultimapGwtSerializationDependencies(Map> map) { + super(map); + } + // TODO(cpovirk): Maybe I should have just one shared superclass for AbstractMultimap itself? +} diff --git a/src/main/java/com/google/common/collect/ArrayTable.java b/src/main/java/com/google/common/collect/ArrayTable.java new file mode 100644 index 0000000..729a45b --- /dev/null +++ b/src/main/java/com/google/common/collect/ArrayTable.java @@ -0,0 +1,777 @@ +/* + * Copyright (C) 2009 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkElementIndex; +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.annotations.Beta; +import com.google.common.annotations.GwtCompatible; +import com.google.common.annotations.GwtIncompatible; +import com.google.common.base.Objects; +import com.google.common.collect.Maps.IteratorBasedAbstractMap; + + +import java.io.Serializable; +import java.lang.reflect.Array; +import java.util.Arrays; +import java.util.Collection; +import java.util.Iterator; +import java.util.Map; +import java.util.Set; +import java.util.Spliterator; + + + +/** + * Fixed-size {@link Table} implementation backed by a two-dimensional array. + * + *

The allowed row and column keys must be supplied when the table is created. The table always + * contains a mapping for every row key / column pair. The value corresponding to a given row and + * column is null unless another value is provided. + * + *

The table's size is constant: the product of the number of supplied row keys and the number of + * supplied column keys. The {@code remove} and {@code clear} methods are not supported by the table + * or its views. The {@link #erase} and {@link #eraseAll} methods may be used instead. + * + *

The ordering of the row and column keys provided when the table is constructed determines the + * iteration ordering across rows and columns in the table's views. None of the view iterators + * support {@link Iterator#remove}. If the table is modified after an iterator is created, the + * iterator remains valid. + * + *

This class requires less memory than the {@link HashBasedTable} and {@link TreeBasedTable} + * implementations, except when the table is sparse. + * + *

Null row keys or column keys are not permitted. + * + *

This class provides methods involving the underlying array structure, where the array indices + * correspond to the position of a row or column in the lists of allowed keys and values. See the + * {@link #at}, {@link #set}, {@link #toArray}, {@link #rowKeyList}, and {@link #columnKeyList} + * methods for more details. + * + *

Note that this implementation is not synchronized. If multiple threads access the same cell of + * an {@code ArrayTable} concurrently and one of the threads modifies its value, there is no + * guarantee that the new value will be fully visible to the other threads. To guarantee that + * modifications are visible, synchronize access to the table. Unlike other {@code Table} + * implementations, synchronization is unnecessary between a thread that writes to one cell and a + * thread that reads from another. + * + *

See the Guava User Guide article on {@code Table}. + * + * @author Jared Levy + * @since 10.0 + */ +@Beta +@GwtCompatible(emulated = true) +public final class ArrayTable extends AbstractTable implements Serializable { + + /** + * Creates an {@code ArrayTable} filled with {@code null}. + * + * @param rowKeys row keys that may be stored in the generated table + * @param columnKeys column keys that may be stored in the generated table + * @throws NullPointerException if any of the provided keys is null + * @throws IllegalArgumentException if {@code rowKeys} or {@code columnKeys} contains duplicates + * or if exactly one of {@code rowKeys} or {@code columnKeys} is empty. + */ + public static ArrayTable create( + Iterable rowKeys, Iterable columnKeys) { + return new ArrayTable<>(rowKeys, columnKeys); + } + + /* + * TODO(jlevy): Add factory methods taking an Enum class, instead of an + * iterable, to specify the allowed row keys and/or column keys. Note that + * custom serialization logic is needed to support different enum sizes during + * serialization and deserialization. + */ + + /** + * Creates an {@code ArrayTable} with the mappings in the provided table. + * + *

If {@code table} includes a mapping with row key {@code r} and a separate mapping with + * column key {@code c}, the returned table contains a mapping with row key {@code r} and column + * key {@code c}. If that row key / column key pair in not in {@code table}, the pair maps to + * {@code null} in the generated table. + * + *

The returned table allows subsequent {@code put} calls with the row keys in {@code + * table.rowKeySet()} and the column keys in {@code table.columnKeySet()}. Calling {@link #put} + * with other keys leads to an {@code IllegalArgumentException}. + * + *

The ordering of {@code table.rowKeySet()} and {@code table.columnKeySet()} determines the + * row and column iteration ordering of the returned table. + * + * @throws NullPointerException if {@code table} has a null key + */ + public static ArrayTable create(Table table) { + return (table instanceof ArrayTable) + ? new ArrayTable((ArrayTable) table) + : new ArrayTable(table); + } + + private final ImmutableList rowList; + private final ImmutableList columnList; + + // TODO(jlevy): Add getters returning rowKeyToIndex and columnKeyToIndex? + private final ImmutableMap rowKeyToIndex; + private final ImmutableMap columnKeyToIndex; + private final V[][] array; + + private ArrayTable(Iterable rowKeys, Iterable columnKeys) { + this.rowList = ImmutableList.copyOf(rowKeys); + this.columnList = ImmutableList.copyOf(columnKeys); + checkArgument(rowList.isEmpty() == columnList.isEmpty()); + + /* + * TODO(jlevy): Support only one of rowKey / columnKey being empty? If we + * do, when columnKeys is empty but rowKeys isn't, rowKeyList() can contain + * elements but rowKeySet() will be empty and containsRow() won't + * acknolwedge them. + */ + rowKeyToIndex = Maps.indexMap(rowList); + columnKeyToIndex = Maps.indexMap(columnList); + + @SuppressWarnings("unchecked") + V[][] tmpArray = (V[][]) new Object[rowList.size()][columnList.size()]; + array = tmpArray; + // Necessary because in GWT the arrays are initialized with "undefined" instead of null. + eraseAll(); + } + + private ArrayTable(Table table) { + this(table.rowKeySet(), table.columnKeySet()); + putAll(table); + } + + private ArrayTable(ArrayTable table) { + rowList = table.rowList; + columnList = table.columnList; + rowKeyToIndex = table.rowKeyToIndex; + columnKeyToIndex = table.columnKeyToIndex; + @SuppressWarnings("unchecked") + V[][] copy = (V[][]) new Object[rowList.size()][columnList.size()]; + array = copy; + for (int i = 0; i < rowList.size(); i++) { + System.arraycopy(table.array[i], 0, copy[i], 0, table.array[i].length); + } + } + + private abstract static class ArrayMap extends IteratorBasedAbstractMap { + private final ImmutableMap keyIndex; + + private ArrayMap(ImmutableMap keyIndex) { + this.keyIndex = keyIndex; + } + + @Override + public Set keySet() { + return keyIndex.keySet(); + } + + K getKey(int index) { + return keyIndex.keySet().asList().get(index); + } + + abstract String getKeyRole(); + + abstract V getValue(int index); + + abstract V setValue(int index, V newValue); + + @Override + public int size() { + return keyIndex.size(); + } + + @Override + public boolean isEmpty() { + return keyIndex.isEmpty(); + } + + Entry getEntry(final int index) { + checkElementIndex(index, size()); + return new AbstractMapEntry() { + @Override + public K getKey() { + return ArrayMap.this.getKey(index); + } + + @Override + public V getValue() { + return ArrayMap.this.getValue(index); + } + + @Override + public V setValue(V value) { + return ArrayMap.this.setValue(index, value); + } + }; + } + + @Override + Iterator> entryIterator() { + return new AbstractIndexedListIterator>(size()) { + @Override + protected Entry get(final int index) { + return getEntry(index); + } + }; + } + + @Override + Spliterator> entrySpliterator() { + return CollectSpliterators.indexed(size(), Spliterator.ORDERED, this::getEntry); + } + + // TODO(lowasser): consider an optimized values() implementation + + @Override + public boolean containsKey(Object key) { + return keyIndex.containsKey(key); + } + + @Override + public V get(Object key) { + Integer index = keyIndex.get(key); + if (index == null) { + return null; + } else { + return getValue(index); + } + } + + @Override + public V put(K key, V value) { + Integer index = keyIndex.get(key); + if (index == null) { + throw new IllegalArgumentException( + getKeyRole() + " " + key + " not in " + keyIndex.keySet()); + } + return setValue(index, value); + } + + @Override + public V remove(Object key) { + throw new UnsupportedOperationException(); + } + + @Override + public void clear() { + throw new UnsupportedOperationException(); + } + } + + /** + * Returns, as an immutable list, the row keys provided when the table was constructed, including + * those that are mapped to null values only. + */ + public ImmutableList rowKeyList() { + return rowList; + } + + /** + * Returns, as an immutable list, the column keys provided when the table was constructed, + * including those that are mapped to null values only. + */ + public ImmutableList columnKeyList() { + return columnList; + } + + /** + * Returns the value corresponding to the specified row and column indices. The same value is + * returned by {@code get(rowKeyList().get(rowIndex), columnKeyList().get(columnIndex))}, but this + * method runs more quickly. + * + * @param rowIndex position of the row key in {@link #rowKeyList()} + * @param columnIndex position of the row key in {@link #columnKeyList()} + * @return the value with the specified row and column + * @throws IndexOutOfBoundsException if either index is negative, {@code rowIndex} is greater than + * or equal to the number of allowed row keys, or {@code columnIndex} is greater than or equal + * to the number of allowed column keys + */ + public V at(int rowIndex, int columnIndex) { + // In GWT array access never throws IndexOutOfBoundsException. + checkElementIndex(rowIndex, rowList.size()); + checkElementIndex(columnIndex, columnList.size()); + return array[rowIndex][columnIndex]; + } + + /** + * Associates {@code value} with the specified row and column indices. The logic {@code + * put(rowKeyList().get(rowIndex), columnKeyList().get(columnIndex), value)} has the same + * behavior, but this method runs more quickly. + * + * @param rowIndex position of the row key in {@link #rowKeyList()} + * @param columnIndex position of the row key in {@link #columnKeyList()} + * @param value value to store in the table + * @return the previous value with the specified row and column + * @throws IndexOutOfBoundsException if either index is negative, {@code rowIndex} is greater than + * or equal to the number of allowed row keys, or {@code columnIndex} is greater than or equal + * to the number of allowed column keys + */ + + public V set(int rowIndex, int columnIndex, V value) { + // In GWT array access never throws IndexOutOfBoundsException. + checkElementIndex(rowIndex, rowList.size()); + checkElementIndex(columnIndex, columnList.size()); + V oldValue = array[rowIndex][columnIndex]; + array[rowIndex][columnIndex] = value; + return oldValue; + } + + /** + * Returns a two-dimensional array with the table contents. The row and column indices correspond + * to the positions of the row and column in the iterables provided during table construction. If + * the table lacks a mapping for a given row and column, the corresponding array element is null. + * + *

Subsequent table changes will not modify the array, and vice versa. + * + * @param valueClass class of values stored in the returned array + */ + @GwtIncompatible // reflection + public V[][] toArray(Class valueClass) { + @SuppressWarnings("unchecked") // TODO: safe? + V[][] copy = (V[][]) Array.newInstance(valueClass, rowList.size(), columnList.size()); + for (int i = 0; i < rowList.size(); i++) { + System.arraycopy(array[i], 0, copy[i], 0, array[i].length); + } + return copy; + } + + /** + * Not supported. Use {@link #eraseAll} instead. + * + * @throws UnsupportedOperationException always + * @deprecated Use {@link #eraseAll} + */ + @Override + @Deprecated + public void clear() { + throw new UnsupportedOperationException(); + } + + /** Associates the value {@code null} with every pair of allowed row and column keys. */ + public void eraseAll() { + for (V[] row : array) { + Arrays.fill(row, null); + } + } + + /** + * Returns {@code true} if the provided keys are among the keys provided when the table was + * constructed. + */ + @Override + public boolean contains(Object rowKey, Object columnKey) { + return containsRow(rowKey) && containsColumn(columnKey); + } + + /** + * Returns {@code true} if the provided column key is among the column keys provided when the + * table was constructed. + */ + @Override + public boolean containsColumn(Object columnKey) { + return columnKeyToIndex.containsKey(columnKey); + } + + /** + * Returns {@code true} if the provided row key is among the row keys provided when the table was + * constructed. + */ + @Override + public boolean containsRow(Object rowKey) { + return rowKeyToIndex.containsKey(rowKey); + } + + @Override + public boolean containsValue(Object value) { + for (V[] row : array) { + for (V element : row) { + if (Objects.equal(value, element)) { + return true; + } + } + } + return false; + } + + @Override + public V get(Object rowKey, Object columnKey) { + Integer rowIndex = rowKeyToIndex.get(rowKey); + Integer columnIndex = columnKeyToIndex.get(columnKey); + return (rowIndex == null || columnIndex == null) ? null : at(rowIndex, columnIndex); + } + + /** + * Returns {@code true} if {@code rowKeyList().size == 0} or {@code columnKeyList().size() == 0}. + */ + @Override + public boolean isEmpty() { + return rowList.isEmpty() || columnList.isEmpty(); + } + + /** + * {@inheritDoc} + * + * @throws IllegalArgumentException if {@code rowKey} is not in {@link #rowKeySet()} or {@code + * columnKey} is not in {@link #columnKeySet()}. + */ + + @Override + public V put(R rowKey, C columnKey, V value) { + checkNotNull(rowKey); + checkNotNull(columnKey); + Integer rowIndex = rowKeyToIndex.get(rowKey); + checkArgument(rowIndex != null, "Row %s not in %s", rowKey, rowList); + Integer columnIndex = columnKeyToIndex.get(columnKey); + checkArgument(columnIndex != null, "Column %s not in %s", columnKey, columnList); + return set(rowIndex, columnIndex, value); + } + + /* + * TODO(jlevy): Consider creating a merge() method, similar to putAll() but + * copying non-null values only. + */ + + /** + * {@inheritDoc} + * + *

If {@code table} is an {@code ArrayTable}, its null values will be stored in this table, + * possibly replacing values that were previously non-null. + * + * @throws NullPointerException if {@code table} has a null key + * @throws IllegalArgumentException if any of the provided table's row keys or column keys is not + * in {@link #rowKeySet()} or {@link #columnKeySet()} + */ + @Override + public void putAll(Table table) { + super.putAll(table); + } + + /** + * Not supported. Use {@link #erase} instead. + * + * @throws UnsupportedOperationException always + * @deprecated Use {@link #erase} + */ + + @Override + @Deprecated + public V remove(Object rowKey, Object columnKey) { + throw new UnsupportedOperationException(); + } + + /** + * Associates the value {@code null} with the specified keys, assuming both keys are valid. If + * either key is null or isn't among the keys provided during construction, this method has no + * effect. + * + *

This method is equivalent to {@code put(rowKey, columnKey, null)} when both provided keys + * are valid. + * + * @param rowKey row key of mapping to be erased + * @param columnKey column key of mapping to be erased + * @return the value previously associated with the keys, or {@code null} if no mapping existed + * for the keys + */ + + public V erase(Object rowKey, Object columnKey) { + Integer rowIndex = rowKeyToIndex.get(rowKey); + Integer columnIndex = columnKeyToIndex.get(columnKey); + if (rowIndex == null || columnIndex == null) { + return null; + } + return set(rowIndex, columnIndex, null); + } + + // TODO(jlevy): Add eraseRow and eraseColumn methods? + + @Override + public int size() { + return rowList.size() * columnList.size(); + } + + /** + * Returns an unmodifiable set of all row key / column key / value triplets. Changes to the table + * will update the returned set. + * + *

The returned set's iterator traverses the mappings with the first row key, the mappings with + * the second row key, and so on. + * + *

The value in the returned cells may change if the table subsequently changes. + * + * @return set of table cells consisting of row key / column key / value triplets + */ + @Override + public Set> cellSet() { + return super.cellSet(); + } + + @Override + Iterator> cellIterator() { + return new AbstractIndexedListIterator>(size()) { + @Override + protected Cell get(final int index) { + return getCell(index); + } + }; + } + + @Override + Spliterator> cellSpliterator() { + return CollectSpliterators.indexed( + size(), Spliterator.ORDERED | Spliterator.NONNULL | Spliterator.DISTINCT, this::getCell); + } + + private Cell getCell(final int index) { + return new Tables.AbstractCell() { + final int rowIndex = index / columnList.size(); + final int columnIndex = index % columnList.size(); + + @Override + public R getRowKey() { + return rowList.get(rowIndex); + } + + @Override + public C getColumnKey() { + return columnList.get(columnIndex); + } + + @Override + public V getValue() { + return at(rowIndex, columnIndex); + } + }; + } + + private V getValue(int index) { + int rowIndex = index / columnList.size(); + int columnIndex = index % columnList.size(); + return at(rowIndex, columnIndex); + } + + /** + * Returns a view of all mappings that have the given column key. If the column key isn't in + * {@link #columnKeySet()}, an empty immutable map is returned. + * + *

Otherwise, for each row key in {@link #rowKeySet()}, the returned map associates the row key + * with the corresponding value in the table. Changes to the returned map will update the + * underlying table, and vice versa. + * + * @param columnKey key of column to search for in the table + * @return the corresponding map from row keys to values + */ + @Override + public Map column(C columnKey) { + checkNotNull(columnKey); + Integer columnIndex = columnKeyToIndex.get(columnKey); + return (columnIndex == null) ? ImmutableMap.of() : new Column(columnIndex); + } + + private class Column extends ArrayMap { + final int columnIndex; + + Column(int columnIndex) { + super(rowKeyToIndex); + this.columnIndex = columnIndex; + } + + @Override + String getKeyRole() { + return "Row"; + } + + @Override + V getValue(int index) { + return at(index, columnIndex); + } + + @Override + V setValue(int index, V newValue) { + return set(index, columnIndex, newValue); + } + } + + /** + * Returns an immutable set of the valid column keys, including those that are associated with + * null values only. + * + * @return immutable set of column keys + */ + @Override + public ImmutableSet columnKeySet() { + return columnKeyToIndex.keySet(); + } + + private transient ColumnMap columnMap; + + @Override + public Map> columnMap() { + ColumnMap map = columnMap; + return (map == null) ? columnMap = new ColumnMap() : map; + } + + + private class ColumnMap extends ArrayMap> { + private ColumnMap() { + super(columnKeyToIndex); + } + + @Override + String getKeyRole() { + return "Column"; + } + + @Override + Map getValue(int index) { + return new Column(index); + } + + @Override + Map setValue(int index, Map newValue) { + throw new UnsupportedOperationException(); + } + + @Override + public Map put(C key, Map value) { + throw new UnsupportedOperationException(); + } + } + + /** + * Returns a view of all mappings that have the given row key. If the row key isn't in {@link + * #rowKeySet()}, an empty immutable map is returned. + * + *

Otherwise, for each column key in {@link #columnKeySet()}, the returned map associates the + * column key with the corresponding value in the table. Changes to the returned map will update + * the underlying table, and vice versa. + * + * @param rowKey key of row to search for in the table + * @return the corresponding map from column keys to values + */ + @Override + public Map row(R rowKey) { + checkNotNull(rowKey); + Integer rowIndex = rowKeyToIndex.get(rowKey); + return (rowIndex == null) ? ImmutableMap.of() : new Row(rowIndex); + } + + private class Row extends ArrayMap { + final int rowIndex; + + Row(int rowIndex) { + super(columnKeyToIndex); + this.rowIndex = rowIndex; + } + + @Override + String getKeyRole() { + return "Column"; + } + + @Override + V getValue(int index) { + return at(rowIndex, index); + } + + @Override + V setValue(int index, V newValue) { + return set(rowIndex, index, newValue); + } + } + + /** + * Returns an immutable set of the valid row keys, including those that are associated with null + * values only. + * + * @return immutable set of row keys + */ + @Override + public ImmutableSet rowKeySet() { + return rowKeyToIndex.keySet(); + } + + private transient RowMap rowMap; + + @Override + public Map> rowMap() { + RowMap map = rowMap; + return (map == null) ? rowMap = new RowMap() : map; + } + + + private class RowMap extends ArrayMap> { + private RowMap() { + super(rowKeyToIndex); + } + + @Override + String getKeyRole() { + return "Row"; + } + + @Override + Map getValue(int index) { + return new Row(index); + } + + @Override + Map setValue(int index, Map newValue) { + throw new UnsupportedOperationException(); + } + + @Override + public Map put(R key, Map value) { + throw new UnsupportedOperationException(); + } + } + + /** + * Returns an unmodifiable collection of all values, which may contain duplicates. Changes to the + * table will update the returned collection. + * + *

The returned collection's iterator traverses the values of the first row key, the values of + * the second row key, and so on. + * + * @return collection of values + */ + @Override + public Collection values() { + return super.values(); + } + + @Override + Iterator valuesIterator() { + return new AbstractIndexedListIterator(size()) { + @Override + protected V get(int index) { + return getValue(index); + } + }; + } + + @Override + Spliterator valuesSpliterator() { + return CollectSpliterators.indexed(size(), Spliterator.ORDERED, this::getValue); + } + + private static final long serialVersionUID = 0; +} diff --git a/src/main/java/com/google/common/collect/BaseImmutableMultimap.java b/src/main/java/com/google/common/collect/BaseImmutableMultimap.java new file mode 100644 index 0000000..6ebdf14 --- /dev/null +++ b/src/main/java/com/google/common/collect/BaseImmutableMultimap.java @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2018 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.common.collect; + +import com.google.common.annotations.GwtCompatible; + +/** + * A dummy superclass of {@link ImmutableMultimap} that can be instanceof'd without ProGuard + * retaining additional implementation details of {@link ImmutableMultimap}. + */ +@GwtCompatible +abstract class BaseImmutableMultimap extends AbstractMultimap {} diff --git a/src/main/java/com/google/common/collect/BiMap.java b/src/main/java/com/google/common/collect/BiMap.java new file mode 100644 index 0000000..ff2b6e2 --- /dev/null +++ b/src/main/java/com/google/common/collect/BiMap.java @@ -0,0 +1,108 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import com.google.common.annotations.GwtCompatible; + +import java.util.Map; +import java.util.Set; + + +/** + * A bimap (or "bidirectional map") is a map that preserves the uniqueness of its values as well as + * that of its keys. This constraint enables bimaps to support an "inverse view", which is another + * bimap containing the same entries as this bimap but with reversed keys and values. + * + *

See the Guava User Guide article on {@code BiMap}. + * + * @author Kevin Bourrillion + * @since 2.0 + */ +@GwtCompatible +public interface BiMap extends Map { + // Modification Operations + + /** + * {@inheritDoc} + * + * @throws IllegalArgumentException if the given value is already bound to a different key in this + * bimap. The bimap will remain unmodified in this event. To avoid this exception, call {@link + * #forcePut} instead. + */ + + @Override + + V put(K key, V value); + + /** + * An alternate form of {@code put} that silently removes any existing entry with the value {@code + * value} before proceeding with the {@link #put} operation. If the bimap previously contained the + * provided key-value mapping, this method has no effect. + * + *

Note that a successful call to this method could cause the size of the bimap to increase by + * one, stay the same, or even decrease by one. + * + *

Warning: If an existing entry with this value is removed, the key for that entry is + * discarded and not returned. + * + * @param key the key with which the specified value is to be associated + * @param value the value to be associated with the specified key + * @return the value which was previously associated with the key, which may be {@code null}, or + * {@code null} if there was no previous entry + */ + + + V forcePut(K key, V value); + + // Bulk Operations + + /** + * {@inheritDoc} + * + *

Warning: the results of calling this method may vary depending on the iteration order + * of {@code map}. + * + * @throws IllegalArgumentException if an attempt to {@code put} any entry fails. Note that some + * map entries may have been added to the bimap before the exception was thrown. + */ + @Override + void putAll(Map map); + + // Views + + /** + * {@inheritDoc} + * + *

Because a bimap has unique values, this method returns a {@link Set}, instead of the {@link + * java.util.Collection} specified in the {@link Map} interface. + */ + @Override + Set values(); + + /** + * Returns the inverse view of this bimap, which maps each of this bimap's values to its + * associated key. The two bimaps are backed by the same data; any changes to one will appear in + * the other. + * + *

Note:There is no guaranteed correspondence between the iteration order of a bimap and + * that of its inverse. + * + * @return the inverse view of this bimap + */ + BiMap inverse(); +} diff --git a/src/main/java/com/google/common/collect/BoundType.java b/src/main/java/com/google/common/collect/BoundType.java new file mode 100644 index 0000000..ce03802 --- /dev/null +++ b/src/main/java/com/google/common/collect/BoundType.java @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2011 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.collect; + +import com.google.common.annotations.GwtCompatible; + +/** + * Indicates whether an endpoint of some range is contained in the range itself ("closed") or not + * ("open"). If a range is unbounded on a side, it is neither open nor closed on that side; the + * bound simply does not exist. + * + * @since 10.0 + */ +@GwtCompatible +public enum BoundType { + /** The endpoint value is not considered part of the set ("exclusive"). */ + OPEN(false), + CLOSED(true); + + final boolean inclusive; + + BoundType(boolean inclusive) { + this.inclusive = inclusive; + } + + /** Returns the bound type corresponding to a boolean value for inclusivity. */ + static BoundType forBoolean(boolean inclusive) { + return inclusive ? CLOSED : OPEN; + } + + BoundType flip() { + return forBoolean(!inclusive); + } +} diff --git a/src/main/java/com/google/common/collect/ByFunctionOrdering.java b/src/main/java/com/google/common/collect/ByFunctionOrdering.java new file mode 100644 index 0000000..e292a06 --- /dev/null +++ b/src/main/java/com/google/common/collect/ByFunctionOrdering.java @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.annotations.GwtCompatible; +import com.google.common.base.Function; +import com.google.common.base.Objects; +import java.io.Serializable; + + +/** + * An ordering that orders elements by applying an order to the result of a function on those + * elements. + */ +@GwtCompatible(serializable = true) +final class ByFunctionOrdering extends Ordering implements Serializable { + final Function function; + final Ordering ordering; + + ByFunctionOrdering(Function function, Ordering ordering) { + this.function = checkNotNull(function); + this.ordering = checkNotNull(ordering); + } + + @Override + public int compare(F left, F right) { + return ordering.compare(function.apply(left), function.apply(right)); + } + + @Override + public boolean equals(Object object) { + if (object == this) { + return true; + } + if (object instanceof ByFunctionOrdering) { + ByFunctionOrdering that = (ByFunctionOrdering) object; + return this.function.equals(that.function) && this.ordering.equals(that.ordering); + } + return false; + } + + @Override + public int hashCode() { + return Objects.hashCode(function, ordering); + } + + @Override + public String toString() { + return ordering + ".onResultOf(" + function + ")"; + } + + private static final long serialVersionUID = 0; +} diff --git a/src/main/java/com/google/common/collect/CartesianList.java b/src/main/java/com/google/common/collect/CartesianList.java new file mode 100644 index 0000000..05ec425 --- /dev/null +++ b/src/main/java/com/google/common/collect/CartesianList.java @@ -0,0 +1,124 @@ +/* + * Copyright (C) 2012 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.collect; + +import static com.google.common.base.Preconditions.checkElementIndex; + +import com.google.common.annotations.GwtCompatible; +import com.google.common.math.IntMath; +import java.util.AbstractList; +import java.util.List; +import java.util.ListIterator; +import java.util.RandomAccess; + + +/** + * Implementation of {@link Lists#cartesianProduct(List)}. + * + * @author Louis Wasserman + */ +@GwtCompatible +final class CartesianList extends AbstractList> implements RandomAccess { + + private final transient ImmutableList> axes; + private final transient int[] axesSizeProduct; + + static List> create(List> lists) { + ImmutableList.Builder> axesBuilder = new ImmutableList.Builder<>(lists.size()); + for (List list : lists) { + List copy = ImmutableList.copyOf(list); + if (copy.isEmpty()) { + return ImmutableList.of(); + } + axesBuilder.add(copy); + } + return new CartesianList(axesBuilder.build()); + } + + CartesianList(ImmutableList> axes) { + this.axes = axes; + int[] axesSizeProduct = new int[axes.size() + 1]; + axesSizeProduct[axes.size()] = 1; + try { + for (int i = axes.size() - 1; i >= 0; i--) { + axesSizeProduct[i] = IntMath.checkedMultiply(axesSizeProduct[i + 1], axes.get(i).size()); + } + } catch (ArithmeticException e) { + throw new IllegalArgumentException( + "Cartesian product too large; must have size at most Integer.MAX_VALUE"); + } + this.axesSizeProduct = axesSizeProduct; + } + + private int getAxisIndexForProductIndex(int index, int axis) { + return (index / axesSizeProduct[axis + 1]) % axes.get(axis).size(); + } + + @Override + public int indexOf(Object o) { + if (!(o instanceof List)) { + return -1; + } + List list = (List) o; + if (list.size() != axes.size()) { + return -1; + } + ListIterator itr = list.listIterator(); + int computedIndex = 0; + while (itr.hasNext()) { + int axisIndex = itr.nextIndex(); + int elemIndex = axes.get(axisIndex).indexOf(itr.next()); + if (elemIndex == -1) { + return -1; + } + computedIndex += elemIndex * axesSizeProduct[axisIndex + 1]; + } + return computedIndex; + } + + @Override + public ImmutableList get(final int index) { + checkElementIndex(index, size()); + return new ImmutableList() { + + @Override + public int size() { + return axes.size(); + } + + @Override + public E get(int axis) { + checkElementIndex(axis, size()); + int axisIndex = getAxisIndexForProductIndex(index, axis); + return axes.get(axis).get(axisIndex); + } + + @Override + boolean isPartialView() { + return true; + } + }; + } + + @Override + public int size() { + return axesSizeProduct[0]; + } + + @Override + public boolean contains(Object o) { + return indexOf(o) != -1; + } +} diff --git a/src/main/java/com/google/common/collect/ClassToInstanceMap.java b/src/main/java/com/google/common/collect/ClassToInstanceMap.java new file mode 100644 index 0000000..f403afd --- /dev/null +++ b/src/main/java/com/google/common/collect/ClassToInstanceMap.java @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import com.google.common.annotations.GwtCompatible; + +import java.util.Map; + + +/** + * A map, each entry of which maps a Java raw type to an + * instance of that type. In addition to implementing {@code Map}, the additional type-safe + * operations {@link #putInstance} and {@link #getInstance} are available. + * + *

Like any other {@code Map}, this map may contain entries for primitive types, + * and a primitive type and its corresponding wrapper type may map to different values. + * + *

See the Guava User Guide article on {@code + * ClassToInstanceMap}. + * + *

To map a generic type to an instance of that type, use {@link + * com.google.common.reflect.TypeToInstanceMap} instead. + * + * @param the common supertype that all entries must share; often this is simply {@link Object} + * @author Kevin Bourrillion + * @since 2.0 + */ +@GwtCompatible +public interface ClassToInstanceMap extends Map, B> { + /** + * Returns the value the specified class is mapped to, or {@code null} if no entry for this class + * is present. This will only return a value that was bound to this specific class, not a value + * that may have been bound to a subtype. + */ + T getInstance(Class type); + + /** + * Maps the specified class to the specified value. Does not associate this value with any + * of the class's supertypes. + * + * @return the value previously associated with this class (possibly {@code null}), or {@code + * null} if there was no previous entry. + */ + + T putInstance(Class type, T value); +} diff --git a/src/main/java/com/google/common/collect/CollectCollectors.java b/src/main/java/com/google/common/collect/CollectCollectors.java new file mode 100644 index 0000000..8e038c8 --- /dev/null +++ b/src/main/java/com/google/common/collect/CollectCollectors.java @@ -0,0 +1,134 @@ +/* + * Copyright (C) 2016 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.annotations.GwtCompatible; +import com.google.common.annotations.GwtIncompatible; +import java.util.Comparator; +import java.util.function.Function; +import java.util.stream.Collector; + +/** Collectors utilities for {@code common.collect} internals. */ +@GwtCompatible +final class CollectCollectors { + static Collector> toImmutableBiMap( + Function keyFunction, + Function valueFunction) { + checkNotNull(keyFunction); + checkNotNull(valueFunction); + return Collector.of( + ImmutableBiMap.Builder::new, + (builder, input) -> builder.put(keyFunction.apply(input), valueFunction.apply(input)), + ImmutableBiMap.Builder::combine, + ImmutableBiMap.Builder::build, + new Collector.Characteristics[0]); + } + + private static final Collector> TO_IMMUTABLE_LIST = + Collector.of( + ImmutableList::builder, + ImmutableList.Builder::add, + ImmutableList.Builder::combine, + ImmutableList.Builder::build); + + static Collector> toImmutableList() { + return (Collector) TO_IMMUTABLE_LIST; + } + + static Collector> toImmutableMap( + Function keyFunction, + Function valueFunction) { + checkNotNull(keyFunction); + checkNotNull(valueFunction); + return Collector.of( + ImmutableMap.Builder::new, + (builder, input) -> builder.put(keyFunction.apply(input), valueFunction.apply(input)), + ImmutableMap.Builder::combine, + ImmutableMap.Builder::build); + } + + private static final Collector> TO_IMMUTABLE_SET = + Collector.of( + ImmutableSet::builder, + ImmutableSet.Builder::add, + ImmutableSet.Builder::combine, + ImmutableSet.Builder::build); + + static Collector> toImmutableSet() { + return (Collector) TO_IMMUTABLE_SET; + } + + static Collector> toImmutableSortedMap( + Comparator comparator, + Function keyFunction, + Function valueFunction) { + checkNotNull(comparator); + checkNotNull(keyFunction); + checkNotNull(valueFunction); + /* + * We will always fail if there are duplicate keys, and the keys are always sorted by + * the Comparator, so the entries can come in in arbitrary order -- so we report UNORDERED. + */ + return Collector.of( + () -> new ImmutableSortedMap.Builder(comparator), + (builder, input) -> builder.put(keyFunction.apply(input), valueFunction.apply(input)), + ImmutableSortedMap.Builder::combine, + ImmutableSortedMap.Builder::build, + Collector.Characteristics.UNORDERED); + } + + static Collector> toImmutableSortedSet( + Comparator comparator) { + checkNotNull(comparator); + return Collector.of( + () -> new ImmutableSortedSet.Builder(comparator), + ImmutableSortedSet.Builder::add, + ImmutableSortedSet.Builder::combine, + ImmutableSortedSet.Builder::build); + } + + @GwtIncompatible + private static final Collector, ?, ImmutableRangeSet> + TO_IMMUTABLE_RANGE_SET = + Collector.of( + ImmutableRangeSet::builder, + ImmutableRangeSet.Builder::add, + ImmutableRangeSet.Builder::combine, + ImmutableRangeSet.Builder::build); + + @GwtIncompatible + static > + Collector, ?, ImmutableRangeSet> toImmutableRangeSet() { + return (Collector) TO_IMMUTABLE_RANGE_SET; + } + + @GwtIncompatible + static , V> + Collector> toImmutableRangeMap( + Function> keyFunction, + Function valueFunction) { + checkNotNull(keyFunction); + checkNotNull(valueFunction); + return Collector.of( + ImmutableRangeMap::builder, + (builder, input) -> builder.put(keyFunction.apply(input), valueFunction.apply(input)), + ImmutableRangeMap.Builder::combine, + ImmutableRangeMap.Builder::build); + } +} diff --git a/src/main/java/com/google/common/collect/CollectPreconditions.java b/src/main/java/com/google/common/collect/CollectPreconditions.java new file mode 100644 index 0000000..5d87fc9 --- /dev/null +++ b/src/main/java/com/google/common/collect/CollectPreconditions.java @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2008 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import static com.google.common.base.Preconditions.checkState; + +import com.google.common.annotations.GwtCompatible; + + +/** Precondition checks useful in collection implementations. */ +@GwtCompatible +final class CollectPreconditions { + + static void checkEntryNotNull(Object key, Object value) { + if (key == null) { + throw new NullPointerException("null key in entry: null=" + value); + } else if (value == null) { + throw new NullPointerException("null value in entry: " + key + "=null"); + } + } + + + static int checkNonnegative(int value, String name) { + if (value < 0) { + throw new IllegalArgumentException(name + " cannot be negative but was: " + value); + } + return value; + } + + + static long checkNonnegative(long value, String name) { + if (value < 0) { + throw new IllegalArgumentException(name + " cannot be negative but was: " + value); + } + return value; + } + + static void checkPositive(int value, String name) { + if (value <= 0) { + throw new IllegalArgumentException(name + " must be positive but was: " + value); + } + } + + /** + * Precondition tester for {@code Iterator.remove()} that throws an exception with a consistent + * error message. + */ + static void checkRemove(boolean canRemove) { + checkState(canRemove, "no calls to next() since the last call to remove()"); + } +} diff --git a/src/main/java/com/google/common/collect/CollectSpliterators.java b/src/main/java/com/google/common/collect/CollectSpliterators.java new file mode 100644 index 0000000..b2541b2 --- /dev/null +++ b/src/main/java/com/google/common/collect/CollectSpliterators.java @@ -0,0 +1,534 @@ +/* + * Copyright (C) 2015 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.annotations.GwtCompatible; + +import java.util.Comparator; +import java.util.Spliterator; +import java.util.function.Consumer; +import java.util.function.DoubleConsumer; +import java.util.function.Function; +import java.util.function.IntConsumer; +import java.util.function.IntFunction; +import java.util.function.LongConsumer; +import java.util.function.Predicate; +import java.util.stream.IntStream; + + +/** Spliterator utilities for {@code common.collect} internals. */ +@GwtCompatible +final class CollectSpliterators { + private CollectSpliterators() {} + + static Spliterator indexed(int size, int extraCharacteristics, IntFunction function) { + return indexed(size, extraCharacteristics, function, null); + } + + static Spliterator indexed( + int size, + int extraCharacteristics, + IntFunction function, + Comparator comparator) { + if (comparator != null) { + checkArgument((extraCharacteristics & Spliterator.SORTED) != 0); + } + class WithCharacteristics implements Spliterator { + private final Spliterator.OfInt delegate; + + WithCharacteristics(Spliterator.OfInt delegate) { + this.delegate = delegate; + } + + @Override + public boolean tryAdvance(Consumer action) { + return delegate.tryAdvance((IntConsumer) i -> action.accept(function.apply(i))); + } + + @Override + public void forEachRemaining(Consumer action) { + delegate.forEachRemaining((IntConsumer) i -> action.accept(function.apply(i))); + } + + @Override + public Spliterator trySplit() { + Spliterator.OfInt split = delegate.trySplit(); + return (split == null) ? null : new WithCharacteristics(split); + } + + @Override + public long estimateSize() { + return delegate.estimateSize(); + } + + @Override + public int characteristics() { + return Spliterator.ORDERED + | Spliterator.SIZED + | Spliterator.SUBSIZED + | extraCharacteristics; + } + + @Override + public Comparator getComparator() { + if (hasCharacteristics(Spliterator.SORTED)) { + return comparator; + } else { + throw new IllegalStateException(); + } + } + } + return new WithCharacteristics(IntStream.range(0, size).spliterator()); + } + + /** + * Returns a {@code Spliterator} over the elements of {@code fromSpliterator} mapped by {@code + * function}. + */ + static Spliterator map( + Spliterator fromSpliterator, + Function function) { + checkNotNull(fromSpliterator); + checkNotNull(function); + return new Spliterator() { + + @Override + public boolean tryAdvance(Consumer action) { + return fromSpliterator.tryAdvance( + fromElement -> action.accept(function.apply(fromElement))); + } + + @Override + public void forEachRemaining(Consumer action) { + fromSpliterator.forEachRemaining(fromElement -> action.accept(function.apply(fromElement))); + } + + @Override + public Spliterator trySplit() { + Spliterator fromSplit = fromSpliterator.trySplit(); + return (fromSplit != null) ? map(fromSplit, function) : null; + } + + @Override + public long estimateSize() { + return fromSpliterator.estimateSize(); + } + + @Override + public int characteristics() { + return fromSpliterator.characteristics() + & ~(Spliterator.DISTINCT | Spliterator.NONNULL | Spliterator.SORTED); + } + }; + } + + /** Returns a {@code Spliterator} filtered by the specified predicate. */ + static Spliterator filter(Spliterator fromSpliterator, Predicate predicate) { + checkNotNull(fromSpliterator); + checkNotNull(predicate); + class Splitr implements Spliterator, Consumer { + T holder = null; + + @Override + public void accept(T t) { + this.holder = t; + } + + @Override + public boolean tryAdvance(Consumer action) { + while (fromSpliterator.tryAdvance(this)) { + try { + if (predicate.test(holder)) { + action.accept(holder); + return true; + } + } finally { + holder = null; + } + } + return false; + } + + @Override + public Spliterator trySplit() { + Spliterator fromSplit = fromSpliterator.trySplit(); + return (fromSplit == null) ? null : filter(fromSplit, predicate); + } + + @Override + public long estimateSize() { + return fromSpliterator.estimateSize() / 2; + } + + @Override + public Comparator getComparator() { + return fromSpliterator.getComparator(); + } + + @Override + public int characteristics() { + return fromSpliterator.characteristics() + & (Spliterator.DISTINCT + | Spliterator.NONNULL + | Spliterator.ORDERED + | Spliterator.SORTED); + } + } + return new Splitr(); + } + + /** + * Returns a {@code Spliterator} that iterates over the elements of the spliterators generated by + * applying {@code function} to the elements of {@code fromSpliterator}. + */ + static Spliterator flatMap( + Spliterator fromSpliterator, + Function> function, + int topCharacteristics, + long topSize) { + checkArgument( + (topCharacteristics & Spliterator.SUBSIZED) == 0, + "flatMap does not support SUBSIZED characteristic"); + checkArgument( + (topCharacteristics & Spliterator.SORTED) == 0, + "flatMap does not support SORTED characteristic"); + checkNotNull(fromSpliterator); + checkNotNull(function); + return new FlatMapSpliteratorOfObject( + null, fromSpliterator, function, topCharacteristics, topSize); + } + + /** + * Returns a {@code Spliterator.OfInt} that iterates over the elements of the spliterators + * generated by applying {@code function} to the elements of {@code fromSpliterator}. (If {@code + * function} returns {@code null} for an input, it is replaced with an empty stream.) + */ + static Spliterator.OfInt flatMapToInt( + Spliterator fromSpliterator, + Function function, + int topCharacteristics, + long topSize) { + checkArgument( + (topCharacteristics & Spliterator.SUBSIZED) == 0, + "flatMap does not support SUBSIZED characteristic"); + checkArgument( + (topCharacteristics & Spliterator.SORTED) == 0, + "flatMap does not support SORTED characteristic"); + checkNotNull(fromSpliterator); + checkNotNull(function); + return new FlatMapSpliteratorOfInt( + null, fromSpliterator, function, topCharacteristics, topSize); + } + + /** + * Returns a {@code Spliterator.OfLong} that iterates over the elements of the spliterators + * generated by applying {@code function} to the elements of {@code fromSpliterator}. (If {@code + * function} returns {@code null} for an input, it is replaced with an empty stream.) + */ + static Spliterator.OfLong flatMapToLong( + Spliterator fromSpliterator, + Function function, + int topCharacteristics, + long topSize) { + checkArgument( + (topCharacteristics & Spliterator.SUBSIZED) == 0, + "flatMap does not support SUBSIZED characteristic"); + checkArgument( + (topCharacteristics & Spliterator.SORTED) == 0, + "flatMap does not support SORTED characteristic"); + checkNotNull(fromSpliterator); + checkNotNull(function); + return new FlatMapSpliteratorOfLong( + null, fromSpliterator, function, topCharacteristics, topSize); + } + + /** + * Returns a {@code Spliterator.OfDouble} that iterates over the elements of the spliterators + * generated by applying {@code function} to the elements of {@code fromSpliterator}. (If {@code + * function} returns {@code null} for an input, it is replaced with an empty stream.) + */ + static Spliterator.OfDouble flatMapToDouble( + Spliterator fromSpliterator, + Function function, + int topCharacteristics, + long topSize) { + checkArgument( + (topCharacteristics & Spliterator.SUBSIZED) == 0, + "flatMap does not support SUBSIZED characteristic"); + checkArgument( + (topCharacteristics & Spliterator.SORTED) == 0, + "flatMap does not support SORTED characteristic"); + checkNotNull(fromSpliterator); + checkNotNull(function); + return new FlatMapSpliteratorOfDouble( + null, fromSpliterator, function, topCharacteristics, topSize); + } + + /** + * Implements the {@link Stream#flatMap} operation on spliterators. + * + * @param the element type of the input spliterator + * @param the element type of the output spliterators + * @param the type of the output spliterators + */ + abstract static class FlatMapSpliterator< + InElementT, OutElementT, OutSpliteratorT extends Spliterator> + implements Spliterator { + /** Factory for constructing {@link FlatMapSpliterator} instances. */ + @FunctionalInterface + interface Factory> { + OutSpliteratorT newFlatMapSpliterator( + OutSpliteratorT prefix, + Spliterator fromSplit, + Function function, + int splitCharacteristics, + long estSplitSize); + } + + OutSpliteratorT prefix; + final Spliterator from; + final Function function; + final Factory factory; + int characteristics; + long estimatedSize; + + FlatMapSpliterator( + OutSpliteratorT prefix, + Spliterator from, + Function function, + Factory factory, + int characteristics, + long estimatedSize) { + this.prefix = prefix; + this.from = from; + this.function = function; + this.factory = factory; + this.characteristics = characteristics; + this.estimatedSize = estimatedSize; + } + + /* + * The tryAdvance and forEachRemaining in FlatMapSpliteratorOfPrimitive are overloads of these + * methods, not overrides. They are annotated @Override because they implement methods from + * Spliterator.OfPrimitive (and override default implementations from Spliterator.OfPrimitive or + * a subtype like Spliterator.OfInt). + */ + + @Override + public final boolean tryAdvance(Consumer action) { + while (true) { + if (prefix != null && prefix.tryAdvance(action)) { + if (estimatedSize != Long.MAX_VALUE) { + estimatedSize--; + } + return true; + } else { + prefix = null; + } + if (!from.tryAdvance(fromElement -> prefix = function.apply(fromElement))) { + return false; + } + } + } + + @Override + public final void forEachRemaining(Consumer action) { + if (prefix != null) { + prefix.forEachRemaining(action); + prefix = null; + } + from.forEachRemaining( + fromElement -> { + Spliterator elements = function.apply(fromElement); + if (elements != null) { + elements.forEachRemaining(action); + } + }); + estimatedSize = 0; + } + + @Override + public final OutSpliteratorT trySplit() { + Spliterator fromSplit = from.trySplit(); + if (fromSplit != null) { + int splitCharacteristics = characteristics & ~Spliterator.SIZED; + long estSplitSize = estimateSize(); + if (estSplitSize < Long.MAX_VALUE) { + estSplitSize /= 2; + this.estimatedSize -= estSplitSize; + this.characteristics = splitCharacteristics; + } + OutSpliteratorT result = + factory.newFlatMapSpliterator( + this.prefix, fromSplit, function, splitCharacteristics, estSplitSize); + this.prefix = null; + return result; + } else if (prefix != null) { + OutSpliteratorT result = prefix; + this.prefix = null; + return result; + } else { + return null; + } + } + + @Override + public final long estimateSize() { + if (prefix != null) { + estimatedSize = Math.max(estimatedSize, prefix.estimateSize()); + } + return Math.max(estimatedSize, 0); + } + + @Override + public final int characteristics() { + return characteristics; + } + } + + /** + * Implementation of {@link Stream#flatMap} with an object spliterator output type. + * + *

To avoid having this type, we could use {@code FlatMapSpliterator} directly. The main + * advantages to having the type are the ability to use its constructor reference below and the + * parallelism with the primitive version. In short, it makes its caller ({@code flatMap}) + * simpler. + * + * @param the element type of the input spliterator + * @param the element type of the output spliterators + */ + static final class FlatMapSpliteratorOfObject + extends FlatMapSpliterator> { + FlatMapSpliteratorOfObject( + Spliterator prefix, + Spliterator from, + Function> function, + int characteristics, + long estimatedSize) { + super( + prefix, from, function, FlatMapSpliteratorOfObject::new, characteristics, estimatedSize); + } + } + + /** + * Implementation of {@link Stream#flatMap} with a primitive spliterator output type. + * + * @param the element type of the input spliterator + * @param the (boxed) element type of the output spliterators + * @param the specialized consumer type for the primitive output type + * @param the primitive spliterator type associated with {@code OutElementT} + */ + abstract static class FlatMapSpliteratorOfPrimitive< + InElementT, + OutElementT, + OutConsumerT, + OutSpliteratorT extends + Spliterator.OfPrimitive> + extends FlatMapSpliterator + implements Spliterator.OfPrimitive { + + FlatMapSpliteratorOfPrimitive( + OutSpliteratorT prefix, + Spliterator from, + Function function, + Factory factory, + int characteristics, + long estimatedSize) { + super(prefix, from, function, factory, characteristics, estimatedSize); + } + + @Override + public final boolean tryAdvance(OutConsumerT action) { + while (true) { + if (prefix != null && prefix.tryAdvance(action)) { + if (estimatedSize != Long.MAX_VALUE) { + estimatedSize--; + } + return true; + } else { + prefix = null; + } + if (!from.tryAdvance(fromElement -> prefix = function.apply(fromElement))) { + return false; + } + } + } + + @Override + public final void forEachRemaining(OutConsumerT action) { + if (prefix != null) { + prefix.forEachRemaining(action); + prefix = null; + } + from.forEachRemaining( + fromElement -> { + OutSpliteratorT elements = function.apply(fromElement); + if (elements != null) { + elements.forEachRemaining(action); + } + }); + estimatedSize = 0; + } + } + + /** Implementation of {@link #flatMapToInt}. */ + static final class FlatMapSpliteratorOfInt + extends FlatMapSpliteratorOfPrimitive + implements Spliterator.OfInt { + FlatMapSpliteratorOfInt( + Spliterator.OfInt prefix, + Spliterator from, + Function function, + int characteristics, + long estimatedSize) { + super(prefix, from, function, FlatMapSpliteratorOfInt::new, characteristics, estimatedSize); + } + } + + /** Implementation of {@link #flatMapToLong}. */ + static final class FlatMapSpliteratorOfLong + extends FlatMapSpliteratorOfPrimitive + implements Spliterator.OfLong { + FlatMapSpliteratorOfLong( + Spliterator.OfLong prefix, + Spliterator from, + Function function, + int characteristics, + long estimatedSize) { + super(prefix, from, function, FlatMapSpliteratorOfLong::new, characteristics, estimatedSize); + } + } + + /** Implementation of {@link #flatMapToDouble}. */ + static final class FlatMapSpliteratorOfDouble + extends FlatMapSpliteratorOfPrimitive< + InElementT, Double, DoubleConsumer, Spliterator.OfDouble> + implements Spliterator.OfDouble { + FlatMapSpliteratorOfDouble( + Spliterator.OfDouble prefix, + Spliterator from, + Function function, + int characteristics, + long estimatedSize) { + super( + prefix, from, function, FlatMapSpliteratorOfDouble::new, characteristics, estimatedSize); + } + } +} diff --git a/src/main/java/com/google/common/collect/Collections2.java b/src/main/java/com/google/common/collect/Collections2.java new file mode 100644 index 0000000..9b8fca6 --- /dev/null +++ b/src/main/java/com/google/common/collect/Collections2.java @@ -0,0 +1,694 @@ +/* + * Copyright (C) 2008 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.collect.CollectPreconditions.checkNonnegative; + +import com.google.common.annotations.Beta; +import com.google.common.annotations.GwtCompatible; +import com.google.common.base.Function; +import com.google.common.base.Predicate; +import com.google.common.base.Predicates; +import com.google.common.math.IntMath; +import com.google.common.primitives.Ints; +import java.util.AbstractCollection; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.Iterator; +import java.util.List; +import java.util.Spliterator; +import java.util.function.Consumer; + + +/** + * Provides static methods for working with {@code Collection} instances. + * + *

Java 8 users: several common uses for this class are now more comprehensively addressed + * by the new {@link java.util.stream.Stream} library. Read the method documentation below for + * comparisons. These methods are not being deprecated, but we gently encourage you to migrate to + * streams. + * + * @author Chris Povirk + * @author Mike Bostock + * @author Jared Levy + * @since 2.0 + */ +@GwtCompatible +public final class Collections2 { + private Collections2() {} + + /** + * Returns the elements of {@code unfiltered} that satisfy a predicate. The returned collection is + * a live view of {@code unfiltered}; changes to one affect the other. + * + *

The resulting collection's iterator does not support {@code remove()}, but all other + * collection methods are supported. When given an element that doesn't satisfy the predicate, the + * collection's {@code add()} and {@code addAll()} methods throw an {@link + * IllegalArgumentException}. When methods such as {@code removeAll()} and {@code clear()} are + * called on the filtered collection, only elements that satisfy the filter will be removed from + * the underlying collection. + * + *

The returned collection isn't threadsafe or serializable, even if {@code unfiltered} is. + * + *

Many of the filtered collection's methods, such as {@code size()}, iterate across every + * element in the underlying collection and determine which elements satisfy the filter. When a + * live view is not needed, it may be faster to copy {@code Iterables.filter(unfiltered, + * predicate)} and use the copy. + * + *

Warning: {@code predicate} must be consistent with equals, as documented at + * {@link Predicate#apply}. Do not provide a predicate such as {@code + * Predicates.instanceOf(ArrayList.class)}, which is inconsistent with equals. (See {@link + * Iterables#filter(Iterable, Class)} for related functionality.) + * + *

{@code Stream} equivalent: {@link java.util.stream.Stream#filter Stream.filter}. + */ + // TODO(kevinb): how can we omit that Iterables link when building gwt + // javadoc? + public static Collection filter(Collection unfiltered, Predicate predicate) { + if (unfiltered instanceof FilteredCollection) { + // Support clear(), removeAll(), and retainAll() when filtering a filtered + // collection. + return ((FilteredCollection) unfiltered).createCombined(predicate); + } + + return new FilteredCollection(checkNotNull(unfiltered), checkNotNull(predicate)); + } + + /** + * Delegates to {@link Collection#contains}. Returns {@code false} if the {@code contains} method + * throws a {@code ClassCastException} or {@code NullPointerException}. + */ + static boolean safeContains(Collection collection, Object object) { + checkNotNull(collection); + try { + return collection.contains(object); + } catch (ClassCastException | NullPointerException e) { + return false; + } + } + + /** + * Delegates to {@link Collection#remove}. Returns {@code false} if the {@code remove} method + * throws a {@code ClassCastException} or {@code NullPointerException}. + */ + static boolean safeRemove(Collection collection, Object object) { + checkNotNull(collection); + try { + return collection.remove(object); + } catch (ClassCastException | NullPointerException e) { + return false; + } + } + + static class FilteredCollection extends AbstractCollection { + final Collection unfiltered; + final Predicate predicate; + + FilteredCollection(Collection unfiltered, Predicate predicate) { + this.unfiltered = unfiltered; + this.predicate = predicate; + } + + FilteredCollection createCombined(Predicate newPredicate) { + return new FilteredCollection(unfiltered, Predicates.and(predicate, newPredicate)); + // . above needed to compile in JDK 5 + } + + @Override + public boolean add(E element) { + checkArgument(predicate.apply(element)); + return unfiltered.add(element); + } + + @Override + public boolean addAll(Collection collection) { + for (E element : collection) { + checkArgument(predicate.apply(element)); + } + return unfiltered.addAll(collection); + } + + @Override + public void clear() { + Iterables.removeIf(unfiltered, predicate); + } + + @Override + public boolean contains(Object element) { + if (safeContains(unfiltered, element)) { + @SuppressWarnings("unchecked") // element is in unfiltered, so it must be an E + E e = (E) element; + return predicate.apply(e); + } + return false; + } + + @Override + public boolean containsAll(Collection collection) { + return containsAllImpl(this, collection); + } + + @Override + public boolean isEmpty() { + return !Iterables.any(unfiltered, predicate); + } + + @Override + public Iterator iterator() { + return Iterators.filter(unfiltered.iterator(), predicate); + } + + @Override + public Spliterator spliterator() { + return CollectSpliterators.filter(unfiltered.spliterator(), predicate); + } + + @Override + public void forEach(Consumer action) { + checkNotNull(action); + unfiltered.forEach( + (E e) -> { + if (predicate.test(e)) { + action.accept(e); + } + }); + } + + @Override + public boolean remove(Object element) { + return contains(element) && unfiltered.remove(element); + } + + @Override + public boolean removeAll(final Collection collection) { + return removeIf(collection::contains); + } + + @Override + public boolean retainAll(final Collection collection) { + return removeIf(element -> !collection.contains(element)); + } + + @Override + public boolean removeIf(java.util.function.Predicate filter) { + checkNotNull(filter); + return unfiltered.removeIf(element -> predicate.apply(element) && filter.test(element)); + } + + @Override + public int size() { + int size = 0; + for (E e : unfiltered) { + if (predicate.apply(e)) { + size++; + } + } + return size; + } + + @Override + public Object[] toArray() { + // creating an ArrayList so filtering happens once + return Lists.newArrayList(iterator()).toArray(); + } + + @Override + public T[] toArray(T[] array) { + return Lists.newArrayList(iterator()).toArray(array); + } + } + + /** + * Returns a collection that applies {@code function} to each element of {@code fromCollection}. + * The returned collection is a live view of {@code fromCollection}; changes to one affect the + * other. + * + *

The returned collection's {@code add()} and {@code addAll()} methods throw an {@link + * UnsupportedOperationException}. All other collection methods are supported, as long as {@code + * fromCollection} supports them. + * + *

The returned collection isn't threadsafe or serializable, even if {@code fromCollection} is. + * + *

When a live view is not needed, it may be faster to copy the transformed collection + * and use the copy. + * + *

If the input {@code Collection} is known to be a {@code List}, consider {@link + * Lists#transform}. If only an {@code Iterable} is available, use {@link Iterables#transform}. + * + *

{@code Stream} equivalent: {@link java.util.stream.Stream#map Stream.map}. + */ + public static Collection transform( + Collection fromCollection, Function function) { + return new TransformedCollection<>(fromCollection, function); + } + + static class TransformedCollection extends AbstractCollection { + final Collection fromCollection; + final Function function; + + TransformedCollection(Collection fromCollection, Function function) { + this.fromCollection = checkNotNull(fromCollection); + this.function = checkNotNull(function); + } + + @Override + public void clear() { + fromCollection.clear(); + } + + @Override + public boolean isEmpty() { + return fromCollection.isEmpty(); + } + + @Override + public Iterator iterator() { + return Iterators.transform(fromCollection.iterator(), function); + } + + @Override + public Spliterator spliterator() { + return CollectSpliterators.map(fromCollection.spliterator(), function); + } + + @Override + public void forEach(Consumer action) { + checkNotNull(action); + fromCollection.forEach((F f) -> action.accept(function.apply(f))); + } + + @Override + public boolean removeIf(java.util.function.Predicate filter) { + checkNotNull(filter); + return fromCollection.removeIf(element -> filter.test(function.apply(element))); + } + + @Override + public int size() { + return fromCollection.size(); + } + } + + /** + * Returns {@code true} if the collection {@code self} contains all of the elements in the + * collection {@code c}. + * + *

This method iterates over the specified collection {@code c}, checking each element returned + * by the iterator in turn to see if it is contained in the specified collection {@code self}. If + * all elements are so contained, {@code true} is returned, otherwise {@code false}. + * + * @param self a collection which might contain all elements in {@code c} + * @param c a collection whose elements might be contained by {@code self} + */ + static boolean containsAllImpl(Collection self, Collection c) { + for (Object o : c) { + if (!self.contains(o)) { + return false; + } + } + return true; + } + + /** An implementation of {@link Collection#toString()}. */ + static String toStringImpl(final Collection collection) { + StringBuilder sb = newStringBuilderForCollection(collection.size()).append('['); + boolean first = true; + for (Object o : collection) { + if (!first) { + sb.append(", "); + } + first = false; + if (o == collection) { + sb.append("(this Collection)"); + } else { + sb.append(o); + } + } + return sb.append(']').toString(); + } + + /** Returns best-effort-sized StringBuilder based on the given collection size. */ + static StringBuilder newStringBuilderForCollection(int size) { + checkNonnegative(size, "size"); + return new StringBuilder((int) Math.min(size * 8L, Ints.MAX_POWER_OF_TWO)); + } + + /** Used to avoid http://bugs.sun.com/view_bug.do?bug_id=6558557 */ + static Collection cast(Iterable iterable) { + return (Collection) iterable; + } + + /** + * Returns a {@link Collection} of all the permutations of the specified {@link Iterable}. + * + *

Notes: This is an implementation of the algorithm for Lexicographical Permutations + * Generation, described in Knuth's "The Art of Computer Programming", Volume 4, Chapter 7, + * Section 7.2.1.2. The iteration order follows the lexicographical order. This means that the + * first permutation will be in ascending order, and the last will be in descending order. + * + *

Duplicate elements are considered equal. For example, the list [1, 1] will have only one + * permutation, instead of two. This is why the elements have to implement {@link Comparable}. + * + *

An empty iterable has only one permutation, which is an empty list. + * + *

This method is equivalent to {@code Collections2.orderedPermutations(list, + * Ordering.natural())}. + * + * @param elements the original iterable whose elements have to be permuted. + * @return an immutable {@link Collection} containing all the different permutations of the + * original iterable. + * @throws NullPointerException if the specified iterable is null or has any null elements. + * @since 12.0 + */ + @Beta + public static > Collection> orderedPermutations( + Iterable elements) { + return orderedPermutations(elements, Ordering.natural()); + } + + /** + * Returns a {@link Collection} of all the permutations of the specified {@link Iterable} using + * the specified {@link Comparator} for establishing the lexicographical ordering. + * + *

Examples: + * + *

{@code
+   * for (List perm : orderedPermutations(asList("b", "c", "a"))) {
+   *   println(perm);
+   * }
+   * // -> ["a", "b", "c"]
+   * // -> ["a", "c", "b"]
+   * // -> ["b", "a", "c"]
+   * // -> ["b", "c", "a"]
+   * // -> ["c", "a", "b"]
+   * // -> ["c", "b", "a"]
+   *
+   * for (List perm : orderedPermutations(asList(1, 2, 2, 1))) {
+   *   println(perm);
+   * }
+   * // -> [1, 1, 2, 2]
+   * // -> [1, 2, 1, 2]
+   * // -> [1, 2, 2, 1]
+   * // -> [2, 1, 1, 2]
+   * // -> [2, 1, 2, 1]
+   * // -> [2, 2, 1, 1]
+   * }
+ * + *

Notes: This is an implementation of the algorithm for Lexicographical Permutations + * Generation, described in Knuth's "The Art of Computer Programming", Volume 4, Chapter 7, + * Section 7.2.1.2. The iteration order follows the lexicographical order. This means that the + * first permutation will be in ascending order, and the last will be in descending order. + * + *

Elements that compare equal are considered equal and no new permutations are created by + * swapping them. + * + *

An empty iterable has only one permutation, which is an empty list. + * + * @param elements the original iterable whose elements have to be permuted. + * @param comparator a comparator for the iterable's elements. + * @return an immutable {@link Collection} containing all the different permutations of the + * original iterable. + * @throws NullPointerException If the specified iterable is null, has any null elements, or if + * the specified comparator is null. + * @since 12.0 + */ + @Beta + public static Collection> orderedPermutations( + Iterable elements, Comparator comparator) { + return new OrderedPermutationCollection(elements, comparator); + } + + private static final class OrderedPermutationCollection extends AbstractCollection> { + final ImmutableList inputList; + final Comparator comparator; + final int size; + + OrderedPermutationCollection(Iterable input, Comparator comparator) { + this.inputList = ImmutableList.sortedCopyOf(comparator, input); + this.comparator = comparator; + this.size = calculateSize(inputList, comparator); + } + + /** + * The number of permutations with repeated elements is calculated as follows: + * + *

    + *
  • For an empty list, it is 1 (base case). + *
  • When r numbers are added to a list of n-r elements, the number of permutations is + * increased by a factor of (n choose r). + *
+ */ + private static int calculateSize( + List sortedInputList, Comparator comparator) { + int permutations = 1; + int n = 1; + int r = 1; + while (n < sortedInputList.size()) { + int comparison = comparator.compare(sortedInputList.get(n - 1), sortedInputList.get(n)); + if (comparison < 0) { + // We move to the next non-repeated element. + permutations = IntMath.saturatedMultiply(permutations, IntMath.binomial(n, r)); + r = 0; + if (permutations == Integer.MAX_VALUE) { + return Integer.MAX_VALUE; + } + } + n++; + r++; + } + return IntMath.saturatedMultiply(permutations, IntMath.binomial(n, r)); + } + + @Override + public int size() { + return size; + } + + @Override + public boolean isEmpty() { + return false; + } + + @Override + public Iterator> iterator() { + return new OrderedPermutationIterator(inputList, comparator); + } + + @Override + public boolean contains(Object obj) { + if (obj instanceof List) { + List list = (List) obj; + return isPermutation(inputList, list); + } + return false; + } + + @Override + public String toString() { + return "orderedPermutationCollection(" + inputList + ")"; + } + } + + private static final class OrderedPermutationIterator extends AbstractIterator> { + List nextPermutation; + final Comparator comparator; + + OrderedPermutationIterator(List list, Comparator comparator) { + this.nextPermutation = Lists.newArrayList(list); + this.comparator = comparator; + } + + @Override + protected List computeNext() { + if (nextPermutation == null) { + return endOfData(); + } + ImmutableList next = ImmutableList.copyOf(nextPermutation); + calculateNextPermutation(); + return next; + } + + void calculateNextPermutation() { + int j = findNextJ(); + if (j == -1) { + nextPermutation = null; + return; + } + + int l = findNextL(j); + Collections.swap(nextPermutation, j, l); + int n = nextPermutation.size(); + Collections.reverse(nextPermutation.subList(j + 1, n)); + } + + int findNextJ() { + for (int k = nextPermutation.size() - 2; k >= 0; k--) { + if (comparator.compare(nextPermutation.get(k), nextPermutation.get(k + 1)) < 0) { + return k; + } + } + return -1; + } + + int findNextL(int j) { + E ak = nextPermutation.get(j); + for (int l = nextPermutation.size() - 1; l > j; l--) { + if (comparator.compare(ak, nextPermutation.get(l)) < 0) { + return l; + } + } + throw new AssertionError("this statement should be unreachable"); + } + } + + /** + * Returns a {@link Collection} of all the permutations of the specified {@link Collection}. + * + *

Notes: This is an implementation of the Plain Changes algorithm for permutations + * generation, described in Knuth's "The Art of Computer Programming", Volume 4, Chapter 7, + * Section 7.2.1.2. + * + *

If the input list contains equal elements, some of the generated permutations will be equal. + * + *

An empty collection has only one permutation, which is an empty list. + * + * @param elements the original collection whose elements have to be permuted. + * @return an immutable {@link Collection} containing all the different permutations of the + * original collection. + * @throws NullPointerException if the specified collection is null or has any null elements. + * @since 12.0 + */ + @Beta + public static Collection> permutations(Collection elements) { + return new PermutationCollection(ImmutableList.copyOf(elements)); + } + + private static final class PermutationCollection extends AbstractCollection> { + final ImmutableList inputList; + + PermutationCollection(ImmutableList input) { + this.inputList = input; + } + + @Override + public int size() { + return IntMath.factorial(inputList.size()); + } + + @Override + public boolean isEmpty() { + return false; + } + + @Override + public Iterator> iterator() { + return new PermutationIterator(inputList); + } + + @Override + public boolean contains(Object obj) { + if (obj instanceof List) { + List list = (List) obj; + return isPermutation(inputList, list); + } + return false; + } + + @Override + public String toString() { + return "permutations(" + inputList + ")"; + } + } + + private static class PermutationIterator extends AbstractIterator> { + final List list; + final int[] c; + final int[] o; + int j; + + PermutationIterator(List list) { + this.list = new ArrayList(list); + int n = list.size(); + c = new int[n]; + o = new int[n]; + Arrays.fill(c, 0); + Arrays.fill(o, 1); + j = Integer.MAX_VALUE; + } + + @Override + protected List computeNext() { + if (j <= 0) { + return endOfData(); + } + ImmutableList next = ImmutableList.copyOf(list); + calculateNextPermutation(); + return next; + } + + void calculateNextPermutation() { + j = list.size() - 1; + int s = 0; + + // Handle the special case of an empty list. Skip the calculation of the + // next permutation. + if (j == -1) { + return; + } + + while (true) { + int q = c[j] + o[j]; + if (q < 0) { + switchDirection(); + continue; + } + if (q == j + 1) { + if (j == 0) { + break; + } + s++; + switchDirection(); + continue; + } + + Collections.swap(list, j - c[j] + s, j - q + s); + c[j] = q; + break; + } + } + + void switchDirection() { + o[j] = -o[j]; + j--; + } + } + + /** Returns {@code true} if the second list is a permutation of the first. */ + private static boolean isPermutation(List first, List second) { + if (first.size() != second.size()) { + return false; + } + Multiset firstMultiset = HashMultiset.create(first); + Multiset secondMultiset = HashMultiset.create(second); + return firstMultiset.equals(secondMultiset); + } +} diff --git a/src/main/java/com/google/common/collect/CompactHashMap.java b/src/main/java/com/google/common/collect/CompactHashMap.java new file mode 100644 index 0000000..fd59eaf --- /dev/null +++ b/src/main/java/com/google/common/collect/CompactHashMap.java @@ -0,0 +1,881 @@ +/* + * Copyright (C) 2012 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.collect.CollectPreconditions.checkRemove; +import static com.google.common.collect.CompactHashing.UNSET; +import static com.google.common.collect.Hashing.smearedHash; + +import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.Objects; +import com.google.common.base.Preconditions; + + +import java.io.IOException; +import java.io.InvalidObjectException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.Serializable; +import java.util.AbstractMap; +import java.util.Arrays; +import java.util.Collection; +import java.util.ConcurrentModificationException; +import java.util.Iterator; +import java.util.Map; +import java.util.NoSuchElementException; +import java.util.Set; +import java.util.Spliterator; +import java.util.Spliterators; +import java.util.function.BiConsumer; +import java.util.function.BiFunction; +import java.util.function.Consumer; + + + +/** + * CompactHashMap is an implementation of a Map. All optional operations (put and remove) are + * supported. Null keys and values are supported. + * + *

{@code containsKey(k)}, {@code put(k, v)} and {@code remove(k)} are all (expected and + * amortized) constant time operations. Expected in the hashtable sense (depends on the hash + * function doing a good job of distributing the elements to the buckets to a distribution not far + * from uniform), and amortized since some operations can trigger a hash table resize. + * + *

Unlike {@code java.util.HashMap}, iteration is only proportional to the actual {@code size()}, + * which is optimal, and not the size of the internal hashtable, which could be much larger + * than {@code size()}. Furthermore, this structure places significantly reduced load on the garbage + * collector by only using a constant number of internal objects. + * + *

If there are no removals, then iteration order for the {@link #entrySet}, {@link #keySet}, and + * {@link #values} views is the same as insertion order. Any removal invalidates any ordering + * guarantees. + * + *

This class should not be assumed to be universally superior to {@code java.util.HashMap}. + * Generally speaking, this class reduces object allocation and memory consumption at the price of + * moderately increased constant factors of CPU. Only use this class when there is a specific reason + * to prioritize memory over CPU. + * + * @author Louis Wasserman + * @author Jon Noack + */ +@GwtIncompatible // not worth using in GWT for now +class CompactHashMap extends AbstractMap implements Serializable { + /* + * TODO: Make this a drop-in replacement for j.u. versions, actually drop them in, and test the + * world. Figure out what sort of space-time tradeoff we're actually going to get here with the + * *Map variants. This class is particularly hard to benchmark, because the benefit is not only in + * less allocation, but also having the GC do less work to scan the heap because of fewer + * references, which is particularly hard to quantify. + */ + + /** Creates an empty {@code CompactHashMap} instance. */ + public static CompactHashMap create() { + return new CompactHashMap<>(); + } + + /** + * Creates a {@code CompactHashMap} instance, with a high enough "initial capacity" that it + * should hold {@code expectedSize} elements without growth. + * + * @param expectedSize the number of elements you expect to add to the returned set + * @return a new, empty {@code CompactHashMap} with enough capacity to hold {@code expectedSize} + * elements without resizing + * @throws IllegalArgumentException if {@code expectedSize} is negative + */ + public static CompactHashMap createWithExpectedSize(int expectedSize) { + return new CompactHashMap<>(expectedSize); + } + + private static final Object NOT_FOUND = new Object(); + + /** + * The hashtable. Its values are indexes to the keys, values, and entries arrays. + * + *

Currently, the UNSET value means "null pointer", and any positive value x is the actual + * index + 1. + * + *

Its size must be a power of two. + */ + private transient Object table; + + /** + * Contains the logical entries, in the range of [0, size()). The high bits of each int are the + * part of the smeared hash of the key not covered by the hashtable mask, whereas the low bits are + * the "next" pointer (pointing to the next entry in the bucket chain), which will always be less + * than or equal to the hashtable mask. + * + *

+   * hash  = aaaaaaaa
+   * mask  = 0000ffff
+   * next  = 0000bbbb
+   * entry = aaaabbbb
+   * 
+ * + *

The pointers in [size(), entries.length) are all "null" (UNSET). + */ + @VisibleForTesting transient int [] entries; + + /** + * The keys of the entries in the map, in the range of [0, size()). The keys in [size(), + * keys.length) are all {@code null}. + */ + @VisibleForTesting transient Object [] keys; + + /** + * The values of the entries in the map, in the range of [0, size()). The values in [size(), + * values.length) are all {@code null}. + */ + @VisibleForTesting transient Object [] values; + + /** + * Keeps track of metadata like the number of hash table bits and modifications of this data + * structure (to make it possible to throw ConcurrentModificationException in the iterator). Note + * that we choose not to make this volatile, so we do less of a "best effort" to track such + * errors, for better performance. + */ + private transient int metadata; + + /** The number of elements contained in the set. */ + private transient int size; + + /** Constructs a new empty instance of {@code CompactHashMap}. */ + CompactHashMap() { + init(CompactHashing.DEFAULT_SIZE); + } + + /** + * Constructs a new instance of {@code CompactHashMap} with the specified capacity. + * + * @param expectedSize the initial capacity of this {@code CompactHashMap}. + */ + CompactHashMap(int expectedSize) { + init(expectedSize); + } + + /** Pseudoconstructor for serialization support. */ + void init(int expectedSize) { + Preconditions.checkArgument(expectedSize >= 0, "Expected size must be >= 0"); + + // Save expectedSize for use in allocArrays() + this.metadata = Math.max(1, Math.min(CompactHashing.MAX_SIZE, expectedSize)); + } + + /** Returns whether arrays need to be allocated. */ + @VisibleForTesting + boolean needsAllocArrays() { + return table == null; + } + + /** Handle lazy allocation of arrays. */ + + int allocArrays() { + Preconditions.checkState(needsAllocArrays(), "Arrays already allocated"); + + int expectedSize = metadata; + int buckets = CompactHashing.tableSize(expectedSize); + this.table = CompactHashing.createTable(buckets); + setHashTableMask(buckets - 1); + + this.entries = new int[expectedSize]; + this.keys = new Object[expectedSize]; + this.values = new Object[expectedSize]; + + return expectedSize; + } + + /** Stores the hash table mask as the number of bits needed to represent an index. */ + private void setHashTableMask(int mask) { + int hashTableBits = Integer.SIZE - Integer.numberOfLeadingZeros(mask); + metadata = + CompactHashing.maskCombine(metadata, hashTableBits, CompactHashing.HASH_TABLE_BITS_MASK); + } + + /** Gets the hash table mask using the stored number of hash table bits. */ + private int hashTableMask() { + return (1 << (metadata & CompactHashing.HASH_TABLE_BITS_MASK)) - 1; + } + + void incrementModCount() { + metadata += CompactHashing.MODIFICATION_COUNT_INCREMENT; + } + + /** + * Mark an access of the specified entry. Used only in {@code CompactLinkedHashMap} for LRU + * ordering. + */ + void accessEntry(int index) { + // no-op by default + } + + + @Override + public V put(K key, V value) { + if (needsAllocArrays()) { + allocArrays(); + } + int[] entries = this.entries; + Object[] keys = this.keys; + Object[] values = this.values; + + int newEntryIndex = this.size; // current size, and pointer to the entry to be appended + int newSize = newEntryIndex + 1; + int hash = smearedHash(key); + int mask = hashTableMask(); + int tableIndex = hash & mask; + int next = CompactHashing.tableGet(table, tableIndex); + if (next == UNSET) { // uninitialized bucket + if (newSize > mask) { + // Resize and add new entry + mask = resizeTable(mask, CompactHashing.newCapacity(mask), hash, newEntryIndex); + } else { + CompactHashing.tableSet(table, tableIndex, newEntryIndex + 1); + } + } else { + int entryIndex; + int entry; + int hashPrefix = CompactHashing.getHashPrefix(hash, mask); + do { + entryIndex = next - 1; + entry = entries[entryIndex]; + if (CompactHashing.getHashPrefix(entry, mask) == hashPrefix + && Objects.equal(key, keys[entryIndex])) { + @SuppressWarnings("unchecked") // known to be a V + + V oldValue = (V) values[entryIndex]; + + values[entryIndex] = value; + accessEntry(entryIndex); + return oldValue; + } + next = CompactHashing.getNext(entry, mask); + } while (next != UNSET); + if (newSize > mask) { + // Resize and add new entry + mask = resizeTable(mask, CompactHashing.newCapacity(mask), hash, newEntryIndex); + } else { + entries[entryIndex] = CompactHashing.maskCombine(entry, newEntryIndex + 1, mask); + } + } + resizeMeMaybe(newSize); + insertEntry(newEntryIndex, key, value, hash, mask); + this.size = newSize; + incrementModCount(); + return null; + } + + /** + * Creates a fresh entry with the specified object at the specified position in the entry arrays. + */ + void insertEntry(int entryIndex, K key, V value, int hash, int mask) { + this.entries[entryIndex] = CompactHashing.maskCombine(hash, UNSET, mask); + this.keys[entryIndex] = key; + this.values[entryIndex] = value; + } + + /** Resizes the entries storage if necessary. */ + private void resizeMeMaybe(int newSize) { + int entriesSize = entries.length; + if (newSize > entriesSize) { + // 1.5x but round up to nearest odd (this is optimal for memory consumption on Android) + int newCapacity = + Math.min(CompactHashing.MAX_SIZE, (entriesSize + Math.max(1, entriesSize >>> 1)) | 1); + if (newCapacity != entriesSize) { + resizeEntries(newCapacity); + } + } + } + + /** + * Resizes the internal entries array to the specified capacity, which may be greater or less than + * the current capacity. + */ + void resizeEntries(int newCapacity) { + this.entries = Arrays.copyOf(entries, newCapacity); + this.keys = Arrays.copyOf(keys, newCapacity); + this.values = Arrays.copyOf(values, newCapacity); + } + + + private int resizeTable(int mask, int newCapacity, int targetHash, int targetEntryIndex) { + Object newTable = CompactHashing.createTable(newCapacity); + int newMask = newCapacity - 1; + + if (targetEntryIndex != UNSET) { + // Add target first; it must be last in the chain because its entry hasn't yet been created + CompactHashing.tableSet(newTable, targetHash & newMask, targetEntryIndex + 1); + } + + Object table = this.table; + int[] entries = this.entries; + + // Loop over current hashtable + for (int tableIndex = 0; tableIndex <= mask; tableIndex++) { + int next = CompactHashing.tableGet(table, tableIndex); + while (next != UNSET) { + int entryIndex = next - 1; + int entry = entries[entryIndex]; + + // Rebuild hash using entry hashPrefix and tableIndex ("hashSuffix") + int hash = CompactHashing.getHashPrefix(entry, mask) | tableIndex; + + int newTableIndex = hash & newMask; + int newNext = CompactHashing.tableGet(newTable, newTableIndex); + CompactHashing.tableSet(newTable, newTableIndex, next); + entries[entryIndex] = CompactHashing.maskCombine(hash, newNext, newMask); + + next = CompactHashing.getNext(entry, mask); + } + } + + this.table = newTable; + setHashTableMask(newMask); + return newMask; + } + + private int indexOf(Object key) { + if (needsAllocArrays()) { + return -1; + } + int hash = smearedHash(key); + int mask = hashTableMask(); + int next = CompactHashing.tableGet(table, hash & mask); + if (next == UNSET) { + return -1; + } + int hashPrefix = CompactHashing.getHashPrefix(hash, mask); + do { + int entryIndex = next - 1; + int entry = entries[entryIndex]; + if (CompactHashing.getHashPrefix(entry, mask) == hashPrefix + && Objects.equal(key, keys[entryIndex])) { + return entryIndex; + } + next = CompactHashing.getNext(entry, mask); + } while (next != UNSET); + return -1; + } + + @Override + public boolean containsKey(Object key) { + return indexOf(key) != -1; + } + + @SuppressWarnings("unchecked") // known to be a V + @Override + public V get(Object key) { + int index = indexOf(key); + if (index == -1) { + return null; + } + accessEntry(index); + return (V) values[index]; + } + + + @SuppressWarnings("unchecked") // known to be a V + @Override + public V remove(Object key) { + Object oldValue = removeHelper(key); + return (oldValue == NOT_FOUND) ? null : (V) oldValue; + } + + private Object removeHelper(Object key) { + if (needsAllocArrays()) { + return NOT_FOUND; + } + int mask = hashTableMask(); + int index = + CompactHashing.remove( + key, /* value= */ null, mask, table, entries, keys, /* values= */ null); + if (index == -1) { + return NOT_FOUND; + } + + Object oldValue = values[index]; + + moveLastEntry(index, mask); + size--; + incrementModCount(); + + return oldValue; + } + + /** + * Moves the last entry in the entry array into {@code dstIndex}, and nulls out its old position. + */ + void moveLastEntry(int dstIndex, int mask) { + int srcIndex = size() - 1; + if (dstIndex < srcIndex) { + // move last entry to deleted spot + Object key = keys[srcIndex]; + keys[dstIndex] = key; + values[dstIndex] = values[srcIndex]; + keys[srcIndex] = null; + values[srcIndex] = null; + + // move the last entry to the removed spot, just like we moved the element + entries[dstIndex] = entries[srcIndex]; + entries[srcIndex] = 0; + + // also need to update whoever's "next" pointer was pointing to the last entry place + int tableIndex = smearedHash(key) & mask; + int next = CompactHashing.tableGet(table, tableIndex); + int srcNext = srcIndex + 1; + if (next == srcNext) { + // we need to update the root pointer + CompactHashing.tableSet(table, tableIndex, dstIndex + 1); + } else { + // we need to update a pointer in an entry + int entryIndex; + int entry; + do { + entryIndex = next - 1; + entry = entries[entryIndex]; + next = CompactHashing.getNext(entry, mask); + } while (next != srcNext); + // here, entries[entryIndex] points to the old entry location; update it + entries[entryIndex] = CompactHashing.maskCombine(entry, dstIndex + 1, mask); + } + } else { + keys[dstIndex] = null; + values[dstIndex] = null; + entries[dstIndex] = 0; + } + } + + int firstEntryIndex() { + return isEmpty() ? -1 : 0; + } + + int getSuccessor(int entryIndex) { + return (entryIndex + 1 < size) ? entryIndex + 1 : -1; + } + + /** + * Updates the index an iterator is pointing to after a call to remove: returns the index of the + * entry that should be looked at after a removal on indexRemoved, with indexBeforeRemove as the + * index that *was* the next entry that would be looked at. + */ + int adjustAfterRemove(int indexBeforeRemove, @SuppressWarnings("unused") int indexRemoved) { + return indexBeforeRemove - 1; + } + + private abstract class Itr implements Iterator { + int expectedMetadata = metadata; + int currentIndex = firstEntryIndex(); + int indexToRemove = -1; + + @Override + public boolean hasNext() { + return currentIndex >= 0; + } + + abstract T getOutput(int entry); + + @Override + public T next() { + checkForConcurrentModification(); + if (!hasNext()) { + throw new NoSuchElementException(); + } + indexToRemove = currentIndex; + T result = getOutput(currentIndex); + currentIndex = getSuccessor(currentIndex); + return result; + } + + @Override + public void remove() { + checkForConcurrentModification(); + checkRemove(indexToRemove >= 0); + incrementExpectedModCount(); + CompactHashMap.this.remove(keys[indexToRemove]); + currentIndex = adjustAfterRemove(currentIndex, indexToRemove); + indexToRemove = -1; + } + + void incrementExpectedModCount() { + expectedMetadata += CompactHashing.MODIFICATION_COUNT_INCREMENT; + } + + private void checkForConcurrentModification() { + if (metadata != expectedMetadata) { + throw new ConcurrentModificationException(); + } + } + } + + @SuppressWarnings("unchecked") // known to be Ks and Vs + @Override + public void replaceAll(BiFunction function) { + checkNotNull(function); + for (int i = 0; i < size; i++) { + values[i] = function.apply((K) keys[i], (V) values[i]); + } + } + + private transient Set keySetView; + + @Override + public Set keySet() { + return (keySetView == null) ? keySetView = createKeySet() : keySetView; + } + + Set createKeySet() { + return new KeySetView(); + } + + + class KeySetView extends Maps.KeySet { + KeySetView() { + super(CompactHashMap.this); + } + + @Override + public Object[] toArray() { + if (needsAllocArrays()) { + return new Object[0]; + } + return ObjectArrays.copyAsObjectArray(keys, 0, size); + } + + @Override + public T[] toArray(T[] a) { + if (needsAllocArrays()) { + if (a.length > 0) { + a[0] = null; + } + return a; + } + return ObjectArrays.toArrayImpl(keys, 0, size, a); + } + + @Override + public boolean remove(Object o) { + return CompactHashMap.this.removeHelper(o) != NOT_FOUND; + } + + @Override + public Iterator iterator() { + return keySetIterator(); + } + + @Override + public Spliterator spliterator() { + if (needsAllocArrays()) { + return Spliterators.spliterator(new Object[0], Spliterator.DISTINCT | Spliterator.ORDERED); + } + return Spliterators.spliterator(keys, 0, size, Spliterator.DISTINCT | Spliterator.ORDERED); + } + + @SuppressWarnings("unchecked") // known to be Ks + @Override + public void forEach(Consumer action) { + checkNotNull(action); + for (int i = firstEntryIndex(); i >= 0; i = getSuccessor(i)) { + action.accept((K) keys[i]); + } + } + } + + Iterator keySetIterator() { + return new Itr() { + @SuppressWarnings("unchecked") // known to be a K + @Override + K getOutput(int entry) { + return (K) keys[entry]; + } + }; + } + + @SuppressWarnings("unchecked") // known to be Ks and Vs + @Override + public void forEach(BiConsumer action) { + checkNotNull(action); + for (int i = firstEntryIndex(); i >= 0; i = getSuccessor(i)) { + action.accept((K) keys[i], (V) values[i]); + } + } + + private transient Set> entrySetView; + + @Override + public Set> entrySet() { + return (entrySetView == null) ? entrySetView = createEntrySet() : entrySetView; + } + + Set> createEntrySet() { + return new EntrySetView(); + } + + + class EntrySetView extends Maps.EntrySet { + @Override + Map map() { + return CompactHashMap.this; + } + + @Override + public Iterator> iterator() { + return entrySetIterator(); + } + + @Override + public Spliterator> spliterator() { + return CollectSpliterators.indexed( + size, Spliterator.DISTINCT | Spliterator.ORDERED, MapEntry::new); + } + + @Override + public boolean contains(Object o) { + if (o instanceof Entry) { + Entry entry = (Entry) o; + int index = indexOf(entry.getKey()); + return index != -1 && Objects.equal(values[index], entry.getValue()); + } + return false; + } + + @Override + public boolean remove(Object o) { + if (o instanceof Entry) { + Entry entry = (Entry) o; + if (needsAllocArrays()) { + return false; + } + int mask = hashTableMask(); + int index = + CompactHashing.remove( + entry.getKey(), entry.getValue(), mask, table, entries, keys, values); + if (index == -1) { + return false; + } + + moveLastEntry(index, mask); + size--; + incrementModCount(); + + return true; + } + return false; + } + } + + Iterator> entrySetIterator() { + return new Itr>() { + @Override + Entry getOutput(int entry) { + return new MapEntry(entry); + } + }; + } + + final class MapEntry extends AbstractMapEntry { + private final K key; + + private int lastKnownIndex; + + @SuppressWarnings("unchecked") // known to be a K + MapEntry(int index) { + this.key = (K) keys[index]; + this.lastKnownIndex = index; + } + + + @Override + public K getKey() { + return key; + } + + private void updateLastKnownIndex() { + if (lastKnownIndex == -1 + || lastKnownIndex >= size() + || !Objects.equal(key, keys[lastKnownIndex])) { + lastKnownIndex = indexOf(key); + } + } + + @SuppressWarnings("unchecked") // known to be a V + + @Override + public V getValue() { + updateLastKnownIndex(); + return (lastKnownIndex == -1) ? null : (V) values[lastKnownIndex]; + } + + @SuppressWarnings("unchecked") // known to be a V + @Override + public V setValue(V value) { + updateLastKnownIndex(); + if (lastKnownIndex == -1) { + put(key, value); + return null; + } else { + V old = (V) values[lastKnownIndex]; + values[lastKnownIndex] = value; + return old; + } + } + } + + @Override + public int size() { + return size; + } + + @Override + public boolean isEmpty() { + return size == 0; + } + + @Override + public boolean containsValue(Object value) { + for (int i = 0; i < size; i++) { + if (Objects.equal(value, values[i])) { + return true; + } + } + return false; + } + + private transient Collection valuesView; + + @Override + public Collection values() { + return (valuesView == null) ? valuesView = createValues() : valuesView; + } + + Collection createValues() { + return new ValuesView(); + } + + + class ValuesView extends Maps.Values { + ValuesView() { + super(CompactHashMap.this); + } + + @Override + public Iterator iterator() { + return valuesIterator(); + } + + @SuppressWarnings("unchecked") // known to be Vs + @Override + public void forEach(Consumer action) { + checkNotNull(action); + for (int i = firstEntryIndex(); i >= 0; i = getSuccessor(i)) { + action.accept((V) values[i]); + } + } + + @Override + public Spliterator spliterator() { + if (needsAllocArrays()) { + return Spliterators.spliterator(new Object[0], Spliterator.ORDERED); + } + return Spliterators.spliterator(values, 0, size, Spliterator.ORDERED); + } + + @Override + public Object[] toArray() { + if (needsAllocArrays()) { + return new Object[0]; + } + return ObjectArrays.copyAsObjectArray(values, 0, size); + } + + @Override + public T[] toArray(T[] a) { + if (needsAllocArrays()) { + if (a.length > 0) { + a[0] = null; + } + return a; + } + return ObjectArrays.toArrayImpl(values, 0, size, a); + } + } + + Iterator valuesIterator() { + return new Itr() { + @SuppressWarnings("unchecked") // known to be a V + @Override + V getOutput(int entry) { + return (V) values[entry]; + } + }; + } + + /** + * Ensures that this {@code CompactHashMap} has the smallest representation in memory, given its + * current size. + */ + public void trimToSize() { + if (needsAllocArrays()) { + return; + } + int size = this.size; + if (size < entries.length) { + resizeEntries(size); + } + int minimumTableSize = CompactHashing.tableSize(size); + int mask = hashTableMask(); + if (minimumTableSize < mask) { // smaller table size will always be less than current mask + resizeTable(mask, minimumTableSize, UNSET, UNSET); + } + } + + @Override + public void clear() { + if (needsAllocArrays()) { + return; + } + incrementModCount(); + Arrays.fill(keys, 0, size, null); + Arrays.fill(values, 0, size, null); + CompactHashing.tableClear(table); + Arrays.fill(entries, 0, size, 0); + this.size = 0; + } + + private void writeObject(ObjectOutputStream stream) throws IOException { + stream.defaultWriteObject(); + stream.writeInt(size); + for (int i = firstEntryIndex(); i >= 0; i = getSuccessor(i)) { + stream.writeObject(keys[i]); + stream.writeObject(values[i]); + } + } + + @SuppressWarnings("unchecked") + private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException { + stream.defaultReadObject(); + int elementCount = stream.readInt(); + if (elementCount < 0) { + throw new InvalidObjectException("Invalid size: " + elementCount); + } + init(elementCount); + for (int i = 0; i < elementCount; i++) { + K key = (K) stream.readObject(); + V value = (V) stream.readObject(); + put(key, value); + } + } +} diff --git a/src/main/java/com/google/common/collect/CompactHashSet.java b/src/main/java/com/google/common/collect/CompactHashSet.java new file mode 100644 index 0000000..0f2cc03 --- /dev/null +++ b/src/main/java/com/google/common/collect/CompactHashSet.java @@ -0,0 +1,591 @@ +/* + * Copyright (C) 2012 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.collect.CollectPreconditions.checkRemove; +import static com.google.common.collect.CompactHashing.UNSET; +import static com.google.common.collect.Hashing.smearedHash; + +import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.Objects; +import com.google.common.base.Preconditions; + +import java.io.IOException; +import java.io.InvalidObjectException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.Serializable; +import java.util.AbstractSet; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.ConcurrentModificationException; +import java.util.Iterator; +import java.util.NoSuchElementException; +import java.util.Spliterator; +import java.util.Spliterators; +import java.util.function.Consumer; + + + +/** + * CompactHashSet is an implementation of a Set. All optional operations (adding and removing) are + * supported. The elements can be any objects. + * + *

{@code contains(x)}, {@code add(x)} and {@code remove(x)}, are all (expected and amortized) + * constant time operations. Expected in the hashtable sense (depends on the hash function doing a + * good job of distributing the elements to the buckets to a distribution not far from uniform), and + * amortized since some operations can trigger a hash table resize. + * + *

Unlike {@code java.util.HashSet}, iteration is only proportional to the actual {@code size()}, + * which is optimal, and not the size of the internal hashtable, which could be much larger + * than {@code size()}. Furthermore, this structure only depends on a fixed number of arrays; {@code + * add(x)} operations do not create objects for the garbage collector to deal with, and for + * every element added, the garbage collector will have to traverse {@code 1.5} references on + * average, in the marking phase, not {@code 5.0} as in {@code java.util.HashSet}. + * + *

If there are no removals, then {@link #iterator iteration} order is the same as insertion + * order. Any removal invalidates any ordering guarantees. + * + *

This class should not be assumed to be universally superior to {@code java.util.HashSet}. + * Generally speaking, this class reduces object allocation and memory consumption at the price of + * moderately increased constant factors of CPU. Only use this class when there is a specific reason + * to prioritize memory over CPU. + * + * @author Dimitris Andreou + * @author Jon Noack + */ +@GwtIncompatible // not worth using in GWT for now +class CompactHashSet extends AbstractSet implements Serializable { + // TODO(user): cache all field accesses in local vars + + /** Creates an empty {@code CompactHashSet} instance. */ + public static CompactHashSet create() { + return new CompactHashSet<>(); + } + + /** + * Creates a mutable {@code CompactHashSet} instance containing the elements of the given + * collection in unspecified order. + * + * @param collection the elements that the set should contain + * @return a new {@code CompactHashSet} containing those elements (minus duplicates) + */ + public static CompactHashSet create(Collection collection) { + CompactHashSet set = createWithExpectedSize(collection.size()); + set.addAll(collection); + return set; + } + + /** + * Creates a mutable {@code CompactHashSet} instance containing the given elements in + * unspecified order. + * + * @param elements the elements that the set should contain + * @return a new {@code CompactHashSet} containing those elements (minus duplicates) + */ + @SafeVarargs + public static CompactHashSet create(E... elements) { + CompactHashSet set = createWithExpectedSize(elements.length); + Collections.addAll(set, elements); + return set; + } + + /** + * Creates a {@code CompactHashSet} instance, with a high enough "initial capacity" that it + * should hold {@code expectedSize} elements without growth. + * + * @param expectedSize the number of elements you expect to add to the returned set + * @return a new, empty {@code CompactHashSet} with enough capacity to hold {@code expectedSize} + * elements without resizing + * @throws IllegalArgumentException if {@code expectedSize} is negative + */ + public static CompactHashSet createWithExpectedSize(int expectedSize) { + return new CompactHashSet<>(expectedSize); + } + + /** + * The hashtable. Its values are indexes to the elements and entries arrays. + * + *

Currently, the UNSET value means "null pointer", and any positive value x is the actual + * index + 1. + * + *

Its size must be a power of two. + */ + private transient Object table; + + /** + * Contains the logical entries, in the range of [0, size()). The high bits of each int are the + * part of the smeared hash of the element not covered by the hashtable mask, whereas the low bits + * are the "next" pointer (pointing to the next entry in the bucket chain), which will always be + * less than or equal to the hashtable mask. + * + *

+   * hash  = aaaaaaaa
+   * mask  = 0000ffff
+   * next  = 0000bbbb
+   * entry = aaaabbbb
+   * 
+ * + *

The pointers in [size(), entries.length) are all "null" (UNSET). + */ + private transient int [] entries; + + /** + * The elements contained in the set, in the range of [0, size()). The elements in [size(), + * elements.length) are all {@code null}. + */ + @VisibleForTesting transient Object [] elements; + + /** + * Keeps track of metadata like the number of hash table bits and modifications of this data + * structure (to make it possible to throw ConcurrentModificationException in the iterator). Note + * that we choose not to make this volatile, so we do less of a "best effort" to track such + * errors, for better performance. + */ + private transient int metadata; + + /** The number of elements contained in the set. */ + private transient int size; + + /** Constructs a new empty instance of {@code CompactHashSet}. */ + CompactHashSet() { + init(CompactHashing.DEFAULT_SIZE); + } + + /** + * Constructs a new instance of {@code CompactHashSet} with the specified capacity. + * + * @param expectedSize the initial capacity of this {@code CompactHashSet}. + */ + CompactHashSet(int expectedSize) { + init(expectedSize); + } + + /** Pseudoconstructor for serialization support. */ + void init(int expectedSize) { + Preconditions.checkArgument(expectedSize >= 0, "Expected size must be >= 0"); + + // Save expectedSize for use in allocArrays() + this.metadata = Math.max(1, Math.min(CompactHashing.MAX_SIZE, expectedSize)); + } + + /** Returns whether arrays need to be allocated. */ + @VisibleForTesting + boolean needsAllocArrays() { + return table == null; + } + + /** Handle lazy allocation of arrays. */ + + int allocArrays() { + Preconditions.checkState(needsAllocArrays(), "Arrays already allocated"); + + int expectedSize = metadata; + int buckets = CompactHashing.tableSize(expectedSize); + this.table = CompactHashing.createTable(buckets); + setHashTableMask(buckets - 1); + + this.entries = new int[expectedSize]; + this.elements = new Object[expectedSize]; + + return expectedSize; + } + + /** Stores the hash table mask as the number of bits needed to represent an index. */ + private void setHashTableMask(int mask) { + int hashTableBits = Integer.SIZE - Integer.numberOfLeadingZeros(mask); + metadata = + CompactHashing.maskCombine(metadata, hashTableBits, CompactHashing.HASH_TABLE_BITS_MASK); + } + + /** Gets the hash table mask using the stored number of hash table bits. */ + private int hashTableMask() { + return (1 << (metadata & CompactHashing.HASH_TABLE_BITS_MASK)) - 1; + } + + void incrementModCount() { + metadata += CompactHashing.MODIFICATION_COUNT_INCREMENT; + } + + + @Override + public boolean add(E object) { + if (needsAllocArrays()) { + allocArrays(); + } + int[] entries = this.entries; + Object[] elements = this.elements; + + int newEntryIndex = this.size; // current size, and pointer to the entry to be appended + int newSize = newEntryIndex + 1; + int hash = smearedHash(object); + int mask = hashTableMask(); + int tableIndex = hash & mask; + int next = CompactHashing.tableGet(table, tableIndex); + if (next == UNSET) { // uninitialized bucket + if (newSize > mask) { + // Resize and add new entry + mask = resizeTable(mask, CompactHashing.newCapacity(mask), hash, newEntryIndex); + } else { + CompactHashing.tableSet(table, tableIndex, newEntryIndex + 1); + } + } else { + int entryIndex; + int entry; + int hashPrefix = CompactHashing.getHashPrefix(hash, mask); + do { + entryIndex = next - 1; + entry = entries[entryIndex]; + if (CompactHashing.getHashPrefix(entry, mask) == hashPrefix + && Objects.equal(object, elements[entryIndex])) { + return false; + } + next = CompactHashing.getNext(entry, mask); + } while (next != UNSET); + if (newSize > mask) { + // Resize and add new entry + mask = resizeTable(mask, CompactHashing.newCapacity(mask), hash, newEntryIndex); + } else { + entries[entryIndex] = CompactHashing.maskCombine(entry, newEntryIndex + 1, mask); + } + } + resizeMeMaybe(newSize); + insertEntry(newEntryIndex, object, hash, mask); + this.size = newSize; + incrementModCount(); + return true; + } + + /** + * Creates a fresh entry with the specified object at the specified position in the entry arrays. + */ + void insertEntry(int entryIndex, E object, int hash, int mask) { + this.entries[entryIndex] = CompactHashing.maskCombine(hash, UNSET, mask); + this.elements[entryIndex] = object; + } + + /** Resizes the entries storage if necessary. */ + private void resizeMeMaybe(int newSize) { + int entriesSize = entries.length; + if (newSize > entriesSize) { + // 1.5x but round up to nearest odd (this is optimal for memory consumption on Android) + int newCapacity = + Math.min(CompactHashing.MAX_SIZE, (entriesSize + Math.max(1, entriesSize >>> 1)) | 1); + if (newCapacity != entriesSize) { + resizeEntries(newCapacity); + } + } + } + + /** + * Resizes the internal entries array to the specified capacity, which may be greater or less than + * the current capacity. + */ + void resizeEntries(int newCapacity) { + this.entries = Arrays.copyOf(entries, newCapacity); + this.elements = Arrays.copyOf(elements, newCapacity); + } + + + private int resizeTable(int mask, int newCapacity, int targetHash, int targetEntryIndex) { + Object newTable = CompactHashing.createTable(newCapacity); + int newMask = newCapacity - 1; + + if (targetEntryIndex != UNSET) { + // Add target first; it must be last in the chain because its entry hasn't yet been created + CompactHashing.tableSet(newTable, targetHash & newMask, targetEntryIndex + 1); + } + + Object table = this.table; + int[] entries = this.entries; + + // Loop over current hashtable + for (int tableIndex = 0; tableIndex <= mask; tableIndex++) { + int next = CompactHashing.tableGet(table, tableIndex); + while (next != UNSET) { + int entryIndex = next - 1; + int entry = entries[entryIndex]; + + // Rebuild hash using entry hashPrefix and tableIndex ("hashSuffix") + int hash = CompactHashing.getHashPrefix(entry, mask) | tableIndex; + + int newTableIndex = hash & newMask; + int newNext = CompactHashing.tableGet(newTable, newTableIndex); + CompactHashing.tableSet(newTable, newTableIndex, next); + entries[entryIndex] = CompactHashing.maskCombine(hash, newNext, newMask); + + next = CompactHashing.getNext(entry, mask); + } + } + + this.table = newTable; + setHashTableMask(newMask); + return newMask; + } + + @Override + public boolean contains(Object object) { + if (needsAllocArrays()) { + return false; + } + int hash = smearedHash(object); + int mask = hashTableMask(); + int next = CompactHashing.tableGet(table, hash & mask); + if (next == UNSET) { + return false; + } + int hashPrefix = CompactHashing.getHashPrefix(hash, mask); + do { + int entryIndex = next - 1; + int entry = entries[entryIndex]; + if (CompactHashing.getHashPrefix(entry, mask) == hashPrefix + && Objects.equal(object, elements[entryIndex])) { + return true; + } + next = CompactHashing.getNext(entry, mask); + } while (next != UNSET); + return false; + } + + + @Override + public boolean remove(Object object) { + if (needsAllocArrays()) { + return false; + } + int mask = hashTableMask(); + int index = + CompactHashing.remove( + object, /* value= */ null, mask, table, entries, elements, /* values= */ null); + if (index == -1) { + return false; + } + + moveLastEntry(index, mask); + size--; + incrementModCount(); + + return true; + } + + /** + * Moves the last entry in the entry array into {@code dstIndex}, and nulls out its old position. + */ + void moveLastEntry(int dstIndex, int mask) { + int srcIndex = size() - 1; + if (dstIndex < srcIndex) { + // move last entry to deleted spot + Object object = elements[srcIndex]; + elements[dstIndex] = object; + elements[srcIndex] = null; + + // move the last entry to the removed spot, just like we moved the element + entries[dstIndex] = entries[srcIndex]; + entries[srcIndex] = 0; + + // also need to update whoever's "next" pointer was pointing to the last entry place + int tableIndex = smearedHash(object) & mask; + int next = CompactHashing.tableGet(table, tableIndex); + int srcNext = srcIndex + 1; + if (next == srcNext) { + // we need to update the root pointer + CompactHashing.tableSet(table, tableIndex, dstIndex + 1); + } else { + // we need to update a pointer in an entry + int entryIndex; + int entry; + do { + entryIndex = next - 1; + entry = entries[entryIndex]; + next = CompactHashing.getNext(entry, mask); + } while (next != srcNext); + // here, entries[entryIndex] points to the old entry location; update it + entries[entryIndex] = CompactHashing.maskCombine(entry, dstIndex + 1, mask); + } + } else { + elements[dstIndex] = null; + entries[dstIndex] = 0; + } + } + + int firstEntryIndex() { + return isEmpty() ? -1 : 0; + } + + int getSuccessor(int entryIndex) { + return (entryIndex + 1 < size) ? entryIndex + 1 : -1; + } + + /** + * Updates the index an iterator is pointing to after a call to remove: returns the index of the + * entry that should be looked at after a removal on indexRemoved, with indexBeforeRemove as the + * index that *was* the next entry that would be looked at. + */ + int adjustAfterRemove(int indexBeforeRemove, @SuppressWarnings("unused") int indexRemoved) { + return indexBeforeRemove - 1; + } + + @Override + public Iterator iterator() { + return new Iterator() { + int expectedMetadata = metadata; + int currentIndex = firstEntryIndex(); + int indexToRemove = -1; + + @Override + public boolean hasNext() { + return currentIndex >= 0; + } + + @SuppressWarnings("unchecked") // known to be Es + @Override + public E next() { + checkForConcurrentModification(); + if (!hasNext()) { + throw new NoSuchElementException(); + } + indexToRemove = currentIndex; + E result = (E) elements[currentIndex]; + currentIndex = getSuccessor(currentIndex); + return result; + } + + @Override + public void remove() { + checkForConcurrentModification(); + checkRemove(indexToRemove >= 0); + incrementExpectedModCount(); + CompactHashSet.this.remove(elements[indexToRemove]); + currentIndex = adjustAfterRemove(currentIndex, indexToRemove); + indexToRemove = -1; + } + + void incrementExpectedModCount() { + expectedMetadata += CompactHashing.MODIFICATION_COUNT_INCREMENT; + } + + private void checkForConcurrentModification() { + if (metadata != expectedMetadata) { + throw new ConcurrentModificationException(); + } + } + }; + } + + @Override + public Spliterator spliterator() { + if (needsAllocArrays()) { + return Spliterators.spliterator(new Object[0], Spliterator.DISTINCT | Spliterator.ORDERED); + } + return Spliterators.spliterator(elements, 0, size, Spliterator.DISTINCT | Spliterator.ORDERED); + } + + @SuppressWarnings("unchecked") // known to be Es + @Override + public void forEach(Consumer action) { + checkNotNull(action); + for (int i = firstEntryIndex(); i >= 0; i = getSuccessor(i)) { + action.accept((E) elements[i]); + } + } + + @Override + public int size() { + return size; + } + + @Override + public boolean isEmpty() { + return size == 0; + } + + @Override + public Object[] toArray() { + if (needsAllocArrays()) { + return new Object[0]; + } + return Arrays.copyOf(elements, size); + } + + + @Override + public T[] toArray(T[] a) { + if (needsAllocArrays()) { + if (a.length > 0) { + a[0] = null; + } + return a; + } + return ObjectArrays.toArrayImpl(elements, 0, size, a); + } + + /** + * Ensures that this {@code CompactHashSet} has the smallest representation in memory, given its + * current size. + */ + public void trimToSize() { + if (needsAllocArrays()) { + return; + } + int size = this.size; + if (size < entries.length) { + resizeEntries(size); + } + int minimumTableSize = CompactHashing.tableSize(size); + int mask = hashTableMask(); + if (minimumTableSize < mask) { // smaller table size will always be less than current mask + resizeTable(mask, minimumTableSize, UNSET, UNSET); + } + } + + @Override + public void clear() { + if (needsAllocArrays()) { + return; + } + incrementModCount(); + Arrays.fill(elements, 0, size, null); + CompactHashing.tableClear(table); + Arrays.fill(entries, 0, size, 0); + this.size = 0; + } + + private void writeObject(ObjectOutputStream stream) throws IOException { + stream.defaultWriteObject(); + stream.writeInt(size); + for (int i = firstEntryIndex(); i >= 0; i = getSuccessor(i)) { + stream.writeObject(elements[i]); + } + } + + @SuppressWarnings("unchecked") + private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException { + stream.defaultReadObject(); + int elementCount = stream.readInt(); + if (elementCount < 0) { + throw new InvalidObjectException("Invalid size: " + elementCount); + } + init(elementCount); + for (int i = 0; i < elementCount; i++) { + E element = (E) stream.readObject(); + add(element); + } + } +} diff --git a/src/main/java/com/google/common/collect/CompactHashing.java b/src/main/java/com/google/common/collect/CompactHashing.java new file mode 100644 index 0000000..fe2d1c4 --- /dev/null +++ b/src/main/java/com/google/common/collect/CompactHashing.java @@ -0,0 +1,183 @@ +/* + * Copyright (C) 2019 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import com.google.common.annotations.GwtIncompatible; +import com.google.common.base.Objects; +import com.google.common.primitives.Ints; +import java.util.Arrays; + + +/** + * Helper classes and static methods for implementing compact hash-based collections. + * + * @author Jon Noack + */ +@GwtIncompatible +final class CompactHashing { + private CompactHashing() {} + + /** Indicates blank table entries. */ + static final byte UNSET = 0; + + /** Number of bits used to store the numbers of hash table bits (max 30). */ + private static final int HASH_TABLE_BITS_MAX_BITS = 5; + + /** Use high bits of metadata for modification count. */ + static final int MODIFICATION_COUNT_INCREMENT = (1 << HASH_TABLE_BITS_MAX_BITS); + + /** Bitmask that selects the low bits of metadata to get hashTableBits. */ + static final int HASH_TABLE_BITS_MASK = (1 << HASH_TABLE_BITS_MAX_BITS) - 1; + + /** Maximum size of a compact hash-based collection (2^30 - 1 because 0 is UNSET). */ + static final int MAX_SIZE = Ints.MAX_POWER_OF_TWO - 1; + + /** Default size of a compact hash-based collection. */ + static final int DEFAULT_SIZE = 3; + + /** + * Minimum size of the hash table of a compact hash-based collection. Because small hash tables + * use a byte[], any smaller size uses the same amount of memory due to object padding. + */ + private static final int MIN_HASH_TABLE_SIZE = 4; + + private static final int BYTE_MAX_SIZE = 1 << Byte.SIZE; // 2^8 = 256 + private static final int BYTE_MASK = (1 << Byte.SIZE) - 1; // 2^8 - 1 = 255 + + private static final int SHORT_MAX_SIZE = 1 << Short.SIZE; // 2^16 = 65_536 + private static final int SHORT_MASK = (1 << Short.SIZE) - 1; // 2^16 - 1 = 65_535 + + /** + * Returns the power of 2 hashtable size required to hold the expected number of items or the + * minimum hashtable size, whichever is greater. + */ + static int tableSize(int expectedSize) { + // We use entries next == 0 to indicate UNSET, so actual capacity is 1 less than requested. + return Math.max(MIN_HASH_TABLE_SIZE, Hashing.closedTableSize(expectedSize + 1, 1.0f)); + } + + /** Creates and returns a properly-sized array with the given number of buckets. */ + static Object createTable(int buckets) { + if (buckets < 2 + || buckets > Ints.MAX_POWER_OF_TWO + || Integer.highestOneBit(buckets) != buckets) { + throw new IllegalArgumentException("must be power of 2 between 2^1 and 2^30: " + buckets); + } + if (buckets <= BYTE_MAX_SIZE) { + return new byte[buckets]; + } else if (buckets <= SHORT_MAX_SIZE) { + return new short[buckets]; + } else { + return new int[buckets]; + } + } + + static void tableClear(Object table) { + if (table instanceof byte[]) { + Arrays.fill((byte[]) table, (byte) 0); + } else if (table instanceof short[]) { + Arrays.fill((short[]) table, (short) 0); + } else { + Arrays.fill((int[]) table, 0); + } + } + + static int tableGet(Object table, int index) { + if (table instanceof byte[]) { + return ((byte[]) table)[index] & BYTE_MASK; // unsigned read + } else if (table instanceof short[]) { + return ((short[]) table)[index] & SHORT_MASK; // unsigned read + } else { + return ((int[]) table)[index]; + } + } + + static void tableSet(Object table, int index, int entry) { + if (table instanceof byte[]) { + ((byte[]) table)[index] = (byte) entry; // unsigned write + } else if (table instanceof short[]) { + ((short[]) table)[index] = (short) entry; // unsigned write + } else { + ((int[]) table)[index] = entry; + } + } + + /** + * Returns a larger power of 2 hashtable size given the current mask. + * + *

For hashtable sizes less than or equal to 32, the returned power of 2 is 4x the current + * hashtable size to reduce expensive rehashing. Otherwise the returned power of 2 is 2x the + * current hashtable size. + */ + static int newCapacity(int mask) { + return ((mask < 32) ? 4 : 2) * (mask + 1); + } + + /** Returns the hash prefix given the current mask. */ + static int getHashPrefix(int value, int mask) { + return value & ~mask; + } + + /** Returns the index, or 0 if the entry is "null". */ + static int getNext(int entry, int mask) { + return entry & mask; + } + + /** Returns a new value combining the prefix and suffix using the given mask. */ + static int maskCombine(int prefix, int suffix, int mask) { + return (prefix & ~mask) | (suffix & mask); + } + + static int remove( + Object key, + Object value, + int mask, + Object table, + int[] entries, + Object[] keys, + Object [] values) { + int hash = Hashing.smearedHash(key); + int tableIndex = hash & mask; + int next = tableGet(table, tableIndex); + if (next == UNSET) { + return -1; + } + int hashPrefix = getHashPrefix(hash, mask); + int lastEntryIndex = -1; + do { + int entryIndex = next - 1; + int entry = entries[entryIndex]; + if (getHashPrefix(entry, mask) == hashPrefix + && Objects.equal(key, keys[entryIndex]) + && (values == null || Objects.equal(value, values[entryIndex]))) { + int newNext = getNext(entry, mask); + if (lastEntryIndex == -1) { + // we need to update the root link from table[] + tableSet(table, tableIndex, newNext); + } else { + // we need to update the link from the chain + entries[lastEntryIndex] = maskCombine(entries[lastEntryIndex], newNext, mask); + } + + return entryIndex; + } + lastEntryIndex = entryIndex; + next = getNext(entry, mask); + } while (next != UNSET); + return -1; + } +} diff --git a/src/main/java/com/google/common/collect/CompactLinkedHashMap.java b/src/main/java/com/google/common/collect/CompactLinkedHashMap.java new file mode 100644 index 0000000..66e564b --- /dev/null +++ b/src/main/java/com/google/common/collect/CompactLinkedHashMap.java @@ -0,0 +1,267 @@ +/* + * Copyright (C) 2012 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.VisibleForTesting; + +import java.util.Arrays; +import java.util.Collection; +import java.util.Set; +import java.util.Spliterator; +import java.util.Spliterators; + + + +/** + * CompactLinkedHashMap is an implementation of a Map with insertion or LRU iteration order, + * maintained with a doubly linked list through the entries. All optional operations (put and + * remove) are supported. Null keys and values are supported. + * + *

{@code containsKey(k)}, {@code put(k, v)} and {@code remove(k)} are all (expected and + * amortized) constant time operations. Expected in the hashtable sense (depends on the hash + * function doing a good job of distributing the elements to the buckets to a distribution not far + * from uniform), and amortized since some operations can trigger a hash table resize. + * + *

As compared with {@link java.util.LinkedHashMap}, this structure places significantly reduced + * load on the garbage collector by only using a constant number of internal objects. + * + *

This class should not be assumed to be universally superior to {@code + * java.util.LinkedHashMap}. Generally speaking, this class reduces object allocation and memory + * consumption at the price of moderately increased constant factors of CPU. Only use this class + * when there is a specific reason to prioritize memory over CPU. + * + * @author Louis Wasserman + */ +@GwtIncompatible // not worth using in GWT for now +class CompactLinkedHashMap extends CompactHashMap { + // TODO(lowasser): implement removeEldestEntry so this can be used as a drop-in replacement + + /** Creates an empty {@code CompactLinkedHashMap} instance. */ + public static CompactLinkedHashMap create() { + return new CompactLinkedHashMap<>(); + } + + /** + * Creates a {@code CompactLinkedHashMap} instance, with a high enough "initial capacity" that it + * should hold {@code expectedSize} elements without rebuilding internal data structures. + * + * @param expectedSize the number of elements you expect to add to the returned set + * @return a new, empty {@code CompactLinkedHashMap} with enough capacity to hold {@code + * expectedSize} elements without resizing + * @throws IllegalArgumentException if {@code expectedSize} is negative + */ + public static CompactLinkedHashMap createWithExpectedSize(int expectedSize) { + return new CompactLinkedHashMap<>(expectedSize); + } + + private static final int ENDPOINT = -2; + + /** + * Contains the link pointers corresponding with the entries, in the range of [0, size()). The + * high 32 bits of each long is the "prev" pointer, whereas the low 32 bits is the "succ" pointer + * (pointing to the next entry in the linked list). The pointers in [size(), entries.length) are + * all "null" (UNSET). + * + *

A node with "prev" pointer equal to {@code ENDPOINT} is the first node in the linked list, + * and a node with "next" pointer equal to {@code ENDPOINT} is the last node. + */ + @VisibleForTesting transient long [] links; + + /** Pointer to the first node in the linked list, or {@code ENDPOINT} if there are no entries. */ + private transient int firstEntry; + + /** Pointer to the last node in the linked list, or {@code ENDPOINT} if there are no entries. */ + private transient int lastEntry; + + private final boolean accessOrder; + + CompactLinkedHashMap() { + this(CompactHashing.DEFAULT_SIZE); + } + + CompactLinkedHashMap(int expectedSize) { + this(expectedSize, false); + } + + CompactLinkedHashMap(int expectedSize, boolean accessOrder) { + super(expectedSize); + this.accessOrder = accessOrder; + } + + @Override + void init(int expectedSize) { + super.init(expectedSize); + this.firstEntry = ENDPOINT; + this.lastEntry = ENDPOINT; + } + + @Override + int allocArrays() { + int expectedSize = super.allocArrays(); + this.links = new long[expectedSize]; + return expectedSize; + } + + private int getPredecessor(int entry) { + return ((int) (links[entry] >>> 32)) - 1; + } + + @Override + int getSuccessor(int entry) { + return ((int) links[entry]) - 1; + } + + private void setSuccessor(int entry, int succ) { + long succMask = (~0L) >>> 32; + links[entry] = (links[entry] & ~succMask) | ((succ + 1) & succMask); + } + + private void setPredecessor(int entry, int pred) { + long predMask = ~0L << 32; + links[entry] = (links[entry] & ~predMask) | ((long) (pred + 1) << 32); + } + + private void setSucceeds(int pred, int succ) { + if (pred == ENDPOINT) { + firstEntry = succ; + } else { + setSuccessor(pred, succ); + } + + if (succ == ENDPOINT) { + lastEntry = pred; + } else { + setPredecessor(succ, pred); + } + } + + @Override + void insertEntry(int entryIndex, K key, V value, int hash, int mask) { + super.insertEntry(entryIndex, key, value, hash, mask); + setSucceeds(lastEntry, entryIndex); + setSucceeds(entryIndex, ENDPOINT); + } + + @Override + void accessEntry(int index) { + if (accessOrder) { + // delete from previous position... + setSucceeds(getPredecessor(index), getSuccessor(index)); + // ...and insert at the end. + setSucceeds(lastEntry, index); + setSucceeds(index, ENDPOINT); + incrementModCount(); + } + } + + @Override + void moveLastEntry(int dstIndex, int mask) { + int srcIndex = size() - 1; + super.moveLastEntry(dstIndex, mask); + + setSucceeds(getPredecessor(dstIndex), getSuccessor(dstIndex)); + if (dstIndex < srcIndex) { + setSucceeds(getPredecessor(srcIndex), dstIndex); + setSucceeds(dstIndex, getSuccessor(srcIndex)); + } + links[srcIndex] = 0; + } + + @Override + void resizeEntries(int newCapacity) { + super.resizeEntries(newCapacity); + links = Arrays.copyOf(links, newCapacity); + } + + @Override + int firstEntryIndex() { + return firstEntry; + } + + @Override + int adjustAfterRemove(int indexBeforeRemove, int indexRemoved) { + return (indexBeforeRemove >= size()) ? indexRemoved : indexBeforeRemove; + } + + @Override + Set> createEntrySet() { + + class EntrySetImpl extends EntrySetView { + @Override + public Spliterator> spliterator() { + return Spliterators.spliterator(this, Spliterator.ORDERED | Spliterator.DISTINCT); + } + } + return new EntrySetImpl(); + } + + @Override + Set createKeySet() { + + class KeySetImpl extends KeySetView { + @Override + public Object[] toArray() { + return ObjectArrays.toArrayImpl(this); + } + + @Override + public T[] toArray(T[] a) { + return ObjectArrays.toArrayImpl(this, a); + } + + @Override + public Spliterator spliterator() { + return Spliterators.spliterator(this, Spliterator.ORDERED | Spliterator.DISTINCT); + } + } + return new KeySetImpl(); + } + + @Override + Collection createValues() { + + class ValuesImpl extends ValuesView { + @Override + public Object[] toArray() { + return ObjectArrays.toArrayImpl(this); + } + + @Override + public T[] toArray(T[] a) { + return ObjectArrays.toArrayImpl(this, a); + } + + @Override + public Spliterator spliterator() { + return Spliterators.spliterator(this, Spliterator.ORDERED); + } + } + return new ValuesImpl(); + } + + @Override + public void clear() { + if (needsAllocArrays()) { + return; + } + this.firstEntry = ENDPOINT; + this.lastEntry = ENDPOINT; + Arrays.fill(links, 0, size(), 0); + super.clear(); + } +} diff --git a/src/main/java/com/google/common/collect/CompactLinkedHashSet.java b/src/main/java/com/google/common/collect/CompactLinkedHashSet.java new file mode 100644 index 0000000..59136bb --- /dev/null +++ b/src/main/java/com/google/common/collect/CompactLinkedHashSet.java @@ -0,0 +1,239 @@ +/* + * Copyright (C) 2012 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import com.google.common.annotations.GwtIncompatible; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Spliterator; +import java.util.Spliterators; + + + +/** + * CompactLinkedHashSet is an implementation of a Set, which a predictable iteration order that + * matches the insertion order. All optional operations (adding and removing) are supported. All + * elements, including {@code null}, are permitted. + * + *

{@code contains(x)}, {@code add(x)} and {@code remove(x)}, are all (expected and amortized) + * constant time operations. Expected in the hashtable sense (depends on the hash function doing a + * good job of distributing the elements to the buckets to a distribution not far from uniform), and + * amortized since some operations can trigger a hash table resize. + * + *

This implementation consumes significantly less memory than {@code java.util.LinkedHashSet} or + * even {@code java.util.HashSet}, and places considerably less load on the garbage collector. Like + * {@code java.util.LinkedHashSet}, it offers insertion-order iteration, with identical behavior. + * + *

This class should not be assumed to be universally superior to {@code + * java.util.LinkedHashSet}. Generally speaking, this class reduces object allocation and memory + * consumption at the price of moderately increased constant factors of CPU. Only use this class + * when there is a specific reason to prioritize memory over CPU. + * + * @author Louis Wasserman + */ +@GwtIncompatible // not worth using in GWT for now +class CompactLinkedHashSet extends CompactHashSet { + + /** Creates an empty {@code CompactLinkedHashSet} instance. */ + public static CompactLinkedHashSet create() { + return new CompactLinkedHashSet<>(); + } + + /** + * Creates a mutable {@code CompactLinkedHashSet} instance containing the elements of the + * given collection in the order returned by the collection's iterator. + * + * @param collection the elements that the set should contain + * @return a new {@code CompactLinkedHashSet} containing those elements (minus duplicates) + */ + public static CompactLinkedHashSet create(Collection collection) { + CompactLinkedHashSet set = createWithExpectedSize(collection.size()); + set.addAll(collection); + return set; + } + + /** + * Creates a {@code CompactLinkedHashSet} instance containing the given elements in unspecified + * order. + * + * @param elements the elements that the set should contain + * @return a new {@code CompactLinkedHashSet} containing those elements (minus duplicates) + */ + @SafeVarargs + public static CompactLinkedHashSet create(E... elements) { + CompactLinkedHashSet set = createWithExpectedSize(elements.length); + Collections.addAll(set, elements); + return set; + } + + /** + * Creates a {@code CompactLinkedHashSet} instance, with a high enough "initial capacity" that it + * should hold {@code expectedSize} elements without rebuilding internal data structures. + * + * @param expectedSize the number of elements you expect to add to the returned set + * @return a new, empty {@code CompactLinkedHashSet} with enough capacity to hold {@code + * expectedSize} elements without resizing + * @throws IllegalArgumentException if {@code expectedSize} is negative + */ + public static CompactLinkedHashSet createWithExpectedSize(int expectedSize) { + return new CompactLinkedHashSet<>(expectedSize); + } + + private static final int ENDPOINT = -2; + + // TODO(user): predecessors and successors should be collocated (reducing cache misses). + // Might also explore collocating all of [hash, next, predecessor, successor] fields of an + // entry in a *single* long[], though that reduces the maximum size of the set by a factor of 2 + + /** + * Pointer to the predecessor of an entry in insertion order. ENDPOINT indicates a node is the + * first node in insertion order; all values at indices ≥ {@link #size()} are UNSET. + */ + private transient int [] predecessor; + + /** + * Pointer to the successor of an entry in insertion order. ENDPOINT indicates a node is the last + * node in insertion order; all values at indices ≥ {@link #size()} are UNSET. + */ + private transient int [] successor; + + /** Pointer to the first node in the linked list, or {@code ENDPOINT} if there are no entries. */ + private transient int firstEntry; + + /** Pointer to the last node in the linked list, or {@code ENDPOINT} if there are no entries. */ + private transient int lastEntry; + + CompactLinkedHashSet() { + super(); + } + + CompactLinkedHashSet(int expectedSize) { + super(expectedSize); + } + + @Override + void init(int expectedSize) { + super.init(expectedSize); + this.firstEntry = ENDPOINT; + this.lastEntry = ENDPOINT; + } + + @Override + int allocArrays() { + int expectedSize = super.allocArrays(); + this.predecessor = new int[expectedSize]; + this.successor = new int[expectedSize]; + return expectedSize; + } + + private int getPredecessor(int entry) { + return predecessor[entry] - 1; + } + + @Override + int getSuccessor(int entry) { + return successor[entry] - 1; + } + + private void setSuccessor(int entry, int succ) { + successor[entry] = succ + 1; + } + + private void setPredecessor(int entry, int pred) { + predecessor[entry] = pred + 1; + } + + private void setSucceeds(int pred, int succ) { + if (pred == ENDPOINT) { + firstEntry = succ; + } else { + setSuccessor(pred, succ); + } + + if (succ == ENDPOINT) { + lastEntry = pred; + } else { + setPredecessor(succ, pred); + } + } + + @Override + void insertEntry(int entryIndex, E object, int hash, int mask) { + super.insertEntry(entryIndex, object, hash, mask); + setSucceeds(lastEntry, entryIndex); + setSucceeds(entryIndex, ENDPOINT); + } + + @Override + void moveLastEntry(int dstIndex, int mask) { + int srcIndex = size() - 1; + super.moveLastEntry(dstIndex, mask); + + setSucceeds(getPredecessor(dstIndex), getSuccessor(dstIndex)); + if (dstIndex < srcIndex) { + setSucceeds(getPredecessor(srcIndex), dstIndex); + setSucceeds(dstIndex, getSuccessor(srcIndex)); + } + predecessor[srcIndex] = 0; + successor[srcIndex] = 0; + } + + @Override + void resizeEntries(int newCapacity) { + super.resizeEntries(newCapacity); + predecessor = Arrays.copyOf(predecessor, newCapacity); + successor = Arrays.copyOf(successor, newCapacity); + } + + @Override + int firstEntryIndex() { + return firstEntry; + } + + @Override + int adjustAfterRemove(int indexBeforeRemove, int indexRemoved) { + return (indexBeforeRemove >= size()) ? indexRemoved : indexBeforeRemove; + } + + @Override + public Object[] toArray() { + return ObjectArrays.toArrayImpl(this); + } + + @Override + public T[] toArray(T[] a) { + return ObjectArrays.toArrayImpl(this, a); + } + + @Override + public Spliterator spliterator() { + return Spliterators.spliterator(this, Spliterator.ORDERED | Spliterator.DISTINCT); + } + + @Override + public void clear() { + if (needsAllocArrays()) { + return; + } + this.firstEntry = ENDPOINT; + this.lastEntry = ENDPOINT; + Arrays.fill(predecessor, 0, size(), 0); + Arrays.fill(successor, 0, size(), 0); + super.clear(); + } +} diff --git a/src/main/java/com/google/common/collect/ComparatorOrdering.java b/src/main/java/com/google/common/collect/ComparatorOrdering.java new file mode 100644 index 0000000..eac6da9 --- /dev/null +++ b/src/main/java/com/google/common/collect/ComparatorOrdering.java @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.annotations.GwtCompatible; +import java.io.Serializable; +import java.util.Comparator; + + +/** An ordering for a pre-existing comparator. */ +@GwtCompatible(serializable = true) +final class ComparatorOrdering extends Ordering implements Serializable { + final Comparator comparator; + + ComparatorOrdering(Comparator comparator) { + this.comparator = checkNotNull(comparator); + } + + @Override + public int compare(T a, T b) { + return comparator.compare(a, b); + } + + @Override + public boolean equals(Object object) { + if (object == this) { + return true; + } + if (object instanceof ComparatorOrdering) { + ComparatorOrdering that = (ComparatorOrdering) object; + return this.comparator.equals(that.comparator); + } + return false; + } + + @Override + public int hashCode() { + return comparator.hashCode(); + } + + @Override + public String toString() { + return comparator.toString(); + } + + private static final long serialVersionUID = 0; +} diff --git a/src/main/java/com/google/common/collect/Comparators.java b/src/main/java/com/google/common/collect/Comparators.java new file mode 100644 index 0000000..06b398f --- /dev/null +++ b/src/main/java/com/google/common/collect/Comparators.java @@ -0,0 +1,193 @@ +/* + * Copyright (C) 2016 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.collect.CollectPreconditions.checkNonnegative; + +import com.google.common.annotations.Beta; +import com.google.common.annotations.GwtCompatible; +import java.util.Comparator; +import java.util.Iterator; +import java.util.List; +import java.util.Optional; +import java.util.stream.Collector; + +/** + * Provides static methods for working with {@link Comparator} instances. For many other helpful + * comparator utilities, see either {@code Comparator} itself (for Java 8 or later), or {@code + * com.google.common.collect.Ordering} (otherwise). + * + *

Relationship to {@code Ordering}

+ * + *

In light of the significant enhancements to {@code Comparator} in Java 8, the overwhelming + * majority of usages of {@code Ordering} can be written using only built-in JDK APIs. This class is + * intended to "fill the gap" and provide those features of {@code Ordering} not already provided by + * the JDK. + * + * @since 21.0 + * @author Louis Wasserman + */ +@GwtCompatible +public final class Comparators { + private Comparators() {} + + /** + * Returns a new comparator which sorts iterables by comparing corresponding elements pairwise + * until a nonzero result is found; imposes "dictionary order." If the end of one iterable is + * reached, but not the other, the shorter iterable is considered to be less than the longer one. + * For example, a lexicographical natural ordering over integers considers {@code [] < [1] < [1, + * 1] < [1, 2] < [2]}. + * + *

Note that {@code Collections.reverseOrder(lexicographical(comparator))} is not equivalent to + * {@code lexicographical(Collections.reverseOrder(comparator))} (consider how each would order + * {@code [1]} and {@code [1, 1]}). + */ + // Note: 90% of the time we don't add type parameters or wildcards that serve only to "tweak" the + // desired return type. However, *nested* generics introduce a special class of problems that we + // think tip it over into being worthwhile. + @Beta + public static Comparator> lexicographical(Comparator comparator) { + return new LexicographicalOrdering(checkNotNull(comparator)); + } + + /** + * Returns {@code true} if each element in {@code iterable} after the first is greater than or + * equal to the element that preceded it, according to the specified comparator. Note that this is + * always true when the iterable has fewer than two elements. + */ + @Beta + public static boolean isInOrder(Iterable iterable, Comparator comparator) { + checkNotNull(comparator); + Iterator it = iterable.iterator(); + if (it.hasNext()) { + T prev = it.next(); + while (it.hasNext()) { + T next = it.next(); + if (comparator.compare(prev, next) > 0) { + return false; + } + prev = next; + } + } + return true; + } + + /** + * Returns {@code true} if each element in {@code iterable} after the first is strictly + * greater than the element that preceded it, according to the specified comparator. Note that + * this is always true when the iterable has fewer than two elements. + */ + @Beta + public static boolean isInStrictOrder( + Iterable iterable, Comparator comparator) { + checkNotNull(comparator); + Iterator it = iterable.iterator(); + if (it.hasNext()) { + T prev = it.next(); + while (it.hasNext()) { + T next = it.next(); + if (comparator.compare(prev, next) >= 0) { + return false; + } + prev = next; + } + } + return true; + } + + /** + * Returns a {@code Collector} that returns the {@code k} smallest (relative to the specified + * {@code Comparator}) input elements, in ascending order, as an unmodifiable {@code List}. Ties + * are broken arbitrarily. + * + *

For example: + * + *

{@code
+   * Stream.of("foo", "quux", "banana", "elephant")
+   *     .collect(least(2, comparingInt(String::length)))
+   * // returns {"foo", "quux"}
+   * }
+ * + *

This {@code Collector} uses O(k) memory and takes expected time O(n) (worst-case O(n log + * k)), as opposed to e.g. {@code Stream.sorted(comparator).limit(k)}, which currently takes O(n + * log n) time and O(n) space. + * + * @throws IllegalArgumentException if {@code k < 0} + * @since 22.0 + */ + public static Collector> least(int k, Comparator comparator) { + checkNonnegative(k, "k"); + checkNotNull(comparator); + return Collector.of( + () -> TopKSelector.least(k, comparator), + TopKSelector::offer, + TopKSelector::combine, + TopKSelector::topK, + Collector.Characteristics.UNORDERED); + } + + /** + * Returns a {@code Collector} that returns the {@code k} greatest (relative to the specified + * {@code Comparator}) input elements, in descending order, as an unmodifiable {@code List}. Ties + * are broken arbitrarily. + * + *

For example: + * + *

{@code
+   * Stream.of("foo", "quux", "banana", "elephant")
+   *     .collect(greatest(2, comparingInt(String::length)))
+   * // returns {"elephant", "banana"}
+   * }
+ * + *

This {@code Collector} uses O(k) memory and takes expected time O(n) (worst-case O(n log + * k)), as opposed to e.g. {@code Stream.sorted(comparator.reversed()).limit(k)}, which currently + * takes O(n log n) time and O(n) space. + * + * @throws IllegalArgumentException if {@code k < 0} + * @since 22.0 + */ + public static Collector> greatest(int k, Comparator comparator) { + return least(k, comparator.reversed()); + } + + /** + * Returns a comparator of {@link Optional} values which treats {@link Optional#empty} as less + * than all other values, and orders the rest using {@code valueComparator} on the contained + * value. + * + * @since 22.0 + */ + @Beta + public static Comparator> emptiesFirst(Comparator valueComparator) { + checkNotNull(valueComparator); + return Comparator.comparing(o -> o.orElse(null), Comparator.nullsFirst(valueComparator)); + } + + /** + * Returns a comparator of {@link Optional} values which treats {@link Optional#empty} as greater + * than all other values, and orders the rest using {@code valueComparator} on the contained + * value. + * + * @since 22.0 + */ + @Beta + public static Comparator> emptiesLast(Comparator valueComparator) { + checkNotNull(valueComparator); + return Comparator.comparing(o -> o.orElse(null), Comparator.nullsLast(valueComparator)); + } +} diff --git a/src/main/java/com/google/common/collect/ComparisonChain.java b/src/main/java/com/google/common/collect/ComparisonChain.java new file mode 100644 index 0000000..ec124cf --- /dev/null +++ b/src/main/java/com/google/common/collect/ComparisonChain.java @@ -0,0 +1,250 @@ +/* + * Copyright (C) 2009 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import com.google.common.annotations.GwtCompatible; +import com.google.common.primitives.Booleans; +import com.google.common.primitives.Ints; +import com.google.common.primitives.Longs; +import java.util.Comparator; + + +/** + * A utility for performing a chained comparison statement. For example: + * + *

{@code
+ * public int compareTo(Foo that) {
+ *   return ComparisonChain.start()
+ *       .compare(this.aString, that.aString)
+ *       .compare(this.anInt, that.anInt)
+ *       .compare(this.anEnum, that.anEnum, Ordering.natural().nullsLast())
+ *       .result();
+ * }
+ * }
+ * + *

The value of this expression will have the same sign as the first nonzero comparison + * result in the chain, or will be zero if every comparison result was zero. + * + *

Note: {@code ComparisonChain} instances are immutable. For this utility to work + * correctly, calls must be chained as illustrated above. + * + *

Performance note: Even though the {@code ComparisonChain} caller always invokes its {@code + * compare} methods unconditionally, the {@code ComparisonChain} implementation stops calling its + * inputs' {@link Comparable#compareTo compareTo} and {@link Comparator#compare compare} methods as + * soon as one of them returns a nonzero result. This optimization is typically important only in + * the presence of expensive {@code compareTo} and {@code compare} implementations. + * + *

See the Guava User Guide article on {@code + * ComparisonChain}. + * + * @author Mark Davis + * @author Kevin Bourrillion + * @since 2.0 + */ +@GwtCompatible +public abstract class ComparisonChain { + private ComparisonChain() {} + + /** Begins a new chained comparison statement. See example in the class documentation. */ + public static ComparisonChain start() { + return ACTIVE; + } + + private static final ComparisonChain ACTIVE = + new ComparisonChain() { + @SuppressWarnings("unchecked") + @Override + public ComparisonChain compare(Comparable left, Comparable right) { + return classify(left.compareTo(right)); + } + + @Override + public ComparisonChain compare( + T left, T right, Comparator comparator) { + return classify(comparator.compare(left, right)); + } + + @Override + public ComparisonChain compare(int left, int right) { + return classify(Ints.compare(left, right)); + } + + @Override + public ComparisonChain compare(long left, long right) { + return classify(Longs.compare(left, right)); + } + + @Override + public ComparisonChain compare(float left, float right) { + return classify(Float.compare(left, right)); + } + + @Override + public ComparisonChain compare(double left, double right) { + return classify(Double.compare(left, right)); + } + + @Override + public ComparisonChain compareTrueFirst(boolean left, boolean right) { + return classify(Booleans.compare(right, left)); // reversed + } + + @Override + public ComparisonChain compareFalseFirst(boolean left, boolean right) { + return classify(Booleans.compare(left, right)); + } + + ComparisonChain classify(int result) { + return (result < 0) ? LESS : (result > 0) ? GREATER : ACTIVE; + } + + @Override + public int result() { + return 0; + } + }; + + private static final ComparisonChain LESS = new InactiveComparisonChain(-1); + + private static final ComparisonChain GREATER = new InactiveComparisonChain(1); + + private static final class InactiveComparisonChain extends ComparisonChain { + final int result; + + InactiveComparisonChain(int result) { + this.result = result; + } + + @Override + public ComparisonChain compare(Comparable left, Comparable right) { + return this; + } + + @Override + public ComparisonChain compare( + T left, T right, Comparator comparator) { + return this; + } + + @Override + public ComparisonChain compare(int left, int right) { + return this; + } + + @Override + public ComparisonChain compare(long left, long right) { + return this; + } + + @Override + public ComparisonChain compare(float left, float right) { + return this; + } + + @Override + public ComparisonChain compare(double left, double right) { + return this; + } + + @Override + public ComparisonChain compareTrueFirst(boolean left, boolean right) { + return this; + } + + @Override + public ComparisonChain compareFalseFirst(boolean left, boolean right) { + return this; + } + + @Override + public int result() { + return result; + } + } + + /** + * Compares two comparable objects as specified by {@link Comparable#compareTo}, if the + * result of this comparison chain has not already been determined. + */ + public abstract ComparisonChain compare(Comparable left, Comparable right); + + /** + * Compares two objects using a comparator, if the result of this comparison chain has not + * already been determined. + */ + public abstract ComparisonChain compare( + T left, T right, Comparator comparator); + + /** + * Compares two {@code int} values as specified by {@link Ints#compare}, if the result of + * this comparison chain has not already been determined. + */ + public abstract ComparisonChain compare(int left, int right); + + /** + * Compares two {@code long} values as specified by {@link Longs#compare}, if the result of + * this comparison chain has not already been determined. + */ + public abstract ComparisonChain compare(long left, long right); + + /** + * Compares two {@code float} values as specified by {@link Float#compare}, if the result + * of this comparison chain has not already been determined. + */ + public abstract ComparisonChain compare(float left, float right); + + /** + * Compares two {@code double} values as specified by {@link Double#compare}, if the result + * of this comparison chain has not already been determined. + */ + public abstract ComparisonChain compare(double left, double right); + + /** + * Discouraged synonym for {@link #compareFalseFirst}. + * + * @deprecated Use {@link #compareFalseFirst}; or, if the parameters passed are being either + * negated or reversed, undo the negation or reversal and use {@link #compareTrueFirst}. + * @since 19.0 + */ + @Deprecated + public final ComparisonChain compare(Boolean left, Boolean right) { + return compareFalseFirst(left, right); + } + + /** + * Compares two {@code boolean} values, considering {@code true} to be less than {@code false}, + * if the result of this comparison chain has not already been determined. + * + * @since 12.0 + */ + public abstract ComparisonChain compareTrueFirst(boolean left, boolean right); + + /** + * Compares two {@code boolean} values, considering {@code false} to be less than {@code true}, + * if the result of this comparison chain has not already been determined. + * + * @since 12.0 (present as {@code compare} since 2.0) + */ + public abstract ComparisonChain compareFalseFirst(boolean left, boolean right); + + /** + * Ends this comparison chain and returns its result: a value having the same sign as the first + * nonzero comparison result in the chain, or zero if every result was zero. + */ + public abstract int result(); +} diff --git a/src/main/java/com/google/common/collect/CompoundOrdering.java b/src/main/java/com/google/common/collect/CompoundOrdering.java new file mode 100644 index 0000000..e803acb --- /dev/null +++ b/src/main/java/com/google/common/collect/CompoundOrdering.java @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import com.google.common.annotations.GwtCompatible; +import java.io.Serializable; +import java.util.Arrays; +import java.util.Comparator; + +/** An ordering that tries several comparators in order. */ +@GwtCompatible(serializable = true) +final class CompoundOrdering extends Ordering implements Serializable { + final Comparator[] comparators; + + CompoundOrdering(Comparator primary, Comparator secondary) { + this.comparators = (Comparator[]) new Comparator[] {primary, secondary}; + } + + CompoundOrdering(Iterable> comparators) { + this.comparators = Iterables.toArray(comparators, new Comparator[0]); + } + + @Override + public int compare(T left, T right) { + for (int i = 0; i < comparators.length; i++) { + int result = comparators[i].compare(left, right); + if (result != 0) { + return result; + } + } + return 0; + } + + @Override + public boolean equals(Object object) { + if (object == this) { + return true; + } + if (object instanceof CompoundOrdering) { + CompoundOrdering that = (CompoundOrdering) object; + return Arrays.equals(this.comparators, that.comparators); + } + return false; + } + + @Override + public int hashCode() { + return Arrays.hashCode(comparators); + } + + @Override + public String toString() { + return "Ordering.compound(" + Arrays.toString(comparators) + ")"; + } + + private static final long serialVersionUID = 0; +} diff --git a/src/main/java/com/google/common/collect/ComputationException.java b/src/main/java/com/google/common/collect/ComputationException.java new file mode 100644 index 0000000..aab6f66 --- /dev/null +++ b/src/main/java/com/google/common/collect/ComputationException.java @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2009 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import com.google.common.annotations.GwtCompatible; + + +/** + * Wraps an exception that occurred during a computation. + * + * @author Bob Lee + * @since 2.0 + * @deprecated This exception is no longer thrown by {@code com.google.common}. Previously, it was + * thrown by {@link MapMaker} computing maps. When support for computing maps was removed from + * {@code MapMaker}, it was added to {@code CacheBuilder}, which throws {@code + * ExecutionException}, {@code UncheckedExecutionException}, and {@code ExecutionError}. Any + * code that is still catching {@code ComputationException} may need to be updated to catch some + * of those types instead. (Note that this type, though deprecated, is not planned to be removed + * from Guava.) + */ +@Deprecated +@GwtCompatible +public class ComputationException extends RuntimeException { + /** Creates a new instance with the given cause. */ + public ComputationException(Throwable cause) { + super(cause); + } + + private static final long serialVersionUID = 0; +} diff --git a/src/main/java/com/google/common/collect/ConcurrentHashMultiset.java b/src/main/java/com/google/common/collect/ConcurrentHashMultiset.java new file mode 100644 index 0000000..bf0e96c --- /dev/null +++ b/src/main/java/com/google/common/collect/ConcurrentHashMultiset.java @@ -0,0 +1,602 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.collect.CollectPreconditions.checkNonnegative; +import static com.google.common.collect.CollectPreconditions.checkRemove; + +import com.google.common.annotations.Beta; +import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.VisibleForTesting; +import com.google.common.collect.Serialization.FieldSetter; +import com.google.common.math.IntMath; +import com.google.common.primitives.Ints; + + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.Serializable; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.atomic.AtomicInteger; + + +/** + * A multiset that supports concurrent modifications and that provides atomic versions of most + * {@code Multiset} operations (exceptions where noted). Null elements are not supported. + * + *

See the Guava User Guide article on {@code + * Multiset}. + * + * @author Cliff L. Biffle + * @author mike nonemacher + * @since 2.0 + */ +@GwtIncompatible +public final class ConcurrentHashMultiset extends AbstractMultiset implements Serializable { + + /* + * The ConcurrentHashMultiset's atomic operations are implemented primarily in terms of + * AtomicInteger's atomic operations, with some help from ConcurrentMap's atomic operations on + * creation and removal (including automatic removal of zeroes). If the modification of an + * AtomicInteger results in zero, we compareAndSet the value to zero; if that succeeds, we remove + * the entry from the Map. If another operation sees a zero in the map, it knows that the entry is + * about to be removed, so this operation may remove it (often by replacing it with a new + * AtomicInteger). + */ + + /** The number of occurrences of each element. */ + private final transient ConcurrentMap countMap; + + // This constant allows the deserialization code to set a final field. This holder class + // makes sure it is not initialized unless an instance is deserialized. + private static class FieldSettersHolder { + static final FieldSetter COUNT_MAP_FIELD_SETTER = + Serialization.getFieldSetter(ConcurrentHashMultiset.class, "countMap"); + } + + /** + * Creates a new, empty {@code ConcurrentHashMultiset} using the default initial capacity, load + * factor, and concurrency settings. + */ + public static ConcurrentHashMultiset create() { + // TODO(schmoe): provide a way to use this class with other (possibly arbitrary) + // ConcurrentMap implementors. One possibility is to extract most of this class into + // an AbstractConcurrentMapMultiset. + return new ConcurrentHashMultiset(new ConcurrentHashMap()); + } + + /** + * Creates a new {@code ConcurrentHashMultiset} containing the specified elements, using the + * default initial capacity, load factor, and concurrency settings. + * + *

This implementation is highly efficient when {@code elements} is itself a {@link Multiset}. + * + * @param elements the elements that the multiset should contain + */ + public static ConcurrentHashMultiset create(Iterable elements) { + ConcurrentHashMultiset multiset = ConcurrentHashMultiset.create(); + Iterables.addAll(multiset, elements); + return multiset; + } + + /** + * Creates a new, empty {@code ConcurrentHashMultiset} using {@code countMap} as the internal + * backing map. + * + *

This instance will assume ownership of {@code countMap}, and other code should not maintain + * references to the map or modify it in any way. + * + *

The returned multiset is serializable if the input map is. + * + * @param countMap backing map for storing the elements in the multiset and their counts. It must + * be empty. + * @throws IllegalArgumentException if {@code countMap} is not empty + * @since 20.0 + */ + @Beta + public static ConcurrentHashMultiset create(ConcurrentMap countMap) { + return new ConcurrentHashMultiset(countMap); + } + + @VisibleForTesting + ConcurrentHashMultiset(ConcurrentMap countMap) { + checkArgument(countMap.isEmpty(), "the backing map (%s) must be empty", countMap); + this.countMap = countMap; + } + + // Query Operations + + /** + * Returns the number of occurrences of {@code element} in this multiset. + * + * @param element the element to look for + * @return the nonnegative number of occurrences of the element + */ + @Override + public int count(Object element) { + AtomicInteger existingCounter = Maps.safeGet(countMap, element); + return (existingCounter == null) ? 0 : existingCounter.get(); + } + + /** + * {@inheritDoc} + * + *

If the data in the multiset is modified by any other threads during this method, it is + * undefined which (if any) of these modifications will be reflected in the result. + */ + @Override + public int size() { + long sum = 0L; + for (AtomicInteger value : countMap.values()) { + sum += value.get(); + } + return Ints.saturatedCast(sum); + } + + /* + * Note: the superclass toArray() methods assume that size() gives a correct + * answer, which ours does not. + */ + + @Override + public Object[] toArray() { + return snapshot().toArray(); + } + + @Override + public T[] toArray(T[] array) { + return snapshot().toArray(array); + } + + /* + * We'd love to use 'new ArrayList(this)' or 'list.addAll(this)', but + * either of these would recurse back to us again! + */ + private List snapshot() { + List list = Lists.newArrayListWithExpectedSize(size()); + for (Multiset.Entry entry : entrySet()) { + E element = entry.getElement(); + for (int i = entry.getCount(); i > 0; i--) { + list.add(element); + } + } + return list; + } + + // Modification Operations + + /** + * Adds a number of occurrences of the specified element to this multiset. + * + * @param element the element to add + * @param occurrences the number of occurrences to add + * @return the previous count of the element before the operation; possibly zero + * @throws IllegalArgumentException if {@code occurrences} is negative, or if the resulting amount + * would exceed {@link Integer#MAX_VALUE} + */ + + @Override + public int add(E element, int occurrences) { + checkNotNull(element); + if (occurrences == 0) { + return count(element); + } + CollectPreconditions.checkPositive(occurrences, "occurences"); + + while (true) { + AtomicInteger existingCounter = Maps.safeGet(countMap, element); + if (existingCounter == null) { + existingCounter = countMap.putIfAbsent(element, new AtomicInteger(occurrences)); + if (existingCounter == null) { + return 0; + } + // existingCounter != null: fall through to operate against the existing AtomicInteger + } + + while (true) { + int oldValue = existingCounter.get(); + if (oldValue != 0) { + try { + int newValue = IntMath.checkedAdd(oldValue, occurrences); + if (existingCounter.compareAndSet(oldValue, newValue)) { + // newValue can't == 0, so no need to check & remove + return oldValue; + } + } catch (ArithmeticException overflow) { + throw new IllegalArgumentException( + "Overflow adding " + occurrences + " occurrences to a count of " + oldValue); + } + } else { + // In the case of a concurrent remove, we might observe a zero value, which means another + // thread is about to remove (element, existingCounter) from the map. Rather than wait, + // we can just do that work here. + AtomicInteger newCounter = new AtomicInteger(occurrences); + if ((countMap.putIfAbsent(element, newCounter) == null) + || countMap.replace(element, existingCounter, newCounter)) { + return 0; + } + break; + } + } + + // If we're still here, there was a race, so just try again. + } + } + + /** + * Removes a number of occurrences of the specified element from this multiset. If the multiset + * contains fewer than this number of occurrences to begin with, all occurrences will be removed. + * + * @param element the element whose occurrences should be removed + * @param occurrences the number of occurrences of the element to remove + * @return the count of the element before the operation; possibly zero + * @throws IllegalArgumentException if {@code occurrences} is negative + */ + /* + * TODO(cpovirk): remove and removeExactly currently accept null inputs only + * if occurrences == 0. This satisfies both NullPointerTester and + * CollectionRemoveTester.testRemove_nullAllowed, but it's not clear that it's + * a good policy, especially because, in order for the test to pass, the + * parameter must be misleadingly annotated as . I suspect that + * we'll want to remove , add an eager checkNotNull, and loosen up + * testRemove_nullAllowed. + */ + + @Override + public int remove(Object element, int occurrences) { + if (occurrences == 0) { + return count(element); + } + CollectPreconditions.checkPositive(occurrences, "occurences"); + + AtomicInteger existingCounter = Maps.safeGet(countMap, element); + if (existingCounter == null) { + return 0; + } + while (true) { + int oldValue = existingCounter.get(); + if (oldValue != 0) { + int newValue = Math.max(0, oldValue - occurrences); + if (existingCounter.compareAndSet(oldValue, newValue)) { + if (newValue == 0) { + // Just CASed to 0; remove the entry to clean up the map. If the removal fails, + // another thread has already replaced it with a new counter, which is fine. + countMap.remove(element, existingCounter); + } + return oldValue; + } + } else { + return 0; + } + } + } + + /** + * Removes exactly the specified number of occurrences of {@code element}, or makes no change if + * this is not possible. + * + *

This method, in contrast to {@link #remove(Object, int)}, has no effect when the element + * count is smaller than {@code occurrences}. + * + * @param element the element to remove + * @param occurrences the number of occurrences of {@code element} to remove + * @return {@code true} if the removal was possible (including if {@code occurrences} is zero) + * @throws IllegalArgumentException if {@code occurrences} is negative + */ + + public boolean removeExactly(Object element, int occurrences) { + if (occurrences == 0) { + return true; + } + CollectPreconditions.checkPositive(occurrences, "occurences"); + + AtomicInteger existingCounter = Maps.safeGet(countMap, element); + if (existingCounter == null) { + return false; + } + while (true) { + int oldValue = existingCounter.get(); + if (oldValue < occurrences) { + return false; + } + int newValue = oldValue - occurrences; + if (existingCounter.compareAndSet(oldValue, newValue)) { + if (newValue == 0) { + // Just CASed to 0; remove the entry to clean up the map. If the removal fails, + // another thread has already replaced it with a new counter, which is fine. + countMap.remove(element, existingCounter); + } + return true; + } + } + } + + /** + * Adds or removes occurrences of {@code element} such that the {@link #count} of the element + * becomes {@code count}. + * + * @return the count of {@code element} in the multiset before this call + * @throws IllegalArgumentException if {@code count} is negative + */ + + @Override + public int setCount(E element, int count) { + checkNotNull(element); + checkNonnegative(count, "count"); + while (true) { + AtomicInteger existingCounter = Maps.safeGet(countMap, element); + if (existingCounter == null) { + if (count == 0) { + return 0; + } else { + existingCounter = countMap.putIfAbsent(element, new AtomicInteger(count)); + if (existingCounter == null) { + return 0; + } + // existingCounter != null: fall through + } + } + + while (true) { + int oldValue = existingCounter.get(); + if (oldValue == 0) { + if (count == 0) { + return 0; + } else { + AtomicInteger newCounter = new AtomicInteger(count); + if ((countMap.putIfAbsent(element, newCounter) == null) + || countMap.replace(element, existingCounter, newCounter)) { + return 0; + } + } + break; + } else { + if (existingCounter.compareAndSet(oldValue, count)) { + if (count == 0) { + // Just CASed to 0; remove the entry to clean up the map. If the removal fails, + // another thread has already replaced it with a new counter, which is fine. + countMap.remove(element, existingCounter); + } + return oldValue; + } + } + } + } + } + + /** + * Sets the number of occurrences of {@code element} to {@code newCount}, but only if the count is + * currently {@code expectedOldCount}. If {@code element} does not appear in the multiset exactly + * {@code expectedOldCount} times, no changes will be made. + * + * @return {@code true} if the change was successful. This usually indicates that the multiset has + * been modified, but not always: in the case that {@code expectedOldCount == newCount}, the + * method will return {@code true} if the condition was met. + * @throws IllegalArgumentException if {@code expectedOldCount} or {@code newCount} is negative + */ + + @Override + public boolean setCount(E element, int expectedOldCount, int newCount) { + checkNotNull(element); + checkNonnegative(expectedOldCount, "oldCount"); + checkNonnegative(newCount, "newCount"); + + AtomicInteger existingCounter = Maps.safeGet(countMap, element); + if (existingCounter == null) { + if (expectedOldCount != 0) { + return false; + } else if (newCount == 0) { + return true; + } else { + // if our write lost the race, it must have lost to a nonzero value, so we can stop + return countMap.putIfAbsent(element, new AtomicInteger(newCount)) == null; + } + } + int oldValue = existingCounter.get(); + if (oldValue == expectedOldCount) { + if (oldValue == 0) { + if (newCount == 0) { + // Just observed a 0; try to remove the entry to clean up the map + countMap.remove(element, existingCounter); + return true; + } else { + AtomicInteger newCounter = new AtomicInteger(newCount); + return (countMap.putIfAbsent(element, newCounter) == null) + || countMap.replace(element, existingCounter, newCounter); + } + } else { + if (existingCounter.compareAndSet(oldValue, newCount)) { + if (newCount == 0) { + // Just CASed to 0; remove the entry to clean up the map. If the removal fails, + // another thread has already replaced it with a new counter, which is fine. + countMap.remove(element, existingCounter); + } + return true; + } + } + } + return false; + } + + // Views + + @Override + Set createElementSet() { + final Set delegate = countMap.keySet(); + return new ForwardingSet() { + @Override + protected Set delegate() { + return delegate; + } + + @Override + public boolean contains(Object object) { + return object != null && Collections2.safeContains(delegate, object); + } + + @Override + public boolean containsAll(Collection collection) { + return standardContainsAll(collection); + } + + @Override + public boolean remove(Object object) { + return object != null && Collections2.safeRemove(delegate, object); + } + + @Override + public boolean removeAll(Collection c) { + return standardRemoveAll(c); + } + }; + } + + @Override + Iterator elementIterator() { + throw new AssertionError("should never be called"); + } + + /** @deprecated Internal method, use {@link #entrySet()}. */ + @Deprecated + @Override + public Set> createEntrySet() { + return new EntrySet(); + } + + @Override + int distinctElements() { + return countMap.size(); + } + + @Override + public boolean isEmpty() { + return countMap.isEmpty(); + } + + @Override + Iterator> entryIterator() { + // AbstractIterator makes this fairly clean, but it doesn't support remove(). To support + // remove(), we create an AbstractIterator, and then use ForwardingIterator to delegate to it. + final Iterator> readOnlyIterator = + new AbstractIterator>() { + private final Iterator> mapEntries = + countMap.entrySet().iterator(); + + @Override + protected Entry computeNext() { + while (true) { + if (!mapEntries.hasNext()) { + return endOfData(); + } + Map.Entry mapEntry = mapEntries.next(); + int count = mapEntry.getValue().get(); + if (count != 0) { + return Multisets.immutableEntry(mapEntry.getKey(), count); + } + } + } + }; + + return new ForwardingIterator>() { + private Entry last; + + @Override + protected Iterator> delegate() { + return readOnlyIterator; + } + + @Override + public Entry next() { + last = super.next(); + return last; + } + + @Override + public void remove() { + checkRemove(last != null); + ConcurrentHashMultiset.this.setCount(last.getElement(), 0); + last = null; + } + }; + } + + @Override + public Iterator iterator() { + return Multisets.iteratorImpl(this); + } + + @Override + public void clear() { + countMap.clear(); + } + + + private class EntrySet extends AbstractMultiset.EntrySet { + @Override + ConcurrentHashMultiset multiset() { + return ConcurrentHashMultiset.this; + } + + /* + * Note: the superclass toArray() methods assume that size() gives a correct + * answer, which ours does not. + */ + + @Override + public Object[] toArray() { + return snapshot().toArray(); + } + + @Override + public T[] toArray(T[] array) { + return snapshot().toArray(array); + } + + private List> snapshot() { + List> list = Lists.newArrayListWithExpectedSize(size()); + // Not Iterables.addAll(list, this), because that'll forward right back here. + Iterators.addAll(list, iterator()); + return list; + } + } + + /** @serialData the ConcurrentMap of elements and their counts. */ + private void writeObject(ObjectOutputStream stream) throws IOException { + stream.defaultWriteObject(); + stream.writeObject(countMap); + } + + private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException { + stream.defaultReadObject(); + @SuppressWarnings("unchecked") // reading data stored by writeObject + ConcurrentMap deserializedCountMap = + (ConcurrentMap) stream.readObject(); + FieldSettersHolder.COUNT_MAP_FIELD_SETTER.set(this, deserializedCountMap); + } + + private static final long serialVersionUID = 1; +} diff --git a/src/main/java/com/google/common/collect/ConsumingQueueIterator.java b/src/main/java/com/google/common/collect/ConsumingQueueIterator.java new file mode 100644 index 0000000..2f288f0 --- /dev/null +++ b/src/main/java/com/google/common/collect/ConsumingQueueIterator.java @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2015 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.collect; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.annotations.GwtCompatible; +import java.util.ArrayDeque; +import java.util.Collections; +import java.util.Queue; + +/** + * An Iterator implementation which draws elements from a queue, removing them from the queue as it + * iterates. + */ +@GwtCompatible +class ConsumingQueueIterator extends AbstractIterator { + private final Queue queue; + + ConsumingQueueIterator(T... elements) { + this.queue = new ArrayDeque(elements.length); + Collections.addAll(queue, elements); + } + + ConsumingQueueIterator(Queue queue) { + this.queue = checkNotNull(queue); + } + + @Override + public T computeNext() { + return queue.isEmpty() ? endOfData() : queue.remove(); + } +} diff --git a/src/main/java/com/google/common/collect/ContiguousSet.java b/src/main/java/com/google/common/collect/ContiguousSet.java new file mode 100644 index 0000000..6755be6 --- /dev/null +++ b/src/main/java/com/google/common/collect/ContiguousSet.java @@ -0,0 +1,258 @@ +/* + * Copyright (C) 2010 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.collect; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.annotations.Beta; +import com.google.common.annotations.GwtCompatible; +import com.google.common.annotations.GwtIncompatible; +import java.util.Collections; +import java.util.NoSuchElementException; +import java.util.Set; + +/** + * A sorted set of contiguous values in a given {@link DiscreteDomain}. Example: + * + *

{@code
+ * ContiguousSet.create(Range.closed(5, 42), DiscreteDomain.integers())
+ * }
+ * + *

Note that because bounded ranges over {@code int} and {@code long} values are so common, this + * particular example can be written as just: + * + *

{@code
+ * ContiguousSet.closed(5, 42)
+ * }
+ * + *

Warning: Be extremely careful what you do with conceptually large instances (such as + * {@code ContiguousSet.create(Range.greaterThan(0), DiscreteDomain.integers()}). Certain operations + * on such a set can be performed efficiently, but others (such as {@link Set#hashCode} or {@link + * Collections#frequency}) can cause major performance problems. + * + * @author Gregory Kick + * @since 10.0 + */ +@GwtCompatible(emulated = true) +@SuppressWarnings("rawtypes") // allow ungenerified Comparable types +public abstract class ContiguousSet extends ImmutableSortedSet { + /** + * Returns a {@code ContiguousSet} containing the same values in the given domain {@linkplain + * Range#contains contained} by the range. + * + * @throws IllegalArgumentException if neither range nor the domain has a lower bound, or if + * neither has an upper bound + * @since 13.0 + */ + public static ContiguousSet create( + Range range, DiscreteDomain domain) { + checkNotNull(range); + checkNotNull(domain); + Range effectiveRange = range; + try { + if (!range.hasLowerBound()) { + effectiveRange = effectiveRange.intersection(Range.atLeast(domain.minValue())); + } + if (!range.hasUpperBound()) { + effectiveRange = effectiveRange.intersection(Range.atMost(domain.maxValue())); + } + } catch (NoSuchElementException e) { + throw new IllegalArgumentException(e); + } + + // Per class spec, we are allowed to throw CCE if necessary + boolean empty = + effectiveRange.isEmpty() + || Range.compareOrThrow( + range.lowerBound.leastValueAbove(domain), + range.upperBound.greatestValueBelow(domain)) + > 0; + + return empty + ? new EmptyContiguousSet(domain) + : new RegularContiguousSet(effectiveRange, domain); + } + + /** + * Returns a nonempty contiguous set containing all {@code int} values from {@code lower} + * (inclusive) to {@code upper} (inclusive). (These are the same values contained in {@code + * Range.closed(lower, upper)}.) + * + * @throws IllegalArgumentException if {@code lower} is greater than {@code upper} + * @since 23.0 + */ + @Beta + public static ContiguousSet closed(int lower, int upper) { + return create(Range.closed(lower, upper), DiscreteDomain.integers()); + } + + /** + * Returns a nonempty contiguous set containing all {@code long} values from {@code lower} + * (inclusive) to {@code upper} (inclusive). (These are the same values contained in {@code + * Range.closed(lower, upper)}.) + * + * @throws IllegalArgumentException if {@code lower} is greater than {@code upper} + * @since 23.0 + */ + @Beta + public static ContiguousSet closed(long lower, long upper) { + return create(Range.closed(lower, upper), DiscreteDomain.longs()); + } + + /** + * Returns a contiguous set containing all {@code int} values from {@code lower} (inclusive) to + * {@code upper} (exclusive). If the endpoints are equal, an empty set is returned. (These are the + * same values contained in {@code Range.closedOpen(lower, upper)}.) + * + * @throws IllegalArgumentException if {@code lower} is greater than {@code upper} + * @since 23.0 + */ + @Beta + public static ContiguousSet closedOpen(int lower, int upper) { + return create(Range.closedOpen(lower, upper), DiscreteDomain.integers()); + } + + /** + * Returns a contiguous set containing all {@code long} values from {@code lower} (inclusive) to + * {@code upper} (exclusive). If the endpoints are equal, an empty set is returned. (These are the + * same values contained in {@code Range.closedOpen(lower, upper)}.) + * + * @throws IllegalArgumentException if {@code lower} is greater than {@code upper} + * @since 23.0 + */ + @Beta + public static ContiguousSet closedOpen(long lower, long upper) { + return create(Range.closedOpen(lower, upper), DiscreteDomain.longs()); + } + + final DiscreteDomain domain; + + ContiguousSet(DiscreteDomain domain) { + super(Ordering.natural()); + this.domain = domain; + } + + @Override + public ContiguousSet headSet(C toElement) { + return headSetImpl(checkNotNull(toElement), false); + } + + /** @since 12.0 */ + @GwtIncompatible // NavigableSet + @Override + public ContiguousSet headSet(C toElement, boolean inclusive) { + return headSetImpl(checkNotNull(toElement), inclusive); + } + + @Override + public ContiguousSet subSet(C fromElement, C toElement) { + checkNotNull(fromElement); + checkNotNull(toElement); + checkArgument(comparator().compare(fromElement, toElement) <= 0); + return subSetImpl(fromElement, true, toElement, false); + } + + /** @since 12.0 */ + @GwtIncompatible // NavigableSet + @Override + public ContiguousSet subSet( + C fromElement, boolean fromInclusive, C toElement, boolean toInclusive) { + checkNotNull(fromElement); + checkNotNull(toElement); + checkArgument(comparator().compare(fromElement, toElement) <= 0); + return subSetImpl(fromElement, fromInclusive, toElement, toInclusive); + } + + @Override + public ContiguousSet tailSet(C fromElement) { + return tailSetImpl(checkNotNull(fromElement), true); + } + + /** @since 12.0 */ + @GwtIncompatible // NavigableSet + @Override + public ContiguousSet tailSet(C fromElement, boolean inclusive) { + return tailSetImpl(checkNotNull(fromElement), inclusive); + } + + /* + * These methods perform most headSet, subSet, and tailSet logic, besides parameter validation. + */ + // TODO(kevinb): we can probably make these real @Overrides now + /* @Override */ + abstract ContiguousSet headSetImpl(C toElement, boolean inclusive); + + /* @Override */ + abstract ContiguousSet subSetImpl( + C fromElement, boolean fromInclusive, C toElement, boolean toInclusive); + + /* @Override */ + abstract ContiguousSet tailSetImpl(C fromElement, boolean inclusive); + + /** + * Returns the set of values that are contained in both this set and the other. + * + *

This method should always be used instead of {@link Sets#intersection} for {@link + * ContiguousSet} instances. + */ + public abstract ContiguousSet intersection(ContiguousSet other); + + /** + * Returns a range, closed on both ends, whose endpoints are the minimum and maximum values + * contained in this set. This is equivalent to {@code range(CLOSED, CLOSED)}. + * + * @throws NoSuchElementException if this set is empty + */ + public abstract Range range(); + + /** + * Returns the minimal range with the given boundary types for which all values in this set are + * {@linkplain Range#contains(Comparable) contained} within the range. + * + *

Note that this method will return ranges with unbounded endpoints if {@link BoundType#OPEN} + * is requested for a domain minimum or maximum. For example, if {@code set} was created from the + * range {@code [1..Integer.MAX_VALUE]} then {@code set.range(CLOSED, OPEN)} must return {@code + * [1..∞)}. + * + * @throws NoSuchElementException if this set is empty + */ + public abstract Range range(BoundType lowerBoundType, BoundType upperBoundType); + + @Override + @GwtIncompatible // NavigableSet + ImmutableSortedSet createDescendingSet() { + return new DescendingImmutableSortedSet(this); + } + + /** Returns a short-hand representation of the contents such as {@code "[1..100]"}. */ + @Override + public String toString() { + return range().toString(); + } + + /** + * Not supported. {@code ContiguousSet} instances are constructed with {@link #create}. This + * method exists only to hide {@link ImmutableSet#builder} from consumers of {@code + * ContiguousSet}. + * + * @throws UnsupportedOperationException always + * @deprecated Use {@link #create}. + */ + @Deprecated + public static ImmutableSortedSet.Builder builder() { + throw new UnsupportedOperationException(); + } +} diff --git a/src/main/java/com/google/common/collect/Count.java b/src/main/java/com/google/common/collect/Count.java new file mode 100644 index 0000000..2370f33 --- /dev/null +++ b/src/main/java/com/google/common/collect/Count.java @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2011 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the + * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import com.google.common.annotations.GwtCompatible; +import java.io.Serializable; + + +/** + * A mutable value of type {@code int}, for multisets to use in tracking counts of values. + * + * @author Louis Wasserman + */ +@GwtCompatible +final class Count implements Serializable { + private int value; + + Count(int value) { + this.value = value; + } + + public int get() { + return value; + } + + public void add(int delta) { + value += delta; + } + + public int addAndGet(int delta) { + return value += delta; + } + + public void set(int newValue) { + value = newValue; + } + + public int getAndSet(int newValue) { + int result = value; + value = newValue; + return result; + } + + @Override + public int hashCode() { + return value; + } + + @Override + public boolean equals(Object obj) { + return obj instanceof Count && ((Count) obj).value == value; + } + + @Override + public String toString() { + return Integer.toString(value); + } +} diff --git a/src/main/java/com/google/common/collect/Cut.java b/src/main/java/com/google/common/collect/Cut.java new file mode 100644 index 0000000..cbe4109 --- /dev/null +++ b/src/main/java/com/google/common/collect/Cut.java @@ -0,0 +1,470 @@ +/* + * Copyright (C) 2009 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.collect; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.annotations.GwtCompatible; +import com.google.common.primitives.Booleans; +import java.io.Serializable; +import java.util.NoSuchElementException; + + +/** + * Implementation detail for the internal structure of {@link Range} instances. Represents a unique + * way of "cutting" a "number line" (actually of instances of type {@code C}, not necessarily + * "numbers") into two sections; this can be done below a certain value, above a certain value, + * below all values or above all values. With this object defined in this way, an interval can + * always be represented by a pair of {@code Cut} instances. + * + * @author Kevin Bourrillion + */ +@GwtCompatible +abstract class Cut implements Comparable>, Serializable { + final C endpoint; + + Cut(C endpoint) { + this.endpoint = endpoint; + } + + abstract boolean isLessThan(C value); + + abstract BoundType typeAsLowerBound(); + + abstract BoundType typeAsUpperBound(); + + abstract Cut withLowerBoundType(BoundType boundType, DiscreteDomain domain); + + abstract Cut withUpperBoundType(BoundType boundType, DiscreteDomain domain); + + abstract void describeAsLowerBound(StringBuilder sb); + + abstract void describeAsUpperBound(StringBuilder sb); + + abstract C leastValueAbove(DiscreteDomain domain); + + abstract C greatestValueBelow(DiscreteDomain domain); + + /* + * The canonical form is a BelowValue cut whenever possible, otherwise ABOVE_ALL, or + * (only in the case of types that are unbounded below) BELOW_ALL. + */ + Cut canonical(DiscreteDomain domain) { + return this; + } + + // note: overridden by {BELOW,ABOVE}_ALL + @Override + public int compareTo(Cut that) { + if (that == belowAll()) { + return 1; + } + if (that == aboveAll()) { + return -1; + } + int result = Range.compareOrThrow(endpoint, that.endpoint); + if (result != 0) { + return result; + } + // same value. below comes before above + return Booleans.compare(this instanceof AboveValue, that instanceof AboveValue); + } + + C endpoint() { + return endpoint; + } + + @SuppressWarnings("unchecked") // catching CCE + @Override + public boolean equals(Object obj) { + if (obj instanceof Cut) { + // It might not really be a Cut, but we'll catch a CCE if it's not + Cut that = (Cut) obj; + try { + int compareResult = compareTo(that); + return compareResult == 0; + } catch (ClassCastException ignored) { + } + } + return false; + } + + // Prevent "missing hashCode" warning by explicitly forcing subclasses implement it + @Override + public abstract int hashCode(); + + /* + * The implementation neither produces nor consumes any non-null instance of type C, so + * casting the type parameter is safe. + */ + @SuppressWarnings("unchecked") + static Cut belowAll() { + return (Cut) BelowAll.INSTANCE; + } + + private static final long serialVersionUID = 0; + + private static final class BelowAll extends Cut> { + private static final BelowAll INSTANCE = new BelowAll(); + + private BelowAll() { + super(null); + } + + @Override + Comparable endpoint() { + throw new IllegalStateException("range unbounded on this side"); + } + + @Override + boolean isLessThan(Comparable value) { + return true; + } + + @Override + BoundType typeAsLowerBound() { + throw new IllegalStateException(); + } + + @Override + BoundType typeAsUpperBound() { + throw new AssertionError("this statement should be unreachable"); + } + + @Override + Cut> withLowerBoundType( + BoundType boundType, DiscreteDomain> domain) { + throw new IllegalStateException(); + } + + @Override + Cut> withUpperBoundType( + BoundType boundType, DiscreteDomain> domain) { + throw new AssertionError("this statement should be unreachable"); + } + + @Override + void describeAsLowerBound(StringBuilder sb) { + sb.append("(-\u221e"); + } + + @Override + void describeAsUpperBound(StringBuilder sb) { + throw new AssertionError(); + } + + @Override + Comparable leastValueAbove(DiscreteDomain> domain) { + return domain.minValue(); + } + + @Override + Comparable greatestValueBelow(DiscreteDomain> domain) { + throw new AssertionError(); + } + + @Override + Cut> canonical(DiscreteDomain> domain) { + try { + return Cut.>belowValue(domain.minValue()); + } catch (NoSuchElementException e) { + return this; + } + } + + @Override + public int compareTo(Cut> o) { + return (o == this) ? 0 : -1; + } + + @Override + public int hashCode() { + return System.identityHashCode(this); + } + + @Override + public String toString() { + return "-\u221e"; + } + + private Object readResolve() { + return INSTANCE; + } + + private static final long serialVersionUID = 0; + } + + /* + * The implementation neither produces nor consumes any non-null instance of + * type C, so casting the type parameter is safe. + */ + @SuppressWarnings("unchecked") + static Cut aboveAll() { + return (Cut) AboveAll.INSTANCE; + } + + private static final class AboveAll extends Cut> { + private static final AboveAll INSTANCE = new AboveAll(); + + private AboveAll() { + super(null); + } + + @Override + Comparable endpoint() { + throw new IllegalStateException("range unbounded on this side"); + } + + @Override + boolean isLessThan(Comparable value) { + return false; + } + + @Override + BoundType typeAsLowerBound() { + throw new AssertionError("this statement should be unreachable"); + } + + @Override + BoundType typeAsUpperBound() { + throw new IllegalStateException(); + } + + @Override + Cut> withLowerBoundType( + BoundType boundType, DiscreteDomain> domain) { + throw new AssertionError("this statement should be unreachable"); + } + + @Override + Cut> withUpperBoundType( + BoundType boundType, DiscreteDomain> domain) { + throw new IllegalStateException(); + } + + @Override + void describeAsLowerBound(StringBuilder sb) { + throw new AssertionError(); + } + + @Override + void describeAsUpperBound(StringBuilder sb) { + sb.append("+\u221e)"); + } + + @Override + Comparable leastValueAbove(DiscreteDomain> domain) { + throw new AssertionError(); + } + + @Override + Comparable greatestValueBelow(DiscreteDomain> domain) { + return domain.maxValue(); + } + + @Override + public int compareTo(Cut> o) { + return (o == this) ? 0 : 1; + } + + @Override + public int hashCode() { + return System.identityHashCode(this); + } + + @Override + public String toString() { + return "+\u221e"; + } + + private Object readResolve() { + return INSTANCE; + } + + private static final long serialVersionUID = 0; + } + + static Cut belowValue(C endpoint) { + return new BelowValue(endpoint); + } + + private static final class BelowValue extends Cut { + BelowValue(C endpoint) { + super(checkNotNull(endpoint)); + } + + @Override + boolean isLessThan(C value) { + return Range.compareOrThrow(endpoint, value) <= 0; + } + + @Override + BoundType typeAsLowerBound() { + return BoundType.CLOSED; + } + + @Override + BoundType typeAsUpperBound() { + return BoundType.OPEN; + } + + @Override + Cut withLowerBoundType(BoundType boundType, DiscreteDomain domain) { + switch (boundType) { + case CLOSED: + return this; + case OPEN: + C previous = domain.previous(endpoint); + return (previous == null) ? Cut.belowAll() : new AboveValue(previous); + default: + throw new AssertionError(); + } + } + + @Override + Cut withUpperBoundType(BoundType boundType, DiscreteDomain domain) { + switch (boundType) { + case CLOSED: + C previous = domain.previous(endpoint); + return (previous == null) ? Cut.aboveAll() : new AboveValue(previous); + case OPEN: + return this; + default: + throw new AssertionError(); + } + } + + @Override + void describeAsLowerBound(StringBuilder sb) { + sb.append('[').append(endpoint); + } + + @Override + void describeAsUpperBound(StringBuilder sb) { + sb.append(endpoint).append(')'); + } + + @Override + C leastValueAbove(DiscreteDomain domain) { + return endpoint; + } + + @Override + C greatestValueBelow(DiscreteDomain domain) { + return domain.previous(endpoint); + } + + @Override + public int hashCode() { + return endpoint.hashCode(); + } + + @Override + public String toString() { + return "\\" + endpoint + "/"; + } + + private static final long serialVersionUID = 0; + } + + static Cut aboveValue(C endpoint) { + return new AboveValue(endpoint); + } + + private static final class AboveValue extends Cut { + AboveValue(C endpoint) { + super(checkNotNull(endpoint)); + } + + @Override + boolean isLessThan(C value) { + return Range.compareOrThrow(endpoint, value) < 0; + } + + @Override + BoundType typeAsLowerBound() { + return BoundType.OPEN; + } + + @Override + BoundType typeAsUpperBound() { + return BoundType.CLOSED; + } + + @Override + Cut withLowerBoundType(BoundType boundType, DiscreteDomain domain) { + switch (boundType) { + case OPEN: + return this; + case CLOSED: + C next = domain.next(endpoint); + return (next == null) ? Cut.belowAll() : belowValue(next); + default: + throw new AssertionError(); + } + } + + @Override + Cut withUpperBoundType(BoundType boundType, DiscreteDomain domain) { + switch (boundType) { + case OPEN: + C next = domain.next(endpoint); + return (next == null) ? Cut.aboveAll() : belowValue(next); + case CLOSED: + return this; + default: + throw new AssertionError(); + } + } + + @Override + void describeAsLowerBound(StringBuilder sb) { + sb.append('(').append(endpoint); + } + + @Override + void describeAsUpperBound(StringBuilder sb) { + sb.append(endpoint).append(']'); + } + + @Override + C leastValueAbove(DiscreteDomain domain) { + return domain.next(endpoint); + } + + @Override + C greatestValueBelow(DiscreteDomain domain) { + return endpoint; + } + + @Override + Cut canonical(DiscreteDomain domain) { + C next = leastValueAbove(domain); + return (next != null) ? belowValue(next) : Cut.aboveAll(); + } + + @Override + public int hashCode() { + return ~endpoint.hashCode(); + } + + @Override + public String toString() { + return "/" + endpoint + "\\"; + } + + private static final long serialVersionUID = 0; + } +} diff --git a/src/main/java/com/google/common/collect/DenseImmutableTable.java b/src/main/java/com/google/common/collect/DenseImmutableTable.java new file mode 100644 index 0000000..1471eb8 --- /dev/null +++ b/src/main/java/com/google/common/collect/DenseImmutableTable.java @@ -0,0 +1,276 @@ +/* + * Copyright (C) 2009 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.collect; + +import com.google.common.annotations.GwtCompatible; +import com.google.common.collect.ImmutableMap.IteratorBasedImmutableMap; + + +import java.util.Map; + + +/** A {@code RegularImmutableTable} optimized for dense data. */ +@GwtCompatible +final class DenseImmutableTable extends RegularImmutableTable { + private final ImmutableMap rowKeyToIndex; + private final ImmutableMap columnKeyToIndex; + private final ImmutableMap> rowMap; + private final ImmutableMap> columnMap; + + @SuppressWarnings("Immutable") // We don't modify this after construction. + private final int[] rowCounts; + + @SuppressWarnings("Immutable") // We don't modify this after construction. + private final int[] columnCounts; + + @SuppressWarnings("Immutable") // We don't modify this after construction. + private final V[][] values; + + // For each cell in iteration order, the index of that cell's row key in the row key list. + @SuppressWarnings("Immutable") // We don't modify this after construction. + private final int[] cellRowIndices; + + // For each cell in iteration order, the index of that cell's column key in the column key list. + @SuppressWarnings("Immutable") // We don't modify this after construction. + private final int[] cellColumnIndices; + + DenseImmutableTable( + ImmutableList> cellList, + ImmutableSet rowSpace, + ImmutableSet columnSpace) { + @SuppressWarnings("unchecked") + V[][] array = (V[][]) new Object[rowSpace.size()][columnSpace.size()]; + this.values = array; + this.rowKeyToIndex = Maps.indexMap(rowSpace); + this.columnKeyToIndex = Maps.indexMap(columnSpace); + rowCounts = new int[rowKeyToIndex.size()]; + columnCounts = new int[columnKeyToIndex.size()]; + int[] cellRowIndices = new int[cellList.size()]; + int[] cellColumnIndices = new int[cellList.size()]; + for (int i = 0; i < cellList.size(); i++) { + Cell cell = cellList.get(i); + R rowKey = cell.getRowKey(); + C columnKey = cell.getColumnKey(); + int rowIndex = rowKeyToIndex.get(rowKey); + int columnIndex = columnKeyToIndex.get(columnKey); + V existingValue = values[rowIndex][columnIndex]; + checkNoDuplicate(rowKey, columnKey, existingValue, cell.getValue()); + values[rowIndex][columnIndex] = cell.getValue(); + rowCounts[rowIndex]++; + columnCounts[columnIndex]++; + cellRowIndices[i] = rowIndex; + cellColumnIndices[i] = columnIndex; + } + this.cellRowIndices = cellRowIndices; + this.cellColumnIndices = cellColumnIndices; + this.rowMap = new RowMap(); + this.columnMap = new ColumnMap(); + } + + /** An immutable map implementation backed by an indexed nullable array. */ + private abstract static class ImmutableArrayMap extends IteratorBasedImmutableMap { + private final int size; + + ImmutableArrayMap(int size) { + this.size = size; + } + + abstract ImmutableMap keyToIndex(); + + // True if getValue never returns null. + private boolean isFull() { + return size == keyToIndex().size(); + } + + K getKey(int index) { + return keyToIndex().keySet().asList().get(index); + } + + abstract V getValue(int keyIndex); + + @Override + ImmutableSet createKeySet() { + return isFull() ? keyToIndex().keySet() : super.createKeySet(); + } + + @Override + public int size() { + return size; + } + + @Override + public V get(Object key) { + Integer keyIndex = keyToIndex().get(key); + return (keyIndex == null) ? null : getValue(keyIndex); + } + + @Override + UnmodifiableIterator> entryIterator() { + return new AbstractIterator>() { + private int index = -1; + private final int maxIndex = keyToIndex().size(); + + @Override + protected Entry computeNext() { + for (index++; index < maxIndex; index++) { + V value = getValue(index); + if (value != null) { + return Maps.immutableEntry(getKey(index), value); + } + } + return endOfData(); + } + }; + } + } + + private final class Row extends ImmutableArrayMap { + private final int rowIndex; + + Row(int rowIndex) { + super(rowCounts[rowIndex]); + this.rowIndex = rowIndex; + } + + @Override + ImmutableMap keyToIndex() { + return columnKeyToIndex; + } + + @Override + V getValue(int keyIndex) { + return values[rowIndex][keyIndex]; + } + + @Override + boolean isPartialView() { + return true; + } + } + + private final class Column extends ImmutableArrayMap { + private final int columnIndex; + + Column(int columnIndex) { + super(columnCounts[columnIndex]); + this.columnIndex = columnIndex; + } + + @Override + ImmutableMap keyToIndex() { + return rowKeyToIndex; + } + + @Override + V getValue(int keyIndex) { + return values[keyIndex][columnIndex]; + } + + @Override + boolean isPartialView() { + return true; + } + } + + + private final class RowMap extends ImmutableArrayMap> { + private RowMap() { + super(rowCounts.length); + } + + @Override + ImmutableMap keyToIndex() { + return rowKeyToIndex; + } + + @Override + ImmutableMap getValue(int keyIndex) { + return new Row(keyIndex); + } + + @Override + boolean isPartialView() { + return false; + } + } + + + private final class ColumnMap extends ImmutableArrayMap> { + private ColumnMap() { + super(columnCounts.length); + } + + @Override + ImmutableMap keyToIndex() { + return columnKeyToIndex; + } + + @Override + ImmutableMap getValue(int keyIndex) { + return new Column(keyIndex); + } + + @Override + boolean isPartialView() { + return false; + } + } + + @Override + public ImmutableMap> columnMap() { + // Casts without copying. + ImmutableMap> columnMap = this.columnMap; + return ImmutableMap.>copyOf(columnMap); + } + + @Override + public ImmutableMap> rowMap() { + // Casts without copying. + ImmutableMap> rowMap = this.rowMap; + return ImmutableMap.>copyOf(rowMap); + } + + @Override + public V get(Object rowKey, Object columnKey) { + Integer rowIndex = rowKeyToIndex.get(rowKey); + Integer columnIndex = columnKeyToIndex.get(columnKey); + return ((rowIndex == null) || (columnIndex == null)) ? null : values[rowIndex][columnIndex]; + } + + @Override + public int size() { + return cellRowIndices.length; + } + + @Override + Cell getCell(int index) { + int rowIndex = cellRowIndices[index]; + int columnIndex = cellColumnIndices[index]; + R rowKey = rowKeySet().asList().get(rowIndex); + C columnKey = columnKeySet().asList().get(columnIndex); + V value = values[rowIndex][columnIndex]; + return cellOf(rowKey, columnKey, value); + } + + @Override + V getValue(int index) { + return values[cellRowIndices[index]][cellColumnIndices[index]]; + } + + @Override + SerializedForm createSerializedForm() { + return SerializedForm.create(this, cellRowIndices, cellColumnIndices); + } +} diff --git a/src/main/java/com/google/common/collect/DescendingImmutableSortedMultiset.java b/src/main/java/com/google/common/collect/DescendingImmutableSortedMultiset.java new file mode 100644 index 0000000..a719e69 --- /dev/null +++ b/src/main/java/com/google/common/collect/DescendingImmutableSortedMultiset.java @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2011 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.collect; + +import com.google.common.annotations.GwtIncompatible; + + +/** + * A descending wrapper around an {@code ImmutableSortedMultiset} + * + * @author Louis Wasserman + */ +@SuppressWarnings("serial") // uses writeReplace, not default serialization +@GwtIncompatible +final class DescendingImmutableSortedMultiset extends ImmutableSortedMultiset { + private final transient ImmutableSortedMultiset forward; + + DescendingImmutableSortedMultiset(ImmutableSortedMultiset forward) { + this.forward = forward; + } + + @Override + public int count(Object element) { + return forward.count(element); + } + + @Override + public Entry firstEntry() { + return forward.lastEntry(); + } + + @Override + public Entry lastEntry() { + return forward.firstEntry(); + } + + @Override + public int size() { + return forward.size(); + } + + @Override + public ImmutableSortedSet elementSet() { + return forward.elementSet().descendingSet(); + } + + @Override + Entry getEntry(int index) { + return forward.entrySet().asList().reverse().get(index); + } + + @Override + public ImmutableSortedMultiset descendingMultiset() { + return forward; + } + + @Override + public ImmutableSortedMultiset headMultiset(E upperBound, BoundType boundType) { + return forward.tailMultiset(upperBound, boundType).descendingMultiset(); + } + + @Override + public ImmutableSortedMultiset tailMultiset(E lowerBound, BoundType boundType) { + return forward.headMultiset(lowerBound, boundType).descendingMultiset(); + } + + @Override + boolean isPartialView() { + return forward.isPartialView(); + } +} diff --git a/src/main/java/com/google/common/collect/DescendingImmutableSortedSet.java b/src/main/java/com/google/common/collect/DescendingImmutableSortedSet.java new file mode 100644 index 0000000..26aeab3 --- /dev/null +++ b/src/main/java/com/google/common/collect/DescendingImmutableSortedSet.java @@ -0,0 +1,119 @@ +/* + * Copyright (C) 2012 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import com.google.common.annotations.GwtIncompatible; + + +/** + * Skeletal implementation of {@link ImmutableSortedSet#descendingSet()}. + * + * @author Louis Wasserman + */ +@GwtIncompatible +final class DescendingImmutableSortedSet extends ImmutableSortedSet { + private final ImmutableSortedSet forward; + + DescendingImmutableSortedSet(ImmutableSortedSet forward) { + super(Ordering.from(forward.comparator()).reverse()); + this.forward = forward; + } + + @Override + public boolean contains(Object object) { + return forward.contains(object); + } + + @Override + public int size() { + return forward.size(); + } + + @Override + public UnmodifiableIterator iterator() { + return forward.descendingIterator(); + } + + @Override + ImmutableSortedSet headSetImpl(E toElement, boolean inclusive) { + return forward.tailSet(toElement, inclusive).descendingSet(); + } + + @Override + ImmutableSortedSet subSetImpl( + E fromElement, boolean fromInclusive, E toElement, boolean toInclusive) { + return forward.subSet(toElement, toInclusive, fromElement, fromInclusive).descendingSet(); + } + + @Override + ImmutableSortedSet tailSetImpl(E fromElement, boolean inclusive) { + return forward.headSet(fromElement, inclusive).descendingSet(); + } + + @Override + @GwtIncompatible("NavigableSet") + public ImmutableSortedSet descendingSet() { + return forward; + } + + @Override + @GwtIncompatible("NavigableSet") + public UnmodifiableIterator descendingIterator() { + return forward.iterator(); + } + + @Override + @GwtIncompatible("NavigableSet") + ImmutableSortedSet createDescendingSet() { + throw new AssertionError("should never be called"); + } + + @Override + public E lower(E element) { + return forward.higher(element); + } + + @Override + public E floor(E element) { + return forward.ceiling(element); + } + + @Override + public E ceiling(E element) { + return forward.floor(element); + } + + @Override + public E higher(E element) { + return forward.lower(element); + } + + @Override + int indexOf(Object target) { + int index = forward.indexOf(target); + if (index == -1) { + return index; + } else { + return size() - 1 - index; + } + } + + @Override + boolean isPartialView() { + return forward.isPartialView(); + } +} diff --git a/src/main/java/com/google/common/collect/DescendingMultiset.java b/src/main/java/com/google/common/collect/DescendingMultiset.java new file mode 100644 index 0000000..4a6bd2a --- /dev/null +++ b/src/main/java/com/google/common/collect/DescendingMultiset.java @@ -0,0 +1,157 @@ +/* + * Copyright (C) 2012 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import com.google.common.annotations.GwtCompatible; + +import java.util.Comparator; +import java.util.Iterator; +import java.util.NavigableSet; +import java.util.Set; + + +/** + * A skeleton implementation of a descending multiset. Only needs {@code forwardMultiset()} and + * {@code entryIterator()}. + * + * @author Louis Wasserman + */ +@GwtCompatible(emulated = true) +abstract class DescendingMultiset extends ForwardingMultiset implements SortedMultiset { + abstract SortedMultiset forwardMultiset(); + + private transient Comparator comparator; + + @Override + public Comparator comparator() { + Comparator result = comparator; + if (result == null) { + return comparator = Ordering.from(forwardMultiset().comparator()).reverse(); + } + return result; + } + + private transient NavigableSet elementSet; + + @Override + public NavigableSet elementSet() { + NavigableSet result = elementSet; + if (result == null) { + return elementSet = new SortedMultisets.NavigableElementSet(this); + } + return result; + } + + @Override + public Entry pollFirstEntry() { + return forwardMultiset().pollLastEntry(); + } + + @Override + public Entry pollLastEntry() { + return forwardMultiset().pollFirstEntry(); + } + + @Override + public SortedMultiset headMultiset(E toElement, BoundType boundType) { + return forwardMultiset().tailMultiset(toElement, boundType).descendingMultiset(); + } + + @Override + public SortedMultiset subMultiset( + E fromElement, BoundType fromBoundType, E toElement, BoundType toBoundType) { + return forwardMultiset() + .subMultiset(toElement, toBoundType, fromElement, fromBoundType) + .descendingMultiset(); + } + + @Override + public SortedMultiset tailMultiset(E fromElement, BoundType boundType) { + return forwardMultiset().headMultiset(fromElement, boundType).descendingMultiset(); + } + + @Override + protected Multiset delegate() { + return forwardMultiset(); + } + + @Override + public SortedMultiset descendingMultiset() { + return forwardMultiset(); + } + + @Override + public Entry firstEntry() { + return forwardMultiset().lastEntry(); + } + + @Override + public Entry lastEntry() { + return forwardMultiset().firstEntry(); + } + + abstract Iterator> entryIterator(); + + private transient Set> entrySet; + + @Override + public Set> entrySet() { + Set> result = entrySet; + return (result == null) ? entrySet = createEntrySet() : result; + } + + Set> createEntrySet() { + + class EntrySetImpl extends Multisets.EntrySet { + @Override + Multiset multiset() { + return DescendingMultiset.this; + } + + @Override + public Iterator> iterator() { + return entryIterator(); + } + + @Override + public int size() { + return forwardMultiset().entrySet().size(); + } + } + return new EntrySetImpl(); + } + + @Override + public Iterator iterator() { + return Multisets.iteratorImpl(this); + } + + @Override + public Object[] toArray() { + return standardToArray(); + } + + @Override + public T[] toArray(T[] array) { + return standardToArray(array); + } + + @Override + public String toString() { + return entrySet().toString(); + } +} diff --git a/src/main/java/com/google/common/collect/DiscreteDomain.java b/src/main/java/com/google/common/collect/DiscreteDomain.java new file mode 100644 index 0000000..61dd6f6 --- /dev/null +++ b/src/main/java/com/google/common/collect/DiscreteDomain.java @@ -0,0 +1,322 @@ +/* + * Copyright (C) 2009 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.collect.CollectPreconditions.checkNonnegative; + +import com.google.common.annotations.GwtCompatible; +import com.google.common.primitives.Ints; + +import java.io.Serializable; +import java.math.BigInteger; +import java.util.NoSuchElementException; + +/** + * A descriptor for a discrete {@code Comparable} domain such as all {@link Integer} + * instances. A discrete domain is one that supports the three basic operations: {@link #next}, + * {@link #previous} and {@link #distance}, according to their specifications. The methods {@link + * #minValue} and {@link #maxValue} should also be overridden for bounded types. + * + *

A discrete domain always represents the entire set of values of its type; it cannot + * represent partial domains such as "prime integers" or "strings of length 5." + * + *

See the Guava User Guide section on {@code + * DiscreteDomain}. + * + * @author Kevin Bourrillion + * @since 10.0 + */ +@GwtCompatible +public abstract class DiscreteDomain { + + /** + * Returns the discrete domain for values of type {@code Integer}. + * + * @since 14.0 (since 10.0 as {@code DiscreteDomains.integers()}) + */ + public static DiscreteDomain integers() { + return IntegerDomain.INSTANCE; + } + + private static final class IntegerDomain extends DiscreteDomain implements Serializable { + private static final IntegerDomain INSTANCE = new IntegerDomain(); + + IntegerDomain() { + super(true); + } + + @Override + public Integer next(Integer value) { + int i = value; + return (i == Integer.MAX_VALUE) ? null : i + 1; + } + + @Override + public Integer previous(Integer value) { + int i = value; + return (i == Integer.MIN_VALUE) ? null : i - 1; + } + + @Override + Integer offset(Integer origin, long distance) { + checkNonnegative(distance, "distance"); + return Ints.checkedCast(origin.longValue() + distance); + } + + @Override + public long distance(Integer start, Integer end) { + return (long) end - start; + } + + @Override + public Integer minValue() { + return Integer.MIN_VALUE; + } + + @Override + public Integer maxValue() { + return Integer.MAX_VALUE; + } + + private Object readResolve() { + return INSTANCE; + } + + @Override + public String toString() { + return "DiscreteDomain.integers()"; + } + + private static final long serialVersionUID = 0; + } + + /** + * Returns the discrete domain for values of type {@code Long}. + * + * @since 14.0 (since 10.0 as {@code DiscreteDomains.longs()}) + */ + public static DiscreteDomain longs() { + return LongDomain.INSTANCE; + } + + private static final class LongDomain extends DiscreteDomain implements Serializable { + private static final LongDomain INSTANCE = new LongDomain(); + + LongDomain() { + super(true); + } + + @Override + public Long next(Long value) { + long l = value; + return (l == Long.MAX_VALUE) ? null : l + 1; + } + + @Override + public Long previous(Long value) { + long l = value; + return (l == Long.MIN_VALUE) ? null : l - 1; + } + + @Override + Long offset(Long origin, long distance) { + checkNonnegative(distance, "distance"); + long result = origin + distance; + if (result < 0) { + checkArgument(origin < 0, "overflow"); + } + return result; + } + + @Override + public long distance(Long start, Long end) { + long result = end - start; + if (end > start && result < 0) { // overflow + return Long.MAX_VALUE; + } + if (end < start && result > 0) { // underflow + return Long.MIN_VALUE; + } + return result; + } + + @Override + public Long minValue() { + return Long.MIN_VALUE; + } + + @Override + public Long maxValue() { + return Long.MAX_VALUE; + } + + private Object readResolve() { + return INSTANCE; + } + + @Override + public String toString() { + return "DiscreteDomain.longs()"; + } + + private static final long serialVersionUID = 0; + } + + /** + * Returns the discrete domain for values of type {@code BigInteger}. + * + * @since 15.0 + */ + public static DiscreteDomain bigIntegers() { + return BigIntegerDomain.INSTANCE; + } + + private static final class BigIntegerDomain extends DiscreteDomain + implements Serializable { + private static final BigIntegerDomain INSTANCE = new BigIntegerDomain(); + + BigIntegerDomain() { + super(true); + } + + private static final BigInteger MIN_LONG = BigInteger.valueOf(Long.MIN_VALUE); + private static final BigInteger MAX_LONG = BigInteger.valueOf(Long.MAX_VALUE); + + @Override + public BigInteger next(BigInteger value) { + return value.add(BigInteger.ONE); + } + + @Override + public BigInteger previous(BigInteger value) { + return value.subtract(BigInteger.ONE); + } + + @Override + BigInteger offset(BigInteger origin, long distance) { + checkNonnegative(distance, "distance"); + return origin.add(BigInteger.valueOf(distance)); + } + + @Override + public long distance(BigInteger start, BigInteger end) { + return end.subtract(start).max(MIN_LONG).min(MAX_LONG).longValue(); + } + + private Object readResolve() { + return INSTANCE; + } + + @Override + public String toString() { + return "DiscreteDomain.bigIntegers()"; + } + + private static final long serialVersionUID = 0; + } + + final boolean supportsFastOffset; + + /** Constructor for use by subclasses. */ + protected DiscreteDomain() { + this(false); + } + + /** Private constructor for built-in DiscreteDomains supporting fast offset. */ + private DiscreteDomain(boolean supportsFastOffset) { + this.supportsFastOffset = supportsFastOffset; + } + + /** + * Returns, conceptually, "origin + distance", or equivalently, the result of calling {@link + * #next} on {@code origin} {@code distance} times. + */ + C offset(C origin, long distance) { + checkNonnegative(distance, "distance"); + for (long i = 0; i < distance; i++) { + origin = next(origin); + } + return origin; + } + + /** + * Returns the unique least value of type {@code C} that is greater than {@code value}, or {@code + * null} if none exists. Inverse operation to {@link #previous}. + * + * @param value any value of type {@code C} + * @return the least value greater than {@code value}, or {@code null} if {@code value} is {@code + * maxValue()} + */ + public abstract C next(C value); + + /** + * Returns the unique greatest value of type {@code C} that is less than {@code value}, or {@code + * null} if none exists. Inverse operation to {@link #next}. + * + * @param value any value of type {@code C} + * @return the greatest value less than {@code value}, or {@code null} if {@code value} is {@code + * minValue()} + */ + public abstract C previous(C value); + + /** + * Returns a signed value indicating how many nested invocations of {@link #next} (if positive) or + * {@link #previous} (if negative) are needed to reach {@code end} starting from {@code start}. + * For example, if {@code end = next(next(next(start)))}, then {@code distance(start, end) == 3} + * and {@code distance(end, start) == -3}. As well, {@code distance(a, a)} is always zero. + * + *

Note that this function is necessarily well-defined for any discrete type. + * + * @return the distance as described above, or {@link Long#MIN_VALUE} or {@link Long#MAX_VALUE} if + * the distance is too small or too large, respectively. + */ + public abstract long distance(C start, C end); + + /** + * Returns the minimum value of type {@code C}, if it has one. The minimum value is the unique + * value for which {@link Comparable#compareTo(Object)} never returns a positive value for any + * input of type {@code C}. + * + *

The default implementation throws {@code NoSuchElementException}. + * + * @return the minimum value of type {@code C}; never null + * @throws NoSuchElementException if the type has no (practical) minimum value; for example, + * {@link java.math.BigInteger} + */ + + public C minValue() { + throw new NoSuchElementException(); + } + + /** + * Returns the maximum value of type {@code C}, if it has one. The maximum value is the unique + * value for which {@link Comparable#compareTo(Object)} never returns a negative value for any + * input of type {@code C}. + * + *

The default implementation throws {@code NoSuchElementException}. + * + * @return the maximum value of type {@code C}; never null + * @throws NoSuchElementException if the type has no (practical) maximum value; for example, + * {@link java.math.BigInteger} + */ + + public C maxValue() { + throw new NoSuchElementException(); + } +} diff --git a/src/main/java/com/google/common/collect/EmptyContiguousSet.java b/src/main/java/com/google/common/collect/EmptyContiguousSet.java new file mode 100644 index 0000000..ce77e23 --- /dev/null +++ b/src/main/java/com/google/common/collect/EmptyContiguousSet.java @@ -0,0 +1,169 @@ +/* + * Copyright (C) 2011 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package com.google.common.collect; + +import com.google.common.annotations.GwtCompatible; +import com.google.common.annotations.GwtIncompatible; +import java.io.Serializable; +import java.util.NoSuchElementException; +import java.util.Set; + + +/** + * An empty contiguous set. + * + * @author Gregory Kick + */ +@GwtCompatible(emulated = true) +@SuppressWarnings("rawtypes") // allow ungenerified Comparable types +final class EmptyContiguousSet extends ContiguousSet { + EmptyContiguousSet(DiscreteDomain domain) { + super(domain); + } + + @Override + public C first() { + throw new NoSuchElementException(); + } + + @Override + public C last() { + throw new NoSuchElementException(); + } + + @Override + public int size() { + return 0; + } + + @Override + public ContiguousSet intersection(ContiguousSet other) { + return this; + } + + @Override + public Range range() { + throw new NoSuchElementException(); + } + + @Override + public Range range(BoundType lowerBoundType, BoundType upperBoundType) { + throw new NoSuchElementException(); + } + + @Override + ContiguousSet headSetImpl(C toElement, boolean inclusive) { + return this; + } + + @Override + ContiguousSet subSetImpl( + C fromElement, boolean fromInclusive, C toElement, boolean toInclusive) { + return this; + } + + @Override + ContiguousSet tailSetImpl(C fromElement, boolean fromInclusive) { + return this; + } + + @Override + public boolean contains(Object object) { + return false; + } + + @GwtIncompatible // not used by GWT emulation + @Override + int indexOf(Object target) { + return -1; + } + + @Override + public UnmodifiableIterator iterator() { + return Iterators.emptyIterator(); + } + + @GwtIncompatible // NavigableSet + @Override + public UnmodifiableIterator descendingIterator() { + return Iterators.emptyIterator(); + } + + @Override + boolean isPartialView() { + return false; + } + + @Override + public boolean isEmpty() { + return true; + } + + @Override + public ImmutableList asList() { + return ImmutableList.of(); + } + + @Override + public String toString() { + return "[]"; + } + + @Override + public boolean equals(Object object) { + if (object instanceof Set) { + Set that = (Set) object; + return that.isEmpty(); + } + return false; + } + + @GwtIncompatible // not used in GWT + @Override + boolean isHashCodeFast() { + return true; + } + + @Override + public int hashCode() { + return 0; + } + + @GwtIncompatible // serialization + private static final class SerializedForm implements Serializable { + private final DiscreteDomain domain; + + private SerializedForm(DiscreteDomain domain) { + this.domain = domain; + } + + private Object readResolve() { + return new EmptyContiguousSet(domain); + } + + private static final long serialVersionUID = 0; + } + + @GwtIncompatible // serialization + @Override + Object writeReplace() { + return new SerializedForm(domain); + } + + @GwtIncompatible // NavigableSet + @Override + ImmutableSortedSet createDescendingSet() { + return ImmutableSortedSet.emptySet(Ordering.natural().reverse()); + } +} diff --git a/src/main/java/com/google/common/collect/EmptyImmutableListMultimap.java b/src/main/java/com/google/common/collect/EmptyImmutableListMultimap.java new file mode 100644 index 0000000..9b167fb --- /dev/null +++ b/src/main/java/com/google/common/collect/EmptyImmutableListMultimap.java @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2008 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import com.google.common.annotations.GwtCompatible; + +/** + * Implementation of {@link ImmutableListMultimap} with no entries. + * + * @author Jared Levy + */ +@GwtCompatible(serializable = true) +class EmptyImmutableListMultimap extends ImmutableListMultimap { + static final EmptyImmutableListMultimap INSTANCE = new EmptyImmutableListMultimap(); + + private EmptyImmutableListMultimap() { + super(ImmutableMap.>of(), 0); + } + + private Object readResolve() { + return INSTANCE; // preserve singleton property + } + + private static final long serialVersionUID = 0; +} diff --git a/src/main/java/com/google/common/collect/EmptyImmutableSetMultimap.java b/src/main/java/com/google/common/collect/EmptyImmutableSetMultimap.java new file mode 100644 index 0000000..ec2ce2e --- /dev/null +++ b/src/main/java/com/google/common/collect/EmptyImmutableSetMultimap.java @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2009 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import com.google.common.annotations.GwtCompatible; + +/** + * Implementation of {@link ImmutableListMultimap} with no entries. + * + * @author Mike Ward + */ +@GwtCompatible(serializable = true) +class EmptyImmutableSetMultimap extends ImmutableSetMultimap { + static final EmptyImmutableSetMultimap INSTANCE = new EmptyImmutableSetMultimap(); + + private EmptyImmutableSetMultimap() { + super(ImmutableMap.>of(), 0, null); + } + + private Object readResolve() { + return INSTANCE; // preserve singleton property + } + + private static final long serialVersionUID = 0; +} diff --git a/src/main/java/com/google/common/collect/EnumBiMap.java b/src/main/java/com/google/common/collect/EnumBiMap.java new file mode 100644 index 0000000..f72b8b9 --- /dev/null +++ b/src/main/java/com/google/common/collect/EnumBiMap.java @@ -0,0 +1,140 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.annotations.GwtCompatible; +import com.google.common.annotations.GwtIncompatible; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.util.EnumMap; +import java.util.Map; + +/** + * A {@code BiMap} backed by two {@code EnumMap} instances. Null keys and values are not permitted. + * An {@code EnumBiMap} and its inverse are both serializable. + * + *

See the Guava User Guide article on {@code BiMap}. + * + * @author Mike Bostock + * @since 2.0 + */ +@GwtCompatible(emulated = true) +public final class EnumBiMap, V extends Enum> extends AbstractBiMap { + private transient Class keyType; + private transient Class valueType; + + /** + * Returns a new, empty {@code EnumBiMap} using the specified key and value types. + * + * @param keyType the key type + * @param valueType the value type + */ + public static , V extends Enum> EnumBiMap create( + Class keyType, Class valueType) { + return new EnumBiMap<>(keyType, valueType); + } + + /** + * Returns a new bimap with the same mappings as the specified map. If the specified map is an + * {@code EnumBiMap}, the new bimap has the same types as the provided map. Otherwise, the + * specified map must contain at least one mapping, in order to determine the key and value types. + * + * @param map the map whose mappings are to be placed in this map + * @throws IllegalArgumentException if map is not an {@code EnumBiMap} instance and contains no + * mappings + */ + public static , V extends Enum> EnumBiMap create(Map map) { + EnumBiMap bimap = create(inferKeyType(map), inferValueType(map)); + bimap.putAll(map); + return bimap; + } + + private EnumBiMap(Class keyType, Class valueType) { + super(new EnumMap(keyType), new EnumMap(valueType)); + this.keyType = keyType; + this.valueType = valueType; + } + + static > Class inferKeyType(Map map) { + if (map instanceof EnumBiMap) { + return ((EnumBiMap) map).keyType(); + } + if (map instanceof EnumHashBiMap) { + return ((EnumHashBiMap) map).keyType(); + } + checkArgument(!map.isEmpty()); + return map.keySet().iterator().next().getDeclaringClass(); + } + + private static > Class inferValueType(Map map) { + if (map instanceof EnumBiMap) { + return ((EnumBiMap) map).valueType; + } + checkArgument(!map.isEmpty()); + return map.values().iterator().next().getDeclaringClass(); + } + + /** Returns the associated key type. */ + public Class keyType() { + return keyType; + } + + /** Returns the associated value type. */ + public Class valueType() { + return valueType; + } + + @Override + K checkKey(K key) { + return checkNotNull(key); + } + + @Override + V checkValue(V value) { + return checkNotNull(value); + } + + /** + * @serialData the key class, value class, number of entries, first key, first value, second key, + * second value, and so on. + */ + @GwtIncompatible // java.io.ObjectOutputStream + private void writeObject(ObjectOutputStream stream) throws IOException { + stream.defaultWriteObject(); + stream.writeObject(keyType); + stream.writeObject(valueType); + Serialization.writeMap(this, stream); + } + + @SuppressWarnings("unchecked") // reading fields populated by writeObject + @GwtIncompatible // java.io.ObjectInputStream + private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException { + stream.defaultReadObject(); + keyType = (Class) stream.readObject(); + valueType = (Class) stream.readObject(); + setDelegates(new EnumMap(keyType), new EnumMap(valueType)); + Serialization.populateMap(this, stream); + } + + @GwtIncompatible // not needed in emulated source. + private static final long serialVersionUID = 0; +} diff --git a/src/main/java/com/google/common/collect/EnumHashBiMap.java b/src/main/java/com/google/common/collect/EnumHashBiMap.java new file mode 100644 index 0000000..7174052 --- /dev/null +++ b/src/main/java/com/google/common/collect/EnumHashBiMap.java @@ -0,0 +1,126 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.annotations.GwtCompatible; +import com.google.common.annotations.GwtIncompatible; + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.util.EnumMap; +import java.util.HashMap; +import java.util.Map; + + +/** + * A {@code BiMap} backed by an {@code EnumMap} instance for keys-to-values, and a {@code HashMap} + * instance for values-to-keys. Null keys are not permitted, but null values are. An {@code + * EnumHashBiMap} and its inverse are both serializable. + * + *

See the Guava User Guide article on {@code BiMap}. + * + * @author Mike Bostock + * @since 2.0 + */ +@GwtCompatible(emulated = true) +public final class EnumHashBiMap, V> extends AbstractBiMap { + private transient Class keyType; + + /** + * Returns a new, empty {@code EnumHashBiMap} using the specified key type. + * + * @param keyType the key type + */ + public static , V> EnumHashBiMap create(Class keyType) { + return new EnumHashBiMap<>(keyType); + } + + /** + * Constructs a new bimap with the same mappings as the specified map. If the specified map is an + * {@code EnumHashBiMap} or an {@link EnumBiMap}, the new bimap has the same key type as the input + * bimap. Otherwise, the specified map must contain at least one mapping, in order to determine + * the key type. + * + * @param map the map whose mappings are to be placed in this map + * @throws IllegalArgumentException if map is not an {@code EnumBiMap} or an {@code EnumHashBiMap} + * instance and contains no mappings + */ + public static , V> EnumHashBiMap create(Map map) { + EnumHashBiMap bimap = create(EnumBiMap.inferKeyType(map)); + bimap.putAll(map); + return bimap; + } + + private EnumHashBiMap(Class keyType) { + super( + new EnumMap(keyType), + Maps.newHashMapWithExpectedSize(keyType.getEnumConstants().length)); + this.keyType = keyType; + } + + // Overriding these 3 methods to show that values may be null (but not keys) + + @Override + K checkKey(K key) { + return checkNotNull(key); + } + + + @Override + public V put(K key, V value) { + return super.put(key, value); + } + + + @Override + public V forcePut(K key, V value) { + return super.forcePut(key, value); + } + + /** Returns the associated key type. */ + public Class keyType() { + return keyType; + } + + /** + * @serialData the key class, number of entries, first key, first value, second key, second value, + * and so on. + */ + @GwtIncompatible // java.io.ObjectOutputStream + private void writeObject(ObjectOutputStream stream) throws IOException { + stream.defaultWriteObject(); + stream.writeObject(keyType); + Serialization.writeMap(this, stream); + } + + @SuppressWarnings("unchecked") // reading field populated by writeObject + @GwtIncompatible // java.io.ObjectInputStream + private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException { + stream.defaultReadObject(); + keyType = (Class) stream.readObject(); + setDelegates( + new EnumMap(keyType), new HashMap(keyType.getEnumConstants().length * 3 / 2)); + Serialization.populateMap(this, stream); + } + + @GwtIncompatible // only needed in emulated source. + private static final long serialVersionUID = 0; +} diff --git a/src/main/java/com/google/common/collect/EnumMultiset.java b/src/main/java/com/google/common/collect/EnumMultiset.java new file mode 100644 index 0000000..b7a25b6 --- /dev/null +++ b/src/main/java/com/google/common/collect/EnumMultiset.java @@ -0,0 +1,317 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.collect; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.collect.CollectPreconditions.checkNonnegative; +import static com.google.common.collect.CollectPreconditions.checkRemove; + +import com.google.common.annotations.GwtCompatible; +import com.google.common.annotations.GwtIncompatible; +import com.google.common.primitives.Ints; + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.Serializable; +import java.util.Arrays; +import java.util.Iterator; +import java.util.NoSuchElementException; +import java.util.function.ObjIntConsumer; + + +/** + * Multiset implementation specialized for enum elements, supporting all single-element operations + * in O(1). + * + *

See the Guava User Guide article on {@code + * Multiset}. + * + * @author Jared Levy + * @since 2.0 + */ +@GwtCompatible(emulated = true) +public final class EnumMultiset> extends AbstractMultiset + implements Serializable { + /** Creates an empty {@code EnumMultiset}. */ + public static > EnumMultiset create(Class type) { + return new EnumMultiset(type); + } + + /** + * Creates a new {@code EnumMultiset} containing the specified elements. + * + *

This implementation is highly efficient when {@code elements} is itself a {@link Multiset}. + * + * @param elements the elements that the multiset should contain + * @throws IllegalArgumentException if {@code elements} is empty + */ + public static > EnumMultiset create(Iterable elements) { + Iterator iterator = elements.iterator(); + checkArgument(iterator.hasNext(), "EnumMultiset constructor passed empty Iterable"); + EnumMultiset multiset = new EnumMultiset<>(iterator.next().getDeclaringClass()); + Iterables.addAll(multiset, elements); + return multiset; + } + + /** + * Returns a new {@code EnumMultiset} instance containing the given elements. Unlike {@link + * EnumMultiset#create(Iterable)}, this method does not produce an exception on an empty iterable. + * + * @since 14.0 + */ + public static > EnumMultiset create(Iterable elements, Class type) { + EnumMultiset result = create(type); + Iterables.addAll(result, elements); + return result; + } + + private transient Class type; + private transient E[] enumConstants; + private transient int[] counts; + private transient int distinctElements; + private transient long size; + + /** Creates an empty {@code EnumMultiset}. */ + private EnumMultiset(Class type) { + this.type = type; + checkArgument(type.isEnum()); + this.enumConstants = type.getEnumConstants(); + this.counts = new int[enumConstants.length]; + } + + private boolean isActuallyE(Object o) { + if (o instanceof Enum) { + Enum e = (Enum) o; + int index = e.ordinal(); + return index < enumConstants.length && enumConstants[index] == e; + } + return false; + } + + /** + * Returns {@code element} cast to {@code E}, if it actually is a nonnull E. Otherwise, throws + * either a NullPointerException or a ClassCastException as appropriate. + */ + void checkIsE(Object element) { + checkNotNull(element); + if (!isActuallyE(element)) { + throw new ClassCastException("Expected an " + type + " but got " + element); + } + } + + @Override + int distinctElements() { + return distinctElements; + } + + @Override + public int size() { + return Ints.saturatedCast(size); + } + + @Override + public int count(Object element) { + if (!isActuallyE(element)) { + return 0; + } + Enum e = (Enum) element; + return counts[e.ordinal()]; + } + + // Modification Operations + + @Override + public int add(E element, int occurrences) { + checkIsE(element); + checkNonnegative(occurrences, "occurrences"); + if (occurrences == 0) { + return count(element); + } + int index = element.ordinal(); + int oldCount = counts[index]; + long newCount = (long) oldCount + occurrences; + checkArgument(newCount <= Integer.MAX_VALUE, "too many occurrences: %s", newCount); + counts[index] = (int) newCount; + if (oldCount == 0) { + distinctElements++; + } + size += occurrences; + return oldCount; + } + + // Modification Operations + + @Override + public int remove(Object element, int occurrences) { + if (!isActuallyE(element)) { + return 0; + } + Enum e = (Enum) element; + checkNonnegative(occurrences, "occurrences"); + if (occurrences == 0) { + return count(element); + } + int index = e.ordinal(); + int oldCount = counts[index]; + if (oldCount == 0) { + return 0; + } else if (oldCount <= occurrences) { + counts[index] = 0; + distinctElements--; + size -= oldCount; + } else { + counts[index] = oldCount - occurrences; + size -= occurrences; + } + return oldCount; + } + + // Modification Operations + + @Override + public int setCount(E element, int count) { + checkIsE(element); + checkNonnegative(count, "count"); + int index = element.ordinal(); + int oldCount = counts[index]; + counts[index] = count; + size += count - oldCount; + if (oldCount == 0 && count > 0) { + distinctElements++; + } else if (oldCount > 0 && count == 0) { + distinctElements--; + } + return oldCount; + } + + @Override + public void clear() { + Arrays.fill(counts, 0); + size = 0; + distinctElements = 0; + } + + abstract class Itr implements Iterator { + int index = 0; + int toRemove = -1; + + abstract T output(int index); + + @Override + public boolean hasNext() { + for (; index < enumConstants.length; index++) { + if (counts[index] > 0) { + return true; + } + } + return false; + } + + @Override + public T next() { + if (!hasNext()) { + throw new NoSuchElementException(); + } + T result = output(index); + toRemove = index; + index++; + return result; + } + + @Override + public void remove() { + checkRemove(toRemove >= 0); + if (counts[toRemove] > 0) { + distinctElements--; + size -= counts[toRemove]; + counts[toRemove] = 0; + } + toRemove = -1; + } + } + + @Override + Iterator elementIterator() { + return new Itr() { + @Override + E output(int index) { + return enumConstants[index]; + } + }; + } + + @Override + Iterator> entryIterator() { + return new Itr>() { + @Override + Entry output(final int index) { + return new Multisets.AbstractEntry() { + @Override + public E getElement() { + return enumConstants[index]; + } + + @Override + public int getCount() { + return counts[index]; + } + }; + } + }; + } + + @Override + public void forEachEntry(ObjIntConsumer action) { + checkNotNull(action); + for (int i = 0; i < enumConstants.length; i++) { + if (counts[i] > 0) { + action.accept(enumConstants[i], counts[i]); + } + } + } + + @Override + public Iterator iterator() { + return Multisets.iteratorImpl(this); + } + + @GwtIncompatible // java.io.ObjectOutputStream + private void writeObject(ObjectOutputStream stream) throws IOException { + stream.defaultWriteObject(); + stream.writeObject(type); + Serialization.writeMultiset(this, stream); + } + + /** + * @serialData the {@code Class} for the enum type, the number of distinct elements, the first + * element, its count, the second element, its count, and so on + */ + @GwtIncompatible // java.io.ObjectInputStream + private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException { + stream.defaultReadObject(); + @SuppressWarnings("unchecked") // reading data stored by writeObject + Class localType = (Class) stream.readObject(); + type = localType; + enumConstants = type.getEnumConstants(); + counts = new int[enumConstants.length]; + Serialization.populateMultiset(this, stream); + } + + @GwtIncompatible // Not needed in emulated source + private static final long serialVersionUID = 0; +} diff --git a/src/main/java/com/google/common/collect/EvictingQueue.java b/src/main/java/com/google/common/collect/EvictingQueue.java new file mode 100644 index 0000000..e22e7f9 --- /dev/null +++ b/src/main/java/com/google/common/collect/EvictingQueue.java @@ -0,0 +1,142 @@ +/* + * Copyright (C) 2012 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.annotations.Beta; +import com.google.common.annotations.GwtCompatible; +import com.google.common.annotations.VisibleForTesting; + +import java.io.Serializable; +import java.util.ArrayDeque; +import java.util.Collection; +import java.util.Queue; + +/** + * A non-blocking queue which automatically evicts elements from the head of the queue when + * attempting to add new elements onto the queue and it is full. This queue orders elements FIFO + * (first-in-first-out). This data structure is logically equivalent to a circular buffer (i.e., + * cyclic buffer or ring buffer). + * + *

An evicting queue must be configured with a maximum size. Each time an element is added to a + * full queue, the queue automatically removes its head element. This is different from conventional + * bounded queues, which either block or reject new elements when full. + * + *

This class is not thread-safe, and does not accept null elements. + * + * @author Kurt Alfred Kluever + * @since 15.0 + */ +@Beta +@GwtCompatible +public final class EvictingQueue extends ForwardingQueue implements Serializable { + + private final Queue delegate; + + @VisibleForTesting final int maxSize; + + private EvictingQueue(int maxSize) { + checkArgument(maxSize >= 0, "maxSize (%s) must >= 0", maxSize); + this.delegate = new ArrayDeque(maxSize); + this.maxSize = maxSize; + } + + /** + * Creates and returns a new evicting queue that will hold up to {@code maxSize} elements. + * + *

When {@code maxSize} is zero, elements will be evicted immediately after being added to the + * queue. + */ + public static EvictingQueue create(int maxSize) { + return new EvictingQueue(maxSize); + } + + /** + * Returns the number of additional elements that this queue can accept without evicting; zero if + * the queue is currently full. + * + * @since 16.0 + */ + public int remainingCapacity() { + return maxSize - size(); + } + + @Override + protected Queue delegate() { + return delegate; + } + + /** + * Adds the given element to this queue. If the queue is currently full, the element at the head + * of the queue is evicted to make room. + * + * @return {@code true} always + */ + @Override + + public boolean offer(E e) { + return add(e); + } + + /** + * Adds the given element to this queue. If the queue is currently full, the element at the head + * of the queue is evicted to make room. + * + * @return {@code true} always + */ + @Override + + public boolean add(E e) { + checkNotNull(e); // check before removing + if (maxSize == 0) { + return true; + } + if (size() == maxSize) { + delegate.remove(); + } + delegate.add(e); + return true; + } + + @Override + + public boolean addAll(Collection collection) { + int size = collection.size(); + if (size >= maxSize) { + clear(); + return Iterables.addAll(this, Iterables.skip(collection, size - maxSize)); + } + return standardAddAll(collection); + } + + @Override + public boolean contains(Object object) { + return delegate().contains(checkNotNull(object)); + } + + @Override + + public boolean remove(Object object) { + return delegate().remove(checkNotNull(object)); + } + + // TODO(kak): Do we want to checkNotNull each element in containsAll, removeAll, and retainAll? + + private static final long serialVersionUID = 0L; +} diff --git a/src/main/java/com/google/common/collect/ExplicitOrdering.java b/src/main/java/com/google/common/collect/ExplicitOrdering.java new file mode 100644 index 0000000..6c4bf54 --- /dev/null +++ b/src/main/java/com/google/common/collect/ExplicitOrdering.java @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import com.google.common.annotations.GwtCompatible; +import java.io.Serializable; +import java.util.List; + + +/** An ordering that compares objects according to a given order. */ +@GwtCompatible(serializable = true) +final class ExplicitOrdering extends Ordering implements Serializable { + final ImmutableMap rankMap; + + ExplicitOrdering(List valuesInOrder) { + this(Maps.indexMap(valuesInOrder)); + } + + ExplicitOrdering(ImmutableMap rankMap) { + this.rankMap = rankMap; + } + + @Override + public int compare(T left, T right) { + return rank(left) - rank(right); // safe because both are nonnegative + } + + private int rank(T value) { + Integer rank = rankMap.get(value); + if (rank == null) { + throw new IncomparableValueException(value); + } + return rank; + } + + @Override + public boolean equals(Object object) { + if (object instanceof ExplicitOrdering) { + ExplicitOrdering that = (ExplicitOrdering) object; + return this.rankMap.equals(that.rankMap); + } + return false; + } + + @Override + public int hashCode() { + return rankMap.hashCode(); + } + + @Override + public String toString() { + return "Ordering.explicit(" + rankMap.keySet() + ")"; + } + + private static final long serialVersionUID = 0; +} diff --git a/src/main/java/com/google/common/collect/FilteredEntryMultimap.java b/src/main/java/com/google/common/collect/FilteredEntryMultimap.java new file mode 100644 index 0000000..98da739 --- /dev/null +++ b/src/main/java/com/google/common/collect/FilteredEntryMultimap.java @@ -0,0 +1,416 @@ +/* + * Copyright (C) 2012 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.base.Predicates.in; +import static com.google.common.base.Predicates.not; +import static com.google.common.collect.CollectPreconditions.checkNonnegative; + +import com.google.common.annotations.GwtCompatible; +import com.google.common.base.MoreObjects; +import com.google.common.base.Predicate; +import com.google.common.collect.Maps.ViewCachingAbstractMap; + +import java.util.Collection; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; + + +/** + * Implementation of {@link Multimaps#filterEntries(Multimap, Predicate)}. + * + * @author Jared Levy + * @author Louis Wasserman + */ +@GwtCompatible +class FilteredEntryMultimap extends AbstractMultimap implements FilteredMultimap { + final Multimap unfiltered; + final Predicate> predicate; + + FilteredEntryMultimap(Multimap unfiltered, Predicate> predicate) { + this.unfiltered = checkNotNull(unfiltered); + this.predicate = checkNotNull(predicate); + } + + @Override + public Multimap unfiltered() { + return unfiltered; + } + + @Override + public Predicate> entryPredicate() { + return predicate; + } + + @Override + public int size() { + return entries().size(); + } + + private boolean satisfies(K key, V value) { + return predicate.apply(Maps.immutableEntry(key, value)); + } + + final class ValuePredicate implements Predicate { + private final K key; + + ValuePredicate(K key) { + this.key = key; + } + + @Override + public boolean apply(V value) { + return satisfies(key, value); + } + } + + static Collection filterCollection( + Collection collection, Predicate predicate) { + if (collection instanceof Set) { + return Sets.filter((Set) collection, predicate); + } else { + return Collections2.filter(collection, predicate); + } + } + + @Override + public boolean containsKey(Object key) { + return asMap().get(key) != null; + } + + @Override + public Collection removeAll(Object key) { + return MoreObjects.firstNonNull(asMap().remove(key), unmodifiableEmptyCollection()); + } + + Collection unmodifiableEmptyCollection() { + // These return false, rather than throwing a UOE, on remove calls. + return (unfiltered instanceof SetMultimap) + ? Collections.emptySet() + : Collections.emptyList(); + } + + @Override + public void clear() { + entries().clear(); + } + + @Override + public Collection get(final K key) { + return filterCollection(unfiltered.get(key), new ValuePredicate(key)); + } + + @Override + Collection> createEntries() { + return filterCollection(unfiltered.entries(), predicate); + } + + @Override + Collection createValues() { + return new FilteredMultimapValues<>(this); + } + + @Override + Iterator> entryIterator() { + throw new AssertionError("should never be called"); + } + + @Override + Map> createAsMap() { + return new AsMap(); + } + + @Override + Set createKeySet() { + return asMap().keySet(); + } + + boolean removeEntriesIf(Predicate>> predicate) { + Iterator>> entryIterator = unfiltered.asMap().entrySet().iterator(); + boolean changed = false; + while (entryIterator.hasNext()) { + Entry> entry = entryIterator.next(); + K key = entry.getKey(); + Collection collection = filterCollection(entry.getValue(), new ValuePredicate(key)); + if (!collection.isEmpty() && predicate.apply(Maps.immutableEntry(key, collection))) { + if (collection.size() == entry.getValue().size()) { + entryIterator.remove(); + } else { + collection.clear(); + } + changed = true; + } + } + return changed; + } + + + class AsMap extends ViewCachingAbstractMap> { + @Override + public boolean containsKey(Object key) { + return get(key) != null; + } + + @Override + public void clear() { + FilteredEntryMultimap.this.clear(); + } + + @Override + public Collection get(Object key) { + Collection result = unfiltered.asMap().get(key); + if (result == null) { + return null; + } + @SuppressWarnings("unchecked") // key is equal to a K, if not a K itself + K k = (K) key; + result = filterCollection(result, new ValuePredicate(k)); + return result.isEmpty() ? null : result; + } + + @Override + public Collection remove(Object key) { + Collection collection = unfiltered.asMap().get(key); + if (collection == null) { + return null; + } + @SuppressWarnings("unchecked") // it's definitely equal to a K + K k = (K) key; + List result = Lists.newArrayList(); + Iterator itr = collection.iterator(); + while (itr.hasNext()) { + V v = itr.next(); + if (satisfies(k, v)) { + itr.remove(); + result.add(v); + } + } + if (result.isEmpty()) { + return null; + } else if (unfiltered instanceof SetMultimap) { + return Collections.unmodifiableSet(Sets.newLinkedHashSet(result)); + } else { + return Collections.unmodifiableList(result); + } + } + + @Override + Set createKeySet() { + + class KeySetImpl extends Maps.KeySet> { + KeySetImpl() { + super(AsMap.this); + } + + @Override + public boolean removeAll(Collection c) { + return removeEntriesIf(Maps.keyPredicateOnEntries(in(c))); + } + + @Override + public boolean retainAll(Collection c) { + return removeEntriesIf(Maps.keyPredicateOnEntries(not(in(c)))); + } + + @Override + public boolean remove(Object o) { + return AsMap.this.remove(o) != null; + } + } + return new KeySetImpl(); + } + + @Override + Set>> createEntrySet() { + + class EntrySetImpl extends Maps.EntrySet> { + @Override + Map> map() { + return AsMap.this; + } + + @Override + public Iterator>> iterator() { + return new AbstractIterator>>() { + final Iterator>> backingIterator = + unfiltered.asMap().entrySet().iterator(); + + @Override + protected Entry> computeNext() { + while (backingIterator.hasNext()) { + Entry> entry = backingIterator.next(); + K key = entry.getKey(); + Collection collection = + filterCollection(entry.getValue(), new ValuePredicate(key)); + if (!collection.isEmpty()) { + return Maps.immutableEntry(key, collection); + } + } + return endOfData(); + } + }; + } + + @Override + public boolean removeAll(Collection c) { + return removeEntriesIf(in(c)); + } + + @Override + public boolean retainAll(Collection c) { + return removeEntriesIf(not(in(c))); + } + + @Override + public int size() { + return Iterators.size(iterator()); + } + } + return new EntrySetImpl(); + } + + @Override + Collection> createValues() { + + class ValuesImpl extends Maps.Values> { + ValuesImpl() { + super(AsMap.this); + } + + @Override + public boolean remove(Object o) { + if (o instanceof Collection) { + Collection c = (Collection) o; + Iterator>> entryIterator = + unfiltered.asMap().entrySet().iterator(); + while (entryIterator.hasNext()) { + Entry> entry = entryIterator.next(); + K key = entry.getKey(); + Collection collection = + filterCollection(entry.getValue(), new ValuePredicate(key)); + if (!collection.isEmpty() && c.equals(collection)) { + if (collection.size() == entry.getValue().size()) { + entryIterator.remove(); + } else { + collection.clear(); + } + return true; + } + } + } + return false; + } + + @Override + public boolean removeAll(Collection c) { + return removeEntriesIf(Maps.>valuePredicateOnEntries(in(c))); + } + + @Override + public boolean retainAll(Collection c) { + return removeEntriesIf(Maps.>valuePredicateOnEntries(not(in(c)))); + } + } + return new ValuesImpl(); + } + } + + @Override + Multiset createKeys() { + return new Keys(); + } + + + class Keys extends Multimaps.Keys { + Keys() { + super(FilteredEntryMultimap.this); + } + + @Override + public int remove(Object key, int occurrences) { + checkNonnegative(occurrences, "occurrences"); + if (occurrences == 0) { + return count(key); + } + Collection collection = unfiltered.asMap().get(key); + if (collection == null) { + return 0; + } + @SuppressWarnings("unchecked") // key is equal to a K, if not a K itself + K k = (K) key; + int oldCount = 0; + Iterator itr = collection.iterator(); + while (itr.hasNext()) { + V v = itr.next(); + if (satisfies(k, v)) { + oldCount++; + if (oldCount <= occurrences) { + itr.remove(); + } + } + } + return oldCount; + } + + @Override + public Set> entrySet() { + return new Multisets.EntrySet() { + + @Override + Multiset multiset() { + return Keys.this; + } + + @Override + public Iterator> iterator() { + return Keys.this.entryIterator(); + } + + @Override + public int size() { + return FilteredEntryMultimap.this.keySet().size(); + } + + private boolean removeEntriesIf(final Predicate> predicate) { + return FilteredEntryMultimap.this.removeEntriesIf( + new Predicate>>() { + @Override + public boolean apply(Map.Entry> entry) { + return predicate.apply( + Multisets.immutableEntry(entry.getKey(), entry.getValue().size())); + } + }); + } + + @Override + public boolean removeAll(Collection c) { + return removeEntriesIf(in(c)); + } + + @Override + public boolean retainAll(Collection c) { + return removeEntriesIf(not(in(c))); + } + }; + } + } +} diff --git a/src/main/java/com/google/common/collect/FilteredEntrySetMultimap.java b/src/main/java/com/google/common/collect/FilteredEntrySetMultimap.java new file mode 100644 index 0000000..94740a4 --- /dev/null +++ b/src/main/java/com/google/common/collect/FilteredEntrySetMultimap.java @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2012 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import com.google.common.annotations.GwtCompatible; +import com.google.common.base.Predicate; +import java.util.Map.Entry; +import java.util.Set; + +/** + * Implementation of {@link Multimaps#filterEntries(SetMultimap, Predicate)}. + * + * @author Louis Wasserman + */ +@GwtCompatible +final class FilteredEntrySetMultimap extends FilteredEntryMultimap + implements FilteredSetMultimap { + + FilteredEntrySetMultimap(SetMultimap unfiltered, Predicate> predicate) { + super(unfiltered, predicate); + } + + @Override + public SetMultimap unfiltered() { + return (SetMultimap) unfiltered; + } + + @Override + public Set get(K key) { + return (Set) super.get(key); + } + + @Override + public Set removeAll(Object key) { + return (Set) super.removeAll(key); + } + + @Override + public Set replaceValues(K key, Iterable values) { + return (Set) super.replaceValues(key, values); + } + + @Override + Set> createEntries() { + return Sets.filter(unfiltered().entries(), entryPredicate()); + } + + @Override + public Set> entries() { + return (Set>) super.entries(); + } +} diff --git a/src/main/java/com/google/common/collect/FilteredKeyListMultimap.java b/src/main/java/com/google/common/collect/FilteredKeyListMultimap.java new file mode 100644 index 0000000..dae72ce --- /dev/null +++ b/src/main/java/com/google/common/collect/FilteredKeyListMultimap.java @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2012 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import com.google.common.annotations.GwtCompatible; +import com.google.common.base.Predicate; +import java.util.List; + + +/** + * Implementation of {@link Multimaps#filterKeys(ListMultimap, Predicate)}. + * + * @author Louis Wasserman + */ +@GwtCompatible +final class FilteredKeyListMultimap extends FilteredKeyMultimap + implements ListMultimap { + FilteredKeyListMultimap(ListMultimap unfiltered, Predicate keyPredicate) { + super(unfiltered, keyPredicate); + } + + @Override + public ListMultimap unfiltered() { + return (ListMultimap) super.unfiltered(); + } + + @Override + public List get(K key) { + return (List) super.get(key); + } + + @Override + public List removeAll(Object key) { + return (List) super.removeAll(key); + } + + @Override + public List replaceValues(K key, Iterable values) { + return (List) super.replaceValues(key, values); + } +} diff --git a/src/main/java/com/google/common/collect/FilteredKeyMultimap.java b/src/main/java/com/google/common/collect/FilteredKeyMultimap.java new file mode 100644 index 0000000..c7c3eef --- /dev/null +++ b/src/main/java/com/google/common/collect/FilteredKeyMultimap.java @@ -0,0 +1,220 @@ +/* + * Copyright (C) 2012 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.collect; + +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.base.Preconditions.checkPositionIndex; + +import com.google.common.annotations.GwtCompatible; +import com.google.common.base.Predicate; + + +import java.util.Collection; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; + + +/** + * Implementation of {@link Multimaps#filterKeys(Multimap, Predicate)}. + * + * @author Louis Wasserman + */ +@GwtCompatible +class FilteredKeyMultimap extends AbstractMultimap implements FilteredMultimap { + final Multimap unfiltered; + final Predicate keyPredicate; + + FilteredKeyMultimap(Multimap unfiltered, Predicate keyPredicate) { + this.unfiltered = checkNotNull(unfiltered); + this.keyPredicate = checkNotNull(keyPredicate); + } + + @Override + public Multimap unfiltered() { + return unfiltered; + } + + @Override + public Predicate> entryPredicate() { + return Maps.keyPredicateOnEntries(keyPredicate); + } + + @Override + public int size() { + int size = 0; + for (Collection collection : asMap().values()) { + size += collection.size(); + } + return size; + } + + @Override + public boolean containsKey(Object key) { + if (unfiltered.containsKey(key)) { + @SuppressWarnings("unchecked") // k is equal to a K, if not one itself + K k = (K) key; + return keyPredicate.apply(k); + } + return false; + } + + @Override + public Collection removeAll(Object key) { + return containsKey(key) ? unfiltered.removeAll(key) : unmodifiableEmptyCollection(); + } + + Collection unmodifiableEmptyCollection() { + if (unfiltered instanceof SetMultimap) { + return ImmutableSet.of(); + } else { + return ImmutableList.of(); + } + } + + @Override + public void clear() { + keySet().clear(); + } + + @Override + Set createKeySet() { + return Sets.filter(unfiltered.keySet(), keyPredicate); + } + + @Override + public Collection get(K key) { + if (keyPredicate.apply(key)) { + return unfiltered.get(key); + } else if (unfiltered instanceof SetMultimap) { + return new AddRejectingSet<>(key); + } else { + return new AddRejectingList<>(key); + } + } + + static class AddRejectingSet extends ForwardingSet { + final K key; + + AddRejectingSet(K key) { + this.key = key; + } + + @Override + public boolean add(V element) { + throw new IllegalArgumentException("Key does not satisfy predicate: " + key); + } + + @Override + public boolean addAll(Collection collection) { + checkNotNull(collection); + throw new IllegalArgumentException("Key does not satisfy predicate: " + key); + } + + @Override + protected Set delegate() { + return Collections.emptySet(); + } + } + + static class AddRejectingList extends ForwardingList { + final K key; + + AddRejectingList(K key) { + this.key = key; + } + + @Override + public boolean add(V v) { + add(0, v); + return true; + } + + @Override + public void add(int index, V element) { + checkPositionIndex(index, 0); + throw new IllegalArgumentException("Key does not satisfy predicate: " + key); + } + + @Override + public boolean addAll(Collection collection) { + addAll(0, collection); + return true; + } + + + @Override + public boolean addAll(int index, Collection elements) { + checkNotNull(elements); + checkPositionIndex(index, 0); + throw new IllegalArgumentException("Key does not satisfy predicate: " + key); + } + + @Override + protected List delegate() { + return Collections.emptyList(); + } + } + + @Override + Iterator> entryIterator() { + throw new AssertionError("should never be called"); + } + + @Override + Collection> createEntries() { + return new Entries(); + } + + + class Entries extends ForwardingCollection> { + @Override + protected Collection> delegate() { + return Collections2.filter(unfiltered.entries(), entryPredicate()); + } + + @Override + @SuppressWarnings("unchecked") + public boolean remove(Object o) { + if (o instanceof Entry) { + Entry entry = (Entry) o; + if (unfiltered.containsKey(entry.getKey()) + // if this holds, then we know entry.getKey() is a K + && keyPredicate.apply((K) entry.getKey())) { + return unfiltered.remove(entry.getKey(), entry.getValue()); + } + } + return false; + } + } + + @Override + Collection createValues() { + return new FilteredMultimapValues<>(this); + } + + @Override + Map> createAsMap() { + return Maps.filterKeys(unfiltered.asMap(), keyPredicate); + } + + @Override + Multiset createKeys() { + return Multisets.filter(unfiltered.keys(), keyPredicate); + } +} diff --git a/src/main/java/com/google/common/collect/FilteredKeySetMultimap.java b/src/main/java/com/google/common/collect/FilteredKeySetMultimap.java new file mode 100644 index 0000000..06c3256 --- /dev/null +++ b/src/main/java/com/google/common/collect/FilteredKeySetMultimap.java @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2012 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import com.google.common.annotations.GwtCompatible; +import com.google.common.base.Predicate; +import java.util.Map.Entry; +import java.util.Set; + + +/** + * Implementation of {@link Multimaps#filterKeys(SetMultimap, Predicate)}. + * + * @author Louis Wasserman + */ +@GwtCompatible +final class FilteredKeySetMultimap extends FilteredKeyMultimap + implements FilteredSetMultimap { + + FilteredKeySetMultimap(SetMultimap unfiltered, Predicate keyPredicate) { + super(unfiltered, keyPredicate); + } + + @Override + public SetMultimap unfiltered() { + return (SetMultimap) unfiltered; + } + + @Override + public Set get(K key) { + return (Set) super.get(key); + } + + @Override + public Set removeAll(Object key) { + return (Set) super.removeAll(key); + } + + @Override + public Set replaceValues(K key, Iterable values) { + return (Set) super.replaceValues(key, values); + } + + @Override + public Set> entries() { + return (Set>) super.entries(); + } + + @Override + Set> createEntries() { + return new EntrySet(); + } + + class EntrySet extends Entries implements Set> { + @Override + public int hashCode() { + return Sets.hashCodeImpl(this); + } + + @Override + public boolean equals(Object o) { + return Sets.equalsImpl(this, o); + } + } +} diff --git a/src/main/java/com/google/common/collect/FilteredMultimap.java b/src/main/java/com/google/common/collect/FilteredMultimap.java new file mode 100644 index 0000000..ef5ed4a --- /dev/null +++ b/src/main/java/com/google/common/collect/FilteredMultimap.java @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2012 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import com.google.common.annotations.GwtCompatible; +import com.google.common.base.Predicate; +import java.util.Map.Entry; + +/** + * An interface for all filtered multimap types. + * + * @author Louis Wasserman + */ +@GwtCompatible +interface FilteredMultimap extends Multimap { + Multimap unfiltered(); + + Predicate> entryPredicate(); +} diff --git a/src/main/java/com/google/common/collect/FilteredMultimapValues.java b/src/main/java/com/google/common/collect/FilteredMultimapValues.java new file mode 100644 index 0000000..c0b87d5 --- /dev/null +++ b/src/main/java/com/google/common/collect/FilteredMultimapValues.java @@ -0,0 +1,95 @@ +/* + * Copyright (C) 2013 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.collect; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.annotations.GwtCompatible; +import com.google.common.base.Objects; +import com.google.common.base.Predicate; +import com.google.common.base.Predicates; + +import java.util.AbstractCollection; +import java.util.Collection; +import java.util.Iterator; +import java.util.Map.Entry; + + +/** + * Implementation for {@link FilteredMultimap#values()}. + * + * @author Louis Wasserman + */ +@GwtCompatible +final class FilteredMultimapValues extends AbstractCollection { + private final FilteredMultimap multimap; + + FilteredMultimapValues(FilteredMultimap multimap) { + this.multimap = checkNotNull(multimap); + } + + @Override + public Iterator iterator() { + return Maps.valueIterator(multimap.entries().iterator()); + } + + @Override + public boolean contains(Object o) { + return multimap.containsValue(o); + } + + @Override + public int size() { + return multimap.size(); + } + + @Override + public boolean remove(Object o) { + Predicate> entryPredicate = multimap.entryPredicate(); + for (Iterator> unfilteredItr = multimap.unfiltered().entries().iterator(); + unfilteredItr.hasNext(); ) { + Entry entry = unfilteredItr.next(); + if (entryPredicate.apply(entry) && Objects.equal(entry.getValue(), o)) { + unfilteredItr.remove(); + return true; + } + } + return false; + } + + @Override + public boolean removeAll(Collection c) { + return Iterables.removeIf( + multimap.unfiltered().entries(), + // explicit > is required to build with JDK6 + Predicates.>and( + multimap.entryPredicate(), Maps.valuePredicateOnEntries(Predicates.in(c)))); + } + + @Override + public boolean retainAll(Collection c) { + return Iterables.removeIf( + multimap.unfiltered().entries(), + // explicit > is required to build with JDK6 + Predicates.>and( + multimap.entryPredicate(), + Maps.valuePredicateOnEntries(Predicates.not(Predicates.in(c))))); + } + + @Override + public void clear() { + multimap.clear(); + } +} diff --git a/src/main/java/com/google/common/collect/FilteredSetMultimap.java b/src/main/java/com/google/common/collect/FilteredSetMultimap.java new file mode 100644 index 0000000..a0a149f --- /dev/null +++ b/src/main/java/com/google/common/collect/FilteredSetMultimap.java @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2012 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import com.google.common.annotations.GwtCompatible; + +/** + * A supertype for filtered {@link SetMultimap} implementations. + * + * @author Louis Wasserman + */ +@GwtCompatible +interface FilteredSetMultimap extends FilteredMultimap, SetMultimap { + @Override + SetMultimap unfiltered(); +} diff --git a/src/main/java/com/google/common/collect/FluentIterable.java b/src/main/java/com/google/common/collect/FluentIterable.java new file mode 100644 index 0000000..6ee1ebd --- /dev/null +++ b/src/main/java/com/google/common/collect/FluentIterable.java @@ -0,0 +1,851 @@ +/* + * Copyright (C) 2008 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.collect; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.annotations.Beta; +import com.google.common.annotations.GwtCompatible; +import com.google.common.annotations.GwtIncompatible; +import com.google.common.base.Function; +import com.google.common.base.Joiner; +import com.google.common.base.Optional; +import com.google.common.base.Predicate; + +import java.util.Arrays; +import java.util.Collection; +import java.util.Comparator; +import java.util.Iterator; +import java.util.List; +import java.util.SortedSet; +import java.util.stream.Stream; + + +/** + * A discouraged (but not deprecated) precursor to Java's superior {@link Stream} library. + * + *

The following types of methods are provided: + * + *

    + *
  • chaining methods which return a new {@code FluentIterable} based in some way on the + * contents of the current one (for example {@link #transform}) + *
  • element extraction methods which facilitate the retrieval of certain elements (for example + * {@link #last}) + *
  • query methods which answer questions about the {@code FluentIterable}'s contents (for + * example {@link #anyMatch}) + *
  • conversion methods which copy the {@code FluentIterable}'s contents into a new collection + * or array (for example {@link #toList}) + *
+ * + *

Several lesser-used features are currently available only as static methods on the {@link + * Iterables} class. + * + *

+ * + *

Comparison to streams

+ * + *

{@link Stream} is similar to this class, but generally more powerful, and certainly more + * standard. Key differences include: + * + *

    + *
  • A stream is single-use; it becomes invalid as soon as any "terminal operation" such + * as {@code findFirst()} or {@code iterator()} is invoked. (Even though {@code Stream} + * contains all the right method signatures to implement {@link Iterable}, it does not + * actually do so, to avoid implying repeat-iterability.) {@code FluentIterable}, on the other + * hand, is multiple-use, and does implement {@link Iterable}. + *
  • Streams offer many features not found here, including {@code min/max}, {@code distinct}, + * {@code reduce}, {@code sorted}, the very powerful {@code collect}, and built-in support for + * parallelizing stream operations. + *
  • {@code FluentIterable} contains several features not available on {@code Stream}, which are + * noted in the method descriptions below. + *
  • Streams include primitive-specialized variants such as {@code IntStream}, the use of which + * is strongly recommended. + *
  • Streams are standard Java, not requiring a third-party dependency. + *
+ * + *

Example

+ * + *

Here is an example that accepts a list from a database call, filters it based on a predicate, + * transforms it by invoking {@code toString()} on each element, and returns the first 10 elements + * as a {@code List}: + * + *

{@code
+ * ImmutableList results =
+ *     FluentIterable.from(database.getClientList())
+ *         .filter(Client::isActiveInLastMonth)
+ *         .transform(Object::toString)
+ *         .limit(10)
+ *         .toList();
+ * }
+ * + * The approximate stream equivalent is: + * + *
{@code
+ * List results =
+ *     database.getClientList()
+ *         .stream()
+ *         .filter(Client::isActiveInLastMonth)
+ *         .map(Object::toString)
+ *         .limit(10)
+ *         .collect(Collectors.toList());
+ * }
+ * + * @author Marcin Mikosik + * @since 12.0 + */ +@GwtCompatible(emulated = true) +public abstract class FluentIterable implements Iterable { + // We store 'iterable' and use it instead of 'this' to allow Iterables to perform instanceof + // checks on the _original_ iterable when FluentIterable.from is used. + // To avoid a self retain cycle under j2objc, we store Optional.absent() instead of + // Optional.of(this). To access the iterator delegate, call #getDelegate(), which converts to + // absent() back to 'this'. + private final Optional> iterableDelegate; + + /** Constructor for use by subclasses. */ + protected FluentIterable() { + this.iterableDelegate = Optional.absent(); + } + + FluentIterable(Iterable iterable) { + checkNotNull(iterable); + this.iterableDelegate = Optional.fromNullable(this != iterable ? iterable : null); + } + + private Iterable getDelegate() { + return iterableDelegate.or(this); + } + + /** + * Returns a fluent iterable that wraps {@code iterable}, or {@code iterable} itself if it is + * already a {@code FluentIterable}. + * + *

{@code Stream} equivalent: {@link Collection#stream} if {@code iterable} is a {@link + * Collection}; {@link Streams#stream(Iterable)} otherwise. + */ + public static FluentIterable from(final Iterable iterable) { + return (iterable instanceof FluentIterable) + ? (FluentIterable) iterable + : new FluentIterable(iterable) { + @Override + public Iterator iterator() { + return iterable.iterator(); + } + }; + } + + /** + * Returns a fluent iterable containing {@code elements} in the specified order. + * + *

The returned iterable is an unmodifiable view of the input array. + * + *

{@code Stream} equivalent: {@link java.util.stream.Stream#of(Object[]) + * Stream.of(T...)}. + * + * @since 20.0 (since 18.0 as an overload of {@code of}) + */ + @Beta + public static FluentIterable from(E[] elements) { + return from(Arrays.asList(elements)); + } + + /** + * Construct a fluent iterable from another fluent iterable. This is obviously never necessary, + * but is intended to help call out cases where one migration from {@code Iterable} to {@code + * FluentIterable} has obviated the need to explicitly convert to a {@code FluentIterable}. + * + * @deprecated instances of {@code FluentIterable} don't need to be converted to {@code + * FluentIterable} + */ + @Deprecated + public static FluentIterable from(FluentIterable iterable) { + return checkNotNull(iterable); + } + + /** + * Returns a fluent iterable that combines two iterables. The returned iterable has an iterator + * that traverses the elements in {@code a}, followed by the elements in {@code b}. The source + * iterators are not polled until necessary. + * + *

The returned iterable's iterator supports {@code remove()} when the corresponding input + * iterator supports it. + * + *

{@code Stream} equivalent: {@link Stream#concat}. + * + * @since 20.0 + */ + @Beta + public static FluentIterable concat(Iterable a, Iterable b) { + return concatNoDefensiveCopy(a, b); + } + + /** + * Returns a fluent iterable that combines three iterables. The returned iterable has an iterator + * that traverses the elements in {@code a}, followed by the elements in {@code b}, followed by + * the elements in {@code c}. The source iterators are not polled until necessary. + * + *

The returned iterable's iterator supports {@code remove()} when the corresponding input + * iterator supports it. + * + *

{@code Stream} equivalent: use nested calls to {@link Stream#concat}, or see the + * advice in {@link #concat(Iterable...)}. + * + * @since 20.0 + */ + @Beta + public static FluentIterable concat( + Iterable a, Iterable b, Iterable c) { + return concatNoDefensiveCopy(a, b, c); + } + + /** + * Returns a fluent iterable that combines four iterables. The returned iterable has an iterator + * that traverses the elements in {@code a}, followed by the elements in {@code b}, followed by + * the elements in {@code c}, followed by the elements in {@code d}. The source iterators are not + * polled until necessary. + * + *

The returned iterable's iterator supports {@code remove()} when the corresponding input + * iterator supports it. + * + *

{@code Stream} equivalent: use nested calls to {@link Stream#concat}, or see the + * advice in {@link #concat(Iterable...)}. + * + * @since 20.0 + */ + @Beta + public static FluentIterable concat( + Iterable a, + Iterable b, + Iterable c, + Iterable d) { + return concatNoDefensiveCopy(a, b, c, d); + } + + /** + * Returns a fluent iterable that combines several iterables. The returned iterable has an + * iterator that traverses the elements of each iterable in {@code inputs}. The input iterators + * are not polled until necessary. + * + *

The returned iterable's iterator supports {@code remove()} when the corresponding input + * iterator supports it. + * + *

{@code Stream} equivalent: to concatenate an arbitrary number of streams, use {@code + * Stream.of(stream1, stream2, ...).flatMap(s -> s)}. If the sources are iterables, use {@code + * Stream.of(iter1, iter2, ...).flatMap(Streams::stream)}. + * + * @throws NullPointerException if any of the provided iterables is {@code null} + * @since 20.0 + */ + @Beta + public static FluentIterable concat(Iterable... inputs) { + return concatNoDefensiveCopy(Arrays.copyOf(inputs, inputs.length)); + } + + /** + * Returns a fluent iterable that combines several iterables. The returned iterable has an + * iterator that traverses the elements of each iterable in {@code inputs}. The input iterators + * are not polled until necessary. + * + *

The returned iterable's iterator supports {@code remove()} when the corresponding input + * iterator supports it. The methods of the returned iterable may throw {@code + * NullPointerException} if any of the input iterators is {@code null}. + * + *

{@code Stream} equivalent: {@code streamOfStreams.flatMap(s -> s)} or {@code + * streamOfIterables.flatMap(Streams::stream)}. (See {@link Streams#stream}.) + * + * @since 20.0 + */ + @Beta + public static FluentIterable concat( + final Iterable> inputs) { + checkNotNull(inputs); + return new FluentIterable() { + @Override + public Iterator iterator() { + return Iterators.concat(Iterators.transform(inputs.iterator(), Iterables.toIterator())); + } + }; + } + + /** Concatenates a varargs array of iterables without making a defensive copy of the array. */ + private static FluentIterable concatNoDefensiveCopy( + final Iterable... inputs) { + for (Iterable input : inputs) { + checkNotNull(input); + } + return new FluentIterable() { + @Override + public Iterator iterator() { + return Iterators.concat( + /* lazily generate the iterators on each input only as needed */ + new AbstractIndexedListIterator>(inputs.length) { + @Override + public Iterator get(int i) { + return inputs[i].iterator(); + } + }); + } + }; + } + + /** + * Returns a fluent iterable containing no elements. + * + *

{@code Stream} equivalent: {@link Stream#empty}. + * + * @since 20.0 + */ + @Beta + public static FluentIterable of() { + return FluentIterable.from(ImmutableList.of()); + } + + /** + * Returns a fluent iterable containing the specified elements in order. + * + *

{@code Stream} equivalent: {@link java.util.stream.Stream#of(Object[]) + * Stream.of(T...)}. + * + * @since 20.0 + */ + @Beta + public static FluentIterable of(E element, E... elements) { + return from(Lists.asList(element, elements)); + } + + /** + * Returns a string representation of this fluent iterable, with the format {@code [e1, e2, ..., + * en]}. + * + *

{@code Stream} equivalent: {@code stream.collect(Collectors.joining(", ", "[", "]"))} + * or (less efficiently) {@code stream.collect(Collectors.toList()).toString()}. + */ + @Override + public String toString() { + return Iterables.toString(getDelegate()); + } + + /** + * Returns the number of elements in this fluent iterable. + * + *

{@code Stream} equivalent: {@link Stream#count}. + */ + public final int size() { + return Iterables.size(getDelegate()); + } + + /** + * Returns {@code true} if this fluent iterable contains any object for which {@code + * equals(target)} is true. + * + *

{@code Stream} equivalent: {@code stream.anyMatch(Predicate.isEqual(target))}. + */ + public final boolean contains(Object target) { + return Iterables.contains(getDelegate(), target); + } + + /** + * Returns a fluent iterable whose {@code Iterator} cycles indefinitely over the elements of this + * fluent iterable. + * + *

That iterator supports {@code remove()} if {@code iterable.iterator()} does. After {@code + * remove()} is called, subsequent cycles omit the removed element, which is no longer in this + * fluent iterable. The iterator's {@code hasNext()} method returns {@code true} until this fluent + * iterable is empty. + * + *

Warning: Typical uses of the resulting iterator may produce an infinite loop. You + * should use an explicit {@code break} or be certain that you will eventually remove all the + * elements. + * + *

{@code Stream} equivalent: if the source iterable has only a single element {@code + * e}, use {@code Stream.generate(() -> e)}. Otherwise, collect your stream into a collection and + * use {@code Stream.generate(() -> collection).flatMap(Collection::stream)}. + */ + public final FluentIterable cycle() { + return from(Iterables.cycle(getDelegate())); + } + + /** + * Returns a fluent iterable whose iterators traverse first the elements of this fluent iterable, + * followed by those of {@code other}. The iterators are not polled until necessary. + * + *

The returned iterable's {@code Iterator} supports {@code remove()} when the corresponding + * {@code Iterator} supports it. + * + *

{@code Stream} equivalent: {@link Stream#concat}. + * + * @since 18.0 + */ + @Beta + public final FluentIterable append(Iterable other) { + return FluentIterable.concat(getDelegate(), other); + } + + /** + * Returns a fluent iterable whose iterators traverse first the elements of this fluent iterable, + * followed by {@code elements}. + * + *

{@code Stream} equivalent: {@code Stream.concat(thisStream, Stream.of(elements))}. + * + * @since 18.0 + */ + @Beta + public final FluentIterable append(E... elements) { + return FluentIterable.concat(getDelegate(), Arrays.asList(elements)); + } + + /** + * Returns the elements from this fluent iterable that satisfy a predicate. The resulting fluent + * iterable's iterator does not support {@code remove()}. + * + *

{@code Stream} equivalent: {@link Stream#filter} (same). + */ + public final FluentIterable filter(Predicate predicate) { + return from(Iterables.filter(getDelegate(), predicate)); + } + + /** + * Returns the elements from this fluent iterable that are instances of class {@code type}. + * + *

{@code Stream} equivalent: {@code stream.filter(type::isInstance).map(type::cast)}. + * This does perform a little more work than necessary, so another option is to insert an + * unchecked cast at some later point: + * + *

+   * {@code @SuppressWarnings("unchecked") // safe because of ::isInstance check
+   * ImmutableList result =
+   *     (ImmutableList) stream.filter(NewType.class::isInstance).collect(toImmutableList());}
+   * 
+ */ + @GwtIncompatible // Class.isInstance + public final FluentIterable filter(Class type) { + return from(Iterables.filter(getDelegate(), type)); + } + + /** + * Returns {@code true} if any element in this fluent iterable satisfies the predicate. + * + *

{@code Stream} equivalent: {@link Stream#anyMatch} (same). + */ + public final boolean anyMatch(Predicate predicate) { + return Iterables.any(getDelegate(), predicate); + } + + /** + * Returns {@code true} if every element in this fluent iterable satisfies the predicate. If this + * fluent iterable is empty, {@code true} is returned. + * + *

{@code Stream} equivalent: {@link Stream#allMatch} (same). + */ + public final boolean allMatch(Predicate predicate) { + return Iterables.all(getDelegate(), predicate); + } + + /** + * Returns an {@link Optional} containing the first element in this fluent iterable that satisfies + * the given predicate, if such an element exists. + * + *

Warning: avoid using a {@code predicate} that matches {@code null}. If {@code null} + * is matched in this fluent iterable, a {@link NullPointerException} will be thrown. + * + *

{@code Stream} equivalent: {@code stream.filter(predicate).findFirst()}. + */ + public final Optional firstMatch(Predicate predicate) { + return Iterables.tryFind(getDelegate(), predicate); + } + + /** + * Returns a fluent iterable that applies {@code function} to each element of this fluent + * iterable. + * + *

The returned fluent iterable's iterator supports {@code remove()} if this iterable's + * iterator does. After a successful {@code remove()} call, this fluent iterable no longer + * contains the corresponding element. + * + *

{@code Stream} equivalent: {@link Stream#map}. + */ + public final FluentIterable transform(Function function) { + return from(Iterables.transform(getDelegate(), function)); + } + + /** + * Applies {@code function} to each element of this fluent iterable and returns a fluent iterable + * with the concatenated combination of results. {@code function} returns an Iterable of results. + * + *

The returned fluent iterable's iterator supports {@code remove()} if this function-returned + * iterables' iterator does. After a successful {@code remove()} call, the returned fluent + * iterable no longer contains the corresponding element. + * + *

{@code Stream} equivalent: {@link Stream#flatMap} (using a function that produces + * streams, not iterables). + * + * @since 13.0 (required {@code Function>} until 14.0) + */ + public FluentIterable transformAndConcat( + Function> function) { + return FluentIterable.concat(transform(function)); + } + + /** + * Returns an {@link Optional} containing the first element in this fluent iterable. If the + * iterable is empty, {@code Optional.absent()} is returned. + * + *

{@code Stream} equivalent: if the goal is to obtain any element, {@link + * Stream#findAny}; if it must specifically be the first element, {@code Stream#findFirst}. + * + * @throws NullPointerException if the first element is null; if this is a possibility, use {@code + * iterator().next()} or {@link Iterables#getFirst} instead. + */ + public final Optional first() { + Iterator iterator = getDelegate().iterator(); + return iterator.hasNext() ? Optional.of(iterator.next()) : Optional.absent(); + } + + /** + * Returns an {@link Optional} containing the last element in this fluent iterable. If the + * iterable is empty, {@code Optional.absent()} is returned. If the underlying {@code iterable} is + * a {@link List} with {@link java.util.RandomAccess} support, then this operation is guaranteed + * to be {@code O(1)}. + * + *

{@code Stream} equivalent: {@code stream.reduce((a, b) -> b)}. + * + * @throws NullPointerException if the last element is null; if this is a possibility, use {@link + * Iterables#getLast} instead. + */ + public final Optional last() { + // Iterables#getLast was inlined here so we don't have to throw/catch a NSEE + + // TODO(kevinb): Support a concurrently modified collection? + Iterable iterable = getDelegate(); + if (iterable instanceof List) { + List list = (List) iterable; + if (list.isEmpty()) { + return Optional.absent(); + } + return Optional.of(list.get(list.size() - 1)); + } + Iterator iterator = iterable.iterator(); + if (!iterator.hasNext()) { + return Optional.absent(); + } + + /* + * TODO(kevinb): consider whether this "optimization" is worthwhile. Users with SortedSets tend + * to know they are SortedSets and probably would not call this method. + */ + if (iterable instanceof SortedSet) { + SortedSet sortedSet = (SortedSet) iterable; + return Optional.of(sortedSet.last()); + } + + while (true) { + E current = iterator.next(); + if (!iterator.hasNext()) { + return Optional.of(current); + } + } + } + + /** + * Returns a view of this fluent iterable that skips its first {@code numberToSkip} elements. If + * this fluent iterable contains fewer than {@code numberToSkip} elements, the returned fluent + * iterable skips all of its elements. + * + *

Modifications to this fluent iterable before a call to {@code iterator()} are reflected in + * the returned fluent iterable. That is, the its iterator skips the first {@code numberToSkip} + * elements that exist when the iterator is created, not when {@code skip()} is called. + * + *

The returned fluent iterable's iterator supports {@code remove()} if the {@code Iterator} of + * this fluent iterable supports it. Note that it is not possible to delete the last + * skipped element by immediately calling {@code remove()} on the returned fluent iterable's + * iterator, as the {@code Iterator} contract states that a call to {@code * remove()} before a + * call to {@code next()} will throw an {@link IllegalStateException}. + * + *

{@code Stream} equivalent: {@link Stream#skip} (same). + */ + public final FluentIterable skip(int numberToSkip) { + return from(Iterables.skip(getDelegate(), numberToSkip)); + } + + /** + * Creates a fluent iterable with the first {@code size} elements of this fluent iterable. If this + * fluent iterable does not contain that many elements, the returned fluent iterable will have the + * same behavior as this fluent iterable. The returned fluent iterable's iterator supports {@code + * remove()} if this fluent iterable's iterator does. + * + *

{@code Stream} equivalent: {@link Stream#limit} (same). + * + * @param maxSize the maximum number of elements in the returned fluent iterable + * @throws IllegalArgumentException if {@code size} is negative + */ + public final FluentIterable limit(int maxSize) { + return from(Iterables.limit(getDelegate(), maxSize)); + } + + /** + * Determines whether this fluent iterable is empty. + * + *

{@code Stream} equivalent: {@code !stream.findAny().isPresent()}. + */ + public final boolean isEmpty() { + return !getDelegate().iterator().hasNext(); + } + + /** + * Returns an {@code ImmutableList} containing all of the elements from this fluent iterable in + * proper sequence. + * + *

{@code Stream} equivalent: pass {@link ImmutableList#toImmutableList} to {@code + * stream.collect()}. + * + * @throws NullPointerException if any element is {@code null} + * @since 14.0 (since 12.0 as {@code toImmutableList()}). + */ + public final ImmutableList toList() { + return ImmutableList.copyOf(getDelegate()); + } + + /** + * Returns an {@code ImmutableList} containing all of the elements from this {@code + * FluentIterable} in the order specified by {@code comparator}. To produce an {@code + * ImmutableList} sorted by its natural ordering, use {@code toSortedList(Ordering.natural())}. + * + *

{@code Stream} equivalent: pass {@link ImmutableList#toImmutableList} to {@code + * stream.sorted(comparator).collect()}. + * + * @param comparator the function by which to sort list elements + * @throws NullPointerException if any element of this iterable is {@code null} + * @since 14.0 (since 13.0 as {@code toSortedImmutableList()}). + */ + public final ImmutableList toSortedList(Comparator comparator) { + return Ordering.from(comparator).immutableSortedCopy(getDelegate()); + } + + /** + * Returns an {@code ImmutableSet} containing all of the elements from this fluent iterable with + * duplicates removed. + * + *

{@code Stream} equivalent: pass {@link ImmutableSet#toImmutableSet} to {@code + * stream.collect()}. + * + * @throws NullPointerException if any element is {@code null} + * @since 14.0 (since 12.0 as {@code toImmutableSet()}). + */ + public final ImmutableSet toSet() { + return ImmutableSet.copyOf(getDelegate()); + } + + /** + * Returns an {@code ImmutableSortedSet} containing all of the elements from this {@code + * FluentIterable} in the order specified by {@code comparator}, with duplicates (determined by + * {@code comparator.compare(x, y) == 0}) removed. To produce an {@code ImmutableSortedSet} sorted + * by its natural ordering, use {@code toSortedSet(Ordering.natural())}. + * + *

{@code Stream} equivalent: pass {@link ImmutableSortedSet#toImmutableSortedSet} to + * {@code stream.collect()}. + * + * @param comparator the function by which to sort set elements + * @throws NullPointerException if any element of this iterable is {@code null} + * @since 14.0 (since 12.0 as {@code toImmutableSortedSet()}). + */ + public final ImmutableSortedSet toSortedSet(Comparator comparator) { + return ImmutableSortedSet.copyOf(comparator, getDelegate()); + } + + /** + * Returns an {@code ImmutableMultiset} containing all of the elements from this fluent iterable. + * + *

{@code Stream} equivalent: pass {@link ImmutableMultiset#toImmutableMultiset} to + * {@code stream.collect()}. + * + * @throws NullPointerException if any element is null + * @since 19.0 + */ + public final ImmutableMultiset toMultiset() { + return ImmutableMultiset.copyOf(getDelegate()); + } + + /** + * Returns an immutable map whose keys are the distinct elements of this {@code FluentIterable} + * and whose value for each key was computed by {@code valueFunction}. The map's iteration order + * is the order of the first appearance of each key in this iterable. + * + *

When there are multiple instances of a key in this iterable, it is unspecified whether + * {@code valueFunction} will be applied to more than one instance of that key and, if it is, + * which result will be mapped to that key in the returned map. + * + *

{@code Stream} equivalent: {@code stream.collect(ImmutableMap.toImmutableMap(k -> k, + * valueFunction))}. + * + * @throws NullPointerException if any element of this iterable is {@code null}, or if {@code + * valueFunction} produces {@code null} for any key + * @since 14.0 + */ + public final ImmutableMap toMap(Function valueFunction) { + return Maps.toMap(getDelegate(), valueFunction); + } + + /** + * Creates an index {@code ImmutableListMultimap} that contains the results of applying a + * specified function to each item in this {@code FluentIterable} of values. Each element of this + * iterable will be stored as a value in the resulting multimap, yielding a multimap with the same + * size as this iterable. The key used to store that value in the multimap will be the result of + * calling the function on that value. The resulting multimap is created as an immutable snapshot. + * In the returned multimap, keys appear in the order they are first encountered, and the values + * corresponding to each key appear in the same order as they are encountered. + * + *

{@code Stream} equivalent: {@code stream.collect(Collectors.groupingBy(keyFunction))} + * behaves similarly, but returns a mutable {@code Map>} instead, and may not preserve + * the order of entries). + * + * @param keyFunction the function used to produce the key for each value + * @throws NullPointerException if any element of this iterable is {@code null}, or if {@code + * keyFunction} produces {@code null} for any key + * @since 14.0 + */ + public final ImmutableListMultimap index(Function keyFunction) { + return Multimaps.index(getDelegate(), keyFunction); + } + + /** + * Returns a map with the contents of this {@code FluentIterable} as its {@code values}, indexed + * by keys derived from those values. In other words, each input value produces an entry in the + * map whose key is the result of applying {@code keyFunction} to that value. These entries appear + * in the same order as they appeared in this fluent iterable. Example usage: + * + *

{@code
+   * Color red = new Color("red", 255, 0, 0);
+   * ...
+   * FluentIterable allColors = FluentIterable.from(ImmutableSet.of(red, green, blue));
+   *
+   * Map colorForName = allColors.uniqueIndex(toStringFunction());
+   * assertThat(colorForName).containsEntry("red", red);
+   * }
+ * + *

If your index may associate multiple values with each key, use {@link #index(Function) + * index}. + * + *

{@code Stream} equivalent: {@code + * stream.collect(ImmutableMap.toImmutableMap(keyFunction, v -> v))}. + * + * @param keyFunction the function used to produce the key for each value + * @return a map mapping the result of evaluating the function {@code keyFunction} on each value + * in this fluent iterable to that value + * @throws IllegalArgumentException if {@code keyFunction} produces the same key for more than one + * value in this fluent iterable + * @throws NullPointerException if any element of this iterable is {@code null}, or if {@code + * keyFunction} produces {@code null} for any key + * @since 14.0 + */ + public final ImmutableMap uniqueIndex(Function keyFunction) { + return Maps.uniqueIndex(getDelegate(), keyFunction); + } + + /** + * Returns an array containing all of the elements from this fluent iterable in iteration order. + * + *

{@code Stream} equivalent: if an object array is acceptable, use {@code + * stream.toArray()}; if {@code type} is a class literal such as {@code MyType.class}, use {@code + * stream.toArray(MyType[]::new)}. Otherwise use {@code stream.toArray( len -> (E[]) + * Array.newInstance(type, len))}. + * + * @param type the type of the elements + * @return a newly-allocated array into which all the elements of this fluent iterable have been + * copied + */ + @GwtIncompatible // Array.newArray(Class, int) + public final E[] toArray(Class type) { + return Iterables.toArray(getDelegate(), type); + } + + /** + * Copies all the elements from this fluent iterable to {@code collection}. This is equivalent to + * calling {@code Iterables.addAll(collection, this)}. + * + *

{@code Stream} equivalent: {@code stream.forEachOrdered(collection::add)} or {@code + * stream.forEach(collection::add)}. + * + * @param collection the collection to copy elements to + * @return {@code collection}, for convenience + * @since 14.0 + */ + + public final > C copyInto(C collection) { + checkNotNull(collection); + Iterable iterable = getDelegate(); + if (iterable instanceof Collection) { + collection.addAll(Collections2.cast(iterable)); + } else { + for (E item : iterable) { + collection.add(item); + } + } + return collection; + } + + /** + * Returns a {@link String} containing all of the elements of this fluent iterable joined with + * {@code joiner}. + * + *

{@code Stream} equivalent: {@code joiner.join(stream.iterator())}, or, if you are not + * using any optional {@code Joiner} features, {@code + * stream.collect(Collectors.joining(delimiter)}. + * + * @since 18.0 + */ + @Beta + public final String join(Joiner joiner) { + return joiner.join(this); + } + + /** + * Returns the element at the specified position in this fluent iterable. + * + *

{@code Stream} equivalent: {@code stream.skip(position).findFirst().get()} (but note + * that this throws different exception types, and throws an exception if {@code null} would be + * returned). + * + * @param position position of the element to return + * @return the element at the specified position in this fluent iterable + * @throws IndexOutOfBoundsException if {@code position} is negative or greater than or equal to + * the size of this fluent iterable + */ + // TODO(kevinb): add ? + public final E get(int position) { + return Iterables.get(getDelegate(), position); + } + + /** + * Returns a stream of this fluent iterable's contents (similar to calling {@link + * Collection#stream} on a collection). + * + *

Note: the earlier in the chain you can switch to {@code Stream} usage (ideally not + * going through {@code FluentIterable} at all), the more performant and idiomatic your code will + * be. This method is a transitional aid, to be used only when really necessary. + * + * @since 21.0 + */ + public final Stream stream() { + return Streams.stream(getDelegate()); + } + + /** Function that transforms {@code Iterable} into a fluent iterable. */ + private static class FromIterableFunction implements Function, FluentIterable> { + @Override + public FluentIterable apply(Iterable fromObject) { + return FluentIterable.from(fromObject); + } + } +} diff --git a/src/main/java/com/google/common/collect/ForwardingBlockingDeque.java b/src/main/java/com/google/common/collect/ForwardingBlockingDeque.java new file mode 100644 index 0000000..7d3895d --- /dev/null +++ b/src/main/java/com/google/common/collect/ForwardingBlockingDeque.java @@ -0,0 +1,132 @@ +/* + * Copyright (C) 2012 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import com.google.common.annotations.GwtIncompatible; +import java.util.Collection; +import java.util.concurrent.BlockingDeque; +import java.util.concurrent.TimeUnit; + +/** + * A {@link BlockingDeque} which forwards all its method calls to another {@code BlockingDeque}. + * Subclasses should override one or more methods to modify the behavior of the backing deque as + * desired per the decorator pattern. + * + *

Warning: The methods of {@code ForwardingBlockingDeque} forward indiscriminately + * to the methods of the delegate. For example, overriding {@link #add} alone will not change + * the behaviour of {@link #offer} which can lead to unexpected behaviour. In this case, you should + * override {@code offer} as well, either providing your own implementation, or delegating to the + * provided {@code standardOffer} method. + * + *

{@code default} method warning: This class does not forward calls to {@code + * default} methods. Instead, it inherits their default implementations. When those implementations + * invoke methods, they invoke methods on the {@code ForwardingBlockingDeque}. + * + *

The {@code standard} methods are not guaranteed to be thread-safe, even when all of the + * methods that they depend on are thread-safe. + * + * @author Emily Soldal + * @since 14.0 + * @deprecated This class has moved to {@code com.google.common.util.concurrent}. Please use {@link + * com.google.common.util.concurrent.ForwardingBlockingDeque} instead. + */ +@Deprecated +@GwtIncompatible +public abstract class ForwardingBlockingDeque extends ForwardingDeque + implements BlockingDeque { + + /** Constructor for use by subclasses. */ + protected ForwardingBlockingDeque() {} + + @Override + protected abstract BlockingDeque delegate(); + + @Override + public int remainingCapacity() { + return delegate().remainingCapacity(); + } + + @Override + public void putFirst(E e) throws InterruptedException { + delegate().putFirst(e); + } + + @Override + public void putLast(E e) throws InterruptedException { + delegate().putLast(e); + } + + @Override + public boolean offerFirst(E e, long timeout, TimeUnit unit) throws InterruptedException { + return delegate().offerFirst(e, timeout, unit); + } + + @Override + public boolean offerLast(E e, long timeout, TimeUnit unit) throws InterruptedException { + return delegate().offerLast(e, timeout, unit); + } + + @Override + public E takeFirst() throws InterruptedException { + return delegate().takeFirst(); + } + + @Override + public E takeLast() throws InterruptedException { + return delegate().takeLast(); + } + + @Override + public E pollFirst(long timeout, TimeUnit unit) throws InterruptedException { + return delegate().pollFirst(timeout, unit); + } + + @Override + public E pollLast(long timeout, TimeUnit unit) throws InterruptedException { + return delegate().pollLast(timeout, unit); + } + + @Override + public void put(E e) throws InterruptedException { + delegate().put(e); + } + + @Override + public boolean offer(E e, long timeout, TimeUnit unit) throws InterruptedException { + return delegate().offer(e, timeout, unit); + } + + @Override + public E take() throws InterruptedException { + return delegate().take(); + } + + @Override + public E poll(long timeout, TimeUnit unit) throws InterruptedException { + return delegate().poll(timeout, unit); + } + + @Override + public int drainTo(Collection c) { + return delegate().drainTo(c); + } + + @Override + public int drainTo(Collection c, int maxElements) { + return delegate().drainTo(c, maxElements); + } +} diff --git a/src/main/java/com/google/common/collect/ForwardingCollection.java b/src/main/java/com/google/common/collect/ForwardingCollection.java new file mode 100644 index 0000000..ae1e5eb --- /dev/null +++ b/src/main/java/com/google/common/collect/ForwardingCollection.java @@ -0,0 +1,256 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import com.google.common.annotations.GwtCompatible; +import com.google.common.base.Objects; + +import java.util.Collection; +import java.util.Iterator; + + +/** + * A collection which forwards all its method calls to another collection. Subclasses should + * override one or more methods to modify the behavior of the backing collection as desired per the + * decorator pattern. + * + *

Warning: The methods of {@code ForwardingCollection} forward indiscriminately to + * the methods of the delegate. For example, overriding {@link #add} alone will not change + * the behavior of {@link #addAll}, which can lead to unexpected behavior. In this case, you should + * override {@code addAll} as well, either providing your own implementation, or delegating to the + * provided {@code standardAddAll} method. + * + *

{@code default} method warning: This class does not forward calls to {@code + * default} methods. Instead, it inherits their default implementations. When those implementations + * invoke methods, they invoke methods on the {@code ForwardingCollection}. + * + *

The {@code standard} methods are not guaranteed to be thread-safe, even when all of the + * methods that they depend on are thread-safe. + * + * @author Kevin Bourrillion + * @author Louis Wasserman + * @since 2.0 + */ +@GwtCompatible +public abstract class ForwardingCollection extends ForwardingObject implements Collection { + // TODO(lowasser): identify places where thread safety is actually lost + + /** Constructor for use by subclasses. */ + protected ForwardingCollection() {} + + @Override + protected abstract Collection delegate(); + + @Override + public Iterator iterator() { + return delegate().iterator(); + } + + @Override + public int size() { + return delegate().size(); + } + + + @Override + public boolean removeAll(Collection collection) { + return delegate().removeAll(collection); + } + + @Override + public boolean isEmpty() { + return delegate().isEmpty(); + } + + @Override + public boolean contains(Object object) { + return delegate().contains(object); + } + + + @Override + public boolean add(E element) { + return delegate().add(element); + } + + + @Override + public boolean remove(Object object) { + return delegate().remove(object); + } + + @Override + public boolean containsAll(Collection collection) { + return delegate().containsAll(collection); + } + + + @Override + public boolean addAll(Collection collection) { + return delegate().addAll(collection); + } + + + @Override + public boolean retainAll(Collection collection) { + return delegate().retainAll(collection); + } + + @Override + public void clear() { + delegate().clear(); + } + + @Override + public Object[] toArray() { + return delegate().toArray(); + } + + + @Override + public T[] toArray(T[] array) { + return delegate().toArray(array); + } + + /** + * A sensible definition of {@link #contains} in terms of {@link #iterator}. If you override + * {@link #iterator}, you may wish to override {@link #contains} to forward to this + * implementation. + * + * @since 7.0 + */ + protected boolean standardContains(Object object) { + return Iterators.contains(iterator(), object); + } + + /** + * A sensible definition of {@link #containsAll} in terms of {@link #contains} . If you override + * {@link #contains}, you may wish to override {@link #containsAll} to forward to this + * implementation. + * + * @since 7.0 + */ + protected boolean standardContainsAll(Collection collection) { + return Collections2.containsAllImpl(this, collection); + } + + /** + * A sensible definition of {@link #addAll} in terms of {@link #add}. If you override {@link + * #add}, you may wish to override {@link #addAll} to forward to this implementation. + * + * @since 7.0 + */ + protected boolean standardAddAll(Collection collection) { + return Iterators.addAll(this, collection.iterator()); + } + + /** + * A sensible definition of {@link #remove} in terms of {@link #iterator}, using the iterator's + * {@code remove} method. If you override {@link #iterator}, you may wish to override {@link + * #remove} to forward to this implementation. + * + * @since 7.0 + */ + protected boolean standardRemove(Object object) { + Iterator iterator = iterator(); + while (iterator.hasNext()) { + if (Objects.equal(iterator.next(), object)) { + iterator.remove(); + return true; + } + } + return false; + } + + /** + * A sensible definition of {@link #removeAll} in terms of {@link #iterator}, using the iterator's + * {@code remove} method. If you override {@link #iterator}, you may wish to override {@link + * #removeAll} to forward to this implementation. + * + * @since 7.0 + */ + protected boolean standardRemoveAll(Collection collection) { + return Iterators.removeAll(iterator(), collection); + } + + /** + * A sensible definition of {@link #retainAll} in terms of {@link #iterator}, using the iterator's + * {@code remove} method. If you override {@link #iterator}, you may wish to override {@link + * #retainAll} to forward to this implementation. + * + * @since 7.0 + */ + protected boolean standardRetainAll(Collection collection) { + return Iterators.retainAll(iterator(), collection); + } + + /** + * A sensible definition of {@link #clear} in terms of {@link #iterator}, using the iterator's + * {@code remove} method. If you override {@link #iterator}, you may wish to override {@link + * #clear} to forward to this implementation. + * + * @since 7.0 + */ + protected void standardClear() { + Iterators.clear(iterator()); + } + + /** + * A sensible definition of {@link #isEmpty} as {@code !iterator().hasNext}. If you override + * {@link #isEmpty}, you may wish to override {@link #isEmpty} to forward to this implementation. + * Alternately, it may be more efficient to implement {@code isEmpty} as {@code size() == 0}. + * + * @since 7.0 + */ + protected boolean standardIsEmpty() { + return !iterator().hasNext(); + } + + /** + * A sensible definition of {@link #toString} in terms of {@link #iterator}. If you override + * {@link #iterator}, you may wish to override {@link #toString} to forward to this + * implementation. + * + * @since 7.0 + */ + protected String standardToString() { + return Collections2.toStringImpl(this); + } + + /** + * A sensible definition of {@link #toArray()} in terms of {@link #toArray(Object[])}. If you + * override {@link #toArray(Object[])}, you may wish to override {@link #toArray} to forward to + * this implementation. + * + * @since 7.0 + */ + protected Object[] standardToArray() { + Object[] newArray = new Object[size()]; + return toArray(newArray); + } + + /** + * A sensible definition of {@link #toArray(Object[])} in terms of {@link #size} and {@link + * #iterator}. If you override either of these methods, you may wish to override {@link #toArray} + * to forward to this implementation. + * + * @since 7.0 + */ + protected T[] standardToArray(T[] array) { + return ObjectArrays.toArrayImpl(this, array); + } +} diff --git a/src/main/java/com/google/common/collect/ForwardingConcurrentMap.java b/src/main/java/com/google/common/collect/ForwardingConcurrentMap.java new file mode 100644 index 0000000..8fe54e4 --- /dev/null +++ b/src/main/java/com/google/common/collect/ForwardingConcurrentMap.java @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import com.google.common.annotations.GwtCompatible; + +import java.util.concurrent.ConcurrentMap; + +/** + * A concurrent map which forwards all its method calls to another concurrent map. Subclasses should + * override one or more methods to modify the behavior of the backing map as desired per the decorator pattern. + * + *

{@code default} method warning: This class forwards calls to only some {@code + * default} methods. Specifically, it forwards calls only for methods that existed before + * {@code default} methods were introduced. For newer methods, like {@code forEach}, it inherits + * their default implementations. When those implementations invoke methods, they invoke methods on + * the {@code ForwardingConcurrentMap}. + * + * @author Charles Fry + * @since 2.0 + */ +@GwtCompatible +public abstract class ForwardingConcurrentMap extends ForwardingMap + implements ConcurrentMap { + + /** Constructor for use by subclasses. */ + protected ForwardingConcurrentMap() {} + + @Override + protected abstract ConcurrentMap delegate(); + + + @Override + public V putIfAbsent(K key, V value) { + return delegate().putIfAbsent(key, value); + } + + + @Override + public boolean remove(Object key, Object value) { + return delegate().remove(key, value); + } + + + @Override + public V replace(K key, V value) { + return delegate().replace(key, value); + } + + + @Override + public boolean replace(K key, V oldValue, V newValue) { + return delegate().replace(key, oldValue, newValue); + } +} diff --git a/src/main/java/com/google/common/collect/ForwardingDeque.java b/src/main/java/com/google/common/collect/ForwardingDeque.java new file mode 100644 index 0000000..182627d --- /dev/null +++ b/src/main/java/com/google/common/collect/ForwardingDeque.java @@ -0,0 +1,143 @@ +/* + * Copyright (C) 2012 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import com.google.common.annotations.GwtIncompatible; + +import java.util.Deque; +import java.util.Iterator; + +/** + * A deque which forwards all its method calls to another deque. Subclasses should override one or + * more methods to modify the behavior of the backing deque as desired per the decorator pattern. + * + *

Warning: The methods of {@code ForwardingDeque} forward indiscriminately to the + * methods of the delegate. For example, overriding {@link #add} alone will not change the + * behavior of {@link #offer} which can lead to unexpected behavior. In this case, you should + * override {@code offer} as well. + * + *

{@code default} method warning: This class does not forward calls to {@code + * default} methods. Instead, it inherits their default implementations. When those implementations + * invoke methods, they invoke methods on the {@code ForwardingDeque}. + * + * @author Kurt Alfred Kluever + * @since 12.0 + */ +@GwtIncompatible +public abstract class ForwardingDeque extends ForwardingQueue implements Deque { + + /** Constructor for use by subclasses. */ + protected ForwardingDeque() {} + + @Override + protected abstract Deque delegate(); + + @Override + public void addFirst(E e) { + delegate().addFirst(e); + } + + @Override + public void addLast(E e) { + delegate().addLast(e); + } + + @Override + public Iterator descendingIterator() { + return delegate().descendingIterator(); + } + + @Override + public E getFirst() { + return delegate().getFirst(); + } + + @Override + public E getLast() { + return delegate().getLast(); + } + + // TODO(cpovirk): Consider removing this? + @Override + public boolean offerFirst(E e) { + return delegate().offerFirst(e); + } + + // TODO(cpovirk): Consider removing this? + @Override + public boolean offerLast(E e) { + return delegate().offerLast(e); + } + + @Override + public E peekFirst() { + return delegate().peekFirst(); + } + + @Override + public E peekLast() { + return delegate().peekLast(); + } + + // TODO(cpovirk): Consider removing this? + @Override + public E pollFirst() { + return delegate().pollFirst(); + } + + // TODO(cpovirk): Consider removing this? + @Override + public E pollLast() { + return delegate().pollLast(); + } + + + @Override + public E pop() { + return delegate().pop(); + } + + @Override + public void push(E e) { + delegate().push(e); + } + + + @Override + public E removeFirst() { + return delegate().removeFirst(); + } + + + @Override + public E removeLast() { + return delegate().removeLast(); + } + + + @Override + public boolean removeFirstOccurrence(Object o) { + return delegate().removeFirstOccurrence(o); + } + + + @Override + public boolean removeLastOccurrence(Object o) { + return delegate().removeLastOccurrence(o); + } +} diff --git a/src/main/java/com/google/common/collect/ForwardingImmutableCollection.java b/src/main/java/com/google/common/collect/ForwardingImmutableCollection.java new file mode 100644 index 0000000..c0b9c5e --- /dev/null +++ b/src/main/java/com/google/common/collect/ForwardingImmutableCollection.java @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2010 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import com.google.common.annotations.GwtCompatible; + +/** + * Dummy class that makes the GWT serialization policy happy. It isn't used on the server-side. + * + * @author Hayward Chan + */ +@GwtCompatible(emulated = true) +class ForwardingImmutableCollection { + private ForwardingImmutableCollection() {} +} diff --git a/src/main/java/com/google/common/collect/ForwardingImmutableList.java b/src/main/java/com/google/common/collect/ForwardingImmutableList.java new file mode 100644 index 0000000..2b9092e --- /dev/null +++ b/src/main/java/com/google/common/collect/ForwardingImmutableList.java @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2012 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import com.google.common.annotations.GwtCompatible; + +/** + * Unused stub class, unreferenced under Java and manually emulated under GWT. + * + * @author Chris Povirk + */ +@GwtCompatible(emulated = true) +abstract class ForwardingImmutableList { + private ForwardingImmutableList() {} +} diff --git a/src/main/java/com/google/common/collect/ForwardingImmutableMap.java b/src/main/java/com/google/common/collect/ForwardingImmutableMap.java new file mode 100644 index 0000000..a367157 --- /dev/null +++ b/src/main/java/com/google/common/collect/ForwardingImmutableMap.java @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2012 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import com.google.common.annotations.GwtCompatible; + +/** + * Unused stub class, unreferenced under Java and manually emulated under GWT. + * + * @author Chris Povirk + */ +@GwtCompatible(emulated = true) +abstract class ForwardingImmutableMap { + private ForwardingImmutableMap() {} +} diff --git a/src/main/java/com/google/common/collect/ForwardingImmutableSet.java b/src/main/java/com/google/common/collect/ForwardingImmutableSet.java new file mode 100644 index 0000000..c7d7bf6 --- /dev/null +++ b/src/main/java/com/google/common/collect/ForwardingImmutableSet.java @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2012 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import com.google.common.annotations.GwtCompatible; + +/** + * Unused stub class, unreferenced under Java and manually emulated under GWT. + * + * @author Chris Povirk + */ +@GwtCompatible(emulated = true) +abstract class ForwardingImmutableSet { + private ForwardingImmutableSet() {} +} diff --git a/src/main/java/com/google/common/collect/ForwardingIterator.java b/src/main/java/com/google/common/collect/ForwardingIterator.java new file mode 100644 index 0000000..7dc11f2 --- /dev/null +++ b/src/main/java/com/google/common/collect/ForwardingIterator.java @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import com.google.common.annotations.GwtCompatible; + +import java.util.Iterator; + +/** + * An iterator which forwards all its method calls to another iterator. Subclasses should override + * one or more methods to modify the behavior of the backing iterator as desired per the decorator pattern. + * + *

{@code default} method warning: This class forwards calls to only some {@code + * default} methods. Specifically, it forwards calls only for methods that existed before {@code default} + * methods were introduced. For newer methods, like {@code forEachRemaining}, it inherits their + * default implementations. When those implementations invoke methods, they invoke methods on the + * {@code ForwardingIterator}. + * + * @author Kevin Bourrillion + * @since 2.0 + */ +@GwtCompatible +public abstract class ForwardingIterator extends ForwardingObject implements Iterator { + + /** Constructor for use by subclasses. */ + protected ForwardingIterator() {} + + @Override + protected abstract Iterator delegate(); + + @Override + public boolean hasNext() { + return delegate().hasNext(); + } + + + @Override + public T next() { + return delegate().next(); + } + + @Override + public void remove() { + delegate().remove(); + } +} diff --git a/src/main/java/com/google/common/collect/ForwardingList.java b/src/main/java/com/google/common/collect/ForwardingList.java new file mode 100644 index 0000000..4a413ef --- /dev/null +++ b/src/main/java/com/google/common/collect/ForwardingList.java @@ -0,0 +1,240 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import com.google.common.annotations.Beta; +import com.google.common.annotations.GwtCompatible; + +import java.util.Collection; +import java.util.Iterator; +import java.util.List; +import java.util.ListIterator; + + +/** + * A list which forwards all its method calls to another list. Subclasses should override one or + * more methods to modify the behavior of the backing list as desired per the decorator pattern. + * + *

This class does not implement {@link java.util.RandomAccess}. If the delegate supports random + * access, the {@code ForwardingList} subclass should implement the {@code RandomAccess} interface. + * + *

Warning: The methods of {@code ForwardingList} forward indiscriminately to the + * methods of the delegate. For example, overriding {@link #add} alone will not change the + * behavior of {@link #addAll}, which can lead to unexpected behavior. In this case, you should + * override {@code addAll} as well, either providing your own implementation, or delegating to the + * provided {@code standardAddAll} method. + * + *

{@code default} method warning: This class does not forward calls to {@code + * default} methods. Instead, it inherits their default implementations. When those implementations + * invoke methods, they invoke methods on the {@code ForwardingList}. + * + *

The {@code standard} methods and any collection views they return are not guaranteed to be + * thread-safe, even when all of the methods that they depend on are thread-safe. + * + * @author Mike Bostock + * @author Louis Wasserman + * @since 2.0 + */ +@GwtCompatible +public abstract class ForwardingList extends ForwardingCollection implements List { + // TODO(lowasser): identify places where thread safety is actually lost + + /** Constructor for use by subclasses. */ + protected ForwardingList() {} + + @Override + protected abstract List delegate(); + + @Override + public void add(int index, E element) { + delegate().add(index, element); + } + + + @Override + public boolean addAll(int index, Collection elements) { + return delegate().addAll(index, elements); + } + + @Override + public E get(int index) { + return delegate().get(index); + } + + @Override + public int indexOf(Object element) { + return delegate().indexOf(element); + } + + @Override + public int lastIndexOf(Object element) { + return delegate().lastIndexOf(element); + } + + @Override + public ListIterator listIterator() { + return delegate().listIterator(); + } + + @Override + public ListIterator listIterator(int index) { + return delegate().listIterator(index); + } + + + @Override + public E remove(int index) { + return delegate().remove(index); + } + + + @Override + public E set(int index, E element) { + return delegate().set(index, element); + } + + @Override + public List subList(int fromIndex, int toIndex) { + return delegate().subList(fromIndex, toIndex); + } + + @Override + public boolean equals(Object object) { + return object == this || delegate().equals(object); + } + + @Override + public int hashCode() { + return delegate().hashCode(); + } + + /** + * A sensible default implementation of {@link #add(Object)}, in terms of {@link #add(int, + * Object)}. If you override {@link #add(int, Object)}, you may wish to override {@link + * #add(Object)} to forward to this implementation. + * + * @since 7.0 + */ + protected boolean standardAdd(E element) { + add(size(), element); + return true; + } + + /** + * A sensible default implementation of {@link #addAll(int, Collection)}, in terms of the {@code + * add} method of {@link #listIterator(int)}. If you override {@link #listIterator(int)}, you may + * wish to override {@link #addAll(int, Collection)} to forward to this implementation. + * + * @since 7.0 + */ + protected boolean standardAddAll(int index, Iterable elements) { + return Lists.addAllImpl(this, index, elements); + } + + /** + * A sensible default implementation of {@link #indexOf}, in terms of {@link #listIterator()}. If + * you override {@link #listIterator()}, you may wish to override {@link #indexOf} to forward to + * this implementation. + * + * @since 7.0 + */ + protected int standardIndexOf(Object element) { + return Lists.indexOfImpl(this, element); + } + + /** + * A sensible default implementation of {@link #lastIndexOf}, in terms of {@link + * #listIterator(int)}. If you override {@link #listIterator(int)}, you may wish to override + * {@link #lastIndexOf} to forward to this implementation. + * + * @since 7.0 + */ + protected int standardLastIndexOf(Object element) { + return Lists.lastIndexOfImpl(this, element); + } + + /** + * A sensible default implementation of {@link #iterator}, in terms of {@link #listIterator()}. If + * you override {@link #listIterator()}, you may wish to override {@link #iterator} to forward to + * this implementation. + * + * @since 7.0 + */ + protected Iterator standardIterator() { + return listIterator(); + } + + /** + * A sensible default implementation of {@link #listIterator()}, in terms of {@link + * #listIterator(int)}. If you override {@link #listIterator(int)}, you may wish to override + * {@link #listIterator()} to forward to this implementation. + * + * @since 7.0 + */ + protected ListIterator standardListIterator() { + return listIterator(0); + } + + /** + * A sensible default implementation of {@link #listIterator(int)}, in terms of {@link #size}, + * {@link #get(int)}, {@link #set(int, Object)}, {@link #add(int, Object)}, and {@link + * #remove(int)}. If you override any of these methods, you may wish to override {@link + * #listIterator(int)} to forward to this implementation. + * + * @since 7.0 + */ + @Beta + protected ListIterator standardListIterator(int start) { + return Lists.listIteratorImpl(this, start); + } + + /** + * A sensible default implementation of {@link #subList(int, int)}. If you override any other + * methods, you may wish to override {@link #subList(int, int)} to forward to this implementation. + * + * @since 7.0 + */ + @Beta + protected List standardSubList(int fromIndex, int toIndex) { + return Lists.subListImpl(this, fromIndex, toIndex); + } + + /** + * A sensible definition of {@link #equals(Object)} in terms of {@link #size} and {@link + * #iterator}. If you override either of those methods, you may wish to override {@link + * #equals(Object)} to forward to this implementation. + * + * @since 7.0 + */ + @Beta + protected boolean standardEquals(Object object) { + return Lists.equalsImpl(this, object); + } + + /** + * A sensible definition of {@link #hashCode} in terms of {@link #iterator}. If you override + * {@link #iterator}, you may wish to override {@link #hashCode} to forward to this + * implementation. + * + * @since 7.0 + */ + @Beta + protected int standardHashCode() { + return Lists.hashCodeImpl(this); + } +} diff --git a/src/main/java/com/google/common/collect/ForwardingListIterator.java b/src/main/java/com/google/common/collect/ForwardingListIterator.java new file mode 100644 index 0000000..1b89ba1 --- /dev/null +++ b/src/main/java/com/google/common/collect/ForwardingListIterator.java @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import com.google.common.annotations.GwtCompatible; + +import java.util.ListIterator; + +/** + * A list iterator which forwards all its method calls to another list iterator. Subclasses should + * override one or more methods to modify the behavior of the backing iterator as desired per the decorator pattern. + * + *

{@code default} method warning: This class forwards calls to only some {@code + * default} methods. Specifically, it forwards calls only for methods that existed before {@code + * default} methods were introduced. For newer methods, like {@code forEachRemaining}, it + * inherits their default implementations. When those implementations invoke methods, they invoke + * methods on the {@code ForwardingListIterator}. + * + * @author Mike Bostock + * @since 2.0 + */ +@GwtCompatible +public abstract class ForwardingListIterator extends ForwardingIterator + implements ListIterator { + + /** Constructor for use by subclasses. */ + protected ForwardingListIterator() {} + + @Override + protected abstract ListIterator delegate(); + + @Override + public void add(E element) { + delegate().add(element); + } + + @Override + public boolean hasPrevious() { + return delegate().hasPrevious(); + } + + @Override + public int nextIndex() { + return delegate().nextIndex(); + } + + + @Override + public E previous() { + return delegate().previous(); + } + + @Override + public int previousIndex() { + return delegate().previousIndex(); + } + + @Override + public void set(E element) { + delegate().set(element); + } +} diff --git a/src/main/java/com/google/common/collect/ForwardingListMultimap.java b/src/main/java/com/google/common/collect/ForwardingListMultimap.java new file mode 100644 index 0000000..f764234 --- /dev/null +++ b/src/main/java/com/google/common/collect/ForwardingListMultimap.java @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2010 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import com.google.common.annotations.GwtCompatible; + +import java.util.List; + + +/** + * A list multimap which forwards all its method calls to another list multimap. Subclasses should + * override one or more methods to modify the behavior of the backing multimap as desired per the decorator pattern. + * + *

{@code default} method warning: This class does not forward calls to {@code + * default} methods. Instead, it inherits their default implementations. When those implementations + * invoke methods, they invoke methods on the {@code ForwardingListMultimap}. + * + * @author Kurt Alfred Kluever + * @since 3.0 + */ +@GwtCompatible +public abstract class ForwardingListMultimap extends ForwardingMultimap + implements ListMultimap { + + /** Constructor for use by subclasses. */ + protected ForwardingListMultimap() {} + + @Override + protected abstract ListMultimap delegate(); + + @Override + public List get(K key) { + return delegate().get(key); + } + + + @Override + public List removeAll(Object key) { + return delegate().removeAll(key); + } + + + @Override + public List replaceValues(K key, Iterable values) { + return delegate().replaceValues(key, values); + } +} diff --git a/src/main/java/com/google/common/collect/ForwardingMap.java b/src/main/java/com/google/common/collect/ForwardingMap.java new file mode 100644 index 0000000..bdb9f0f --- /dev/null +++ b/src/main/java/com/google/common/collect/ForwardingMap.java @@ -0,0 +1,305 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import com.google.common.annotations.Beta; +import com.google.common.annotations.GwtCompatible; +import com.google.common.base.Objects; + +import java.util.Collection; +import java.util.Iterator; +import java.util.Map; +import java.util.Set; + + +/** + * A map which forwards all its method calls to another map. Subclasses should override one or more + * methods to modify the behavior of the backing map as desired per the decorator pattern. + * + *

Warning: The methods of {@code ForwardingMap} forward indiscriminately to the + * methods of the delegate. For example, overriding {@link #put} alone will not change the + * behavior of {@link #putAll}, which can lead to unexpected behavior. In this case, you should + * override {@code putAll} as well, either providing your own implementation, or delegating to the + * provided {@code standardPutAll} method. + * + *

{@code default} method warning: This class does not forward calls to {@code + * default} methods. Instead, it inherits their default implementations. When those implementations + * invoke methods, they invoke methods on the {@code ForwardingMap}. + * + *

Each of the {@code standard} methods, where appropriate, use {@link Objects#equal} to test + * equality for both keys and values. This may not be the desired behavior for map implementations + * that use non-standard notions of key equality, such as a {@code SortedMap} whose comparator is + * not consistent with {@code equals}. + * + *

The {@code standard} methods and the collection views they return are not guaranteed to be + * thread-safe, even when all of the methods that they depend on are thread-safe. + * + * @author Kevin Bourrillion + * @author Jared Levy + * @author Louis Wasserman + * @since 2.0 + */ +@GwtCompatible +public abstract class ForwardingMap extends ForwardingObject implements Map { + // TODO(lowasser): identify places where thread safety is actually lost + + /** Constructor for use by subclasses. */ + protected ForwardingMap() {} + + @Override + protected abstract Map delegate(); + + @Override + public int size() { + return delegate().size(); + } + + @Override + public boolean isEmpty() { + return delegate().isEmpty(); + } + + + @Override + public V remove(Object object) { + return delegate().remove(object); + } + + @Override + public void clear() { + delegate().clear(); + } + + @Override + public boolean containsKey(Object key) { + return delegate().containsKey(key); + } + + @Override + public boolean containsValue(Object value) { + return delegate().containsValue(value); + } + + @Override + public V get(Object key) { + return delegate().get(key); + } + + + @Override + public V put(K key, V value) { + return delegate().put(key, value); + } + + @Override + public void putAll(Map map) { + delegate().putAll(map); + } + + @Override + public Set keySet() { + return delegate().keySet(); + } + + @Override + public Collection values() { + return delegate().values(); + } + + @Override + public Set> entrySet() { + return delegate().entrySet(); + } + + @Override + public boolean equals(Object object) { + return object == this || delegate().equals(object); + } + + @Override + public int hashCode() { + return delegate().hashCode(); + } + + /** + * A sensible definition of {@link #putAll(Map)} in terms of {@link #put(Object, Object)}. If you + * override {@link #put(Object, Object)}, you may wish to override {@link #putAll(Map)} to forward + * to this implementation. + * + * @since 7.0 + */ + protected void standardPutAll(Map map) { + Maps.putAllImpl(this, map); + } + + /** + * A sensible, albeit inefficient, definition of {@link #remove} in terms of the {@code iterator} + * method of {@link #entrySet}. If you override {@link #entrySet}, you may wish to override {@link + * #remove} to forward to this implementation. + * + *

Alternately, you may wish to override {@link #remove} with {@code keySet().remove}, assuming + * that approach would not lead to an infinite loop. + * + * @since 7.0 + */ + @Beta + protected V standardRemove(Object key) { + Iterator> entryIterator = entrySet().iterator(); + while (entryIterator.hasNext()) { + Entry entry = entryIterator.next(); + if (Objects.equal(entry.getKey(), key)) { + V value = entry.getValue(); + entryIterator.remove(); + return value; + } + } + return null; + } + + /** + * A sensible definition of {@link #clear} in terms of the {@code iterator} method of {@link + * #entrySet}. In many cases, you may wish to override {@link #clear} to forward to this + * implementation. + * + * @since 7.0 + */ + protected void standardClear() { + Iterators.clear(entrySet().iterator()); + } + + /** + * A sensible implementation of {@link Map#keySet} in terms of the following methods: {@link + * ForwardingMap#clear}, {@link ForwardingMap#containsKey}, {@link ForwardingMap#isEmpty}, {@link + * ForwardingMap#remove}, {@link ForwardingMap#size}, and the {@link Set#iterator} method of + * {@link ForwardingMap#entrySet}. In many cases, you may wish to override {@link + * ForwardingMap#keySet} to forward to this implementation or a subclass thereof. + * + * @since 10.0 + */ + @Beta + protected class StandardKeySet extends Maps.KeySet { + /** Constructor for use by subclasses. */ + public StandardKeySet() { + super(ForwardingMap.this); + } + } + + /** + * A sensible, albeit inefficient, definition of {@link #containsKey} in terms of the {@code + * iterator} method of {@link #entrySet}. If you override {@link #entrySet}, you may wish to + * override {@link #containsKey} to forward to this implementation. + * + * @since 7.0 + */ + @Beta + protected boolean standardContainsKey(Object key) { + return Maps.containsKeyImpl(this, key); + } + + /** + * A sensible implementation of {@link Map#values} in terms of the following methods: {@link + * ForwardingMap#clear}, {@link ForwardingMap#containsValue}, {@link ForwardingMap#isEmpty}, + * {@link ForwardingMap#size}, and the {@link Set#iterator} method of {@link + * ForwardingMap#entrySet}. In many cases, you may wish to override {@link ForwardingMap#values} + * to forward to this implementation or a subclass thereof. + * + * @since 10.0 + */ + @Beta + protected class StandardValues extends Maps.Values { + /** Constructor for use by subclasses. */ + public StandardValues() { + super(ForwardingMap.this); + } + } + + /** + * A sensible definition of {@link #containsValue} in terms of the {@code iterator} method of + * {@link #entrySet}. If you override {@link #entrySet}, you may wish to override {@link + * #containsValue} to forward to this implementation. + * + * @since 7.0 + */ + protected boolean standardContainsValue(Object value) { + return Maps.containsValueImpl(this, value); + } + + /** + * A sensible implementation of {@link Map#entrySet} in terms of the following methods: {@link + * ForwardingMap#clear}, {@link ForwardingMap#containsKey}, {@link ForwardingMap#get}, {@link + * ForwardingMap#isEmpty}, {@link ForwardingMap#remove}, and {@link ForwardingMap#size}. In many + * cases, you may wish to override {@link #entrySet} to forward to this implementation or a + * subclass thereof. + * + * @since 10.0 + */ + @Beta + protected abstract class StandardEntrySet extends Maps.EntrySet { + /** Constructor for use by subclasses. */ + public StandardEntrySet() {} + + @Override + Map map() { + return ForwardingMap.this; + } + } + + /** + * A sensible definition of {@link #isEmpty} in terms of the {@code iterator} method of {@link + * #entrySet}. If you override {@link #entrySet}, you may wish to override {@link #isEmpty} to + * forward to this implementation. + * + * @since 7.0 + */ + protected boolean standardIsEmpty() { + return !entrySet().iterator().hasNext(); + } + + /** + * A sensible definition of {@link #equals} in terms of the {@code equals} method of {@link + * #entrySet}. If you override {@link #entrySet}, you may wish to override {@link #equals} to + * forward to this implementation. + * + * @since 7.0 + */ + protected boolean standardEquals(Object object) { + return Maps.equalsImpl(this, object); + } + + /** + * A sensible definition of {@link #hashCode} in terms of the {@code iterator} method of {@link + * #entrySet}. If you override {@link #entrySet}, you may wish to override {@link #hashCode} to + * forward to this implementation. + * + * @since 7.0 + */ + protected int standardHashCode() { + return Sets.hashCodeImpl(entrySet()); + } + + /** + * A sensible definition of {@link #toString} in terms of the {@code iterator} method of {@link + * #entrySet}. If you override {@link #entrySet}, you may wish to override {@link #toString} to + * forward to this implementation. + * + * @since 7.0 + */ + protected String standardToString() { + return Maps.toStringImpl(this); + } +} diff --git a/src/main/java/com/google/common/collect/ForwardingMapEntry.java b/src/main/java/com/google/common/collect/ForwardingMapEntry.java new file mode 100644 index 0000000..152457a --- /dev/null +++ b/src/main/java/com/google/common/collect/ForwardingMapEntry.java @@ -0,0 +1,124 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import com.google.common.annotations.Beta; +import com.google.common.annotations.GwtCompatible; +import com.google.common.base.Objects; +import java.util.Map; +import java.util.Map.Entry; + + +/** + * A map entry which forwards all its method calls to another map entry. Subclasses should override + * one or more methods to modify the behavior of the backing map entry as desired per the decorator pattern. + * + *

Warning: The methods of {@code ForwardingMapEntry} forward indiscriminately to + * the methods of the delegate. For example, overriding {@link #getValue} alone will not + * change the behavior of {@link #equals}, which can lead to unexpected behavior. In this case, you + * should override {@code equals} as well, either providing your own implementation, or delegating + * to the provided {@code standardEquals} method. + * + *

Each of the {@code standard} methods, where appropriate, use {@link Objects#equal} to test + * equality for both keys and values. This may not be the desired behavior for map implementations + * that use non-standard notions of key equality, such as the entry of a {@code SortedMap} whose + * comparator is not consistent with {@code equals}. + * + *

The {@code standard} methods are not guaranteed to be thread-safe, even when all of the + * methods that they depend on are thread-safe. + * + * @author Mike Bostock + * @author Louis Wasserman + * @since 2.0 + */ +@GwtCompatible +public abstract class ForwardingMapEntry extends ForwardingObject implements Map.Entry { + // TODO(lowasser): identify places where thread safety is actually lost + + /** Constructor for use by subclasses. */ + protected ForwardingMapEntry() {} + + @Override + protected abstract Entry delegate(); + + @Override + public K getKey() { + return delegate().getKey(); + } + + @Override + public V getValue() { + return delegate().getValue(); + } + + @Override + public V setValue(V value) { + return delegate().setValue(value); + } + + @Override + public boolean equals(Object object) { + return delegate().equals(object); + } + + @Override + public int hashCode() { + return delegate().hashCode(); + } + + /** + * A sensible definition of {@link #equals(Object)} in terms of {@link #getKey()} and {@link + * #getValue()}. If you override either of these methods, you may wish to override {@link + * #equals(Object)} to forward to this implementation. + * + * @since 7.0 + */ + protected boolean standardEquals(Object object) { + if (object instanceof Entry) { + Entry that = (Entry) object; + return Objects.equal(this.getKey(), that.getKey()) + && Objects.equal(this.getValue(), that.getValue()); + } + return false; + } + + /** + * A sensible definition of {@link #hashCode()} in terms of {@link #getKey()} and {@link + * #getValue()}. If you override either of these methods, you may wish to override {@link + * #hashCode()} to forward to this implementation. + * + * @since 7.0 + */ + protected int standardHashCode() { + K k = getKey(); + V v = getValue(); + return ((k == null) ? 0 : k.hashCode()) ^ ((v == null) ? 0 : v.hashCode()); + } + + /** + * A sensible definition of {@link #toString} in terms of {@link #getKey} and {@link #getValue}. + * If you override either of these methods, you may wish to override {@link #equals} to forward to + * this implementation. + * + * @since 7.0 + */ + @Beta + protected String standardToString() { + return getKey() + "=" + getValue(); + } +} diff --git a/src/main/java/com/google/common/collect/ForwardingMultimap.java b/src/main/java/com/google/common/collect/ForwardingMultimap.java new file mode 100644 index 0000000..7d559a8 --- /dev/null +++ b/src/main/java/com/google/common/collect/ForwardingMultimap.java @@ -0,0 +1,153 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import com.google.common.annotations.GwtCompatible; + +import java.util.Collection; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; + + +/** + * A multimap which forwards all its method calls to another multimap. Subclasses should override + * one or more methods to modify the behavior of the backing multimap as desired per the decorator pattern. + * + *

{@code default} method warning: This class does not forward calls to {@code + * default} methods. Instead, it inherits their default implementations. When those implementations + * invoke methods, they invoke methods on the {@code ForwardingMultimap}. + * + * @author Robert Konigsberg + * @since 2.0 + */ +@GwtCompatible +public abstract class ForwardingMultimap extends ForwardingObject implements Multimap { + + /** Constructor for use by subclasses. */ + protected ForwardingMultimap() {} + + @Override + protected abstract Multimap delegate(); + + @Override + public Map> asMap() { + return delegate().asMap(); + } + + @Override + public void clear() { + delegate().clear(); + } + + @Override + public boolean containsEntry(Object key, Object value) { + return delegate().containsEntry(key, value); + } + + @Override + public boolean containsKey(Object key) { + return delegate().containsKey(key); + } + + @Override + public boolean containsValue(Object value) { + return delegate().containsValue(value); + } + + @Override + public Collection> entries() { + return delegate().entries(); + } + + @Override + public Collection get(K key) { + return delegate().get(key); + } + + @Override + public boolean isEmpty() { + return delegate().isEmpty(); + } + + @Override + public Multiset keys() { + return delegate().keys(); + } + + @Override + public Set keySet() { + return delegate().keySet(); + } + + + @Override + public boolean put(K key, V value) { + return delegate().put(key, value); + } + + + @Override + public boolean putAll(K key, Iterable values) { + return delegate().putAll(key, values); + } + + + @Override + public boolean putAll(Multimap multimap) { + return delegate().putAll(multimap); + } + + + @Override + public boolean remove(Object key, Object value) { + return delegate().remove(key, value); + } + + + @Override + public Collection removeAll(Object key) { + return delegate().removeAll(key); + } + + + @Override + public Collection replaceValues(K key, Iterable values) { + return delegate().replaceValues(key, values); + } + + @Override + public int size() { + return delegate().size(); + } + + @Override + public Collection values() { + return delegate().values(); + } + + @Override + public boolean equals(Object object) { + return object == this || delegate().equals(object); + } + + @Override + public int hashCode() { + return delegate().hashCode(); + } +} diff --git a/src/main/java/com/google/common/collect/ForwardingMultiset.java b/src/main/java/com/google/common/collect/ForwardingMultiset.java new file mode 100644 index 0000000..a22cc22 --- /dev/null +++ b/src/main/java/com/google/common/collect/ForwardingMultiset.java @@ -0,0 +1,314 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import com.google.common.annotations.Beta; +import com.google.common.annotations.GwtCompatible; +import com.google.common.base.Objects; + +import java.util.Collection; +import java.util.Iterator; +import java.util.Set; + + +/** + * A multiset which forwards all its method calls to another multiset. Subclasses should override + * one or more methods to modify the behavior of the backing multiset as desired per the decorator pattern. + * + *

Warning: The methods of {@code ForwardingMultiset} forward indiscriminately to + * the methods of the delegate. For example, overriding {@link #add(Object, int)} alone will + * not change the behavior of {@link #add(Object)}, which can lead to unexpected behavior. In + * this case, you should override {@code add(Object)} as well, either providing your own + * implementation, or delegating to the provided {@code standardAdd} method. + * + *

{@code default} method warning: This class does not forward calls to {@code + * default} methods. Instead, it inherits their default implementations. When those implementations + * invoke methods, they invoke methods on the {@code ForwardingMultiset}. + * + *

The {@code standard} methods and any collection views they return are not guaranteed to be + * thread-safe, even when all of the methods that they depend on are thread-safe. + * + * @author Kevin Bourrillion + * @author Louis Wasserman + * @since 2.0 + */ +@GwtCompatible +public abstract class ForwardingMultiset extends ForwardingCollection implements Multiset { + + /** Constructor for use by subclasses. */ + protected ForwardingMultiset() {} + + @Override + protected abstract Multiset delegate(); + + @Override + public int count(Object element) { + return delegate().count(element); + } + + + @Override + public int add(E element, int occurrences) { + return delegate().add(element, occurrences); + } + + + @Override + public int remove(Object element, int occurrences) { + return delegate().remove(element, occurrences); + } + + @Override + public Set elementSet() { + return delegate().elementSet(); + } + + @Override + public Set> entrySet() { + return delegate().entrySet(); + } + + @Override + public boolean equals(Object object) { + return object == this || delegate().equals(object); + } + + @Override + public int hashCode() { + return delegate().hashCode(); + } + + + @Override + public int setCount(E element, int count) { + return delegate().setCount(element, count); + } + + + @Override + public boolean setCount(E element, int oldCount, int newCount) { + return delegate().setCount(element, oldCount, newCount); + } + + /** + * A sensible definition of {@link #contains} in terms of {@link #count}. If you override {@link + * #count}, you may wish to override {@link #contains} to forward to this implementation. + * + * @since 7.0 + */ + @Override + protected boolean standardContains(Object object) { + return count(object) > 0; + } + + /** + * A sensible definition of {@link #clear} in terms of the {@code iterator} method of {@link + * #entrySet}. If you override {@link #entrySet}, you may wish to override {@link #clear} to + * forward to this implementation. + * + * @since 7.0 + */ + @Override + protected void standardClear() { + Iterators.clear(entrySet().iterator()); + } + + /** + * A sensible, albeit inefficient, definition of {@link #count} in terms of {@link #entrySet}. If + * you override {@link #entrySet}, you may wish to override {@link #count} to forward to this + * implementation. + * + * @since 7.0 + */ + @Beta + protected int standardCount(Object object) { + for (Entry entry : this.entrySet()) { + if (Objects.equal(entry.getElement(), object)) { + return entry.getCount(); + } + } + return 0; + } + + /** + * A sensible definition of {@link #add(Object)} in terms of {@link #add(Object, int)}. If you + * override {@link #add(Object, int)}, you may wish to override {@link #add(Object)} to forward to + * this implementation. + * + * @since 7.0 + */ + protected boolean standardAdd(E element) { + add(element, 1); + return true; + } + + /** + * A sensible definition of {@link #addAll(Collection)} in terms of {@link #add(Object)} and + * {@link #add(Object, int)}. If you override either of these methods, you may wish to override + * {@link #addAll(Collection)} to forward to this implementation. + * + * @since 7.0 + */ + @Beta + @Override + protected boolean standardAddAll(Collection elementsToAdd) { + return Multisets.addAllImpl(this, elementsToAdd); + } + + /** + * A sensible definition of {@link #remove(Object)} in terms of {@link #remove(Object, int)}. If + * you override {@link #remove(Object, int)}, you may wish to override {@link #remove(Object)} to + * forward to this implementation. + * + * @since 7.0 + */ + @Override + protected boolean standardRemove(Object element) { + return remove(element, 1) > 0; + } + + /** + * A sensible definition of {@link #removeAll} in terms of the {@code removeAll} method of {@link + * #elementSet}. If you override {@link #elementSet}, you may wish to override {@link #removeAll} + * to forward to this implementation. + * + * @since 7.0 + */ + @Override + protected boolean standardRemoveAll(Collection elementsToRemove) { + return Multisets.removeAllImpl(this, elementsToRemove); + } + + /** + * A sensible definition of {@link #retainAll} in terms of the {@code retainAll} method of {@link + * #elementSet}. If you override {@link #elementSet}, you may wish to override {@link #retainAll} + * to forward to this implementation. + * + * @since 7.0 + */ + @Override + protected boolean standardRetainAll(Collection elementsToRetain) { + return Multisets.retainAllImpl(this, elementsToRetain); + } + + /** + * A sensible definition of {@link #setCount(Object, int)} in terms of {@link #count(Object)}, + * {@link #add(Object, int)}, and {@link #remove(Object, int)}. {@link #entrySet()}. If you + * override any of these methods, you may wish to override {@link #setCount(Object, int)} to + * forward to this implementation. + * + * @since 7.0 + */ + protected int standardSetCount(E element, int count) { + return Multisets.setCountImpl(this, element, count); + } + + /** + * A sensible definition of {@link #setCount(Object, int, int)} in terms of {@link #count(Object)} + * and {@link #setCount(Object, int)}. If you override either of these methods, you may wish to + * override {@link #setCount(Object, int, int)} to forward to this implementation. + * + * @since 7.0 + */ + protected boolean standardSetCount(E element, int oldCount, int newCount) { + return Multisets.setCountImpl(this, element, oldCount, newCount); + } + + /** + * A sensible implementation of {@link Multiset#elementSet} in terms of the following methods: + * {@link ForwardingMultiset#clear}, {@link ForwardingMultiset#contains}, {@link + * ForwardingMultiset#containsAll}, {@link ForwardingMultiset#count}, {@link + * ForwardingMultiset#isEmpty}, the {@link Set#size} and {@link Set#iterator} methods of {@link + * ForwardingMultiset#entrySet}, and {@link ForwardingMultiset#remove(Object, int)}. In many + * situations, you may wish to override {@link ForwardingMultiset#elementSet} to forward to this + * implementation or a subclass thereof. + * + * @since 10.0 + */ + @Beta + protected class StandardElementSet extends Multisets.ElementSet { + /** Constructor for use by subclasses. */ + public StandardElementSet() {} + + @Override + Multiset multiset() { + return ForwardingMultiset.this; + } + + @Override + public Iterator iterator() { + return Multisets.elementIterator(multiset().entrySet().iterator()); + } + } + + /** + * A sensible definition of {@link #iterator} in terms of {@link #entrySet} and {@link + * #remove(Object)}. If you override either of these methods, you may wish to override {@link + * #iterator} to forward to this implementation. + * + * @since 7.0 + */ + protected Iterator standardIterator() { + return Multisets.iteratorImpl(this); + } + + /** + * A sensible, albeit inefficient, definition of {@link #size} in terms of {@link #entrySet}. If + * you override {@link #entrySet}, you may wish to override {@link #size} to forward to this + * implementation. + * + * @since 7.0 + */ + protected int standardSize() { + return Multisets.linearTimeSizeImpl(this); + } + + /** + * A sensible, albeit inefficient, definition of {@link #equals} in terms of {@code + * entrySet().size()} and {@link #count}. If you override either of these methods, you may wish to + * override {@link #equals} to forward to this implementation. + * + * @since 7.0 + */ + protected boolean standardEquals(Object object) { + return Multisets.equalsImpl(this, object); + } + + /** + * A sensible definition of {@link #hashCode} as {@code entrySet().hashCode()} . If you override + * {@link #entrySet}, you may wish to override {@link #hashCode} to forward to this + * implementation. + * + * @since 7.0 + */ + protected int standardHashCode() { + return entrySet().hashCode(); + } + + /** + * A sensible definition of {@link #toString} as {@code entrySet().toString()} . If you override + * {@link #entrySet}, you may wish to override {@link #toString} to forward to this + * implementation. + * + * @since 7.0 + */ + @Override + protected String standardToString() { + return entrySet().toString(); + } +} diff --git a/src/main/java/com/google/common/collect/ForwardingNavigableMap.java b/src/main/java/com/google/common/collect/ForwardingNavigableMap.java new file mode 100644 index 0000000..5f23d05 --- /dev/null +++ b/src/main/java/com/google/common/collect/ForwardingNavigableMap.java @@ -0,0 +1,407 @@ +/* + * Copyright (C) 2012 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import static com.google.common.collect.CollectPreconditions.checkRemove; +import static com.google.common.collect.Maps.keyOrNull; + +import com.google.common.annotations.Beta; +import com.google.common.annotations.GwtIncompatible; +import java.util.Iterator; +import java.util.NavigableMap; +import java.util.NavigableSet; +import java.util.NoSuchElementException; +import java.util.SortedMap; +import java.util.function.BiFunction; + +/** + * A navigable map which forwards all its method calls to another navigable map. Subclasses should + * override one or more methods to modify the behavior of the backing map as desired per the decorator pattern. + * + *

Warning: The methods of {@code ForwardingNavigableMap} forward indiscriminately + * to the methods of the delegate. For example, overriding {@link #put} alone will not change + * the behavior of {@link #putAll}, which can lead to unexpected behavior. In this case, you should + * override {@code putAll} as well, either providing your own implementation, or delegating to the + * provided {@code standardPutAll} method. + * + *

{@code default} method warning: This class does not forward calls to {@code + * default} methods. Instead, it inherits their default implementations. When those implementations + * invoke methods, they invoke methods on the {@code ForwardingNavigableMap}. + * + *

Each of the {@code standard} methods uses the map's comparator (or the natural ordering of the + * elements, if there is no comparator) to test element equality. As a result, if the comparator is + * not consistent with equals, some of the standard implementations may violate the {@code Map} + * contract. + * + *

The {@code standard} methods and the collection views they return are not guaranteed to be + * thread-safe, even when all of the methods that they depend on are thread-safe. + * + * @author Louis Wasserman + * @since 12.0 + */ +@GwtIncompatible +public abstract class ForwardingNavigableMap extends ForwardingSortedMap + implements NavigableMap { + + /** Constructor for use by subclasses. */ + protected ForwardingNavigableMap() {} + + @Override + protected abstract NavigableMap delegate(); + + @Override + public Entry lowerEntry(K key) { + return delegate().lowerEntry(key); + } + + /** + * A sensible definition of {@link #lowerEntry} in terms of the {@code lastEntry()} of {@link + * #headMap(Object, boolean)}. If you override {@code headMap}, you may wish to override {@code + * lowerEntry} to forward to this implementation. + */ + protected Entry standardLowerEntry(K key) { + return headMap(key, false).lastEntry(); + } + + @Override + public K lowerKey(K key) { + return delegate().lowerKey(key); + } + + /** + * A sensible definition of {@link #lowerKey} in terms of {@code lowerEntry}. If you override + * {@link #lowerEntry}, you may wish to override {@code lowerKey} to forward to this + * implementation. + */ + protected K standardLowerKey(K key) { + return keyOrNull(lowerEntry(key)); + } + + @Override + public Entry floorEntry(K key) { + return delegate().floorEntry(key); + } + + /** + * A sensible definition of {@link #floorEntry} in terms of the {@code lastEntry()} of {@link + * #headMap(Object, boolean)}. If you override {@code headMap}, you may wish to override {@code + * floorEntry} to forward to this implementation. + */ + protected Entry standardFloorEntry(K key) { + return headMap(key, true).lastEntry(); + } + + @Override + public K floorKey(K key) { + return delegate().floorKey(key); + } + + /** + * A sensible definition of {@link #floorKey} in terms of {@code floorEntry}. If you override + * {@code floorEntry}, you may wish to override {@code floorKey} to forward to this + * implementation. + */ + protected K standardFloorKey(K key) { + return keyOrNull(floorEntry(key)); + } + + @Override + public Entry ceilingEntry(K key) { + return delegate().ceilingEntry(key); + } + + /** + * A sensible definition of {@link #ceilingEntry} in terms of the {@code firstEntry()} of {@link + * #tailMap(Object, boolean)}. If you override {@code tailMap}, you may wish to override {@code + * ceilingEntry} to forward to this implementation. + */ + protected Entry standardCeilingEntry(K key) { + return tailMap(key, true).firstEntry(); + } + + @Override + public K ceilingKey(K key) { + return delegate().ceilingKey(key); + } + + /** + * A sensible definition of {@link #ceilingKey} in terms of {@code ceilingEntry}. If you override + * {@code ceilingEntry}, you may wish to override {@code ceilingKey} to forward to this + * implementation. + */ + protected K standardCeilingKey(K key) { + return keyOrNull(ceilingEntry(key)); + } + + @Override + public Entry higherEntry(K key) { + return delegate().higherEntry(key); + } + + /** + * A sensible definition of {@link #higherEntry} in terms of the {@code firstEntry()} of {@link + * #tailMap(Object, boolean)}. If you override {@code tailMap}, you may wish to override {@code + * higherEntry} to forward to this implementation. + */ + protected Entry standardHigherEntry(K key) { + return tailMap(key, false).firstEntry(); + } + + @Override + public K higherKey(K key) { + return delegate().higherKey(key); + } + + /** + * A sensible definition of {@link #higherKey} in terms of {@code higherEntry}. If you override + * {@code higherEntry}, you may wish to override {@code higherKey} to forward to this + * implementation. + */ + protected K standardHigherKey(K key) { + return keyOrNull(higherEntry(key)); + } + + @Override + public Entry firstEntry() { + return delegate().firstEntry(); + } + + /** + * A sensible definition of {@link #firstEntry} in terms of the {@code iterator()} of {@link + * #entrySet}. If you override {@code entrySet}, you may wish to override {@code firstEntry} to + * forward to this implementation. + */ + protected Entry standardFirstEntry() { + return Iterables.getFirst(entrySet(), null); + } + + /** + * A sensible definition of {@link #firstKey} in terms of {@code firstEntry}. If you override + * {@code firstEntry}, you may wish to override {@code firstKey} to forward to this + * implementation. + */ + protected K standardFirstKey() { + Entry entry = firstEntry(); + if (entry == null) { + throw new NoSuchElementException(); + } else { + return entry.getKey(); + } + } + + @Override + public Entry lastEntry() { + return delegate().lastEntry(); + } + + /** + * A sensible definition of {@link #lastEntry} in terms of the {@code iterator()} of the {@link + * #entrySet} of {@link #descendingMap}. If you override {@code descendingMap}, you may wish to + * override {@code lastEntry} to forward to this implementation. + */ + protected Entry standardLastEntry() { + return Iterables.getFirst(descendingMap().entrySet(), null); + } + + /** + * A sensible definition of {@link #lastKey} in terms of {@code lastEntry}. If you override {@code + * lastEntry}, you may wish to override {@code lastKey} to forward to this implementation. + */ + protected K standardLastKey() { + Entry entry = lastEntry(); + if (entry == null) { + throw new NoSuchElementException(); + } else { + return entry.getKey(); + } + } + + @Override + public Entry pollFirstEntry() { + return delegate().pollFirstEntry(); + } + + /** + * A sensible definition of {@link #pollFirstEntry} in terms of the {@code iterator} of {@code + * entrySet}. If you override {@code entrySet}, you may wish to override {@code pollFirstEntry} to + * forward to this implementation. + */ + protected Entry standardPollFirstEntry() { + return Iterators.pollNext(entrySet().iterator()); + } + + @Override + public Entry pollLastEntry() { + return delegate().pollLastEntry(); + } + + /** + * A sensible definition of {@link #pollFirstEntry} in terms of the {@code iterator} of the {@code + * entrySet} of {@code descendingMap}. If you override {@code descendingMap}, you may wish to + * override {@code pollFirstEntry} to forward to this implementation. + */ + protected Entry standardPollLastEntry() { + return Iterators.pollNext(descendingMap().entrySet().iterator()); + } + + @Override + public NavigableMap descendingMap() { + return delegate().descendingMap(); + } + + /** + * A sensible implementation of {@link NavigableMap#descendingMap} in terms of the methods of this + * {@code NavigableMap}. In many cases, you may wish to override {@link + * ForwardingNavigableMap#descendingMap} to forward to this implementation or a subclass thereof. + * + *

In particular, this map iterates over entries with repeated calls to {@link + * NavigableMap#lowerEntry}. If a more efficient means of iteration is available, you may wish to + * override the {@code entryIterator()} method of this class. + * + * @since 12.0 + */ + @Beta + protected class StandardDescendingMap extends Maps.DescendingMap { + /** Constructor for use by subclasses. */ + public StandardDescendingMap() {} + + @Override + NavigableMap forward() { + return ForwardingNavigableMap.this; + } + + @Override + public void replaceAll(BiFunction function) { + forward().replaceAll(function); + } + + @Override + protected Iterator> entryIterator() { + return new Iterator>() { + private Entry toRemove = null; + private Entry nextOrNull = forward().lastEntry(); + + @Override + public boolean hasNext() { + return nextOrNull != null; + } + + @Override + public java.util.Map.Entry next() { + if (!hasNext()) { + throw new NoSuchElementException(); + } + try { + return nextOrNull; + } finally { + toRemove = nextOrNull; + nextOrNull = forward().lowerEntry(nextOrNull.getKey()); + } + } + + @Override + public void remove() { + checkRemove(toRemove != null); + forward().remove(toRemove.getKey()); + toRemove = null; + } + }; + } + } + + @Override + public NavigableSet navigableKeySet() { + return delegate().navigableKeySet(); + } + + /** + * A sensible implementation of {@link NavigableMap#navigableKeySet} in terms of the methods of + * this {@code NavigableMap}. In many cases, you may wish to override {@link + * ForwardingNavigableMap#navigableKeySet} to forward to this implementation or a subclass + * thereof. + * + * @since 12.0 + */ + @Beta + protected class StandardNavigableKeySet extends Maps.NavigableKeySet { + /** Constructor for use by subclasses. */ + public StandardNavigableKeySet() { + super(ForwardingNavigableMap.this); + } + } + + @Override + public NavigableSet descendingKeySet() { + return delegate().descendingKeySet(); + } + + /** + * A sensible definition of {@link #descendingKeySet} as the {@code navigableKeySet} of {@link + * #descendingMap}. (The {@link StandardDescendingMap} implementation implements {@code + * navigableKeySet} on its own, so as not to cause an infinite loop.) If you override {@code + * descendingMap}, you may wish to override {@code descendingKeySet} to forward to this + * implementation. + */ + @Beta + protected NavigableSet standardDescendingKeySet() { + return descendingMap().navigableKeySet(); + } + + /** + * A sensible definition of {@link #subMap(Object, Object)} in terms of {@link #subMap(Object, + * boolean, Object, boolean)}. If you override {@code subMap(K, boolean, K, boolean)}, you may + * wish to override {@code subMap} to forward to this implementation. + */ + @Override + protected SortedMap standardSubMap(K fromKey, K toKey) { + return subMap(fromKey, true, toKey, false); + } + + @Override + public NavigableMap subMap(K fromKey, boolean fromInclusive, K toKey, boolean toInclusive) { + return delegate().subMap(fromKey, fromInclusive, toKey, toInclusive); + } + + @Override + public NavigableMap headMap(K toKey, boolean inclusive) { + return delegate().headMap(toKey, inclusive); + } + + @Override + public NavigableMap tailMap(K fromKey, boolean inclusive) { + return delegate().tailMap(fromKey, inclusive); + } + + /** + * A sensible definition of {@link #headMap(Object)} in terms of {@link #headMap(Object, + * boolean)}. If you override {@code headMap(K, boolean)}, you may wish to override {@code + * headMap} to forward to this implementation. + */ + protected SortedMap standardHeadMap(K toKey) { + return headMap(toKey, false); + } + + /** + * A sensible definition of {@link #tailMap(Object)} in terms of {@link #tailMap(Object, + * boolean)}. If you override {@code tailMap(K, boolean)}, you may wish to override {@code + * tailMap} to forward to this implementation. + */ + protected SortedMap standardTailMap(K fromKey) { + return tailMap(fromKey, true); + } +} diff --git a/src/main/java/com/google/common/collect/ForwardingNavigableSet.java b/src/main/java/com/google/common/collect/ForwardingNavigableSet.java new file mode 100644 index 0000000..827698e --- /dev/null +++ b/src/main/java/com/google/common/collect/ForwardingNavigableSet.java @@ -0,0 +1,235 @@ +/* + * Copyright (C) 2012 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import com.google.common.annotations.Beta; +import com.google.common.annotations.GwtIncompatible; +import java.util.Iterator; +import java.util.NavigableSet; +import java.util.SortedSet; + +/** + * A navigable set which forwards all its method calls to another navigable set. Subclasses should + * override one or more methods to modify the behavior of the backing set as desired per the decorator pattern. + * + *

Warning: The methods of {@code ForwardingNavigableSet} forward indiscriminately + * to the methods of the delegate. For example, overriding {@link #add} alone will not change + * the behavior of {@link #addAll}, which can lead to unexpected behavior. In this case, you should + * override {@code addAll} as well, either providing your own implementation, or delegating to the + * provided {@code standardAddAll} method. + * + *

{@code default} method warning: This class does not forward calls to {@code + * default} methods. Instead, it inherits their default implementations. When those implementations + * invoke methods, they invoke methods on the {@code ForwardingNavigableSet}. + * + *

Each of the {@code standard} methods uses the set's comparator (or the natural ordering of the + * elements, if there is no comparator) to test element equality. As a result, if the comparator is + * not consistent with equals, some of the standard implementations may violate the {@code Set} + * contract. + * + *

The {@code standard} methods and the collection views they return are not guaranteed to be + * thread-safe, even when all of the methods that they depend on are thread-safe. + * + * @author Louis Wasserman + * @since 12.0 + */ +@GwtIncompatible +public abstract class ForwardingNavigableSet extends ForwardingSortedSet + implements NavigableSet { + + /** Constructor for use by subclasses. */ + protected ForwardingNavigableSet() {} + + @Override + protected abstract NavigableSet delegate(); + + @Override + public E lower(E e) { + return delegate().lower(e); + } + + /** + * A sensible definition of {@link #lower} in terms of the {@code descendingIterator} method of + * {@link #headSet(Object, boolean)}. If you override {@link #headSet(Object, boolean)}, you may + * wish to override {@link #lower} to forward to this implementation. + */ + protected E standardLower(E e) { + return Iterators.getNext(headSet(e, false).descendingIterator(), null); + } + + @Override + public E floor(E e) { + return delegate().floor(e); + } + + /** + * A sensible definition of {@link #floor} in terms of the {@code descendingIterator} method of + * {@link #headSet(Object, boolean)}. If you override {@link #headSet(Object, boolean)}, you may + * wish to override {@link #floor} to forward to this implementation. + */ + protected E standardFloor(E e) { + return Iterators.getNext(headSet(e, true).descendingIterator(), null); + } + + @Override + public E ceiling(E e) { + return delegate().ceiling(e); + } + + /** + * A sensible definition of {@link #ceiling} in terms of the {@code iterator} method of {@link + * #tailSet(Object, boolean)}. If you override {@link #tailSet(Object, boolean)}, you may wish to + * override {@link #ceiling} to forward to this implementation. + */ + protected E standardCeiling(E e) { + return Iterators.getNext(tailSet(e, true).iterator(), null); + } + + @Override + public E higher(E e) { + return delegate().higher(e); + } + + /** + * A sensible definition of {@link #higher} in terms of the {@code iterator} method of {@link + * #tailSet(Object, boolean)}. If you override {@link #tailSet(Object, boolean)}, you may wish to + * override {@link #higher} to forward to this implementation. + */ + protected E standardHigher(E e) { + return Iterators.getNext(tailSet(e, false).iterator(), null); + } + + @Override + public E pollFirst() { + return delegate().pollFirst(); + } + + /** + * A sensible definition of {@link #pollFirst} in terms of the {@code iterator} method. If you + * override {@link #iterator} you may wish to override {@link #pollFirst} to forward to this + * implementation. + */ + protected E standardPollFirst() { + return Iterators.pollNext(iterator()); + } + + @Override + public E pollLast() { + return delegate().pollLast(); + } + + /** + * A sensible definition of {@link #pollLast} in terms of the {@code descendingIterator} method. + * If you override {@link #descendingIterator} you may wish to override {@link #pollLast} to + * forward to this implementation. + */ + protected E standardPollLast() { + return Iterators.pollNext(descendingIterator()); + } + + protected E standardFirst() { + return iterator().next(); + } + + protected E standardLast() { + return descendingIterator().next(); + } + + @Override + public NavigableSet descendingSet() { + return delegate().descendingSet(); + } + + /** + * A sensible implementation of {@link NavigableSet#descendingSet} in terms of the other methods + * of {@link NavigableSet}, notably including {@link NavigableSet#descendingIterator}. + * + *

In many cases, you may wish to override {@link ForwardingNavigableSet#descendingSet} to + * forward to this implementation or a subclass thereof. + * + * @since 12.0 + */ + @Beta + protected class StandardDescendingSet extends Sets.DescendingSet { + /** Constructor for use by subclasses. */ + public StandardDescendingSet() { + super(ForwardingNavigableSet.this); + } + } + + @Override + public Iterator descendingIterator() { + return delegate().descendingIterator(); + } + + @Override + public NavigableSet subSet( + E fromElement, boolean fromInclusive, E toElement, boolean toInclusive) { + return delegate().subSet(fromElement, fromInclusive, toElement, toInclusive); + } + + /** + * A sensible definition of {@link #subSet(Object, boolean, Object, boolean)} in terms of the + * {@code headSet} and {@code tailSet} methods. In many cases, you may wish to override {@link + * #subSet(Object, boolean, Object, boolean)} to forward to this implementation. + */ + @Beta + protected NavigableSet standardSubSet( + E fromElement, boolean fromInclusive, E toElement, boolean toInclusive) { + return tailSet(fromElement, fromInclusive).headSet(toElement, toInclusive); + } + + /** + * A sensible definition of {@link #subSet(Object, Object)} in terms of the {@link #subSet(Object, + * boolean, Object, boolean)} method. If you override {@link #subSet(Object, boolean, Object, + * boolean)}, you may wish to override {@link #subSet(Object, Object)} to forward to this + * implementation. + */ + @Override + protected SortedSet standardSubSet(E fromElement, E toElement) { + return subSet(fromElement, true, toElement, false); + } + + @Override + public NavigableSet headSet(E toElement, boolean inclusive) { + return delegate().headSet(toElement, inclusive); + } + + /** + * A sensible definition of {@link #headSet(Object)} in terms of the {@link #headSet(Object, + * boolean)} method. If you override {@link #headSet(Object, boolean)}, you may wish to override + * {@link #headSet(Object)} to forward to this implementation. + */ + protected SortedSet standardHeadSet(E toElement) { + return headSet(toElement, false); + } + + @Override + public NavigableSet tailSet(E fromElement, boolean inclusive) { + return delegate().tailSet(fromElement, inclusive); + } + + /** + * A sensible definition of {@link #tailSet(Object)} in terms of the {@link #tailSet(Object, + * boolean)} method. If you override {@link #tailSet(Object, boolean)}, you may wish to override + * {@link #tailSet(Object)} to forward to this implementation. + */ + protected SortedSet standardTailSet(E fromElement) { + return tailSet(fromElement, true); + } +} diff --git a/src/main/java/com/google/common/collect/ForwardingObject.java b/src/main/java/com/google/common/collect/ForwardingObject.java new file mode 100644 index 0000000..712b14f --- /dev/null +++ b/src/main/java/com/google/common/collect/ForwardingObject.java @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import com.google.common.annotations.GwtCompatible; +import java.io.Serializable; + +/** + * An abstract base class for implementing the decorator pattern. The {@link + * #delegate()} method must be overridden to return the instance being decorated. + * + *

This class does not forward the {@code hashCode} and {@code equals} methods through to + * the backing object, but relies on {@code Object}'s implementation. This is necessary to preserve + * the symmetry of {@code equals}. Custom definitions of equality are usually based on an interface, + * such as {@code Set} or {@code List}, so that the implementation of {@code equals} can cast the + * object being tested for equality to the custom interface. {@code ForwardingObject} implements no + * such custom interfaces directly; they are implemented only in subclasses. Therefore, forwarding + * {@code equals} would break symmetry, as the forwarding object might consider itself equal to the + * object being tested, but the reverse could not be true. This behavior is consistent with the + * JDK's collection wrappers, such as {@link java.util.Collections#unmodifiableCollection}. Use an + * interface-specific subclass of {@code ForwardingObject}, such as {@link ForwardingList}, to + * preserve equality behavior, or override {@code equals} directly. + * + *

The {@code toString} method is forwarded to the delegate. Although this class does not + * implement {@link Serializable}, a serializable subclass may be created since this class has a + * parameter-less constructor. + * + * @author Mike Bostock + * @since 2.0 + */ +@GwtCompatible +public abstract class ForwardingObject { + + /** Constructor for use by subclasses. */ + protected ForwardingObject() {} + + /** + * Returns the backing delegate instance that methods are forwarded to. Abstract subclasses + * generally override this method with an abstract method that has a more specific return type, + * such as {@link ForwardingSet#delegate}. Concrete subclasses override this method to supply the + * instance being decorated. + */ + protected abstract Object delegate(); + + /** Returns the string representation generated by the delegate's {@code toString} method. */ + @Override + public String toString() { + return delegate().toString(); + } + + /* No equals or hashCode. See class comments for details. */ +} diff --git a/src/main/java/com/google/common/collect/ForwardingQueue.java b/src/main/java/com/google/common/collect/ForwardingQueue.java new file mode 100644 index 0000000..fe3f73a --- /dev/null +++ b/src/main/java/com/google/common/collect/ForwardingQueue.java @@ -0,0 +1,124 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import com.google.common.annotations.GwtCompatible; + +import java.util.NoSuchElementException; +import java.util.Queue; + +/** + * A queue which forwards all its method calls to another queue. Subclasses should override one or + * more methods to modify the behavior of the backing queue as desired per the decorator pattern. + * + *

Warning: The methods of {@code ForwardingQueue} forward indiscriminately to the + * methods of the delegate. For example, overriding {@link #add} alone will not change the + * behavior of {@link #offer} which can lead to unexpected behavior. In this case, you should + * override {@code offer} as well, either providing your own implementation, or delegating to the + * provided {@code standardOffer} method. + * + *

{@code default} method warning: This class does not forward calls to {@code + * default} methods. Instead, it inherits their default implementations. When those implementations + * invoke methods, they invoke methods on the {@code ForwardingQueue}. + * + *

The {@code standard} methods are not guaranteed to be thread-safe, even when all of the + * methods that they depend on are thread-safe. + * + * @author Mike Bostock + * @author Louis Wasserman + * @since 2.0 + */ +@GwtCompatible +public abstract class ForwardingQueue extends ForwardingCollection implements Queue { + + /** Constructor for use by subclasses. */ + protected ForwardingQueue() {} + + @Override + protected abstract Queue delegate(); + + // TODO(cpovirk): Consider removing this? + @Override + public boolean offer(E o) { + return delegate().offer(o); + } + + // TODO(cpovirk): Consider removing this? + @Override + public E poll() { + return delegate().poll(); + } + + + @Override + public E remove() { + return delegate().remove(); + } + + @Override + public E peek() { + return delegate().peek(); + } + + @Override + public E element() { + return delegate().element(); + } + + /** + * A sensible definition of {@link #offer} in terms of {@link #add}. If you override {@link #add}, + * you may wish to override {@link #offer} to forward to this implementation. + * + * @since 7.0 + */ + protected boolean standardOffer(E e) { + try { + return add(e); + } catch (IllegalStateException caught) { + return false; + } + } + + /** + * A sensible definition of {@link #peek} in terms of {@link #element}. If you override {@link + * #element}, you may wish to override {@link #peek} to forward to this implementation. + * + * @since 7.0 + */ + protected E standardPeek() { + try { + return element(); + } catch (NoSuchElementException caught) { + return null; + } + } + + /** + * A sensible definition of {@link #poll} in terms of {@link #remove}. If you override {@link + * #remove}, you may wish to override {@link #poll} to forward to this implementation. + * + * @since 7.0 + */ + protected E standardPoll() { + try { + return remove(); + } catch (NoSuchElementException caught) { + return null; + } + } +} diff --git a/src/main/java/com/google/common/collect/ForwardingSet.java b/src/main/java/com/google/common/collect/ForwardingSet.java new file mode 100644 index 0000000..1112d13 --- /dev/null +++ b/src/main/java/com/google/common/collect/ForwardingSet.java @@ -0,0 +1,100 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.annotations.GwtCompatible; +import java.util.Collection; +import java.util.Set; + + +/** + * A set which forwards all its method calls to another set. Subclasses should override one or more + * methods to modify the behavior of the backing set as desired per the decorator pattern. + * + *

Warning: The methods of {@code ForwardingSet} forward indiscriminately to the + * methods of the delegate. For example, overriding {@link #add} alone will not change the + * behavior of {@link #addAll}, which can lead to unexpected behavior. In this case, you should + * override {@code addAll} as well, either providing your own implementation, or delegating to the + * provided {@code standardAddAll} method. + * + *

{@code default} method warning: This class does not forward calls to {@code + * default} methods. Instead, it inherits their default implementations. When those implementations + * invoke methods, they invoke methods on the {@code ForwardingSet}. + * + *

The {@code standard} methods are not guaranteed to be thread-safe, even when all of the + * methods that they depend on are thread-safe. + * + * @author Kevin Bourrillion + * @author Louis Wasserman + * @since 2.0 + */ +@GwtCompatible +public abstract class ForwardingSet extends ForwardingCollection implements Set { + // TODO(lowasser): identify places where thread safety is actually lost + + /** Constructor for use by subclasses. */ + protected ForwardingSet() {} + + @Override + protected abstract Set delegate(); + + @Override + public boolean equals(Object object) { + return object == this || delegate().equals(object); + } + + @Override + public int hashCode() { + return delegate().hashCode(); + } + + /** + * A sensible definition of {@link #removeAll} in terms of {@link #iterator} and {@link #remove}. + * If you override {@code iterator} or {@code remove}, you may wish to override {@link #removeAll} + * to forward to this implementation. + * + * @since 7.0 (this version overrides the {@code ForwardingCollection} version as of 12.0) + */ + @Override + protected boolean standardRemoveAll(Collection collection) { + return Sets.removeAllImpl(this, checkNotNull(collection)); // for GWT + } + + /** + * A sensible definition of {@link #equals} in terms of {@link #size} and {@link #containsAll}. If + * you override either of those methods, you may wish to override {@link #equals} to forward to + * this implementation. + * + * @since 7.0 + */ + protected boolean standardEquals(Object object) { + return Sets.equalsImpl(this, object); + } + + /** + * A sensible definition of {@link #hashCode} in terms of {@link #iterator}. If you override + * {@link #iterator}, you may wish to override {@link #equals} to forward to this implementation. + * + * @since 7.0 + */ + protected int standardHashCode() { + return Sets.hashCodeImpl(this); + } +} diff --git a/src/main/java/com/google/common/collect/ForwardingSetMultimap.java b/src/main/java/com/google/common/collect/ForwardingSetMultimap.java new file mode 100644 index 0000000..1462238 --- /dev/null +++ b/src/main/java/com/google/common/collect/ForwardingSetMultimap.java @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2010 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import com.google.common.annotations.GwtCompatible; + +import java.util.Map.Entry; +import java.util.Set; + + +/** + * A set multimap which forwards all its method calls to another set multimap. Subclasses should + * override one or more methods to modify the behavior of the backing multimap as desired per the decorator pattern. + * + *

{@code default} method warning: This class does not forward calls to {@code + * default} methods. Instead, it inherits their default implementations. When those implementations + * invoke methods, they invoke methods on the {@code ForwardingSetMultimap}. + * + * @author Kurt Alfred Kluever + * @since 3.0 + */ +@GwtCompatible +public abstract class ForwardingSetMultimap extends ForwardingMultimap + implements SetMultimap { + + @Override + protected abstract SetMultimap delegate(); + + @Override + public Set> entries() { + return delegate().entries(); + } + + @Override + public Set get(K key) { + return delegate().get(key); + } + + + @Override + public Set removeAll(Object key) { + return delegate().removeAll(key); + } + + + @Override + public Set replaceValues(K key, Iterable values) { + return delegate().replaceValues(key, values); + } +} diff --git a/src/main/java/com/google/common/collect/ForwardingSortedMap.java b/src/main/java/com/google/common/collect/ForwardingSortedMap.java new file mode 100644 index 0000000..fc29d7a --- /dev/null +++ b/src/main/java/com/google/common/collect/ForwardingSortedMap.java @@ -0,0 +1,153 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import static com.google.common.base.Preconditions.checkArgument; + +import com.google.common.annotations.Beta; +import com.google.common.annotations.GwtCompatible; +import java.util.Comparator; +import java.util.NoSuchElementException; +import java.util.SortedMap; + + +/** + * A sorted map which forwards all its method calls to another sorted map. Subclasses should + * override one or more methods to modify the behavior of the backing sorted map as desired per the + * decorator pattern. + * + *

Warning: The methods of {@code ForwardingSortedMap} forward indiscriminately to + * the methods of the delegate. For example, overriding {@link #put} alone will not change + * the behavior of {@link #putAll}, which can lead to unexpected behavior. In this case, you should + * override {@code putAll} as well, either providing your own implementation, or delegating to the + * provided {@code standardPutAll} method. + * + *

{@code default} method warning: This class does not forward calls to {@code + * default} methods. Instead, it inherits their default implementations. When those implementations + * invoke methods, they invoke methods on the {@code ForwardingSortedMap}. + * + *

Each of the {@code standard} methods, where appropriate, use the comparator of the map to test + * equality for both keys and values, unlike {@code ForwardingMap}. + * + *

The {@code standard} methods and the collection views they return are not guaranteed to be + * thread-safe, even when all of the methods that they depend on are thread-safe. + * + * @author Mike Bostock + * @author Louis Wasserman + * @since 2.0 + */ +@GwtCompatible +public abstract class ForwardingSortedMap extends ForwardingMap + implements SortedMap { + // TODO(lowasser): identify places where thread safety is actually lost + + /** Constructor for use by subclasses. */ + protected ForwardingSortedMap() {} + + @Override + protected abstract SortedMap delegate(); + + @Override + public Comparator comparator() { + return delegate().comparator(); + } + + @Override + public K firstKey() { + return delegate().firstKey(); + } + + @Override + public SortedMap headMap(K toKey) { + return delegate().headMap(toKey); + } + + @Override + public K lastKey() { + return delegate().lastKey(); + } + + @Override + public SortedMap subMap(K fromKey, K toKey) { + return delegate().subMap(fromKey, toKey); + } + + @Override + public SortedMap tailMap(K fromKey) { + return delegate().tailMap(fromKey); + } + + /** + * A sensible implementation of {@link SortedMap#keySet} in terms of the methods of {@code + * ForwardingSortedMap}. In many cases, you may wish to override {@link + * ForwardingSortedMap#keySet} to forward to this implementation or a subclass thereof. + * + * @since 15.0 + */ + @Beta + protected class StandardKeySet extends Maps.SortedKeySet { + /** Constructor for use by subclasses. */ + public StandardKeySet() { + super(ForwardingSortedMap.this); + } + } + + // unsafe, but worst case is a CCE is thrown, which callers will be expecting + @SuppressWarnings("unchecked") + private int unsafeCompare(Object k1, Object k2) { + Comparator comparator = comparator(); + if (comparator == null) { + return ((Comparable) k1).compareTo(k2); + } else { + return ((Comparator) comparator).compare(k1, k2); + } + } + + /** + * A sensible definition of {@link #containsKey} in terms of the {@code firstKey()} method of + * {@link #tailMap}. If you override {@link #tailMap}, you may wish to override {@link + * #containsKey} to forward to this implementation. + * + * @since 7.0 + */ + @Override + @Beta + protected boolean standardContainsKey(Object key) { + try { + // any CCE will be caught + @SuppressWarnings("unchecked") + SortedMap self = (SortedMap) this; + Object ceilingKey = self.tailMap(key).firstKey(); + return unsafeCompare(ceilingKey, key) == 0; + } catch (ClassCastException | NoSuchElementException | NullPointerException e) { + return false; + } + } + + /** + * A sensible default implementation of {@link #subMap(Object, Object)} in terms of {@link + * #headMap(Object)} and {@link #tailMap(Object)}. In some situations, you may wish to override + * {@link #subMap(Object, Object)} to forward to this implementation. + * + * @since 7.0 + */ + @Beta + protected SortedMap standardSubMap(K fromKey, K toKey) { + checkArgument(unsafeCompare(fromKey, toKey) <= 0, "fromKey must be <= toKey"); + return tailMap(fromKey).headMap(toKey); + } +} diff --git a/src/main/java/com/google/common/collect/ForwardingSortedMultiset.java b/src/main/java/com/google/common/collect/ForwardingSortedMultiset.java new file mode 100644 index 0000000..1d34fb3 --- /dev/null +++ b/src/main/java/com/google/common/collect/ForwardingSortedMultiset.java @@ -0,0 +1,226 @@ +/* + * Copyright (C) 2011 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the + * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import com.google.common.annotations.Beta; +import com.google.common.annotations.GwtCompatible; +import java.util.Comparator; +import java.util.Iterator; +import java.util.NavigableSet; + +/** + * A sorted multiset which forwards all its method calls to another sorted multiset. Subclasses + * should override one or more methods to modify the behavior of the backing multiset as desired per + * the decorator pattern. + * + *

Warning: The methods of {@code ForwardingSortedMultiset} forward + * indiscriminately to the methods of the delegate. For example, overriding {@link + * #add(Object, int)} alone will not change the behavior of {@link #add(Object)}, which can + * lead to unexpected behavior. In this case, you should override {@code add(Object)} as well, + * either providing your own implementation, or delegating to the provided {@code standardAdd} + * method. + * + *

{@code default} method warning: This class does not forward calls to {@code + * default} methods. Instead, it inherits their default implementations. When those implementations + * invoke methods, they invoke methods on the {@code ForwardingSortedMultiset}. + * + *

The {@code standard} methods and any collection views they return are not guaranteed to be + * thread-safe, even when all of the methods that they depend on are thread-safe. + * + * @author Louis Wasserman + * @since 15.0 + */ +@Beta +@GwtCompatible(emulated = true) +public abstract class ForwardingSortedMultiset extends ForwardingMultiset + implements SortedMultiset { + /** Constructor for use by subclasses. */ + protected ForwardingSortedMultiset() {} + + @Override + protected abstract SortedMultiset delegate(); + + @Override + public NavigableSet elementSet() { + return delegate().elementSet(); + } + + /** + * A sensible implementation of {@link SortedMultiset#elementSet} in terms of the following + * methods: {@link SortedMultiset#clear}, {@link SortedMultiset#comparator}, {@link + * SortedMultiset#contains}, {@link SortedMultiset#containsAll}, {@link SortedMultiset#count}, + * {@link SortedMultiset#firstEntry} {@link SortedMultiset#headMultiset}, {@link + * SortedMultiset#isEmpty}, {@link SortedMultiset#lastEntry}, {@link SortedMultiset#subMultiset}, + * {@link SortedMultiset#tailMultiset}, the {@code size()} and {@code iterator()} methods of + * {@link SortedMultiset#entrySet}, and {@link SortedMultiset#remove(Object, int)}. In many + * situations, you may wish to override {@link SortedMultiset#elementSet} to forward to this + * implementation or a subclass thereof. + * + * @since 15.0 + */ + protected class StandardElementSet extends SortedMultisets.NavigableElementSet { + /** Constructor for use by subclasses. */ + public StandardElementSet() { + super(ForwardingSortedMultiset.this); + } + } + + @Override + public Comparator comparator() { + return delegate().comparator(); + } + + @Override + public SortedMultiset descendingMultiset() { + return delegate().descendingMultiset(); + } + + /** + * A skeleton implementation of a descending multiset view. Normally, {@link + * #descendingMultiset()} will not reflect any changes you make to the behavior of methods such as + * {@link #add(Object)} or {@link #pollFirstEntry}. This skeleton implementation correctly + * delegates each of its operations to the appropriate methods of this {@code + * ForwardingSortedMultiset}. + * + *

In many cases, you may wish to override {@link #descendingMultiset()} to return an instance + * of a subclass of {@code StandardDescendingMultiset}. + * + * @since 15.0 + */ + protected abstract class StandardDescendingMultiset extends DescendingMultiset { + /** Constructor for use by subclasses. */ + public StandardDescendingMultiset() {} + + @Override + SortedMultiset forwardMultiset() { + return ForwardingSortedMultiset.this; + } + } + + @Override + public Entry firstEntry() { + return delegate().firstEntry(); + } + + /** + * A sensible definition of {@link #firstEntry()} in terms of {@code entrySet().iterator()}. + * + *

If you override {@link #entrySet()}, you may wish to override {@link #firstEntry()} to + * forward to this implementation. + */ + protected Entry standardFirstEntry() { + Iterator> entryIterator = entrySet().iterator(); + if (!entryIterator.hasNext()) { + return null; + } + Entry entry = entryIterator.next(); + return Multisets.immutableEntry(entry.getElement(), entry.getCount()); + } + + @Override + public Entry lastEntry() { + return delegate().lastEntry(); + } + + /** + * A sensible definition of {@link #lastEntry()} in terms of {@code + * descendingMultiset().entrySet().iterator()}. + * + *

If you override {@link #descendingMultiset} or {@link #entrySet()}, you may wish to override + * {@link #firstEntry()} to forward to this implementation. + */ + protected Entry standardLastEntry() { + Iterator> entryIterator = descendingMultiset().entrySet().iterator(); + if (!entryIterator.hasNext()) { + return null; + } + Entry entry = entryIterator.next(); + return Multisets.immutableEntry(entry.getElement(), entry.getCount()); + } + + @Override + public Entry pollFirstEntry() { + return delegate().pollFirstEntry(); + } + + /** + * A sensible definition of {@link #pollFirstEntry()} in terms of {@code entrySet().iterator()}. + * + *

If you override {@link #entrySet()}, you may wish to override {@link #pollFirstEntry()} to + * forward to this implementation. + */ + protected Entry standardPollFirstEntry() { + Iterator> entryIterator = entrySet().iterator(); + if (!entryIterator.hasNext()) { + return null; + } + Entry entry = entryIterator.next(); + entry = Multisets.immutableEntry(entry.getElement(), entry.getCount()); + entryIterator.remove(); + return entry; + } + + @Override + public Entry pollLastEntry() { + return delegate().pollLastEntry(); + } + + /** + * A sensible definition of {@link #pollLastEntry()} in terms of {@code + * descendingMultiset().entrySet().iterator()}. + * + *

If you override {@link #descendingMultiset()} or {@link #entrySet()}, you may wish to + * override {@link #pollLastEntry()} to forward to this implementation. + */ + protected Entry standardPollLastEntry() { + Iterator> entryIterator = descendingMultiset().entrySet().iterator(); + if (!entryIterator.hasNext()) { + return null; + } + Entry entry = entryIterator.next(); + entry = Multisets.immutableEntry(entry.getElement(), entry.getCount()); + entryIterator.remove(); + return entry; + } + + @Override + public SortedMultiset headMultiset(E upperBound, BoundType boundType) { + return delegate().headMultiset(upperBound, boundType); + } + + @Override + public SortedMultiset subMultiset( + E lowerBound, BoundType lowerBoundType, E upperBound, BoundType upperBoundType) { + return delegate().subMultiset(lowerBound, lowerBoundType, upperBound, upperBoundType); + } + + /** + * A sensible definition of {@link #subMultiset(Object, BoundType, Object, BoundType)} in terms of + * {@link #headMultiset(Object, BoundType) headMultiset} and {@link #tailMultiset(Object, + * BoundType) tailMultiset}. + * + *

If you override either of these methods, you may wish to override {@link + * #subMultiset(Object, BoundType, Object, BoundType)} to forward to this implementation. + */ + protected SortedMultiset standardSubMultiset( + E lowerBound, BoundType lowerBoundType, E upperBound, BoundType upperBoundType) { + return tailMultiset(lowerBound, lowerBoundType).headMultiset(upperBound, upperBoundType); + } + + @Override + public SortedMultiset tailMultiset(E lowerBound, BoundType boundType) { + return delegate().tailMultiset(lowerBound, boundType); + } +} diff --git a/src/main/java/com/google/common/collect/ForwardingSortedSet.java b/src/main/java/com/google/common/collect/ForwardingSortedSet.java new file mode 100644 index 0000000..5bc13ba --- /dev/null +++ b/src/main/java/com/google/common/collect/ForwardingSortedSet.java @@ -0,0 +1,162 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import com.google.common.annotations.Beta; +import com.google.common.annotations.GwtCompatible; +import java.util.Comparator; +import java.util.Iterator; +import java.util.NoSuchElementException; +import java.util.SortedSet; + + +/** + * A sorted set which forwards all its method calls to another sorted set. Subclasses should + * override one or more methods to modify the behavior of the backing sorted set as desired per the + * decorator pattern. + * + *

Warning: The methods of {@code ForwardingSortedSet} forward indiscriminately to + * the methods of the delegate. For example, overriding {@link #add} alone will not change + * the behavior of {@link #addAll}, which can lead to unexpected behavior. In this case, you should + * override {@code addAll} as well, either providing your own implementation, or delegating to the + * provided {@code standardAddAll} method. + * + *

{@code default} method warning: This class does not forward calls to {@code + * default} methods. Instead, it inherits their default implementations. When those implementations + * invoke methods, they invoke methods on the {@code ForwardingSortedSet}. + * + *

Each of the {@code standard} methods, where appropriate, uses the set's comparator (or the + * natural ordering of the elements, if there is no comparator) to test element equality. As a + * result, if the comparator is not consistent with equals, some of the standard implementations may + * violate the {@code Set} contract. + * + *

The {@code standard} methods and the collection views they return are not guaranteed to be + * thread-safe, even when all of the methods that they depend on are thread-safe. + * + * @author Mike Bostock + * @author Louis Wasserman + * @since 2.0 + */ +@GwtCompatible +public abstract class ForwardingSortedSet extends ForwardingSet implements SortedSet { + + /** Constructor for use by subclasses. */ + protected ForwardingSortedSet() {} + + @Override + protected abstract SortedSet delegate(); + + @Override + public Comparator comparator() { + return delegate().comparator(); + } + + @Override + public E first() { + return delegate().first(); + } + + @Override + public SortedSet headSet(E toElement) { + return delegate().headSet(toElement); + } + + @Override + public E last() { + return delegate().last(); + } + + @Override + public SortedSet subSet(E fromElement, E toElement) { + return delegate().subSet(fromElement, toElement); + } + + @Override + public SortedSet tailSet(E fromElement) { + return delegate().tailSet(fromElement); + } + + // unsafe, but worst case is a CCE is thrown, which callers will be expecting + @SuppressWarnings("unchecked") + private int unsafeCompare(Object o1, Object o2) { + Comparator comparator = comparator(); + return (comparator == null) + ? ((Comparable) o1).compareTo(o2) + : ((Comparator) comparator).compare(o1, o2); + } + + /** + * A sensible definition of {@link #contains} in terms of the {@code first()} method of {@link + * #tailSet}. If you override {@link #tailSet}, you may wish to override {@link #contains} to + * forward to this implementation. + * + * @since 7.0 + */ + @Override + @Beta + protected boolean standardContains(Object object) { + try { + // any ClassCastExceptions are caught + @SuppressWarnings("unchecked") + SortedSet self = (SortedSet) this; + Object ceiling = self.tailSet(object).first(); + return unsafeCompare(ceiling, object) == 0; + } catch (ClassCastException | NoSuchElementException | NullPointerException e) { + return false; + } + } + + /** + * A sensible definition of {@link #remove} in terms of the {@code iterator()} method of {@link + * #tailSet}. If you override {@link #tailSet}, you may wish to override {@link #remove} to + * forward to this implementation. + * + * @since 7.0 + */ + @Override + @Beta + protected boolean standardRemove(Object object) { + try { + // any ClassCastExceptions are caught + @SuppressWarnings("unchecked") + SortedSet self = (SortedSet) this; + Iterator iterator = self.tailSet(object).iterator(); + if (iterator.hasNext()) { + Object ceiling = iterator.next(); + if (unsafeCompare(ceiling, object) == 0) { + iterator.remove(); + return true; + } + } + } catch (ClassCastException | NullPointerException e) { + return false; + } + return false; + } + + /** + * A sensible default implementation of {@link #subSet(Object, Object)} in terms of {@link + * #headSet(Object)} and {@link #tailSet(Object)}. In some situations, you may wish to override + * {@link #subSet(Object, Object)} to forward to this implementation. + * + * @since 7.0 + */ + @Beta + protected SortedSet standardSubSet(E fromElement, E toElement) { + return tailSet(fromElement).headSet(toElement); + } +} diff --git a/src/main/java/com/google/common/collect/ForwardingSortedSetMultimap.java b/src/main/java/com/google/common/collect/ForwardingSortedSetMultimap.java new file mode 100644 index 0000000..22eb1c0 --- /dev/null +++ b/src/main/java/com/google/common/collect/ForwardingSortedSetMultimap.java @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2010 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import com.google.common.annotations.GwtCompatible; +import java.util.Comparator; +import java.util.SortedSet; + + +/** + * A sorted set multimap which forwards all its method calls to another sorted set multimap. + * Subclasses should override one or more methods to modify the behavior of the backing multimap as + * desired per the decorator pattern. + * + *

{@code default} method warning: This class does not forward calls to {@code + * default} methods. Instead, it inherits their default implementations. When those implementations + * invoke methods, they invoke methods on the {@code ForwardingSortedSetMultimap}. + * + * @author Kurt Alfred Kluever + * @since 3.0 + */ +@GwtCompatible +public abstract class ForwardingSortedSetMultimap extends ForwardingSetMultimap + implements SortedSetMultimap { + + /** Constructor for use by subclasses. */ + protected ForwardingSortedSetMultimap() {} + + @Override + protected abstract SortedSetMultimap delegate(); + + @Override + public SortedSet get(K key) { + return delegate().get(key); + } + + @Override + public SortedSet removeAll(Object key) { + return delegate().removeAll(key); + } + + @Override + public SortedSet replaceValues(K key, Iterable values) { + return delegate().replaceValues(key, values); + } + + @Override + public Comparator valueComparator() { + return delegate().valueComparator(); + } +} diff --git a/src/main/java/com/google/common/collect/ForwardingTable.java b/src/main/java/com/google/common/collect/ForwardingTable.java new file mode 100644 index 0000000..f0225d3 --- /dev/null +++ b/src/main/java/com/google/common/collect/ForwardingTable.java @@ -0,0 +1,147 @@ +/* + * Copyright (C) 2009 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import com.google.common.annotations.GwtCompatible; + +import java.util.Collection; +import java.util.Map; +import java.util.Set; + +/** + * A table which forwards all its method calls to another table. Subclasses should override one or + * more methods to modify the behavior of the backing map as desired per the decorator pattern. + * + * @author Gregory Kick + * @since 7.0 + */ +@GwtCompatible +public abstract class ForwardingTable extends ForwardingObject implements Table { + /** Constructor for use by subclasses. */ + protected ForwardingTable() {} + + @Override + protected abstract Table delegate(); + + @Override + public Set> cellSet() { + return delegate().cellSet(); + } + + @Override + public void clear() { + delegate().clear(); + } + + @Override + public Map column(C columnKey) { + return delegate().column(columnKey); + } + + @Override + public Set columnKeySet() { + return delegate().columnKeySet(); + } + + @Override + public Map> columnMap() { + return delegate().columnMap(); + } + + @Override + public boolean contains(Object rowKey, Object columnKey) { + return delegate().contains(rowKey, columnKey); + } + + @Override + public boolean containsColumn(Object columnKey) { + return delegate().containsColumn(columnKey); + } + + @Override + public boolean containsRow(Object rowKey) { + return delegate().containsRow(rowKey); + } + + @Override + public boolean containsValue(Object value) { + return delegate().containsValue(value); + } + + @Override + public V get(Object rowKey, Object columnKey) { + return delegate().get(rowKey, columnKey); + } + + @Override + public boolean isEmpty() { + return delegate().isEmpty(); + } + + + @Override + public V put(R rowKey, C columnKey, V value) { + return delegate().put(rowKey, columnKey, value); + } + + @Override + public void putAll(Table table) { + delegate().putAll(table); + } + + + @Override + public V remove(Object rowKey, Object columnKey) { + return delegate().remove(rowKey, columnKey); + } + + @Override + public Map row(R rowKey) { + return delegate().row(rowKey); + } + + @Override + public Set rowKeySet() { + return delegate().rowKeySet(); + } + + @Override + public Map> rowMap() { + return delegate().rowMap(); + } + + @Override + public int size() { + return delegate().size(); + } + + @Override + public Collection values() { + return delegate().values(); + } + + @Override + public boolean equals(Object obj) { + return (obj == this) || delegate().equals(obj); + } + + @Override + public int hashCode() { + return delegate().hashCode(); + } +} diff --git a/src/main/java/com/google/common/collect/GeneralRange.java b/src/main/java/com/google/common/collect/GeneralRange.java new file mode 100644 index 0000000..eaddb98 --- /dev/null +++ b/src/main/java/com/google/common/collect/GeneralRange.java @@ -0,0 +1,295 @@ +/* + * Copyright (C) 2011 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the + * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.collect.BoundType.CLOSED; +import static com.google.common.collect.BoundType.OPEN; + +import com.google.common.annotations.GwtCompatible; +import com.google.common.base.Objects; +import java.io.Serializable; +import java.util.Comparator; + + + +/** + * A generalized interval on any ordering, for internal use. Supports {@code null}. Unlike {@link + * Range}, this allows the use of an arbitrary comparator. This is designed for use in the + * implementation of subcollections of sorted collection types. + * + *

Whenever possible, use {@code Range} instead, which is better supported. + * + * @author Louis Wasserman + */ +@GwtCompatible(serializable = true) +final class GeneralRange implements Serializable { + /** Converts a Range to a GeneralRange. */ + static GeneralRange from(Range range) { + T lowerEndpoint = range.hasLowerBound() ? range.lowerEndpoint() : null; + BoundType lowerBoundType = range.hasLowerBound() ? range.lowerBoundType() : OPEN; + + T upperEndpoint = range.hasUpperBound() ? range.upperEndpoint() : null; + BoundType upperBoundType = range.hasUpperBound() ? range.upperBoundType() : OPEN; + return new GeneralRange( + Ordering.natural(), + range.hasLowerBound(), + lowerEndpoint, + lowerBoundType, + range.hasUpperBound(), + upperEndpoint, + upperBoundType); + } + + /** Returns the whole range relative to the specified comparator. */ + static GeneralRange all(Comparator comparator) { + return new GeneralRange(comparator, false, null, OPEN, false, null, OPEN); + } + + /** + * Returns everything above the endpoint relative to the specified comparator, with the specified + * endpoint behavior. + */ + static GeneralRange downTo( + Comparator comparator, T endpoint, BoundType boundType) { + return new GeneralRange(comparator, true, endpoint, boundType, false, null, OPEN); + } + + /** + * Returns everything below the endpoint relative to the specified comparator, with the specified + * endpoint behavior. + */ + static GeneralRange upTo( + Comparator comparator, T endpoint, BoundType boundType) { + return new GeneralRange(comparator, false, null, OPEN, true, endpoint, boundType); + } + + /** + * Returns everything between the endpoints relative to the specified comparator, with the + * specified endpoint behavior. + */ + static GeneralRange range( + Comparator comparator, + T lower, + BoundType lowerType, + T upper, + BoundType upperType) { + return new GeneralRange(comparator, true, lower, lowerType, true, upper, upperType); + } + + private final Comparator comparator; + private final boolean hasLowerBound; + private final T lowerEndpoint; + private final BoundType lowerBoundType; + private final boolean hasUpperBound; + private final T upperEndpoint; + private final BoundType upperBoundType; + + private GeneralRange( + Comparator comparator, + boolean hasLowerBound, + T lowerEndpoint, + BoundType lowerBoundType, + boolean hasUpperBound, + T upperEndpoint, + BoundType upperBoundType) { + this.comparator = checkNotNull(comparator); + this.hasLowerBound = hasLowerBound; + this.hasUpperBound = hasUpperBound; + this.lowerEndpoint = lowerEndpoint; + this.lowerBoundType = checkNotNull(lowerBoundType); + this.upperEndpoint = upperEndpoint; + this.upperBoundType = checkNotNull(upperBoundType); + + if (hasLowerBound) { + comparator.compare(lowerEndpoint, lowerEndpoint); + } + if (hasUpperBound) { + comparator.compare(upperEndpoint, upperEndpoint); + } + if (hasLowerBound && hasUpperBound) { + int cmp = comparator.compare(lowerEndpoint, upperEndpoint); + // be consistent with Range + checkArgument( + cmp <= 0, "lowerEndpoint (%s) > upperEndpoint (%s)", lowerEndpoint, upperEndpoint); + if (cmp == 0) { + checkArgument(lowerBoundType != OPEN | upperBoundType != OPEN); + } + } + } + + Comparator comparator() { + return comparator; + } + + boolean hasLowerBound() { + return hasLowerBound; + } + + boolean hasUpperBound() { + return hasUpperBound; + } + + boolean isEmpty() { + return (hasUpperBound() && tooLow(getUpperEndpoint())) + || (hasLowerBound() && tooHigh(getLowerEndpoint())); + } + + boolean tooLow(T t) { + if (!hasLowerBound()) { + return false; + } + T lbound = getLowerEndpoint(); + int cmp = comparator.compare(t, lbound); + return cmp < 0 | (cmp == 0 & getLowerBoundType() == OPEN); + } + + boolean tooHigh(T t) { + if (!hasUpperBound()) { + return false; + } + T ubound = getUpperEndpoint(); + int cmp = comparator.compare(t, ubound); + return cmp > 0 | (cmp == 0 & getUpperBoundType() == OPEN); + } + + boolean contains(T t) { + return !tooLow(t) && !tooHigh(t); + } + + /** + * Returns the intersection of the two ranges, or an empty range if their intersection is empty. + */ + GeneralRange intersect(GeneralRange other) { + checkNotNull(other); + checkArgument(comparator.equals(other.comparator)); + + boolean hasLowBound = this.hasLowerBound; + T lowEnd = getLowerEndpoint(); + BoundType lowType = getLowerBoundType(); + if (!hasLowerBound()) { + hasLowBound = other.hasLowerBound; + lowEnd = other.getLowerEndpoint(); + lowType = other.getLowerBoundType(); + } else if (other.hasLowerBound()) { + int cmp = comparator.compare(getLowerEndpoint(), other.getLowerEndpoint()); + if (cmp < 0 || (cmp == 0 && other.getLowerBoundType() == OPEN)) { + lowEnd = other.getLowerEndpoint(); + lowType = other.getLowerBoundType(); + } + } + + boolean hasUpBound = this.hasUpperBound; + T upEnd = getUpperEndpoint(); + BoundType upType = getUpperBoundType(); + if (!hasUpperBound()) { + hasUpBound = other.hasUpperBound; + upEnd = other.getUpperEndpoint(); + upType = other.getUpperBoundType(); + } else if (other.hasUpperBound()) { + int cmp = comparator.compare(getUpperEndpoint(), other.getUpperEndpoint()); + if (cmp > 0 || (cmp == 0 && other.getUpperBoundType() == OPEN)) { + upEnd = other.getUpperEndpoint(); + upType = other.getUpperBoundType(); + } + } + + if (hasLowBound && hasUpBound) { + int cmp = comparator.compare(lowEnd, upEnd); + if (cmp > 0 || (cmp == 0 && lowType == OPEN && upType == OPEN)) { + // force allowed empty range + lowEnd = upEnd; + lowType = OPEN; + upType = CLOSED; + } + } + + return new GeneralRange(comparator, hasLowBound, lowEnd, lowType, hasUpBound, upEnd, upType); + } + + @Override + public boolean equals(Object obj) { + if (obj instanceof GeneralRange) { + GeneralRange r = (GeneralRange) obj; + return comparator.equals(r.comparator) + && hasLowerBound == r.hasLowerBound + && hasUpperBound == r.hasUpperBound + && getLowerBoundType().equals(r.getLowerBoundType()) + && getUpperBoundType().equals(r.getUpperBoundType()) + && Objects.equal(getLowerEndpoint(), r.getLowerEndpoint()) + && Objects.equal(getUpperEndpoint(), r.getUpperEndpoint()); + } + return false; + } + + @Override + public int hashCode() { + return Objects.hashCode( + comparator, + getLowerEndpoint(), + getLowerBoundType(), + getUpperEndpoint(), + getUpperBoundType()); + } + + private transient GeneralRange reverse; + + /** Returns the same range relative to the reversed comparator. */ + GeneralRange reverse() { + GeneralRange result = reverse; + if (result == null) { + result = + new GeneralRange( + Ordering.from(comparator).reverse(), + hasUpperBound, + getUpperEndpoint(), + getUpperBoundType(), + hasLowerBound, + getLowerEndpoint(), + getLowerBoundType()); + result.reverse = this; + return this.reverse = result; + } + return result; + } + + @Override + public String toString() { + return comparator + + ":" + + (lowerBoundType == CLOSED ? '[' : '(') + + (hasLowerBound ? lowerEndpoint : "-\u221e") + + ',' + + (hasUpperBound ? upperEndpoint : "\u221e") + + (upperBoundType == CLOSED ? ']' : ')'); + } + + T getLowerEndpoint() { + return lowerEndpoint; + } + + BoundType getLowerBoundType() { + return lowerBoundType; + } + + T getUpperEndpoint() { + return upperEndpoint; + } + + BoundType getUpperBoundType() { + return upperBoundType; + } +} diff --git a/src/main/java/com/google/common/collect/GwtTransient.java b/src/main/java/com/google/common/collect/GwtTransient.java new file mode 100644 index 0000000..9c09c53 --- /dev/null +++ b/src/main/java/com/google/common/collect/GwtTransient.java @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2011 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import com.google.common.annotations.GwtCompatible; +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +/** + * Private replacement for {@link com.google.gwt.user.client.rpc.GwtTransient} to work around + * build-system quirks. This annotation should be used only in {@code + * com.google.common.collect}. + */ +@Documented +@GwtCompatible +@Retention(RUNTIME) +@Target(FIELD) +@interface GwtTransient {} diff --git a/src/main/java/com/google/common/collect/HashBasedTable.java b/src/main/java/com/google/common/collect/HashBasedTable.java new file mode 100644 index 0000000..84b2dda --- /dev/null +++ b/src/main/java/com/google/common/collect/HashBasedTable.java @@ -0,0 +1,145 @@ +/* + * Copyright (C) 2008 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import static com.google.common.collect.CollectPreconditions.checkNonnegative; + +import com.google.common.annotations.GwtCompatible; +import com.google.common.base.Supplier; + +import java.io.Serializable; +import java.util.LinkedHashMap; +import java.util.Map; + + +/** + * Implementation of {@link Table} using linked hash tables. This guarantees predictable iteration + * order of the various views. + * + *

The views returned by {@link #column}, {@link #columnKeySet()}, and {@link #columnMap()} have + * iterators that don't support {@code remove()}. Otherwise, all optional operations are supported. + * Null row keys, columns keys, and values are not supported. + * + *

Lookups by row key are often faster than lookups by column key, because the data is stored in + * a {@code Map>}. A method call like {@code column(columnKey).get(rowKey)} still runs + * quickly, since the row key is provided. However, {@code column(columnKey).size()} takes longer, + * since an iteration across all row keys occurs. + * + *

Note that this implementation is not synchronized. If multiple threads access this table + * concurrently and one of the threads modifies the table, it must be synchronized externally. + * + *

See the Guava User Guide article on {@code Table}. + * + * @author Jared Levy + * @since 7.0 + */ +@GwtCompatible(serializable = true) +public class HashBasedTable extends StandardTable { + private static class Factory implements Supplier>, Serializable { + final int expectedSize; + + Factory(int expectedSize) { + this.expectedSize = expectedSize; + } + + @Override + public Map get() { + return Maps.newLinkedHashMapWithExpectedSize(expectedSize); + } + + private static final long serialVersionUID = 0; + } + + /** Creates an empty {@code HashBasedTable}. */ + public static HashBasedTable create() { + return new HashBasedTable<>(new LinkedHashMap>(), new Factory(0)); + } + + /** + * Creates an empty {@code HashBasedTable} with the specified map sizes. + * + * @param expectedRows the expected number of distinct row keys + * @param expectedCellsPerRow the expected number of column key / value mappings in each row + * @throws IllegalArgumentException if {@code expectedRows} or {@code expectedCellsPerRow} is + * negative + */ + public static HashBasedTable create( + int expectedRows, int expectedCellsPerRow) { + checkNonnegative(expectedCellsPerRow, "expectedCellsPerRow"); + Map> backingMap = Maps.newLinkedHashMapWithExpectedSize(expectedRows); + return new HashBasedTable<>(backingMap, new Factory(expectedCellsPerRow)); + } + + /** + * Creates a {@code HashBasedTable} with the same mappings as the specified table. + * + * @param table the table to copy + * @throws NullPointerException if any of the row keys, column keys, or values in {@code table} is + * null + */ + public static HashBasedTable create( + Table table) { + HashBasedTable result = create(); + result.putAll(table); + return result; + } + + HashBasedTable(Map> backingMap, Factory factory) { + super(backingMap, factory); + } + + // Overriding so NullPointerTester test passes. + + @Override + public boolean contains(Object rowKey, Object columnKey) { + return super.contains(rowKey, columnKey); + } + + @Override + public boolean containsColumn(Object columnKey) { + return super.containsColumn(columnKey); + } + + @Override + public boolean containsRow(Object rowKey) { + return super.containsRow(rowKey); + } + + @Override + public boolean containsValue(Object value) { + return super.containsValue(value); + } + + @Override + public V get(Object rowKey, Object columnKey) { + return super.get(rowKey, columnKey); + } + + @Override + public boolean equals(Object obj) { + return super.equals(obj); + } + + + @Override + public V remove(Object rowKey, Object columnKey) { + return super.remove(rowKey, columnKey); + } + + private static final long serialVersionUID = 0; +} diff --git a/src/main/java/com/google/common/collect/HashBiMap.java b/src/main/java/com/google/common/collect/HashBiMap.java new file mode 100644 index 0000000..f202e98 --- /dev/null +++ b/src/main/java/com/google/common/collect/HashBiMap.java @@ -0,0 +1,764 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.collect; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.collect.CollectPreconditions.checkNonnegative; +import static com.google.common.collect.CollectPreconditions.checkRemove; +import static com.google.common.collect.Hashing.smearedHash; + +import com.google.common.annotations.GwtCompatible; +import com.google.common.annotations.GwtIncompatible; +import com.google.common.base.Objects; +import com.google.common.collect.Maps.IteratorBasedAbstractMap; + + + + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.Serializable; +import java.util.Arrays; +import java.util.ConcurrentModificationException; +import java.util.Iterator; +import java.util.Map; +import java.util.NoSuchElementException; +import java.util.Set; +import java.util.function.BiConsumer; +import java.util.function.BiFunction; + + + +/** + * A {@link BiMap} backed by two hash tables. This implementation allows null keys and values. A + * {@code HashBiMap} and its inverse are both serializable. + * + *

This implementation guarantees insertion-based iteration order of its keys. + * + *

See the Guava User Guide article on {@code BiMap} . + * + * @author Louis Wasserman + * @author Mike Bostock + * @since 2.0 + */ +@GwtCompatible(emulated = true) +public final class HashBiMap extends IteratorBasedAbstractMap + implements BiMap, Serializable { + + /** Returns a new, empty {@code HashBiMap} with the default initial capacity (16). */ + public static HashBiMap create() { + return create(16); + } + + /** + * Constructs a new, empty bimap with the specified expected size. + * + * @param expectedSize the expected number of entries + * @throws IllegalArgumentException if the specified expected size is negative + */ + public static HashBiMap create(int expectedSize) { + return new HashBiMap<>(expectedSize); + } + + /** + * Constructs a new bimap containing initial values from {@code map}. The bimap is created with an + * initial capacity sufficient to hold the mappings in the specified map. + */ + public static HashBiMap create(Map map) { + HashBiMap bimap = create(map.size()); + bimap.putAll(map); + return bimap; + } + + private static final class BiEntry extends ImmutableEntry { + final int keyHash; + final int valueHash; + + BiEntry nextInKToVBucket; + BiEntry nextInVToKBucket; + + BiEntry nextInKeyInsertionOrder; + BiEntry prevInKeyInsertionOrder; + + BiEntry(K key, int keyHash, V value, int valueHash) { + super(key, value); + this.keyHash = keyHash; + this.valueHash = valueHash; + } + } + + private static final double LOAD_FACTOR = 1.0; + + private transient BiEntry[] hashTableKToV; + private transient BiEntry[] hashTableVToK; + private transient BiEntry firstInKeyInsertionOrder; + private transient BiEntry lastInKeyInsertionOrder; + private transient int size; + private transient int mask; + private transient int modCount; + + private HashBiMap(int expectedSize) { + init(expectedSize); + } + + private void init(int expectedSize) { + checkNonnegative(expectedSize, "expectedSize"); + int tableSize = Hashing.closedTableSize(expectedSize, LOAD_FACTOR); + this.hashTableKToV = createTable(tableSize); + this.hashTableVToK = createTable(tableSize); + this.firstInKeyInsertionOrder = null; + this.lastInKeyInsertionOrder = null; + this.size = 0; + this.mask = tableSize - 1; + this.modCount = 0; + } + + /** + * Finds and removes {@code entry} from the bucket linked lists in both the key-to-value direction + * and the value-to-key direction. + */ + private void delete(BiEntry entry) { + int keyBucket = entry.keyHash & mask; + BiEntry prevBucketEntry = null; + for (BiEntry bucketEntry = hashTableKToV[keyBucket]; + true; + bucketEntry = bucketEntry.nextInKToVBucket) { + if (bucketEntry == entry) { + if (prevBucketEntry == null) { + hashTableKToV[keyBucket] = entry.nextInKToVBucket; + } else { + prevBucketEntry.nextInKToVBucket = entry.nextInKToVBucket; + } + break; + } + prevBucketEntry = bucketEntry; + } + + int valueBucket = entry.valueHash & mask; + prevBucketEntry = null; + for (BiEntry bucketEntry = hashTableVToK[valueBucket]; + true; + bucketEntry = bucketEntry.nextInVToKBucket) { + if (bucketEntry == entry) { + if (prevBucketEntry == null) { + hashTableVToK[valueBucket] = entry.nextInVToKBucket; + } else { + prevBucketEntry.nextInVToKBucket = entry.nextInVToKBucket; + } + break; + } + prevBucketEntry = bucketEntry; + } + + if (entry.prevInKeyInsertionOrder == null) { + firstInKeyInsertionOrder = entry.nextInKeyInsertionOrder; + } else { + entry.prevInKeyInsertionOrder.nextInKeyInsertionOrder = entry.nextInKeyInsertionOrder; + } + + if (entry.nextInKeyInsertionOrder == null) { + lastInKeyInsertionOrder = entry.prevInKeyInsertionOrder; + } else { + entry.nextInKeyInsertionOrder.prevInKeyInsertionOrder = entry.prevInKeyInsertionOrder; + } + + size--; + modCount++; + } + + private void insert(BiEntry entry, BiEntry oldEntryForKey) { + int keyBucket = entry.keyHash & mask; + entry.nextInKToVBucket = hashTableKToV[keyBucket]; + hashTableKToV[keyBucket] = entry; + + int valueBucket = entry.valueHash & mask; + entry.nextInVToKBucket = hashTableVToK[valueBucket]; + hashTableVToK[valueBucket] = entry; + + if (oldEntryForKey == null) { + entry.prevInKeyInsertionOrder = lastInKeyInsertionOrder; + entry.nextInKeyInsertionOrder = null; + if (lastInKeyInsertionOrder == null) { + firstInKeyInsertionOrder = entry; + } else { + lastInKeyInsertionOrder.nextInKeyInsertionOrder = entry; + } + lastInKeyInsertionOrder = entry; + } else { + entry.prevInKeyInsertionOrder = oldEntryForKey.prevInKeyInsertionOrder; + if (entry.prevInKeyInsertionOrder == null) { + firstInKeyInsertionOrder = entry; + } else { + entry.prevInKeyInsertionOrder.nextInKeyInsertionOrder = entry; + } + entry.nextInKeyInsertionOrder = oldEntryForKey.nextInKeyInsertionOrder; + if (entry.nextInKeyInsertionOrder == null) { + lastInKeyInsertionOrder = entry; + } else { + entry.nextInKeyInsertionOrder.prevInKeyInsertionOrder = entry; + } + } + + size++; + modCount++; + } + + private BiEntry seekByKey(Object key, int keyHash) { + for (BiEntry entry = hashTableKToV[keyHash & mask]; + entry != null; + entry = entry.nextInKToVBucket) { + if (keyHash == entry.keyHash && Objects.equal(key, entry.key)) { + return entry; + } + } + return null; + } + + private BiEntry seekByValue(Object value, int valueHash) { + for (BiEntry entry = hashTableVToK[valueHash & mask]; + entry != null; + entry = entry.nextInVToKBucket) { + if (valueHash == entry.valueHash && Objects.equal(value, entry.value)) { + return entry; + } + } + return null; + } + + @Override + public boolean containsKey(Object key) { + return seekByKey(key, smearedHash(key)) != null; + } + + /** + * Returns {@code true} if this BiMap contains an entry whose value is equal to {@code value} (or, + * equivalently, if this inverse view contains a key that is equal to {@code value}). + * + *

Due to the property that values in a BiMap are unique, this will tend to execute in + * faster-than-linear time. + * + * @param value the object to search for in the values of this BiMap + * @return true if a mapping exists from a key to the specified value + */ + @Override + public boolean containsValue(Object value) { + return seekByValue(value, smearedHash(value)) != null; + } + + @Override + public V get(Object key) { + return Maps.valueOrNull(seekByKey(key, smearedHash(key))); + } + + + @Override + public V put(K key, V value) { + return put(key, value, false); + } + + private V put(K key, V value, boolean force) { + int keyHash = smearedHash(key); + int valueHash = smearedHash(value); + + BiEntry oldEntryForKey = seekByKey(key, keyHash); + if (oldEntryForKey != null + && valueHash == oldEntryForKey.valueHash + && Objects.equal(value, oldEntryForKey.value)) { + return value; + } + + BiEntry oldEntryForValue = seekByValue(value, valueHash); + if (oldEntryForValue != null) { + if (force) { + delete(oldEntryForValue); + } else { + throw new IllegalArgumentException("value already present: " + value); + } + } + + BiEntry newEntry = new BiEntry<>(key, keyHash, value, valueHash); + if (oldEntryForKey != null) { + delete(oldEntryForKey); + insert(newEntry, oldEntryForKey); + oldEntryForKey.prevInKeyInsertionOrder = null; + oldEntryForKey.nextInKeyInsertionOrder = null; + return oldEntryForKey.value; + } else { + insert(newEntry, null); + rehashIfNecessary(); + return null; + } + } + + + @Override + + public V forcePut(K key, V value) { + return put(key, value, true); + } + + private K putInverse(V value, K key, boolean force) { + int valueHash = smearedHash(value); + int keyHash = smearedHash(key); + + BiEntry oldEntryForValue = seekByValue(value, valueHash); + BiEntry oldEntryForKey = seekByKey(key, keyHash); + if (oldEntryForValue != null + && keyHash == oldEntryForValue.keyHash + && Objects.equal(key, oldEntryForValue.key)) { + return key; + } else if (oldEntryForKey != null && !force) { + throw new IllegalArgumentException("key already present: " + key); + } + + /* + * The ordering here is important: if we deleted the key entry and then the value entry, + * the key entry's prev or next pointer might point to the dead value entry, and when we + * put the new entry in the key entry's position in iteration order, it might invalidate + * the linked list. + */ + + if (oldEntryForValue != null) { + delete(oldEntryForValue); + } + + if (oldEntryForKey != null) { + delete(oldEntryForKey); + } + + BiEntry newEntry = new BiEntry<>(key, keyHash, value, valueHash); + insert(newEntry, oldEntryForKey); + + if (oldEntryForKey != null) { + oldEntryForKey.prevInKeyInsertionOrder = null; + oldEntryForKey.nextInKeyInsertionOrder = null; + } + if (oldEntryForValue != null) { + oldEntryForValue.prevInKeyInsertionOrder = null; + oldEntryForValue.nextInKeyInsertionOrder = null; + } + rehashIfNecessary(); + return Maps.keyOrNull(oldEntryForValue); + } + + private void rehashIfNecessary() { + BiEntry[] oldKToV = hashTableKToV; + if (Hashing.needsResizing(size, oldKToV.length, LOAD_FACTOR)) { + int newTableSize = oldKToV.length * 2; + + this.hashTableKToV = createTable(newTableSize); + this.hashTableVToK = createTable(newTableSize); + this.mask = newTableSize - 1; + this.size = 0; + + for (BiEntry entry = firstInKeyInsertionOrder; + entry != null; + entry = entry.nextInKeyInsertionOrder) { + insert(entry, entry); + } + this.modCount++; + } + } + + @SuppressWarnings("unchecked") + private BiEntry[] createTable(int length) { + return new BiEntry[length]; + } + + + @Override + + public V remove(Object key) { + BiEntry entry = seekByKey(key, smearedHash(key)); + if (entry == null) { + return null; + } else { + delete(entry); + entry.prevInKeyInsertionOrder = null; + entry.nextInKeyInsertionOrder = null; + return entry.value; + } + } + + @Override + public void clear() { + size = 0; + Arrays.fill(hashTableKToV, null); + Arrays.fill(hashTableVToK, null); + firstInKeyInsertionOrder = null; + lastInKeyInsertionOrder = null; + modCount++; + } + + @Override + public int size() { + return size; + } + + abstract class Itr implements Iterator { + BiEntry next = firstInKeyInsertionOrder; + BiEntry toRemove = null; + int expectedModCount = modCount; + int remaining = size(); + + @Override + public boolean hasNext() { + if (modCount != expectedModCount) { + throw new ConcurrentModificationException(); + } + return next != null && remaining > 0; + } + + @Override + public T next() { + if (!hasNext()) { + throw new NoSuchElementException(); + } + + BiEntry entry = next; + next = entry.nextInKeyInsertionOrder; + toRemove = entry; + remaining--; + return output(entry); + } + + @Override + public void remove() { + if (modCount != expectedModCount) { + throw new ConcurrentModificationException(); + } + checkRemove(toRemove != null); + delete(toRemove); + expectedModCount = modCount; + toRemove = null; + } + + abstract T output(BiEntry entry); + } + + @Override + public Set keySet() { + return new KeySet(); + } + + + private final class KeySet extends Maps.KeySet { + KeySet() { + super(HashBiMap.this); + } + + @Override + public Iterator iterator() { + return new Itr() { + @Override + K output(BiEntry entry) { + return entry.key; + } + }; + } + + @Override + public boolean remove(Object o) { + BiEntry entry = seekByKey(o, smearedHash(o)); + if (entry == null) { + return false; + } else { + delete(entry); + entry.prevInKeyInsertionOrder = null; + entry.nextInKeyInsertionOrder = null; + return true; + } + } + } + + @Override + public Set values() { + return inverse().keySet(); + } + + @Override + Iterator> entryIterator() { + return new Itr>() { + @Override + Entry output(BiEntry entry) { + return new MapEntry(entry); + } + + class MapEntry extends AbstractMapEntry { + BiEntry delegate; + + MapEntry(BiEntry entry) { + this.delegate = entry; + } + + @Override + public K getKey() { + return delegate.key; + } + + @Override + public V getValue() { + return delegate.value; + } + + @Override + public V setValue(V value) { + V oldValue = delegate.value; + int valueHash = smearedHash(value); + if (valueHash == delegate.valueHash && Objects.equal(value, oldValue)) { + return value; + } + checkArgument(seekByValue(value, valueHash) == null, "value already present: %s", value); + delete(delegate); + BiEntry newEntry = new BiEntry<>(delegate.key, delegate.keyHash, value, valueHash); + insert(newEntry, delegate); + delegate.prevInKeyInsertionOrder = null; + delegate.nextInKeyInsertionOrder = null; + expectedModCount = modCount; + if (toRemove == delegate) { + toRemove = newEntry; + } + delegate = newEntry; + return oldValue; + } + } + }; + } + + @Override + public void forEach(BiConsumer action) { + checkNotNull(action); + for (BiEntry entry = firstInKeyInsertionOrder; + entry != null; + entry = entry.nextInKeyInsertionOrder) { + action.accept(entry.key, entry.value); + } + } + + @Override + public void replaceAll(BiFunction function) { + checkNotNull(function); + BiEntry oldFirst = firstInKeyInsertionOrder; + clear(); + for (BiEntry entry = oldFirst; entry != null; entry = entry.nextInKeyInsertionOrder) { + put(entry.key, function.apply(entry.key, entry.value)); + } + } + + private transient BiMap inverse; + + @Override + public BiMap inverse() { + BiMap result = inverse; + return (result == null) ? inverse = new Inverse() : result; + } + + private final class Inverse extends IteratorBasedAbstractMap + implements BiMap, Serializable { + BiMap forward() { + return HashBiMap.this; + } + + @Override + public int size() { + return size; + } + + @Override + public void clear() { + forward().clear(); + } + + @Override + public boolean containsKey(Object value) { + return forward().containsValue(value); + } + + @Override + public K get(Object value) { + return Maps.keyOrNull(seekByValue(value, smearedHash(value))); + } + + + @Override + + public K put(V value, K key) { + return putInverse(value, key, false); + } + + @Override + + public K forcePut(V value, K key) { + return putInverse(value, key, true); + } + + @Override + + public K remove(Object value) { + BiEntry entry = seekByValue(value, smearedHash(value)); + if (entry == null) { + return null; + } else { + delete(entry); + entry.prevInKeyInsertionOrder = null; + entry.nextInKeyInsertionOrder = null; + return entry.key; + } + } + + @Override + public BiMap inverse() { + return forward(); + } + + @Override + public Set keySet() { + return new InverseKeySet(); + } + + + private final class InverseKeySet extends Maps.KeySet { + InverseKeySet() { + super(Inverse.this); + } + + @Override + public boolean remove(Object o) { + BiEntry entry = seekByValue(o, smearedHash(o)); + if (entry == null) { + return false; + } else { + delete(entry); + return true; + } + } + + @Override + public Iterator iterator() { + return new Itr() { + @Override + V output(BiEntry entry) { + return entry.value; + } + }; + } + } + + @Override + public Set values() { + return forward().keySet(); + } + + @Override + Iterator> entryIterator() { + return new Itr>() { + @Override + Entry output(BiEntry entry) { + return new InverseEntry(entry); + } + + class InverseEntry extends AbstractMapEntry { + BiEntry delegate; + + InverseEntry(BiEntry entry) { + this.delegate = entry; + } + + @Override + public V getKey() { + return delegate.value; + } + + @Override + public K getValue() { + return delegate.key; + } + + @Override + public K setValue(K key) { + K oldKey = delegate.key; + int keyHash = smearedHash(key); + if (keyHash == delegate.keyHash && Objects.equal(key, oldKey)) { + return key; + } + checkArgument(seekByKey(key, keyHash) == null, "value already present: %s", key); + delete(delegate); + BiEntry newEntry = + new BiEntry<>(key, keyHash, delegate.value, delegate.valueHash); + delegate = newEntry; + insert(newEntry, null); + expectedModCount = modCount; + return oldKey; + } + } + }; + } + + @Override + public void forEach(BiConsumer action) { + checkNotNull(action); + HashBiMap.this.forEach((k, v) -> action.accept(v, k)); + } + + @Override + public void replaceAll(BiFunction function) { + checkNotNull(function); + BiEntry oldFirst = firstInKeyInsertionOrder; + clear(); + for (BiEntry entry = oldFirst; entry != null; entry = entry.nextInKeyInsertionOrder) { + put(entry.value, function.apply(entry.value, entry.key)); + } + } + + Object writeReplace() { + return new InverseSerializedForm<>(HashBiMap.this); + } + } + + private static final class InverseSerializedForm implements Serializable { + private final HashBiMap bimap; + + InverseSerializedForm(HashBiMap bimap) { + this.bimap = bimap; + } + + Object readResolve() { + return bimap.inverse(); + } + } + + /** + * @serialData the number of entries, first key, first value, second key, second value, and so on. + */ + @GwtIncompatible // java.io.ObjectOutputStream + private void writeObject(ObjectOutputStream stream) throws IOException { + stream.defaultWriteObject(); + Serialization.writeMap(this, stream); + } + + @GwtIncompatible // java.io.ObjectInputStream + private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException { + stream.defaultReadObject(); + int size = Serialization.readCount(stream); + init(16); // resist hostile attempts to allocate gratuitous heap + Serialization.populateMap(this, stream, size); + } + + @GwtIncompatible // Not needed in emulated source + private static final long serialVersionUID = 0; +} diff --git a/src/main/java/com/google/common/collect/HashMultimap.java b/src/main/java/com/google/common/collect/HashMultimap.java new file mode 100644 index 0000000..250f4f6 --- /dev/null +++ b/src/main/java/com/google/common/collect/HashMultimap.java @@ -0,0 +1,141 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import com.google.common.annotations.GwtCompatible; +import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.Preconditions; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.util.Collection; +import java.util.Map; +import java.util.Set; + +/** + * Implementation of {@link Multimap} using hash tables. + * + *

The multimap does not store duplicate key-value pairs. Adding a new key-value pair equal to an + * existing key-value pair has no effect. + * + *

Keys and values may be null. All optional multimap methods are supported, and all returned + * views are modifiable. + * + *

This class is not threadsafe when any concurrent operations update the multimap. Concurrent + * read operations will work correctly. To allow concurrent update operations, wrap your multimap + * with a call to {@link Multimaps#synchronizedSetMultimap}. + * + * @author Jared Levy + * @since 2.0 + */ +@GwtCompatible(serializable = true, emulated = true) +public final class HashMultimap extends HashMultimapGwtSerializationDependencies { + private static final int DEFAULT_VALUES_PER_KEY = 2; + + @VisibleForTesting transient int expectedValuesPerKey = DEFAULT_VALUES_PER_KEY; + + /** + * Creates a new, empty {@code HashMultimap} with the default initial capacities. + * + *

This method will soon be deprecated in favor of {@code + * MultimapBuilder.hashKeys().hashSetValues().build()}. + */ + public static HashMultimap create() { + return new HashMultimap<>(); + } + + /** + * Constructs an empty {@code HashMultimap} with enough capacity to hold the specified numbers of + * keys and values without rehashing. + * + *

This method will soon be deprecated in favor of {@code + * MultimapBuilder.hashKeys(expectedKeys).hashSetValues(expectedValuesPerKey).build()}. + * + * @param expectedKeys the expected number of distinct keys + * @param expectedValuesPerKey the expected average number of values per key + * @throws IllegalArgumentException if {@code expectedKeys} or {@code expectedValuesPerKey} is + * negative + */ + public static HashMultimap create(int expectedKeys, int expectedValuesPerKey) { + return new HashMultimap<>(expectedKeys, expectedValuesPerKey); + } + + /** + * Constructs a {@code HashMultimap} with the same mappings as the specified multimap. If a + * key-value mapping appears multiple times in the input multimap, it only appears once in the + * constructed multimap. + * + *

This method will soon be deprecated in favor of {@code + * MultimapBuilder.hashKeys().hashSetValues().build(multimap)}. + * + * @param multimap the multimap whose contents are copied to this multimap + */ + public static HashMultimap create(Multimap multimap) { + return new HashMultimap<>(multimap); + } + + private HashMultimap() { + this(12, DEFAULT_VALUES_PER_KEY); + } + + private HashMultimap(int expectedKeys, int expectedValuesPerKey) { + super(Platform.>newHashMapWithExpectedSize(expectedKeys)); + Preconditions.checkArgument(expectedValuesPerKey >= 0); + this.expectedValuesPerKey = expectedValuesPerKey; + } + + private HashMultimap(Multimap multimap) { + super(Platform.>newHashMapWithExpectedSize(multimap.keySet().size())); + putAll(multimap); + } + + /** + * {@inheritDoc} + * + *

Creates an empty {@code HashSet} for a collection of values for one key. + * + * @return a new {@code HashSet} containing a collection of values for one key + */ + @Override + Set createCollection() { + return Platform.newHashSetWithExpectedSize(expectedValuesPerKey); + } + + /** + * @serialData expectedValuesPerKey, number of distinct keys, and then for each distinct key: the + * key, number of values for that key, and the key's values + */ + @GwtIncompatible // java.io.ObjectOutputStream + private void writeObject(ObjectOutputStream stream) throws IOException { + stream.defaultWriteObject(); + Serialization.writeMultimap(this, stream); + } + + @GwtIncompatible // java.io.ObjectInputStream + private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException { + stream.defaultReadObject(); + expectedValuesPerKey = DEFAULT_VALUES_PER_KEY; + int distinctKeys = Serialization.readCount(stream); + Map> map = Platform.newHashMapWithExpectedSize(12); + setMap(map); + Serialization.populateMultimap(this, stream, distinctKeys); + } + + @GwtIncompatible // Not needed in emulated source + private static final long serialVersionUID = 0; +} diff --git a/src/main/java/com/google/common/collect/HashMultimapGwtSerializationDependencies.java b/src/main/java/com/google/common/collect/HashMultimapGwtSerializationDependencies.java new file mode 100644 index 0000000..0922c38 --- /dev/null +++ b/src/main/java/com/google/common/collect/HashMultimapGwtSerializationDependencies.java @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2016 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import com.google.common.annotations.GwtCompatible; +import java.util.Collection; +import java.util.Map; + +/** + * A dummy superclass to support GWT serialization of the element types of a {@link HashMultimap}. + * The GWT supersource for this class contains a field for each type. + * + *

For details about this hack, see {@code GwtSerializationDependencies}, which takes the same + * approach but with a subclass rather than a superclass. + * + *

TODO(cpovirk): Consider applying this subclass approach to our other types. + */ +@GwtCompatible(emulated = true) +abstract class HashMultimapGwtSerializationDependencies extends AbstractSetMultimap { + HashMultimapGwtSerializationDependencies(Map> map) { + super(map); + } +} diff --git a/src/main/java/com/google/common/collect/HashMultiset.java b/src/main/java/com/google/common/collect/HashMultiset.java new file mode 100644 index 0000000..d820434 --- /dev/null +++ b/src/main/java/com/google/common/collect/HashMultiset.java @@ -0,0 +1,93 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import com.google.common.annotations.GwtCompatible; +import com.google.common.annotations.GwtIncompatible; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.util.HashMap; + +/** + * Multiset implementation backed by a {@link HashMap}. + * + * @author Kevin Bourrillion + * @author Jared Levy + * @since 2.0 + */ +@GwtCompatible(serializable = true, emulated = true) +public final class HashMultiset extends AbstractMapBasedMultiset { + + /** Creates a new, empty {@code HashMultiset} using the default initial capacity. */ + public static HashMultiset create() { + return new HashMultiset(); + } + + /** + * Creates a new, empty {@code HashMultiset} with the specified expected number of distinct + * elements. + * + * @param distinctElements the expected number of distinct elements + * @throws IllegalArgumentException if {@code distinctElements} is negative + */ + public static HashMultiset create(int distinctElements) { + return new HashMultiset(distinctElements); + } + + /** + * Creates a new {@code HashMultiset} containing the specified elements. + * + *

This implementation is highly efficient when {@code elements} is itself a {@link Multiset}. + * + * @param elements the elements that the multiset should contain + */ + public static HashMultiset create(Iterable elements) { + HashMultiset multiset = create(Multisets.inferDistinctElements(elements)); + Iterables.addAll(multiset, elements); + return multiset; + } + + private HashMultiset() { + super(new HashMap()); + } + + private HashMultiset(int distinctElements) { + super(Maps.newHashMapWithExpectedSize(distinctElements)); + } + + /** + * @serialData the number of distinct elements, the first element, its count, the second element, + * its count, and so on + */ + @GwtIncompatible // java.io.ObjectOutputStream + private void writeObject(ObjectOutputStream stream) throws IOException { + stream.defaultWriteObject(); + Serialization.writeMultiset(this, stream); + } + + @GwtIncompatible // java.io.ObjectInputStream + private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException { + stream.defaultReadObject(); + int distinctElements = Serialization.readCount(stream); + setBackingMap(Maps.newHashMap()); + Serialization.populateMultiset(this, stream, distinctElements); + } + + @GwtIncompatible // Not needed in emulated source. + private static final long serialVersionUID = 0; +} diff --git a/src/main/java/com/google/common/collect/Hashing.java b/src/main/java/com/google/common/collect/Hashing.java new file mode 100644 index 0000000..96f3e0a --- /dev/null +++ b/src/main/java/com/google/common/collect/Hashing.java @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2008 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import com.google.common.annotations.GwtCompatible; +import com.google.common.primitives.Ints; + + +/** + * Static methods for implementing hash-based collections. + * + * @author Kevin Bourrillion + * @author Jesse Wilson + * @author Austin Appleby + */ +@GwtCompatible +final class Hashing { + private Hashing() {} + + /* + * These should be ints, but we need to use longs to force GWT to do the multiplications with + * enough precision. + */ + private static final long C1 = 0xcc9e2d51; + private static final long C2 = 0x1b873593; + + /* + * This method was rewritten in Java from an intermediate step of the Murmur hash function in + * http://code.google.com/p/smhasher/source/browse/trunk/MurmurHash3.cpp, which contained the + * following header: + * + * MurmurHash3 was written by Austin Appleby, and is placed in the public domain. The author + * hereby disclaims copyright to this source code. + */ + static int smear(int hashCode) { + return (int) (C2 * Integer.rotateLeft((int) (hashCode * C1), 15)); + } + + static int smearedHash(Object o) { + return smear((o == null) ? 0 : o.hashCode()); + } + + private static final int MAX_TABLE_SIZE = Ints.MAX_POWER_OF_TWO; + + static int closedTableSize(int expectedEntries, double loadFactor) { + // Get the recommended table size. + // Round down to the nearest power of 2. + expectedEntries = Math.max(expectedEntries, 2); + int tableSize = Integer.highestOneBit(expectedEntries); + // Check to make sure that we will not exceed the maximum load factor. + if (expectedEntries > (int) (loadFactor * tableSize)) { + tableSize <<= 1; + return (tableSize > 0) ? tableSize : MAX_TABLE_SIZE; + } + return tableSize; + } + + static boolean needsResizing(int size, int tableSize, double loadFactor) { + return size > loadFactor * tableSize && tableSize < MAX_TABLE_SIZE; + } +} diff --git a/src/main/java/com/google/common/collect/ImmutableAsList.java b/src/main/java/com/google/common/collect/ImmutableAsList.java new file mode 100644 index 0000000..528a8dc --- /dev/null +++ b/src/main/java/com/google/common/collect/ImmutableAsList.java @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2009 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import com.google.common.annotations.GwtCompatible; +import com.google.common.annotations.GwtIncompatible; +import java.io.InvalidObjectException; +import java.io.ObjectInputStream; +import java.io.Serializable; + +/** + * List returned by {@link ImmutableCollection#asList} that delegates {@code contains} checks to the + * backing collection. + * + * @author Jared Levy + * @author Louis Wasserman + */ +@GwtCompatible(serializable = true, emulated = true) +@SuppressWarnings("serial") +abstract class ImmutableAsList extends ImmutableList { + abstract ImmutableCollection delegateCollection(); + + @Override + public boolean contains(Object target) { + // The collection's contains() is at least as fast as ImmutableList's + // and is often faster. + return delegateCollection().contains(target); + } + + @Override + public int size() { + return delegateCollection().size(); + } + + @Override + public boolean isEmpty() { + return delegateCollection().isEmpty(); + } + + @Override + boolean isPartialView() { + return delegateCollection().isPartialView(); + } + + /** Serialized form that leads to the same performance as the original list. */ + @GwtIncompatible // serialization + static class SerializedForm implements Serializable { + final ImmutableCollection collection; + + SerializedForm(ImmutableCollection collection) { + this.collection = collection; + } + + Object readResolve() { + return collection.asList(); + } + + private static final long serialVersionUID = 0; + } + + @GwtIncompatible // serialization + private void readObject(ObjectInputStream stream) throws InvalidObjectException { + throw new InvalidObjectException("Use SerializedForm"); + } + + @GwtIncompatible // serialization + @Override + Object writeReplace() { + return new SerializedForm(delegateCollection()); + } +} diff --git a/src/main/java/com/google/common/collect/ImmutableBiMap.java b/src/main/java/com/google/common/collect/ImmutableBiMap.java new file mode 100644 index 0000000..67670c5 --- /dev/null +++ b/src/main/java/com/google/common/collect/ImmutableBiMap.java @@ -0,0 +1,434 @@ +/* + * Copyright (C) 2008 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import static com.google.common.base.Preconditions.checkState; +import static com.google.common.collect.CollectPreconditions.checkNonnegative; + +import com.google.common.annotations.Beta; +import com.google.common.annotations.GwtCompatible; +import com.google.common.annotations.VisibleForTesting; + +import java.util.Arrays; +import java.util.Comparator; +import java.util.Map; +import java.util.function.Function; +import java.util.stream.Collector; +import java.util.stream.Collectors; + +/** + * A {@link BiMap} whose contents will never change, with many other important properties detailed + * at {@link ImmutableCollection}. + * + * @author Jared Levy + * @since 2.0 + */ +@GwtCompatible(serializable = true, emulated = true) +public abstract class ImmutableBiMap extends ImmutableBiMapFauxverideShim + implements BiMap { + + /** + * Returns a {@link Collector} that accumulates elements into an {@code ImmutableBiMap} whose keys + * and values are the result of applying the provided mapping functions to the input elements. + * Entries appear in the result {@code ImmutableBiMap} in encounter order. + * + *

If the mapped keys or values contain duplicates (according to {@link Object#equals(Object)}, + * an {@code IllegalArgumentException} is thrown when the collection operation is performed. (This + * differs from the {@code Collector} returned by {@link Collectors#toMap(Function, Function)}, + * which throws an {@code IllegalStateException}.) + * + * @since 21.0 + */ + public static Collector> toImmutableBiMap( + Function keyFunction, + Function valueFunction) { + return CollectCollectors.toImmutableBiMap(keyFunction, valueFunction); + } + + /** Returns the empty bimap. */ + // Casting to any type is safe because the set will never hold any elements. + @SuppressWarnings("unchecked") + public static ImmutableBiMap of() { + return (ImmutableBiMap) RegularImmutableBiMap.EMPTY; + } + + /** Returns an immutable bimap containing a single entry. */ + public static ImmutableBiMap of(K k1, V v1) { + return new SingletonImmutableBiMap<>(k1, v1); + } + + /** + * Returns an immutable map containing the given entries, in order. + * + * @throws IllegalArgumentException if duplicate keys or values are added + */ + public static ImmutableBiMap of(K k1, V v1, K k2, V v2) { + return RegularImmutableBiMap.fromEntries(entryOf(k1, v1), entryOf(k2, v2)); + } + + /** + * Returns an immutable map containing the given entries, in order. + * + * @throws IllegalArgumentException if duplicate keys or values are added + */ + public static ImmutableBiMap of(K k1, V v1, K k2, V v2, K k3, V v3) { + return RegularImmutableBiMap.fromEntries(entryOf(k1, v1), entryOf(k2, v2), entryOf(k3, v3)); + } + + /** + * Returns an immutable map containing the given entries, in order. + * + * @throws IllegalArgumentException if duplicate keys or values are added + */ + public static ImmutableBiMap of(K k1, V v1, K k2, V v2, K k3, V v3, K k4, V v4) { + return RegularImmutableBiMap.fromEntries( + entryOf(k1, v1), entryOf(k2, v2), entryOf(k3, v3), entryOf(k4, v4)); + } + + /** + * Returns an immutable map containing the given entries, in order. + * + * @throws IllegalArgumentException if duplicate keys or values are added + */ + public static ImmutableBiMap of( + K k1, V v1, K k2, V v2, K k3, V v3, K k4, V v4, K k5, V v5) { + return RegularImmutableBiMap.fromEntries( + entryOf(k1, v1), entryOf(k2, v2), entryOf(k3, v3), entryOf(k4, v4), entryOf(k5, v5)); + } + + // looking for of() with > 5 entries? Use the builder instead. + + /** + * Returns a new builder. The generated builder is equivalent to the builder created by the {@link + * Builder} constructor. + */ + public static Builder builder() { + return new Builder<>(); + } + + /** + * Returns a new builder, expecting the specified number of entries to be added. + * + *

If {@code expectedSize} is exactly the number of entries added to the builder before {@link + * Builder#build} is called, the builder is likely to perform better than an unsized {@link + * #builder()} would have. + * + *

It is not specified if any performance benefits apply if {@code expectedSize} is close to, + * but not exactly, the number of entries added to the builder. + * + * @since 23.1 + */ + @Beta + public static Builder builderWithExpectedSize(int expectedSize) { + checkNonnegative(expectedSize, "expectedSize"); + return new Builder<>(expectedSize); + } + + /** + * A builder for creating immutable bimap instances, especially {@code public static final} bimaps + * ("constant bimaps"). Example: + * + *

{@code
+   * static final ImmutableBiMap WORD_TO_INT =
+   *     new ImmutableBiMap.Builder()
+   *         .put("one", 1)
+   *         .put("two", 2)
+   *         .put("three", 3)
+   *         .build();
+   * }
+ * + *

For small immutable bimaps, the {@code ImmutableBiMap.of()} methods are even more + * convenient. + * + *

By default, a {@code Builder} will generate bimaps that iterate over entries in the order + * they were inserted into the builder. For example, in the above example, {@code + * WORD_TO_INT.entrySet()} is guaranteed to iterate over the entries in the order {@code "one"=1, + * "two"=2, "three"=3}, and {@code keySet()} and {@code values()} respect the same order. If you + * want a different order, consider using {@link #orderEntriesByValue(Comparator)}, which changes + * this builder to sort entries by value. + * + *

Builder instances can be reused - it is safe to call {@link #build} multiple times to build + * multiple bimaps in series. Each bimap is a superset of the bimaps created before it. + * + * @since 2.0 + */ + public static final class Builder extends ImmutableMap.Builder { + + /** + * Creates a new builder. The returned builder is equivalent to the builder generated by {@link + * ImmutableBiMap#builder}. + */ + public Builder() {} + + Builder(int size) { + super(size); + } + + /** + * Associates {@code key} with {@code value} in the built bimap. Duplicate keys or values are + * not allowed, and will cause {@link #build} to fail. + */ + + @Override + public Builder put(K key, V value) { + super.put(key, value); + return this; + } + + /** + * Adds the given {@code entry} to the bimap. Duplicate keys or values are not allowed, and will + * cause {@link #build} to fail. + * + * @since 19.0 + */ + + @Override + public Builder put(Entry entry) { + super.put(entry); + return this; + } + + /** + * Associates all of the given map's keys and values in the built bimap. Duplicate keys or + * values are not allowed, and will cause {@link #build} to fail. + * + * @throws NullPointerException if any key or value in {@code map} is null + */ + + @Override + public Builder putAll(Map map) { + super.putAll(map); + return this; + } + + /** + * Adds all of the given entries to the built bimap. Duplicate keys or values are not allowed, + * and will cause {@link #build} to fail. + * + * @throws NullPointerException if any key, value, or entry is null + * @since 19.0 + */ + + @Beta + @Override + public Builder putAll(Iterable> entries) { + super.putAll(entries); + return this; + } + + /** + * Configures this {@code Builder} to order entries by value according to the specified + * comparator. + * + *

The sort order is stable, that is, if two entries have values that compare as equivalent, + * the entry that was inserted first will be first in the built map's iteration order. + * + * @throws IllegalStateException if this method was already called + * @since 19.0 + */ + + @Beta + @Override + public Builder orderEntriesByValue(Comparator valueComparator) { + super.orderEntriesByValue(valueComparator); + return this; + } + + @Override + + Builder combine(ImmutableMap.Builder builder) { + super.combine(builder); + return this; + } + + /** + * Returns a newly-created immutable bimap. The iteration order of the returned bimap is the + * order in which entries were inserted into the builder, unless {@link #orderEntriesByValue} + * was called, in which case entries are sorted by value. + * + * @throws IllegalArgumentException if duplicate keys or values were added + */ + @Override + public ImmutableBiMap build() { + switch (size) { + case 0: + return of(); + case 1: + return of(entries[0].getKey(), entries[0].getValue()); + default: + /* + * If entries is full, or if hash flooding is detected, then this implementation may end + * up using the entries array directly and writing over the entry objects with + * non-terminal entries, but this is safe; if this Builder is used further, it will grow + * the entries array (so it can't affect the original array), and future build() calls + * will always copy any entry objects that cannot be safely reused. + */ + if (valueComparator != null) { + if (entriesUsed) { + entries = Arrays.copyOf(entries, size); + } + Arrays.sort( + entries, + 0, + size, + Ordering.from(valueComparator).onResultOf(Maps.valueFunction())); + } + entriesUsed = true; + return RegularImmutableBiMap.fromEntryArray(size, entries); + } + } + + @Override + @VisibleForTesting + ImmutableBiMap buildJdkBacked() { + checkState( + valueComparator == null, + "buildJdkBacked is for tests only, doesn't support orderEntriesByValue"); + switch (size) { + case 0: + return of(); + case 1: + return of(entries[0].getKey(), entries[0].getValue()); + default: + entriesUsed = true; + return RegularImmutableBiMap.fromEntryArray(size, entries); + } + } + } + + /** + * Returns an immutable bimap containing the same entries as {@code map}. If {@code map} somehow + * contains entries with duplicate keys (for example, if it is a {@code SortedMap} whose + * comparator is not consistent with equals), the results of this method are undefined. + * + *

The returned {@code BiMap} iterates over entries in the same order as the {@code entrySet} + * of the original map. + * + *

Despite the method name, this method attempts to avoid actually copying the data when it is + * safe to do so. The exact circumstances under which a copy will or will not be performed are + * undocumented and subject to change. + * + * @throws IllegalArgumentException if two keys have the same value or two values have the same + * key + * @throws NullPointerException if any key or value in {@code map} is null + */ + public static ImmutableBiMap copyOf(Map map) { + if (map instanceof ImmutableBiMap) { + @SuppressWarnings("unchecked") // safe since map is not writable + ImmutableBiMap bimap = (ImmutableBiMap) map; + // TODO(lowasser): if we need to make a copy of a BiMap because the + // forward map is a view, don't make a copy of the non-view delegate map + if (!bimap.isPartialView()) { + return bimap; + } + } + return copyOf(map.entrySet()); + } + + /** + * Returns an immutable bimap containing the given entries. The returned bimap iterates over + * entries in the same order as the original iterable. + * + * @throws IllegalArgumentException if two keys have the same value or two values have the same + * key + * @throws NullPointerException if any key, value, or entry is null + * @since 19.0 + */ + @Beta + public static ImmutableBiMap copyOf( + Iterable> entries) { + @SuppressWarnings("unchecked") // we'll only be using getKey and getValue, which are covariant + Entry[] entryArray = (Entry[]) Iterables.toArray(entries, EMPTY_ENTRY_ARRAY); + switch (entryArray.length) { + case 0: + return of(); + case 1: + Entry entry = entryArray[0]; + return of(entry.getKey(), entry.getValue()); + default: + /* + * The current implementation will end up using entryArray directly, though it will write + * over the (arbitrary, potentially mutable) Entry objects actually stored in entryArray. + */ + return RegularImmutableBiMap.fromEntries(entryArray); + } + } + + ImmutableBiMap() {} + + /** + * {@inheritDoc} + * + *

The inverse of an {@code ImmutableBiMap} is another {@code ImmutableBiMap}. + */ + @Override + public abstract ImmutableBiMap inverse(); + + /** + * Returns an immutable set of the values in this map, in the same order they appear in {@link + * #entrySet}. + */ + @Override + public ImmutableSet values() { + return inverse().keySet(); + } + + @Override + final ImmutableSet createValues() { + throw new AssertionError("should never be called"); + } + + /** + * Guaranteed to throw an exception and leave the bimap unmodified. + * + * @throws UnsupportedOperationException always + * @deprecated Unsupported operation. + */ + + @Deprecated + @Override + public V forcePut(K key, V value) { + throw new UnsupportedOperationException(); + } + + /** + * Serialized type for all ImmutableBiMap instances. It captures the logical contents and they are + * reconstructed using public factory methods. This ensures that the implementation types remain + * as implementation details. + * + *

Since the bimap is immutable, ImmutableBiMap doesn't require special logic for keeping the + * bimap and its inverse in sync during serialization, the way AbstractBiMap does. + */ + private static class SerializedForm extends ImmutableMap.SerializedForm { + SerializedForm(ImmutableBiMap bimap) { + super(bimap); + } + + @Override + Object readResolve() { + Builder builder = new Builder<>(); + return createMap(builder); + } + + private static final long serialVersionUID = 0; + } + + @Override + Object writeReplace() { + return new SerializedForm(this); + } +} diff --git a/src/main/java/com/google/common/collect/ImmutableBiMapFauxverideShim.java b/src/main/java/com/google/common/collect/ImmutableBiMapFauxverideShim.java new file mode 100644 index 0000000..dd787d5 --- /dev/null +++ b/src/main/java/com/google/common/collect/ImmutableBiMapFauxverideShim.java @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2015 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import com.google.common.annotations.GwtIncompatible; +import java.util.function.BinaryOperator; +import java.util.function.Function; +import java.util.stream.Collector; + +/** + * "Overrides" the {@link ImmutableMap} static methods that lack {@link ImmutableBiMap} equivalents + * with deprecated, exception-throwing versions. See {@link ImmutableSortedSetFauxverideShim} for + * details. + * + * @author Louis Wasserman + */ +@GwtIncompatible +abstract class ImmutableBiMapFauxverideShim extends ImmutableMap { + /** + * Not supported. Use {@link ImmutableBiMap#toImmutableBiMap} instead. This method exists only to + * hide {@link ImmutableMap#toImmutableMap(Function, Function)} from consumers of {@code + * ImmutableBiMap}. + * + * @throws UnsupportedOperationException always + * @deprecated Use {@link ImmutableBiMap#toImmutableBiMap}. + */ + @Deprecated + public static Collector> toImmutableMap( + Function keyFunction, + Function valueFunction) { + throw new UnsupportedOperationException(); + } + + /** + * Not supported. This method does not make sense for {@code BiMap}. This method exists only to + * hide {@link ImmutableMap#toImmutableMap(Function, Function, BinaryOperator)} from consumers of + * {@code ImmutableBiMap}. + * + * @throws UnsupportedOperationException always + * @deprecated + */ + @Deprecated + public static Collector> toImmutableMap( + Function keyFunction, + Function valueFunction, + BinaryOperator mergeFunction) { + throw new UnsupportedOperationException(); + } +} diff --git a/src/main/java/com/google/common/collect/ImmutableClassToInstanceMap.java b/src/main/java/com/google/common/collect/ImmutableClassToInstanceMap.java new file mode 100644 index 0000000..17c9fad --- /dev/null +++ b/src/main/java/com/google/common/collect/ImmutableClassToInstanceMap.java @@ -0,0 +1,192 @@ +/* + * Copyright (C) 2009 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.annotations.GwtIncompatible; +import com.google.common.primitives.Primitives; + + +import java.io.Serializable; +import java.util.Map; + + +/** + * A {@link ClassToInstanceMap} whose contents will never change, with many other important + * properties detailed at {@link ImmutableCollection}. + * + * @author Kevin Bourrillion + * @since 2.0 + */ +@GwtIncompatible +public final class ImmutableClassToInstanceMap extends ForwardingMap, B> + implements ClassToInstanceMap, Serializable { + + private static final ImmutableClassToInstanceMap EMPTY = + new ImmutableClassToInstanceMap<>(ImmutableMap., Object>of()); + + /** + * Returns an empty {@code ImmutableClassToInstanceMap}. + * + * @since 19.0 + */ + @SuppressWarnings("unchecked") + public static ImmutableClassToInstanceMap of() { + return (ImmutableClassToInstanceMap) EMPTY; + } + + /** + * Returns an {@code ImmutableClassToInstanceMap} containing a single entry. + * + * @since 19.0 + */ + public static ImmutableClassToInstanceMap of(Class type, T value) { + ImmutableMap, B> map = ImmutableMap., B>of(type, value); + return new ImmutableClassToInstanceMap(map); + } + + /** + * Returns a new builder. The generated builder is equivalent to the builder created by the {@link + * Builder} constructor. + */ + public static Builder builder() { + return new Builder(); + } + + /** + * A builder for creating immutable class-to-instance maps. Example: + * + *
{@code
+   * static final ImmutableClassToInstanceMap HANDLERS =
+   *     new ImmutableClassToInstanceMap.Builder()
+   *         .put(FooHandler.class, new FooHandler())
+   *         .put(BarHandler.class, new SubBarHandler())
+   *         .put(Handler.class, new QuuxHandler())
+   *         .build();
+   * }
+ * + *

After invoking {@link #build()} it is still possible to add more entries and build again. + * Thus each map generated by this builder will be a superset of any map generated before it. + * + * @since 2.0 + */ + public static final class Builder { + private final ImmutableMap.Builder, B> mapBuilder = ImmutableMap.builder(); + + /** + * Associates {@code key} with {@code value} in the built map. Duplicate keys are not allowed, + * and will cause {@link #build} to fail. + */ + + public Builder put(Class key, T value) { + mapBuilder.put(key, value); + return this; + } + + /** + * Associates all of {@code map's} keys and values in the built map. Duplicate keys are not + * allowed, and will cause {@link #build} to fail. + * + * @throws NullPointerException if any key or value in {@code map} is null + * @throws ClassCastException if any value is not an instance of the type specified by its key + */ + + public Builder putAll(Map, ? extends T> map) { + for (Entry, ? extends T> entry : map.entrySet()) { + Class type = entry.getKey(); + T value = entry.getValue(); + mapBuilder.put(type, cast(type, value)); + } + return this; + } + + private static T cast(Class type, B value) { + return Primitives.wrap(type).cast(value); + } + + /** + * Returns a new immutable class-to-instance map containing the entries provided to this + * builder. + * + * @throws IllegalArgumentException if duplicate keys were added + */ + public ImmutableClassToInstanceMap build() { + ImmutableMap, B> map = mapBuilder.build(); + if (map.isEmpty()) { + return of(); + } else { + return new ImmutableClassToInstanceMap(map); + } + } + } + + /** + * Returns an immutable map containing the same entries as {@code map}. If {@code map} somehow + * contains entries with duplicate keys (for example, if it is a {@code SortedMap} whose + * comparator is not consistent with equals), the results of this method are undefined. + * + *

Note: Despite what the method name suggests, if {@code map} is an {@code + * ImmutableClassToInstanceMap}, no copy will actually be performed. + * + * @throws NullPointerException if any key or value in {@code map} is null + * @throws ClassCastException if any value is not an instance of the type specified by its key + */ + public static ImmutableClassToInstanceMap copyOf( + Map, ? extends S> map) { + if (map instanceof ImmutableClassToInstanceMap) { + @SuppressWarnings("unchecked") // covariant casts safe (unmodifiable) + ImmutableClassToInstanceMap cast = (ImmutableClassToInstanceMap) map; + return cast; + } + return new Builder().putAll(map).build(); + } + + private final ImmutableMap, B> delegate; + + private ImmutableClassToInstanceMap(ImmutableMap, B> delegate) { + this.delegate = delegate; + } + + @Override + protected Map, B> delegate() { + return delegate; + } + + @Override + @SuppressWarnings("unchecked") // value could not get in if not a T + public T getInstance(Class type) { + return (T) delegate.get(checkNotNull(type)); + } + + /** + * Guaranteed to throw an exception and leave the map unmodified. + * + * @throws UnsupportedOperationException always + * @deprecated Unsupported operation. + */ + + @Deprecated + @Override + public T putInstance(Class type, T value) { + throw new UnsupportedOperationException(); + } + + Object readResolve() { + return isEmpty() ? of() : this; + } +} diff --git a/src/main/java/com/google/common/collect/ImmutableCollection.java b/src/main/java/com/google/common/collect/ImmutableCollection.java new file mode 100644 index 0000000..263a485 --- /dev/null +++ b/src/main/java/com/google/common/collect/ImmutableCollection.java @@ -0,0 +1,471 @@ +/* + * Copyright (C) 2008 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.annotations.GwtCompatible; + +import java.io.Serializable; +import java.util.AbstractCollection; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Spliterator; +import java.util.Spliterators; +import java.util.function.Predicate; + + +/** + * A {@link Collection} whose contents will never change, and which offers a few additional + * guarantees detailed below. + * + *

Warning: avoid direct usage of {@link ImmutableCollection} as a type (just as + * with {@link Collection} itself). Prefer subtypes such as {@link ImmutableSet} or {@link + * ImmutableList}, which have well-defined {@link #equals} semantics, thus avoiding a common source + * of bugs and confusion. + * + *

About all {@code Immutable-} collections

+ * + *

The remainder of this documentation applies to every public {@code Immutable-} type in this + * package, whether it is a subtype of {@code ImmutableCollection} or not. + * + *

Guarantees

+ * + *

Each makes the following guarantees: + * + *

    + *
  • Shallow immutability. Elements can never be added, removed or replaced in this + * collection. This is a stronger guarantee than that of {@link + * Collections#unmodifiableCollection}, whose contents change whenever the wrapped collection + * is modified. + *
  • Null-hostility. This collection will never contain a null element. + *
  • Deterministic iteration. The iteration order is always well-defined, depending on + * how the collection was created. Typically this is insertion order unless an explicit + * ordering is otherwise specified (e.g. {@link ImmutableSortedSet#naturalOrder}). See the + * appropriate factory method for details. View collections such as {@link + * ImmutableMultiset#elementSet} iterate in the same order as the parent, except as noted. + *
  • Thread safety. It is safe to access this collection concurrently from multiple + * threads. + *
  • Integrity. This type cannot be subclassed outside this package (which would allow + * these guarantees to be violated). + *
+ * + *

"Interfaces", not implementations

+ * + *

These are classes instead of interfaces to prevent external subtyping, but should be thought + * of as interfaces in every important sense. Each public class such as {@link ImmutableSet} is a + * type offering meaningful behavioral guarantees. This is substantially different from the + * case of (say) {@link HashSet}, which is an implementation, with semantics that were + * largely defined by its supertype. + * + *

For field types and method return types, you should generally use the immutable type (such as + * {@link ImmutableList}) instead of the general collection interface type (such as {@link List}). + * This communicates to your callers all of the semantic guarantees listed above, which is almost + * always very useful information. + * + *

On the other hand, a parameter type of {@link ImmutableList} is generally a nuisance to + * callers. Instead, accept {@link Iterable} and have your method or constructor body pass it to the + * appropriate {@code copyOf} method itself. + * + *

Expressing the immutability guarantee directly in the type that user code references is a + * powerful advantage. Although Java offers certain immutable collection factory methods, such as + * {@link Collections#singleton(Object)} and {@code Set.of}, + * we recommend using these classes instead for this reason (as well as for consistency). + * + *

Creation

+ * + *

Except for logically "abstract" types like {@code ImmutableCollection} itself, each {@code + * Immutable} type provides the static operations you need to obtain instances of that type. These + * usually include: + * + *

    + *
  • Static methods named {@code of}, accepting an explicit list of elements or entries. + *
  • Static methods named {@code copyOf} (or {@code copyOfSorted}), accepting an existing + * collection whose contents should be copied. + *
  • A static nested {@code Builder} class which can be used to populate a new immutable + * instance. + *
+ * + *

Warnings

+ * + *
    + *
  • Warning: as with any collection, it is almost always a bad idea to modify an element + * (in a way that affects its {@link Object#equals} behavior) while it is contained in a + * collection. Undefined behavior and bugs will result. It's generally best to avoid using + * mutable objects as elements at all, as many users may expect your "immutable" object to be + * deeply immutable. + *
+ * + *

Performance notes

+ * + *
    + *
  • Implementations can be generally assumed to prioritize memory efficiency, then speed of + * access, and lastly speed of creation. + *
  • The {@code copyOf} methods will sometimes recognize that the actual copy operation is + * unnecessary; for example, {@code copyOf(copyOf(anArrayList))} should copy the data only + * once. This reduces the expense of habitually making defensive copies at API boundaries. + * However, the precise conditions for skipping the copy operation are undefined. + *
  • Warning: a view collection such as {@link ImmutableMap#keySet} or {@link + * ImmutableList#subList} may retain a reference to the entire data set, preventing it from + * being garbage collected. If some of the data is no longer reachable through other means, + * this constitutes a memory leak. Pass the view collection to the appropriate {@code copyOf} + * method to obtain a correctly-sized copy. + *
  • The performance of using the associated {@code Builder} class can be assumed to be no + * worse, and possibly better, than creating a mutable collection and copying it. + *
  • Implementations generally do not cache hash codes. If your element or key type has a slow + * {@code hashCode} implementation, it should cache it itself. + *
+ * + *

Example usage

+ * + *
{@code
+ * class Foo {
+ *   private static final ImmutableSet RESERVED_CODES =
+ *       ImmutableSet.of("AZ", "CQ", "ZX");
+ *
+ *   private final ImmutableSet codes;
+ *
+ *   public Foo(Iterable codes) {
+ *     this.codes = ImmutableSet.copyOf(codes);
+ *     checkArgument(Collections.disjoint(this.codes, RESERVED_CODES));
+ *   }
+ * }
+ * }
+ * + *

See also

+ * + *

See the Guava User Guide article on immutable collections. + * + * @since 2.0 + */ +@GwtCompatible(emulated = true) +@SuppressWarnings("serial") // we're overriding default serialization +// TODO(kevinb): I think we should push everything down to "BaseImmutableCollection" or something, +// just to do everything we can to emphasize the "practically an interface" nature of this class. +public abstract class ImmutableCollection extends AbstractCollection implements Serializable { + /* + * We expect SIZED (and SUBSIZED, if applicable) to be added by the spliterator factory methods. + * These are properties of the collection as a whole; SIZED and SUBSIZED are more properties of + * the spliterator implementation. + */ + static final int SPLITERATOR_CHARACTERISTICS = + Spliterator.IMMUTABLE | Spliterator.NONNULL | Spliterator.ORDERED; + + ImmutableCollection() {} + + /** Returns an unmodifiable iterator across the elements in this collection. */ + @Override + public abstract UnmodifiableIterator iterator(); + + @Override + public Spliterator spliterator() { + return Spliterators.spliterator(this, SPLITERATOR_CHARACTERISTICS); + } + + private static final Object[] EMPTY_ARRAY = {}; + + @Override + public final Object[] toArray() { + return toArray(EMPTY_ARRAY); + } + + + @Override + public final T[] toArray(T[] other) { + checkNotNull(other); + int size = size(); + + if (other.length < size) { + Object[] internal = internalArray(); + if (internal != null) { + return Platform.copy(internal, internalArrayStart(), internalArrayEnd(), other); + } + other = ObjectArrays.newArray(other, size); + } else if (other.length > size) { + other[size] = null; + } + copyIntoArray(other, 0); + return other; + } + + /** If this collection is backed by an array of its elements in insertion order, returns it. */ + + Object[] internalArray() { + return null; + } + + /** + * If this collection is backed by an array of its elements in insertion order, returns the offset + * where this collection's elements start. + */ + int internalArrayStart() { + throw new UnsupportedOperationException(); + } + + /** + * If this collection is backed by an array of its elements in insertion order, returns the offset + * where this collection's elements end. + */ + int internalArrayEnd() { + throw new UnsupportedOperationException(); + } + + @Override + public abstract boolean contains(Object object); + + /** + * Guaranteed to throw an exception and leave the collection unmodified. + * + * @throws UnsupportedOperationException always + * @deprecated Unsupported operation. + */ + + @Deprecated + @Override + public final boolean add(E e) { + throw new UnsupportedOperationException(); + } + + /** + * Guaranteed to throw an exception and leave the collection unmodified. + * + * @throws UnsupportedOperationException always + * @deprecated Unsupported operation. + */ + + @Deprecated + @Override + public final boolean remove(Object object) { + throw new UnsupportedOperationException(); + } + + /** + * Guaranteed to throw an exception and leave the collection unmodified. + * + * @throws UnsupportedOperationException always + * @deprecated Unsupported operation. + */ + + @Deprecated + @Override + public final boolean addAll(Collection newElements) { + throw new UnsupportedOperationException(); + } + + /** + * Guaranteed to throw an exception and leave the collection unmodified. + * + * @throws UnsupportedOperationException always + * @deprecated Unsupported operation. + */ + + @Deprecated + @Override + public final boolean removeAll(Collection oldElements) { + throw new UnsupportedOperationException(); + } + + /** + * Guaranteed to throw an exception and leave the collection unmodified. + * + * @throws UnsupportedOperationException always + * @deprecated Unsupported operation. + */ + + @Deprecated + @Override + public final boolean removeIf(Predicate filter) { + throw new UnsupportedOperationException(); + } + + /** + * Guaranteed to throw an exception and leave the collection unmodified. + * + * @throws UnsupportedOperationException always + * @deprecated Unsupported operation. + */ + @Deprecated + @Override + public final boolean retainAll(Collection elementsToKeep) { + throw new UnsupportedOperationException(); + } + + /** + * Guaranteed to throw an exception and leave the collection unmodified. + * + * @throws UnsupportedOperationException always + * @deprecated Unsupported operation. + */ + @Deprecated + @Override + public final void clear() { + throw new UnsupportedOperationException(); + } + + /** + * Returns an {@code ImmutableList} containing the same elements, in the same order, as this + * collection. + * + *

Performance note: in most cases this method can return quickly without actually + * copying anything. The exact circumstances under which the copy is performed are undefined and + * subject to change. + * + * @since 2.0 + */ + public ImmutableList asList() { + switch (size()) { + case 0: + return ImmutableList.of(); + case 1: + return ImmutableList.of(iterator().next()); + default: + return new RegularImmutableAsList(this, toArray()); + } + } + + /** + * Returns {@code true} if this immutable collection's implementation contains references to + * user-created objects that aren't accessible via this collection's methods. This is generally + * used to determine whether {@code copyOf} implementations should make an explicit copy to avoid + * memory leaks. + */ + abstract boolean isPartialView(); + + /** + * Copies the contents of this immutable collection into the specified array at the specified + * offset. Returns {@code offset + size()}. + */ + + int copyIntoArray(Object[] dst, int offset) { + for (E e : this) { + dst[offset++] = e; + } + return offset; + } + + Object writeReplace() { + // We serialize by default to ImmutableList, the simplest thing that works. + return new ImmutableList.SerializedForm(toArray()); + } + + /** + * Abstract base class for builders of {@link ImmutableCollection} types. + * + * @since 10.0 + */ + public abstract static class Builder { + static final int DEFAULT_INITIAL_CAPACITY = 4; + + static int expandedCapacity(int oldCapacity, int minCapacity) { + if (minCapacity < 0) { + throw new AssertionError("cannot store more than MAX_VALUE elements"); + } + // careful of overflow! + int newCapacity = oldCapacity + (oldCapacity >> 1) + 1; + if (newCapacity < minCapacity) { + newCapacity = Integer.highestOneBit(minCapacity - 1) << 1; + } + if (newCapacity < 0) { + newCapacity = Integer.MAX_VALUE; + // guaranteed to be >= newCapacity + } + return newCapacity; + } + + Builder() {} + + /** + * Adds {@code element} to the {@code ImmutableCollection} being built. + * + *

Note that each builder class covariantly returns its own type from this method. + * + * @param element the element to add + * @return this {@code Builder} instance + * @throws NullPointerException if {@code element} is null + */ + + public abstract Builder add(E element); + + /** + * Adds each element of {@code elements} to the {@code ImmutableCollection} being built. + * + *

Note that each builder class overrides this method in order to covariantly return its own + * type. + * + * @param elements the elements to add + * @return this {@code Builder} instance + * @throws NullPointerException if {@code elements} is null or contains a null element + */ + + public Builder add(E... elements) { + for (E element : elements) { + add(element); + } + return this; + } + + /** + * Adds each element of {@code elements} to the {@code ImmutableCollection} being built. + * + *

Note that each builder class overrides this method in order to covariantly return its own + * type. + * + * @param elements the elements to add + * @return this {@code Builder} instance + * @throws NullPointerException if {@code elements} is null or contains a null element + */ + + public Builder addAll(Iterable elements) { + for (E element : elements) { + add(element); + } + return this; + } + + /** + * Adds each element of {@code elements} to the {@code ImmutableCollection} being built. + * + *

Note that each builder class overrides this method in order to covariantly return its own + * type. + * + * @param elements the elements to add + * @return this {@code Builder} instance + * @throws NullPointerException if {@code elements} is null or contains a null element + */ + + public Builder addAll(Iterator elements) { + while (elements.hasNext()) { + add(elements.next()); + } + return this; + } + + /** + * Returns a newly-created {@code ImmutableCollection} of the appropriate type, containing the + * elements provided to this builder. + * + *

Note that each builder class covariantly returns the appropriate type of {@code + * ImmutableCollection} from this method. + */ + public abstract ImmutableCollection build(); + } +} diff --git a/src/main/java/com/google/common/collect/ImmutableEntry.java b/src/main/java/com/google/common/collect/ImmutableEntry.java new file mode 100644 index 0000000..2e747d1 --- /dev/null +++ b/src/main/java/com/google/common/collect/ImmutableEntry.java @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2008 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import com.google.common.annotations.GwtCompatible; +import java.io.Serializable; + + +/** @see com.google.common.collect.Maps#immutableEntry(Object, Object) */ +@GwtCompatible(serializable = true) +class ImmutableEntry extends AbstractMapEntry implements Serializable { + final K key; + final V value; + + ImmutableEntry(K key, V value) { + this.key = key; + this.value = value; + } + + @Override + public final K getKey() { + return key; + } + + @Override + public final V getValue() { + return value; + } + + @Override + public final V setValue(V value) { + throw new UnsupportedOperationException(); + } + + private static final long serialVersionUID = 0; +} diff --git a/src/main/java/com/google/common/collect/ImmutableEnumMap.java b/src/main/java/com/google/common/collect/ImmutableEnumMap.java new file mode 100644 index 0000000..4d4be7e --- /dev/null +++ b/src/main/java/com/google/common/collect/ImmutableEnumMap.java @@ -0,0 +1,134 @@ +/* + * Copyright (C) 2012 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import static com.google.common.base.Preconditions.checkArgument; + +import com.google.common.annotations.GwtCompatible; +import com.google.common.collect.ImmutableMap.IteratorBasedImmutableMap; +import java.io.Serializable; +import java.util.EnumMap; +import java.util.Spliterator; +import java.util.function.BiConsumer; + + +/** + * Implementation of {@link ImmutableMap} backed by a non-empty {@link java.util.EnumMap}. + * + * @author Louis Wasserman + */ +@GwtCompatible(serializable = true, emulated = true) +@SuppressWarnings("serial") // we're overriding default serialization +final class ImmutableEnumMap, V> extends IteratorBasedImmutableMap { + static , V> ImmutableMap asImmutable(EnumMap map) { + switch (map.size()) { + case 0: + return ImmutableMap.of(); + case 1: + Entry entry = Iterables.getOnlyElement(map.entrySet()); + return ImmutableMap.of(entry.getKey(), entry.getValue()); + default: + return new ImmutableEnumMap<>(map); + } + } + + private final transient EnumMap delegate; + + private ImmutableEnumMap(EnumMap delegate) { + this.delegate = delegate; + checkArgument(!delegate.isEmpty()); + } + + @Override + UnmodifiableIterator keyIterator() { + return Iterators.unmodifiableIterator(delegate.keySet().iterator()); + } + + @Override + Spliterator keySpliterator() { + return delegate.keySet().spliterator(); + } + + @Override + public int size() { + return delegate.size(); + } + + @Override + public boolean containsKey(Object key) { + return delegate.containsKey(key); + } + + @Override + public V get(Object key) { + return delegate.get(key); + } + + @Override + public boolean equals(Object object) { + if (object == this) { + return true; + } + if (object instanceof ImmutableEnumMap) { + object = ((ImmutableEnumMap) object).delegate; + } + return delegate.equals(object); + } + + @Override + UnmodifiableIterator> entryIterator() { + return Maps.unmodifiableEntryIterator(delegate.entrySet().iterator()); + } + + @Override + Spliterator> entrySpliterator() { + return CollectSpliterators.map(delegate.entrySet().spliterator(), Maps::unmodifiableEntry); + } + + @Override + public void forEach(BiConsumer action) { + delegate.forEach(action); + } + + @Override + boolean isPartialView() { + return false; + } + + // All callers of the constructor are restricted to >. + @Override + Object writeReplace() { + return new EnumSerializedForm<>(delegate); + } + + /* + * This class is used to serialize ImmutableEnumMap instances. + */ + private static class EnumSerializedForm, V> implements Serializable { + final EnumMap delegate; + + EnumSerializedForm(EnumMap delegate) { + this.delegate = delegate; + } + + Object readResolve() { + return new ImmutableEnumMap<>(delegate); + } + + private static final long serialVersionUID = 0; + } +} diff --git a/src/main/java/com/google/common/collect/ImmutableEnumSet.java b/src/main/java/com/google/common/collect/ImmutableEnumSet.java new file mode 100644 index 0000000..924266f --- /dev/null +++ b/src/main/java/com/google/common/collect/ImmutableEnumSet.java @@ -0,0 +1,156 @@ +/* + * Copyright (C) 2009 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import com.google.common.annotations.GwtCompatible; + +import java.io.Serializable; +import java.util.Collection; +import java.util.EnumSet; +import java.util.Spliterator; +import java.util.function.Consumer; + +/** + * Implementation of {@link ImmutableSet} backed by a non-empty {@link java.util.EnumSet}. + * + * @author Jared Levy + */ +@GwtCompatible(serializable = true, emulated = true) +@SuppressWarnings("serial") // we're overriding default serialization +final class ImmutableEnumSet> extends ImmutableSet { + @SuppressWarnings("rawtypes") // necessary to compile against Java 8 + static ImmutableSet asImmutable(EnumSet set) { + switch (set.size()) { + case 0: + return ImmutableSet.of(); + case 1: + return ImmutableSet.of(Iterables.getOnlyElement(set)); + default: + return new ImmutableEnumSet(set); + } + } + + /* + * Notes on EnumSet and >: + * + * This class isn't an arbitrary ForwardingImmutableSet because we need to + * know that calling {@code clone()} during deserialization will return an + * object that no one else has a reference to, allowing us to guarantee + * immutability. Hence, we support only {@link EnumSet}. + */ + private final transient EnumSet delegate; + + private ImmutableEnumSet(EnumSet delegate) { + this.delegate = delegate; + } + + @Override + boolean isPartialView() { + return false; + } + + @Override + public UnmodifiableIterator iterator() { + return Iterators.unmodifiableIterator(delegate.iterator()); + } + + @Override + public Spliterator spliterator() { + return delegate.spliterator(); + } + + @Override + public void forEach(Consumer action) { + delegate.forEach(action); + } + + @Override + public int size() { + return delegate.size(); + } + + @Override + public boolean contains(Object object) { + return delegate.contains(object); + } + + @Override + public boolean containsAll(Collection collection) { + if (collection instanceof ImmutableEnumSet) { + collection = ((ImmutableEnumSet) collection).delegate; + } + return delegate.containsAll(collection); + } + + @Override + public boolean isEmpty() { + return delegate.isEmpty(); + } + + @Override + public boolean equals(Object object) { + if (object == this) { + return true; + } + if (object instanceof ImmutableEnumSet) { + object = ((ImmutableEnumSet) object).delegate; + } + return delegate.equals(object); + } + + @Override + boolean isHashCodeFast() { + return true; + } + + private transient int hashCode; + + @Override + public int hashCode() { + int result = hashCode; + return (result == 0) ? hashCode = delegate.hashCode() : result; + } + + @Override + public String toString() { + return delegate.toString(); + } + + // All callers of the constructor are restricted to >. + @Override + Object writeReplace() { + return new EnumSerializedForm(delegate); + } + + /* + * This class is used to serialize ImmutableEnumSet instances. + */ + private static class EnumSerializedForm> implements Serializable { + final EnumSet delegate; + + EnumSerializedForm(EnumSet delegate) { + this.delegate = delegate; + } + + Object readResolve() { + // EJ2 #76: Write readObject() methods defensively. + return new ImmutableEnumSet(delegate.clone()); + } + + private static final long serialVersionUID = 0; + } +} diff --git a/src/main/java/com/google/common/collect/ImmutableList.java b/src/main/java/com/google/common/collect/ImmutableList.java new file mode 100644 index 0000000..5b3fa5c --- /dev/null +++ b/src/main/java/com/google/common/collect/ImmutableList.java @@ -0,0 +1,867 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkElementIndex; +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.base.Preconditions.checkPositionIndexes; +import static com.google.common.collect.CollectPreconditions.checkNonnegative; +import static com.google.common.collect.ObjectArrays.checkElementsNotNull; +import static com.google.common.collect.RegularImmutableList.EMPTY; + +import com.google.common.annotations.Beta; +import com.google.common.annotations.GwtCompatible; +import com.google.common.annotations.VisibleForTesting; + +import java.io.InvalidObjectException; +import java.io.ObjectInputStream; +import java.io.Serializable; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.Iterator; +import java.util.List; +import java.util.RandomAccess; +import java.util.Spliterator; +import java.util.function.Consumer; +import java.util.function.UnaryOperator; +import java.util.stream.Collector; + + +/** + * A {@link List} whose contents will never change, with many other important properties detailed at + * {@link ImmutableCollection}. + * + *

See the Guava User Guide article on immutable collections. + * + * @see ImmutableMap + * @see ImmutableSet + * @author Kevin Bourrillion + * @since 2.0 + */ +@GwtCompatible(serializable = true, emulated = true) +@SuppressWarnings("serial") // we're overriding default serialization +public abstract class ImmutableList extends ImmutableCollection + implements List, RandomAccess { + + /** + * Returns a {@code Collector} that accumulates the input elements into a new {@code + * ImmutableList}, in encounter order. + * + * @since 21.0 + */ + public static Collector> toImmutableList() { + return CollectCollectors.toImmutableList(); + } + + /** + * Returns the empty immutable list. This list behaves and performs comparably to {@link + * Collections#emptyList}, and is preferable mainly for consistency and maintainability of your + * code. + */ + // Casting to any type is safe because the list will never hold any elements. + @SuppressWarnings("unchecked") + public static ImmutableList of() { + return (ImmutableList) EMPTY; + } + + /** + * Returns an immutable list containing a single element. This list behaves and performs + * comparably to {@link Collections#singletonList}, but will not accept a null element. It is + * preferable mainly for consistency and maintainability of your code. + * + * @throws NullPointerException if {@code element} is null + */ + public static ImmutableList of(E element) { + return new SingletonImmutableList(element); + } + + /** + * Returns an immutable list containing the given elements, in order. + * + * @throws NullPointerException if any element is null + */ + public static ImmutableList of(E e1, E e2) { + return construct(e1, e2); + } + + /** + * Returns an immutable list containing the given elements, in order. + * + * @throws NullPointerException if any element is null + */ + public static ImmutableList of(E e1, E e2, E e3) { + return construct(e1, e2, e3); + } + + /** + * Returns an immutable list containing the given elements, in order. + * + * @throws NullPointerException if any element is null + */ + public static ImmutableList of(E e1, E e2, E e3, E e4) { + return construct(e1, e2, e3, e4); + } + + /** + * Returns an immutable list containing the given elements, in order. + * + * @throws NullPointerException if any element is null + */ + public static ImmutableList of(E e1, E e2, E e3, E e4, E e5) { + return construct(e1, e2, e3, e4, e5); + } + + /** + * Returns an immutable list containing the given elements, in order. + * + * @throws NullPointerException if any element is null + */ + public static ImmutableList of(E e1, E e2, E e3, E e4, E e5, E e6) { + return construct(e1, e2, e3, e4, e5, e6); + } + + /** + * Returns an immutable list containing the given elements, in order. + * + * @throws NullPointerException if any element is null + */ + public static ImmutableList of(E e1, E e2, E e3, E e4, E e5, E e6, E e7) { + return construct(e1, e2, e3, e4, e5, e6, e7); + } + + /** + * Returns an immutable list containing the given elements, in order. + * + * @throws NullPointerException if any element is null + */ + public static ImmutableList of(E e1, E e2, E e3, E e4, E e5, E e6, E e7, E e8) { + return construct(e1, e2, e3, e4, e5, e6, e7, e8); + } + + /** + * Returns an immutable list containing the given elements, in order. + * + * @throws NullPointerException if any element is null + */ + public static ImmutableList of(E e1, E e2, E e3, E e4, E e5, E e6, E e7, E e8, E e9) { + return construct(e1, e2, e3, e4, e5, e6, e7, e8, e9); + } + + /** + * Returns an immutable list containing the given elements, in order. + * + * @throws NullPointerException if any element is null + */ + public static ImmutableList of( + E e1, E e2, E e3, E e4, E e5, E e6, E e7, E e8, E e9, E e10) { + return construct(e1, e2, e3, e4, e5, e6, e7, e8, e9, e10); + } + + /** + * Returns an immutable list containing the given elements, in order. + * + * @throws NullPointerException if any element is null + */ + public static ImmutableList of( + E e1, E e2, E e3, E e4, E e5, E e6, E e7, E e8, E e9, E e10, E e11) { + return construct(e1, e2, e3, e4, e5, e6, e7, e8, e9, e10, e11); + } + + // These go up to eleven. After that, you just get the varargs form, and + // whatever warnings might come along with it. :( + + /** + * Returns an immutable list containing the given elements, in order. + * + *

The array {@code others} must not be longer than {@code Integer.MAX_VALUE - 12}. + * + * @throws NullPointerException if any element is null + * @since 3.0 (source-compatible since 2.0) + */ + @SafeVarargs // For Eclipse. For internal javac we have disabled this pointless type of warning. + public static ImmutableList of( + E e1, E e2, E e3, E e4, E e5, E e6, E e7, E e8, E e9, E e10, E e11, E e12, E... others) { + checkArgument( + others.length <= Integer.MAX_VALUE - 12, "the total number of elements must fit in an int"); + Object[] array = new Object[12 + others.length]; + array[0] = e1; + array[1] = e2; + array[2] = e3; + array[3] = e4; + array[4] = e5; + array[5] = e6; + array[6] = e7; + array[7] = e8; + array[8] = e9; + array[9] = e10; + array[10] = e11; + array[11] = e12; + System.arraycopy(others, 0, array, 12, others.length); + return construct(array); + } + + /** + * Returns an immutable list containing the given elements, in order. If {@code elements} is a + * {@link Collection}, this method behaves exactly as {@link #copyOf(Collection)}; otherwise, it + * behaves exactly as {@code copyOf(elements.iterator()}. + * + * @throws NullPointerException if {@code elements} contains a null element + */ + public static ImmutableList copyOf(Iterable elements) { + checkNotNull(elements); // TODO(kevinb): is this here only for GWT? + return (elements instanceof Collection) + ? copyOf((Collection) elements) + : copyOf(elements.iterator()); + } + + /** + * Returns an immutable list containing the given elements, in order. + * + *

Despite the method name, this method attempts to avoid actually copying the data when it is + * safe to do so. The exact circumstances under which a copy will or will not be performed are + * undocumented and subject to change. + * + *

Note that if {@code list} is a {@code List}, then {@code ImmutableList.copyOf(list)} + * returns an {@code ImmutableList} containing each of the strings in {@code list}, while + * ImmutableList.of(list)} returns an {@code ImmutableList>} containing one element + * (the given list itself). + * + *

This method is safe to use even when {@code elements} is a synchronized or concurrent + * collection that is currently being modified by another thread. + * + * @throws NullPointerException if {@code elements} contains a null element + */ + public static ImmutableList copyOf(Collection elements) { + if (elements instanceof ImmutableCollection) { + @SuppressWarnings("unchecked") // all supported methods are covariant + ImmutableList list = ((ImmutableCollection) elements).asList(); + return list.isPartialView() ? ImmutableList.asImmutableList(list.toArray()) : list; + } + return construct(elements.toArray()); + } + + /** + * Returns an immutable list containing the given elements, in order. + * + * @throws NullPointerException if {@code elements} contains a null element + */ + public static ImmutableList copyOf(Iterator elements) { + // We special-case for 0 or 1 elements, but going further is madness. + if (!elements.hasNext()) { + return of(); + } + E first = elements.next(); + if (!elements.hasNext()) { + return of(first); + } else { + return new ImmutableList.Builder().add(first).addAll(elements).build(); + } + } + + /** + * Returns an immutable list containing the given elements, in order. + * + * @throws NullPointerException if {@code elements} contains a null element + * @since 3.0 + */ + public static ImmutableList copyOf(E[] elements) { + switch (elements.length) { + case 0: + return of(); + case 1: + return of(elements[0]); + default: + return construct(elements.clone()); + } + } + + /** + * Returns an immutable list containing the given elements, sorted according to their natural + * order. The sorting algorithm used is stable, so elements that compare as equal will stay in the + * order in which they appear in the input. + * + *

If your data has no duplicates, or you wish to deduplicate elements, use {@code + * ImmutableSortedSet.copyOf(elements)}; if you want a {@code List} you can use its {@code + * asList()} view. + * + *

Java 8 users: If you want to convert a {@link java.util.stream.Stream} to a sorted + * {@code ImmutableList}, use {@code stream.sorted().collect(toImmutableList())}. + * + * @throws NullPointerException if any element in the input is null + * @since 21.0 + */ + public static > ImmutableList sortedCopyOf( + Iterable elements) { + Comparable[] array = Iterables.toArray(elements, new Comparable[0]); + checkElementsNotNull((Object[]) array); + Arrays.sort(array); + return asImmutableList(array); + } + + /** + * Returns an immutable list containing the given elements, in sorted order relative to the + * specified comparator. The sorting algorithm used is stable, so elements that compare as equal + * will stay in the order in which they appear in the input. + * + *

If your data has no duplicates, or you wish to deduplicate elements, use {@code + * ImmutableSortedSet.copyOf(comparator, elements)}; if you want a {@code List} you can use its + * {@code asList()} view. + * + *

Java 8 users: If you want to convert a {@link java.util.stream.Stream} to a sorted + * {@code ImmutableList}, use {@code stream.sorted(comparator).collect(toImmutableList())}. + * + * @throws NullPointerException if any element in the input is null + * @since 21.0 + */ + public static ImmutableList sortedCopyOf( + Comparator comparator, Iterable elements) { + checkNotNull(comparator); + @SuppressWarnings("unchecked") // all supported methods are covariant + E[] array = (E[]) Iterables.toArray(elements); + checkElementsNotNull(array); + Arrays.sort(array, comparator); + return asImmutableList(array); + } + + /** Views the array as an immutable list. Checks for nulls; does not copy. */ + private static ImmutableList construct(Object... elements) { + return asImmutableList(checkElementsNotNull(elements)); + } + + /** + * Views the array as an immutable list. Does not check for nulls; does not copy. + * + *

The array must be internally created. + */ + static ImmutableList asImmutableList(Object[] elements) { + return asImmutableList(elements, elements.length); + } + + /** + * Views the array as an immutable list. Copies if the specified range does not cover the complete + * array. Does not check for nulls. + */ + static ImmutableList asImmutableList(Object[] elements, int length) { + switch (length) { + case 0: + return of(); + case 1: + return of((E) elements[0]); + default: + if (length < elements.length) { + elements = Arrays.copyOf(elements, length); + } + return new RegularImmutableList(elements); + } + } + + ImmutableList() {} + + // This declaration is needed to make List.iterator() and + // ImmutableCollection.iterator() consistent. + @Override + public UnmodifiableIterator iterator() { + return listIterator(); + } + + @Override + public UnmodifiableListIterator listIterator() { + return listIterator(0); + } + + @Override + public UnmodifiableListIterator listIterator(int index) { + return new AbstractIndexedListIterator(size(), index) { + @Override + protected E get(int index) { + return ImmutableList.this.get(index); + } + }; + } + + @Override + public void forEach(Consumer consumer) { + checkNotNull(consumer); + int n = size(); + for (int i = 0; i < n; i++) { + consumer.accept(get(i)); + } + } + + @Override + public int indexOf(Object object) { + return (object == null) ? -1 : Lists.indexOfImpl(this, object); + } + + @Override + public int lastIndexOf(Object object) { + return (object == null) ? -1 : Lists.lastIndexOfImpl(this, object); + } + + @Override + public boolean contains(Object object) { + return indexOf(object) >= 0; + } + + // constrain the return type to ImmutableList + + /** + * Returns an immutable list of the elements between the specified {@code fromIndex}, inclusive, + * and {@code toIndex}, exclusive. (If {@code fromIndex} and {@code toIndex} are equal, the empty + * immutable list is returned.) + */ + @Override + public ImmutableList subList(int fromIndex, int toIndex) { + checkPositionIndexes(fromIndex, toIndex, size()); + int length = toIndex - fromIndex; + if (length == size()) { + return this; + } else if (length == 0) { + return of(); + } else if (length == 1) { + return of(get(fromIndex)); + } else { + return subListUnchecked(fromIndex, toIndex); + } + } + + /** + * Called by the default implementation of {@link #subList} when {@code toIndex - fromIndex > 1}, + * after index validation has already been performed. + */ + ImmutableList subListUnchecked(int fromIndex, int toIndex) { + return new SubList(fromIndex, toIndex - fromIndex); + } + + class SubList extends ImmutableList { + final transient int offset; + final transient int length; + + SubList(int offset, int length) { + this.offset = offset; + this.length = length; + } + + @Override + public int size() { + return length; + } + + @Override + public E get(int index) { + checkElementIndex(index, length); + return ImmutableList.this.get(index + offset); + } + + @Override + public ImmutableList subList(int fromIndex, int toIndex) { + checkPositionIndexes(fromIndex, toIndex, length); + return ImmutableList.this.subList(fromIndex + offset, toIndex + offset); + } + + @Override + boolean isPartialView() { + return true; + } + } + + /** + * Guaranteed to throw an exception and leave the list unmodified. + * + * @throws UnsupportedOperationException always + * @deprecated Unsupported operation. + */ + + @Deprecated + @Override + public final boolean addAll(int index, Collection newElements) { + throw new UnsupportedOperationException(); + } + + /** + * Guaranteed to throw an exception and leave the list unmodified. + * + * @throws UnsupportedOperationException always + * @deprecated Unsupported operation. + */ + + @Deprecated + @Override + public final E set(int index, E element) { + throw new UnsupportedOperationException(); + } + + /** + * Guaranteed to throw an exception and leave the list unmodified. + * + * @throws UnsupportedOperationException always + * @deprecated Unsupported operation. + */ + @Deprecated + @Override + public final void add(int index, E element) { + throw new UnsupportedOperationException(); + } + + /** + * Guaranteed to throw an exception and leave the list unmodified. + * + * @throws UnsupportedOperationException always + * @deprecated Unsupported operation. + */ + + @Deprecated + @Override + public final E remove(int index) { + throw new UnsupportedOperationException(); + } + + /** + * Guaranteed to throw an exception and leave the list unmodified. + * + * @throws UnsupportedOperationException always + * @deprecated Unsupported operation. + */ + @Deprecated + @Override + public final void replaceAll(UnaryOperator operator) { + throw new UnsupportedOperationException(); + } + + /** + * Guaranteed to throw an exception and leave the list unmodified. + * + * @throws UnsupportedOperationException always + * @deprecated Unsupported operation. + */ + @Deprecated + @Override + public final void sort(Comparator c) { + throw new UnsupportedOperationException(); + } + + /** + * Returns this list instance. + * + * @since 2.0 + */ + @Override + public final ImmutableList asList() { + return this; + } + + @Override + public Spliterator spliterator() { + return CollectSpliterators.indexed(size(), SPLITERATOR_CHARACTERISTICS, this::get); + } + + @Override + int copyIntoArray(Object[] dst, int offset) { + // this loop is faster for RandomAccess instances, which ImmutableLists are + int size = size(); + for (int i = 0; i < size; i++) { + dst[offset + i] = get(i); + } + return offset + size; + } + + /** + * Returns a view of this immutable list in reverse order. For example, {@code ImmutableList.of(1, + * 2, 3).reverse()} is equivalent to {@code ImmutableList.of(3, 2, 1)}. + * + * @return a view of this immutable list in reverse order + * @since 7.0 + */ + public ImmutableList reverse() { + return (size() <= 1) ? this : new ReverseImmutableList(this); + } + + private static class ReverseImmutableList extends ImmutableList { + private final transient ImmutableList forwardList; + + ReverseImmutableList(ImmutableList backingList) { + this.forwardList = backingList; + } + + private int reverseIndex(int index) { + return (size() - 1) - index; + } + + private int reversePosition(int index) { + return size() - index; + } + + @Override + public ImmutableList reverse() { + return forwardList; + } + + @Override + public boolean contains(Object object) { + return forwardList.contains(object); + } + + @Override + public int indexOf(Object object) { + int index = forwardList.lastIndexOf(object); + return (index >= 0) ? reverseIndex(index) : -1; + } + + @Override + public int lastIndexOf(Object object) { + int index = forwardList.indexOf(object); + return (index >= 0) ? reverseIndex(index) : -1; + } + + @Override + public ImmutableList subList(int fromIndex, int toIndex) { + checkPositionIndexes(fromIndex, toIndex, size()); + return forwardList.subList(reversePosition(toIndex), reversePosition(fromIndex)).reverse(); + } + + @Override + public E get(int index) { + checkElementIndex(index, size()); + return forwardList.get(reverseIndex(index)); + } + + @Override + public int size() { + return forwardList.size(); + } + + @Override + boolean isPartialView() { + return forwardList.isPartialView(); + } + } + + @Override + public boolean equals(Object obj) { + return Lists.equalsImpl(this, obj); + } + + @Override + public int hashCode() { + int hashCode = 1; + int n = size(); + for (int i = 0; i < n; i++) { + hashCode = 31 * hashCode + get(i).hashCode(); + + hashCode = ~~hashCode; + // needed to deal with GWT integer overflow + } + return hashCode; + } + + /* + * Serializes ImmutableLists as their logical contents. This ensures that + * implementation types do not leak into the serialized representation. + */ + static class SerializedForm implements Serializable { + final Object[] elements; + + SerializedForm(Object[] elements) { + this.elements = elements; + } + + Object readResolve() { + return copyOf(elements); + } + + private static final long serialVersionUID = 0; + } + + private void readObject(ObjectInputStream stream) throws InvalidObjectException { + throw new InvalidObjectException("Use SerializedForm"); + } + + @Override + Object writeReplace() { + return new SerializedForm(toArray()); + } + + /** + * Returns a new builder. The generated builder is equivalent to the builder created by the {@link + * Builder} constructor. + */ + public static Builder builder() { + return new Builder(); + } + + /** + * Returns a new builder, expecting the specified number of elements to be added. + * + *

If {@code expectedSize} is exactly the number of elements added to the builder before {@link + * Builder#build} is called, the builder is likely to perform better than an unsized {@link + * #builder()} would have. + * + *

It is not specified if any performance benefits apply if {@code expectedSize} is close to, + * but not exactly, the number of elements added to the builder. + * + * @since 23.1 + */ + @Beta + public static Builder builderWithExpectedSize(int expectedSize) { + checkNonnegative(expectedSize, "expectedSize"); + return new ImmutableList.Builder(expectedSize); + } + + /** + * A builder for creating immutable list instances, especially {@code public static final} lists + * ("constant lists"). Example: + * + *

{@code
+   * public static final ImmutableList GOOGLE_COLORS
+   *     = new ImmutableList.Builder()
+   *         .addAll(WEBSAFE_COLORS)
+   *         .add(new Color(0, 191, 255))
+   *         .build();
+   * }
+ * + *

Elements appear in the resulting list in the same order they were added to the builder. + * + *

Builder instances can be reused; it is safe to call {@link #build} multiple times to build + * multiple lists in series. Each new list contains all the elements of the ones created before + * it. + * + * @since 2.0 + */ + public static final class Builder extends ImmutableCollection.Builder { + @VisibleForTesting Object[] contents; + private int size; + private boolean forceCopy; + + /** + * Creates a new builder. The returned builder is equivalent to the builder generated by {@link + * ImmutableList#builder}. + */ + public Builder() { + this(DEFAULT_INITIAL_CAPACITY); + } + + Builder(int capacity) { + this.contents = new Object[capacity]; + this.size = 0; + } + + private void getReadyToExpandTo(int minCapacity) { + if (contents.length < minCapacity) { + this.contents = Arrays.copyOf(contents, expandedCapacity(contents.length, minCapacity)); + forceCopy = false; + } else if (forceCopy) { + contents = Arrays.copyOf(contents, contents.length); + forceCopy = false; + } + } + + /** + * Adds {@code element} to the {@code ImmutableList}. + * + * @param element the element to add + * @return this {@code Builder} object + * @throws NullPointerException if {@code element} is null + */ + + @Override + public Builder add(E element) { + checkNotNull(element); + getReadyToExpandTo(size + 1); + contents[size++] = element; + return this; + } + + /** + * Adds each element of {@code elements} to the {@code ImmutableList}. + * + * @param elements the {@code Iterable} to add to the {@code ImmutableList} + * @return this {@code Builder} object + * @throws NullPointerException if {@code elements} is null or contains a null element + */ + + @Override + public Builder add(E... elements) { + checkElementsNotNull(elements); + add(elements, elements.length); + return this; + } + + private void add(Object[] elements, int n) { + getReadyToExpandTo(size + n); + System.arraycopy(elements, 0, contents, size, n); + size += n; + } + + /** + * Adds each element of {@code elements} to the {@code ImmutableList}. + * + * @param elements the {@code Iterable} to add to the {@code ImmutableList} + * @return this {@code Builder} object + * @throws NullPointerException if {@code elements} is null or contains a null element + */ + + @Override + public Builder addAll(Iterable elements) { + checkNotNull(elements); + if (elements instanceof Collection) { + Collection collection = (Collection) elements; + getReadyToExpandTo(size + collection.size()); + if (collection instanceof ImmutableCollection) { + ImmutableCollection immutableCollection = (ImmutableCollection) collection; + size = immutableCollection.copyIntoArray(contents, size); + return this; + } + } + super.addAll(elements); + return this; + } + + /** + * Adds each element of {@code elements} to the {@code ImmutableList}. + * + * @param elements the {@code Iterator} to add to the {@code ImmutableList} + * @return this {@code Builder} object + * @throws NullPointerException if {@code elements} is null or contains a null element + */ + + @Override + public Builder addAll(Iterator elements) { + super.addAll(elements); + return this; + } + + + Builder combine(Builder builder) { + checkNotNull(builder); + add(builder.contents, builder.size); + return this; + } + + /** + * Returns a newly-created {@code ImmutableList} based on the contents of the {@code Builder}. + */ + @Override + public ImmutableList build() { + forceCopy = true; + return asImmutableList(contents, size); + } + } +} diff --git a/src/main/java/com/google/common/collect/ImmutableListMultimap.java b/src/main/java/com/google/common/collect/ImmutableListMultimap.java new file mode 100644 index 0000000..da1a8b5 --- /dev/null +++ b/src/main/java/com/google/common/collect/ImmutableListMultimap.java @@ -0,0 +1,510 @@ +/* + * Copyright (C) 2008 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.annotations.Beta; +import com.google.common.annotations.GwtCompatible; +import com.google.common.annotations.GwtIncompatible; +import com.google.common.base.Preconditions; + + + +import java.io.IOException; +import java.io.InvalidObjectException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.util.Collection; +import java.util.Comparator; +import java.util.Map; +import java.util.Map.Entry; +import java.util.function.Function; +import java.util.stream.Collector; +import java.util.stream.Collectors; +import java.util.stream.Stream; + + +/** + * A {@link ListMultimap} whose contents will never change, with many other important properties + * detailed at {@link ImmutableCollection}. + * + *

See the Guava User Guide article on immutable collections. + * + * @author Jared Levy + * @since 2.0 + */ +@GwtCompatible(serializable = true, emulated = true) +public class ImmutableListMultimap extends ImmutableMultimap + implements ListMultimap { + /** + * Returns a {@link Collector} that accumulates elements into an {@code ImmutableListMultimap} + * whose keys and values are the result of applying the provided mapping functions to the input + * elements. + * + *

For streams with defined encounter order (as defined in the Ordering section of the {@link + * java.util.stream} Javadoc), that order is preserved, but entries are grouped by key. + * + *

Example: + * + *

{@code
+   * static final Multimap FIRST_LETTER_MULTIMAP =
+   *     Stream.of("banana", "apple", "carrot", "asparagus", "cherry")
+   *         .collect(toImmutableListMultimap(str -> str.charAt(0), str -> str.substring(1)));
+   *
+   * // is equivalent to
+   *
+   * static final Multimap FIRST_LETTER_MULTIMAP =
+   *     new ImmutableListMultimap.Builder()
+   *         .put('b', "anana")
+   *         .putAll('a', "pple", "sparagus")
+   *         .putAll('c', "arrot", "herry")
+   *         .build();
+   * }
+ * + * @since 21.0 + */ + public static Collector> toImmutableListMultimap( + Function keyFunction, + Function valueFunction) { + checkNotNull(keyFunction, "keyFunction"); + checkNotNull(valueFunction, "valueFunction"); + return Collector.of( + ImmutableListMultimap::builder, + (builder, t) -> builder.put(keyFunction.apply(t), valueFunction.apply(t)), + ImmutableListMultimap.Builder::combine, + ImmutableListMultimap.Builder::build); + } + + /** + * Returns a {@code Collector} accumulating entries into an {@code ImmutableListMultimap}. Each + * input element is mapped to a key and a stream of values, each of which are put into the + * resulting {@code Multimap}, in the encounter order of the stream and the encounter order of the + * streams of values. + * + *

Example: + * + *

{@code
+   * static final ImmutableListMultimap FIRST_LETTER_MULTIMAP =
+   *     Stream.of("banana", "apple", "carrot", "asparagus", "cherry")
+   *         .collect(
+   *             flatteningToImmutableListMultimap(
+   *                  str -> str.charAt(0),
+   *                  str -> str.substring(1).chars().mapToObj(c -> (char) c));
+   *
+   * // is equivalent to
+   *
+   * static final ImmutableListMultimap FIRST_LETTER_MULTIMAP =
+   *     ImmutableListMultimap.builder()
+   *         .putAll('b', Arrays.asList('a', 'n', 'a', 'n', 'a'))
+   *         .putAll('a', Arrays.asList('p', 'p', 'l', 'e'))
+   *         .putAll('c', Arrays.asList('a', 'r', 'r', 'o', 't'))
+   *         .putAll('a', Arrays.asList('s', 'p', 'a', 'r', 'a', 'g', 'u', 's'))
+   *         .putAll('c', Arrays.asList('h', 'e', 'r', 'r', 'y'))
+   *         .build();
+   * }
+   * }
+ * + * @since 21.0 + */ + public static + Collector> flatteningToImmutableListMultimap( + Function keyFunction, + Function> valuesFunction) { + checkNotNull(keyFunction); + checkNotNull(valuesFunction); + return Collectors.collectingAndThen( + Multimaps.flatteningToMultimap( + input -> checkNotNull(keyFunction.apply(input)), + input -> valuesFunction.apply(input).peek(Preconditions::checkNotNull), + MultimapBuilder.linkedHashKeys().arrayListValues()::build), + ImmutableListMultimap::copyOf); + } + + /** Returns the empty multimap. */ + // Casting is safe because the multimap will never hold any elements. + @SuppressWarnings("unchecked") + public static ImmutableListMultimap of() { + return (ImmutableListMultimap) EmptyImmutableListMultimap.INSTANCE; + } + + /** Returns an immutable multimap containing a single entry. */ + public static ImmutableListMultimap of(K k1, V v1) { + ImmutableListMultimap.Builder builder = ImmutableListMultimap.builder(); + builder.put(k1, v1); + return builder.build(); + } + + /** Returns an immutable multimap containing the given entries, in order. */ + public static ImmutableListMultimap of(K k1, V v1, K k2, V v2) { + ImmutableListMultimap.Builder builder = ImmutableListMultimap.builder(); + builder.put(k1, v1); + builder.put(k2, v2); + return builder.build(); + } + + /** Returns an immutable multimap containing the given entries, in order. */ + public static ImmutableListMultimap of(K k1, V v1, K k2, V v2, K k3, V v3) { + ImmutableListMultimap.Builder builder = ImmutableListMultimap.builder(); + builder.put(k1, v1); + builder.put(k2, v2); + builder.put(k3, v3); + return builder.build(); + } + + /** Returns an immutable multimap containing the given entries, in order. */ + public static ImmutableListMultimap of( + K k1, V v1, K k2, V v2, K k3, V v3, K k4, V v4) { + ImmutableListMultimap.Builder builder = ImmutableListMultimap.builder(); + builder.put(k1, v1); + builder.put(k2, v2); + builder.put(k3, v3); + builder.put(k4, v4); + return builder.build(); + } + + /** Returns an immutable multimap containing the given entries, in order. */ + public static ImmutableListMultimap of( + K k1, V v1, K k2, V v2, K k3, V v3, K k4, V v4, K k5, V v5) { + ImmutableListMultimap.Builder builder = ImmutableListMultimap.builder(); + builder.put(k1, v1); + builder.put(k2, v2); + builder.put(k3, v3); + builder.put(k4, v4); + builder.put(k5, v5); + return builder.build(); + } + + // looking for of() with > 5 entries? Use the builder instead. + + /** + * Returns a new builder. The generated builder is equivalent to the builder created by the {@link + * Builder} constructor. + */ + public static Builder builder() { + return new Builder<>(); + } + + /** + * A builder for creating immutable {@code ListMultimap} instances, especially {@code public + * static final} multimaps ("constant multimaps"). Example: + * + *
{@code
+   * static final Multimap STRING_TO_INTEGER_MULTIMAP =
+   *     new ImmutableListMultimap.Builder()
+   *         .put("one", 1)
+   *         .putAll("several", 1, 2, 3)
+   *         .putAll("many", 1, 2, 3, 4, 5)
+   *         .build();
+   * }
+ * + *

Builder instances can be reused; it is safe to call {@link #build} multiple times to build + * multiple multimaps in series. Each multimap contains the key-value mappings in the previously + * created multimaps. + * + * @since 2.0 + */ + public static final class Builder extends ImmutableMultimap.Builder { + /** + * Creates a new builder. The returned builder is equivalent to the builder generated by {@link + * ImmutableListMultimap#builder}. + */ + public Builder() {} + + + @Override + public Builder put(K key, V value) { + super.put(key, value); + return this; + } + + /** + * {@inheritDoc} + * + * @since 11.0 + */ + + @Override + public Builder put(Entry entry) { + super.put(entry); + return this; + } + + /** + * {@inheritDoc} + * + * @since 19.0 + */ + + @Beta + @Override + public Builder putAll(Iterable> entries) { + super.putAll(entries); + return this; + } + + + @Override + public Builder putAll(K key, Iterable values) { + super.putAll(key, values); + return this; + } + + + @Override + public Builder putAll(K key, V... values) { + super.putAll(key, values); + return this; + } + + + @Override + public Builder putAll(Multimap multimap) { + super.putAll(multimap); + return this; + } + + + @Override + Builder combine(ImmutableMultimap.Builder other) { + super.combine(other); + return this; + } + + /** + * {@inheritDoc} + * + * @since 8.0 + */ + + @Override + public Builder orderKeysBy(Comparator keyComparator) { + super.orderKeysBy(keyComparator); + return this; + } + + /** + * {@inheritDoc} + * + * @since 8.0 + */ + + @Override + public Builder orderValuesBy(Comparator valueComparator) { + super.orderValuesBy(valueComparator); + return this; + } + + /** Returns a newly-created immutable list multimap. */ + @Override + public ImmutableListMultimap build() { + return (ImmutableListMultimap) super.build(); + } + } + + /** + * Returns an immutable multimap containing the same mappings as {@code multimap}. The generated + * multimap's key and value orderings correspond to the iteration ordering of the {@code + * multimap.asMap()} view. + * + *

Despite the method name, this method attempts to avoid actually copying the data when it is + * safe to do so. The exact circumstances under which a copy will or will not be performed are + * undocumented and subject to change. + * + * @throws NullPointerException if any key or value in {@code multimap} is null + */ + public static ImmutableListMultimap copyOf( + Multimap multimap) { + if (multimap.isEmpty()) { + return of(); + } + + // TODO(lowasser): copy ImmutableSetMultimap by using asList() on the sets + if (multimap instanceof ImmutableListMultimap) { + @SuppressWarnings("unchecked") // safe since multimap is not writable + ImmutableListMultimap kvMultimap = (ImmutableListMultimap) multimap; + if (!kvMultimap.isPartialView()) { + return kvMultimap; + } + } + + return fromMapEntries(multimap.asMap().entrySet(), null); + } + + /** + * Returns an immutable multimap containing the specified entries. The returned multimap iterates + * over keys in the order they were first encountered in the input, and the values for each key + * are iterated in the order they were encountered. + * + * @throws NullPointerException if any key, value, or entry is null + * @since 19.0 + */ + @Beta + public static ImmutableListMultimap copyOf( + Iterable> entries) { + return new Builder().putAll(entries).build(); + } + + /** Creates an ImmutableListMultimap from an asMap.entrySet. */ + static ImmutableListMultimap fromMapEntries( + Collection>> mapEntries, + Comparator valueComparator) { + if (mapEntries.isEmpty()) { + return of(); + } + ImmutableMap.Builder> builder = + new ImmutableMap.Builder<>(mapEntries.size()); + int size = 0; + + for (Entry> entry : mapEntries) { + K key = entry.getKey(); + Collection values = entry.getValue(); + ImmutableList list = + (valueComparator == null) + ? ImmutableList.copyOf(values) + : ImmutableList.sortedCopyOf(valueComparator, values); + if (!list.isEmpty()) { + builder.put(key, list); + size += list.size(); + } + } + + return new ImmutableListMultimap<>(builder.build(), size); + } + + ImmutableListMultimap(ImmutableMap> map, int size) { + super(map, size); + } + + // views + + /** + * Returns an immutable list of the values for the given key. If no mappings in the multimap have + * the provided key, an empty immutable list is returned. The values are in the same order as the + * parameters used to build this multimap. + */ + @Override + public ImmutableList get(K key) { + // This cast is safe as its type is known in constructor. + ImmutableList list = (ImmutableList) map.get(key); + return (list == null) ? ImmutableList.of() : list; + } + + private transient ImmutableListMultimap inverse; + + /** + * {@inheritDoc} + * + *

Because an inverse of a list multimap can contain multiple pairs with the same key and + * value, this method returns an {@code ImmutableListMultimap} rather than the {@code + * ImmutableMultimap} specified in the {@code ImmutableMultimap} class. + * + * @since 11.0 + */ + @Override + public ImmutableListMultimap inverse() { + ImmutableListMultimap result = inverse; + return (result == null) ? (inverse = invert()) : result; + } + + private ImmutableListMultimap invert() { + Builder builder = builder(); + for (Entry entry : entries()) { + builder.put(entry.getValue(), entry.getKey()); + } + ImmutableListMultimap invertedMultimap = builder.build(); + invertedMultimap.inverse = this; + return invertedMultimap; + } + + /** + * Guaranteed to throw an exception and leave the multimap unmodified. + * + * @throws UnsupportedOperationException always + * @deprecated Unsupported operation. + */ + + @Deprecated + @Override + public ImmutableList removeAll(Object key) { + throw new UnsupportedOperationException(); + } + + /** + * Guaranteed to throw an exception and leave the multimap unmodified. + * + * @throws UnsupportedOperationException always + * @deprecated Unsupported operation. + */ + + @Deprecated + @Override + public ImmutableList replaceValues(K key, Iterable values) { + throw new UnsupportedOperationException(); + } + + /** + * @serialData number of distinct keys, and then for each distinct key: the key, the number of + * values for that key, and the key's values + */ + @GwtIncompatible // java.io.ObjectOutputStream + private void writeObject(ObjectOutputStream stream) throws IOException { + stream.defaultWriteObject(); + Serialization.writeMultimap(this, stream); + } + + @GwtIncompatible // java.io.ObjectInputStream + private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException { + stream.defaultReadObject(); + int keyCount = stream.readInt(); + if (keyCount < 0) { + throw new InvalidObjectException("Invalid key count " + keyCount); + } + ImmutableMap.Builder> builder = ImmutableMap.builder(); + int tmpSize = 0; + + for (int i = 0; i < keyCount; i++) { + Object key = stream.readObject(); + int valueCount = stream.readInt(); + if (valueCount <= 0) { + throw new InvalidObjectException("Invalid value count " + valueCount); + } + + ImmutableList.Builder valuesBuilder = ImmutableList.builder(); + for (int j = 0; j < valueCount; j++) { + valuesBuilder.add(stream.readObject()); + } + builder.put(key, valuesBuilder.build()); + tmpSize += valueCount; + } + + ImmutableMap> tmpMap; + try { + tmpMap = builder.build(); + } catch (IllegalArgumentException e) { + throw (InvalidObjectException) new InvalidObjectException(e.getMessage()).initCause(e); + } + + FieldSettersHolder.MAP_FIELD_SETTER.set(this, tmpMap); + FieldSettersHolder.SIZE_FIELD_SETTER.set(this, tmpSize); + } + + @GwtIncompatible // Not needed in emulated source + private static final long serialVersionUID = 0; +} diff --git a/src/main/java/com/google/common/collect/ImmutableMap.java b/src/main/java/com/google/common/collect/ImmutableMap.java new file mode 100644 index 0000000..f36e55b --- /dev/null +++ b/src/main/java/com/google/common/collect/ImmutableMap.java @@ -0,0 +1,925 @@ +/* + * Copyright (C) 2008 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.base.Preconditions.checkState; +import static com.google.common.collect.CollectPreconditions.checkEntryNotNull; +import static com.google.common.collect.CollectPreconditions.checkNonnegative; + +import com.google.common.annotations.Beta; +import com.google.common.annotations.GwtCompatible; +import com.google.common.annotations.VisibleForTesting; + + + + +import java.io.Serializable; +import java.util.AbstractMap; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.EnumMap; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.SortedMap; +import java.util.Spliterator; +import java.util.Spliterators; +import java.util.function.BiFunction; +import java.util.function.BinaryOperator; +import java.util.function.Function; +import java.util.stream.Collector; +import java.util.stream.Collectors; + + + +/** + * A {@link Map} whose contents will never change, with many other important properties detailed at + * {@link ImmutableCollection}. + * + *

See the Guava User Guide article on immutable collections. + * + * @author Jesse Wilson + * @author Kevin Bourrillion + * @since 2.0 + */ +@GwtCompatible(serializable = true, emulated = true) +@SuppressWarnings("serial") // we're overriding default serialization +public abstract class ImmutableMap implements Map, Serializable { + + /** + * Returns a {@link Collector} that accumulates elements into an {@code ImmutableMap} whose keys + * and values are the result of applying the provided mapping functions to the input elements. + * Entries appear in the result {@code ImmutableMap} in encounter order. + * + *

If the mapped keys contain duplicates (according to {@link Object#equals(Object)}, an {@code + * IllegalArgumentException} is thrown when the collection operation is performed. (This differs + * from the {@code Collector} returned by {@link Collectors#toMap(Function, Function)}, which + * throws an {@code IllegalStateException}.) + * + * @since 21.0 + */ + public static Collector> toImmutableMap( + Function keyFunction, + Function valueFunction) { + return CollectCollectors.toImmutableMap(keyFunction, valueFunction); + } + + /** + * Returns a {@link Collector} that accumulates elements into an {@code ImmutableMap} whose keys + * and values are the result of applying the provided mapping functions to the input elements. + * + *

If the mapped keys contain duplicates (according to {@link Object#equals(Object)}), the + * values are merged using the specified merging function. Entries will appear in the encounter + * order of the first occurrence of the key. + * + * @since 21.0 + */ + public static Collector> toImmutableMap( + Function keyFunction, + Function valueFunction, + BinaryOperator mergeFunction) { + checkNotNull(keyFunction); + checkNotNull(valueFunction); + checkNotNull(mergeFunction); + return Collectors.collectingAndThen( + Collectors.toMap(keyFunction, valueFunction, mergeFunction, LinkedHashMap::new), + ImmutableMap::copyOf); + } + + /** + * Returns the empty map. This map behaves and performs comparably to {@link + * Collections#emptyMap}, and is preferable mainly for consistency and maintainability of your + * code. + */ + @SuppressWarnings("unchecked") + public static ImmutableMap of() { + return (ImmutableMap) RegularImmutableMap.EMPTY; + } + + /** + * Returns an immutable map containing a single entry. This map behaves and performs comparably to + * {@link Collections#singletonMap} but will not accept a null key or value. It is preferable + * mainly for consistency and maintainability of your code. + */ + public static ImmutableMap of(K k1, V v1) { + return ImmutableBiMap.of(k1, v1); + } + + /** + * Returns an immutable map containing the given entries, in order. + * + * @throws IllegalArgumentException if duplicate keys are provided + */ + public static ImmutableMap of(K k1, V v1, K k2, V v2) { + return RegularImmutableMap.fromEntries(entryOf(k1, v1), entryOf(k2, v2)); + } + + /** + * Returns an immutable map containing the given entries, in order. + * + * @throws IllegalArgumentException if duplicate keys are provided + */ + public static ImmutableMap of(K k1, V v1, K k2, V v2, K k3, V v3) { + return RegularImmutableMap.fromEntries(entryOf(k1, v1), entryOf(k2, v2), entryOf(k3, v3)); + } + + /** + * Returns an immutable map containing the given entries, in order. + * + * @throws IllegalArgumentException if duplicate keys are provided + */ + public static ImmutableMap of(K k1, V v1, K k2, V v2, K k3, V v3, K k4, V v4) { + return RegularImmutableMap.fromEntries( + entryOf(k1, v1), entryOf(k2, v2), entryOf(k3, v3), entryOf(k4, v4)); + } + + /** + * Returns an immutable map containing the given entries, in order. + * + * @throws IllegalArgumentException if duplicate keys are provided + */ + public static ImmutableMap of( + K k1, V v1, K k2, V v2, K k3, V v3, K k4, V v4, K k5, V v5) { + return RegularImmutableMap.fromEntries( + entryOf(k1, v1), entryOf(k2, v2), entryOf(k3, v3), entryOf(k4, v4), entryOf(k5, v5)); + } + + // looking for of() with > 5 entries? Use the builder instead. + + /** + * Verifies that {@code key} and {@code value} are non-null, and returns a new immutable entry + * with those values. + * + *

A call to {@link Entry#setValue} on the returned entry will always throw {@link + * UnsupportedOperationException}. + */ + static Entry entryOf(K key, V value) { + checkEntryNotNull(key, value); + return new AbstractMap.SimpleImmutableEntry<>(key, value); + } + + /** + * Returns a new builder. The generated builder is equivalent to the builder created by the {@link + * Builder} constructor. + */ + public static Builder builder() { + return new Builder<>(); + } + + /** + * Returns a new builder, expecting the specified number of entries to be added. + * + *

If {@code expectedSize} is exactly the number of entries added to the builder before {@link + * Builder#build} is called, the builder is likely to perform better than an unsized {@link + * #builder()} would have. + * + *

It is not specified if any performance benefits apply if {@code expectedSize} is close to, + * but not exactly, the number of entries added to the builder. + * + * @since 23.1 + */ + @Beta + public static Builder builderWithExpectedSize(int expectedSize) { + checkNonnegative(expectedSize, "expectedSize"); + return new Builder<>(expectedSize); + } + + static void checkNoConflict( + boolean safe, String conflictDescription, Entry entry1, Entry entry2) { + if (!safe) { + throw conflictException(conflictDescription, entry1, entry2); + } + } + + static IllegalArgumentException conflictException( + String conflictDescription, Object entry1, Object entry2) { + return new IllegalArgumentException( + "Multiple entries with same " + conflictDescription + ": " + entry1 + " and " + entry2); + } + + /** + * A builder for creating immutable map instances, especially {@code public static final} maps + * ("constant maps"). Example: + * + *

{@code
+   * static final ImmutableMap WORD_TO_INT =
+   *     new ImmutableMap.Builder()
+   *         .put("one", 1)
+   *         .put("two", 2)
+   *         .put("three", 3)
+   *         .build();
+   * }
+ * + *

For small immutable maps, the {@code ImmutableMap.of()} methods are even more + * convenient. + * + *

By default, a {@code Builder} will generate maps that iterate over entries in the order they + * were inserted into the builder, equivalently to {@code LinkedHashMap}. For example, in the + * above example, {@code WORD_TO_INT.entrySet()} is guaranteed to iterate over the entries in the + * order {@code "one"=1, "two"=2, "three"=3}, and {@code keySet()} and {@code values()} respect + * the same order. If you want a different order, consider using {@link ImmutableSortedMap} to + * sort by keys, or call {@link #orderEntriesByValue(Comparator)}, which changes this builder to + * sort entries by value. + * + *

Builder instances can be reused - it is safe to call {@link #build} multiple times to build + * multiple maps in series. Each map is a superset of the maps created before it. + * + * @since 2.0 + */ + public static class Builder { + Comparator valueComparator; + Entry[] entries; + int size; + boolean entriesUsed; + + /** + * Creates a new builder. The returned builder is equivalent to the builder generated by {@link + * ImmutableMap#builder}. + */ + public Builder() { + this(ImmutableCollection.Builder.DEFAULT_INITIAL_CAPACITY); + } + + @SuppressWarnings("unchecked") + Builder(int initialCapacity) { + this.entries = new Entry[initialCapacity]; + this.size = 0; + this.entriesUsed = false; + } + + private void ensureCapacity(int minCapacity) { + if (minCapacity > entries.length) { + entries = + Arrays.copyOf( + entries, ImmutableCollection.Builder.expandedCapacity(entries.length, minCapacity)); + entriesUsed = false; + } + } + + /** + * Associates {@code key} with {@code value} in the built map. Duplicate keys are not allowed, + * and will cause {@link #build} to fail. + */ + + public Builder put(K key, V value) { + ensureCapacity(size + 1); + Entry entry = entryOf(key, value); + // don't inline this: we want to fail atomically if key or value is null + entries[size++] = entry; + return this; + } + + /** + * Adds the given {@code entry} to the map, making it immutable if necessary. Duplicate keys are + * not allowed, and will cause {@link #build} to fail. + * + * @since 11.0 + */ + + public Builder put(Entry entry) { + return put(entry.getKey(), entry.getValue()); + } + + /** + * Associates all of the given map's keys and values in the built map. Duplicate keys are not + * allowed, and will cause {@link #build} to fail. + * + * @throws NullPointerException if any key or value in {@code map} is null + */ + + public Builder putAll(Map map) { + return putAll(map.entrySet()); + } + + /** + * Adds all of the given entries to the built map. Duplicate keys are not allowed, and will + * cause {@link #build} to fail. + * + * @throws NullPointerException if any key, value, or entry is null + * @since 19.0 + */ + + @Beta + public Builder putAll(Iterable> entries) { + if (entries instanceof Collection) { + ensureCapacity(size + ((Collection) entries).size()); + } + for (Entry entry : entries) { + put(entry); + } + return this; + } + + /** + * Configures this {@code Builder} to order entries by value according to the specified + * comparator. + * + *

The sort order is stable, that is, if two entries have values that compare as equivalent, + * the entry that was inserted first will be first in the built map's iteration order. + * + * @throws IllegalStateException if this method was already called + * @since 19.0 + */ + + @Beta + public Builder orderEntriesByValue(Comparator valueComparator) { + checkState(this.valueComparator == null, "valueComparator was already set"); + this.valueComparator = checkNotNull(valueComparator, "valueComparator"); + return this; + } + + + Builder combine(Builder other) { + checkNotNull(other); + ensureCapacity(this.size + other.size); + System.arraycopy(other.entries, 0, this.entries, this.size, other.size); + this.size += other.size; + return this; + } + + /* + * TODO(kevinb): Should build() and the ImmutableBiMap & ImmutableSortedMap + * versions throw an IllegalStateException instead? + */ + + /** + * Returns a newly-created immutable map. The iteration order of the returned map is the order + * in which entries were inserted into the builder, unless {@link #orderEntriesByValue} was + * called, in which case entries are sorted by value. + * + * @throws IllegalArgumentException if duplicate keys were added + */ + public ImmutableMap build() { + /* + * If entries is full, or if hash flooding is detected, then this implementation may end up + * using the entries array directly and writing over the entry objects with non-terminal + * entries, but this is safe; if this Builder is used further, it will grow the entries array + * (so it can't affect the original array), and future build() calls will always copy any + * entry objects that cannot be safely reused. + */ + if (valueComparator != null) { + if (entriesUsed) { + entries = Arrays.copyOf(entries, size); + } + Arrays.sort( + entries, 0, size, Ordering.from(valueComparator).onResultOf(Maps.valueFunction())); + } + switch (size) { + case 0: + return of(); + case 1: + return of(entries[0].getKey(), entries[0].getValue()); + default: + entriesUsed = true; + return RegularImmutableMap.fromEntryArray(size, entries); + } + } + + @VisibleForTesting // only for testing JDK backed implementation + ImmutableMap buildJdkBacked() { + checkState( + valueComparator == null, "buildJdkBacked is only for testing; can't use valueComparator"); + switch (size) { + case 0: + return of(); + case 1: + return of(entries[0].getKey(), entries[0].getValue()); + default: + entriesUsed = true; + return JdkBackedImmutableMap.create(size, entries); + } + } + } + + /** + * Returns an immutable map containing the same entries as {@code map}. The returned map iterates + * over entries in the same order as the {@code entrySet} of the original map. If {@code map} + * somehow contains entries with duplicate keys (for example, if it is a {@code SortedMap} whose + * comparator is not consistent with equals), the results of this method are undefined. + * + *

Despite the method name, this method attempts to avoid actually copying the data when it is + * safe to do so. The exact circumstances under which a copy will or will not be performed are + * undocumented and subject to change. + * + * @throws NullPointerException if any key or value in {@code map} is null + */ + public static ImmutableMap copyOf(Map map) { + if ((map instanceof ImmutableMap) && !(map instanceof SortedMap)) { + @SuppressWarnings("unchecked") // safe since map is not writable + ImmutableMap kvMap = (ImmutableMap) map; + if (!kvMap.isPartialView()) { + return kvMap; + } + } else if (map instanceof EnumMap) { + @SuppressWarnings("unchecked") // safe since map is not writable + ImmutableMap kvMap = (ImmutableMap) copyOfEnumMap((EnumMap) map); + return kvMap; + } + return copyOf(map.entrySet()); + } + + /** + * Returns an immutable map containing the specified entries. The returned map iterates over + * entries in the same order as the original iterable. + * + * @throws NullPointerException if any key, value, or entry is null + * @throws IllegalArgumentException if two entries have the same key + * @since 19.0 + */ + @Beta + public static ImmutableMap copyOf( + Iterable> entries) { + @SuppressWarnings("unchecked") // we'll only be using getKey and getValue, which are covariant + Entry[] entryArray = (Entry[]) Iterables.toArray(entries, EMPTY_ENTRY_ARRAY); + switch (entryArray.length) { + case 0: + return of(); + case 1: + Entry onlyEntry = entryArray[0]; + return of(onlyEntry.getKey(), onlyEntry.getValue()); + default: + /* + * The current implementation will end up using entryArray directly, though it will write + * over the (arbitrary, potentially mutable) Entry objects actually stored in entryArray. + */ + return RegularImmutableMap.fromEntries(entryArray); + } + } + + private static , V> ImmutableMap copyOfEnumMap( + EnumMap original) { + EnumMap copy = new EnumMap<>(original); + for (Entry entry : copy.entrySet()) { + checkEntryNotNull(entry.getKey(), entry.getValue()); + } + return ImmutableEnumMap.asImmutable(copy); + } + + static final Entry[] EMPTY_ENTRY_ARRAY = new Entry[0]; + + abstract static class IteratorBasedImmutableMap extends ImmutableMap { + abstract UnmodifiableIterator> entryIterator(); + + Spliterator> entrySpliterator() { + return Spliterators.spliterator( + entryIterator(), + size(), + Spliterator.DISTINCT | Spliterator.NONNULL | Spliterator.IMMUTABLE | Spliterator.ORDERED); + } + + @Override + ImmutableSet createKeySet() { + return new ImmutableMapKeySet<>(this); + } + + @Override + ImmutableSet> createEntrySet() { + class EntrySetImpl extends ImmutableMapEntrySet { + @Override + ImmutableMap map() { + return IteratorBasedImmutableMap.this; + } + + @Override + public UnmodifiableIterator> iterator() { + return entryIterator(); + } + } + return new EntrySetImpl(); + } + + @Override + ImmutableCollection createValues() { + return new ImmutableMapValues<>(this); + } + } + + ImmutableMap() {} + + /** + * Guaranteed to throw an exception and leave the map unmodified. + * + * @throws UnsupportedOperationException always + * @deprecated Unsupported operation. + */ + + @Deprecated + @Override + public final V put(K k, V v) { + throw new UnsupportedOperationException(); + } + + /** + * Guaranteed to throw an exception and leave the map unmodified. + * + * @throws UnsupportedOperationException always + * @deprecated Unsupported operation. + */ + + @Deprecated + @Override + public final V putIfAbsent(K key, V value) { + throw new UnsupportedOperationException(); + } + + /** + * Guaranteed to throw an exception and leave the map unmodified. + * + * @throws UnsupportedOperationException always + * @deprecated Unsupported operation. + */ + @Deprecated + @Override + public final boolean replace(K key, V oldValue, V newValue) { + throw new UnsupportedOperationException(); + } + + /** + * Guaranteed to throw an exception and leave the map unmodified. + * + * @throws UnsupportedOperationException always + * @deprecated Unsupported operation. + */ + @Deprecated + @Override + public final V replace(K key, V value) { + throw new UnsupportedOperationException(); + } + + /** + * Guaranteed to throw an exception and leave the map unmodified. + * + * @throws UnsupportedOperationException always + * @deprecated Unsupported operation. + */ + @Deprecated + @Override + public final V computeIfAbsent(K key, Function mappingFunction) { + throw new UnsupportedOperationException(); + } + + /** + * Guaranteed to throw an exception and leave the map unmodified. + * + * @throws UnsupportedOperationException always + * @deprecated Unsupported operation. + */ + @Deprecated + @Override + public final V computeIfPresent( + K key, BiFunction remappingFunction) { + throw new UnsupportedOperationException(); + } + + /** + * Guaranteed to throw an exception and leave the map unmodified. + * + * @throws UnsupportedOperationException always + * @deprecated Unsupported operation. + */ + @Deprecated + @Override + public final V compute(K key, BiFunction remappingFunction) { + throw new UnsupportedOperationException(); + } + + /** + * Guaranteed to throw an exception and leave the map unmodified. + * + * @throws UnsupportedOperationException always + * @deprecated Unsupported operation. + */ + @Deprecated + @Override + public final V merge( + K key, V value, BiFunction remappingFunction) { + throw new UnsupportedOperationException(); + } + + /** + * Guaranteed to throw an exception and leave the map unmodified. + * + * @throws UnsupportedOperationException always + * @deprecated Unsupported operation. + */ + @Deprecated + @Override + public final void putAll(Map map) { + throw new UnsupportedOperationException(); + } + + /** + * Guaranteed to throw an exception and leave the map unmodified. + * + * @throws UnsupportedOperationException always + * @deprecated Unsupported operation. + */ + @Deprecated + @Override + public final void replaceAll(BiFunction function) { + throw new UnsupportedOperationException(); + } + + /** + * Guaranteed to throw an exception and leave the map unmodified. + * + * @throws UnsupportedOperationException always + * @deprecated Unsupported operation. + */ + @Deprecated + @Override + public final V remove(Object o) { + throw new UnsupportedOperationException(); + } + + /** + * Guaranteed to throw an exception and leave the map unmodified. + * + * @throws UnsupportedOperationException always + * @deprecated Unsupported operation. + */ + @Deprecated + @Override + public final boolean remove(Object key, Object value) { + throw new UnsupportedOperationException(); + } + + /** + * Guaranteed to throw an exception and leave the map unmodified. + * + * @throws UnsupportedOperationException always + * @deprecated Unsupported operation. + */ + @Deprecated + @Override + public final void clear() { + throw new UnsupportedOperationException(); + } + + @Override + public boolean isEmpty() { + return size() == 0; + } + + @Override + public boolean containsKey(Object key) { + return get(key) != null; + } + + @Override + public boolean containsValue(Object value) { + return values().contains(value); + } + + // Overriding to mark it Nullable + @Override + public abstract V get(Object key); + + /** + * @since 21.0 (but only since 23.5 in the Android flavor). + * Note, however, that Java 8 users can call this method with any version and flavor of Guava. + */ + @Override + public final V getOrDefault(Object key, V defaultValue) { + V result = get(key); + return (result != null) ? result : defaultValue; + } + + private transient ImmutableSet> entrySet; + + /** + * Returns an immutable set of the mappings in this map. The iteration order is specified by the + * method used to create this map. Typically, this is insertion order. + */ + @Override + public ImmutableSet> entrySet() { + ImmutableSet> result = entrySet; + return (result == null) ? entrySet = createEntrySet() : result; + } + + abstract ImmutableSet> createEntrySet(); + + private transient ImmutableSet keySet; + + /** + * Returns an immutable set of the keys in this map, in the same order that they appear in {@link + * #entrySet}. + */ + @Override + public ImmutableSet keySet() { + ImmutableSet result = keySet; + return (result == null) ? keySet = createKeySet() : result; + } + + /* + * This could have a good default implementation of return new ImmutableKeySet(this), + * but ProGuard can't figure out how to eliminate that default when RegularImmutableMap + * overrides it. + */ + abstract ImmutableSet createKeySet(); + + UnmodifiableIterator keyIterator() { + final UnmodifiableIterator> entryIterator = entrySet().iterator(); + return new UnmodifiableIterator() { + @Override + public boolean hasNext() { + return entryIterator.hasNext(); + } + + @Override + public K next() { + return entryIterator.next().getKey(); + } + }; + } + + Spliterator keySpliterator() { + return CollectSpliterators.map(entrySet().spliterator(), Entry::getKey); + } + + private transient ImmutableCollection values; + + /** + * Returns an immutable collection of the values in this map, in the same order that they appear + * in {@link #entrySet}. + */ + @Override + public ImmutableCollection values() { + ImmutableCollection result = values; + return (result == null) ? values = createValues() : result; + } + + /* + * This could have a good default implementation of {@code return new + * ImmutableMapValues(this)}, but ProGuard can't figure out how to eliminate that default + * when RegularImmutableMap overrides it. + */ + abstract ImmutableCollection createValues(); + + // cached so that this.multimapView().inverse() only computes inverse once + private transient ImmutableSetMultimap multimapView; + + /** + * Returns a multimap view of the map. + * + * @since 14.0 + */ + public ImmutableSetMultimap asMultimap() { + if (isEmpty()) { + return ImmutableSetMultimap.of(); + } + ImmutableSetMultimap result = multimapView; + return (result == null) + ? (multimapView = + new ImmutableSetMultimap<>(new MapViewOfValuesAsSingletonSets(), size(), null)) + : result; + } + + + private final class MapViewOfValuesAsSingletonSets + extends IteratorBasedImmutableMap> { + + @Override + public int size() { + return ImmutableMap.this.size(); + } + + @Override + ImmutableSet createKeySet() { + return ImmutableMap.this.keySet(); + } + + @Override + public boolean containsKey(Object key) { + return ImmutableMap.this.containsKey(key); + } + + @Override + public ImmutableSet get(Object key) { + V outerValue = ImmutableMap.this.get(key); + return (outerValue == null) ? null : ImmutableSet.of(outerValue); + } + + @Override + boolean isPartialView() { + return ImmutableMap.this.isPartialView(); + } + + @Override + public int hashCode() { + // ImmutableSet.of(value).hashCode() == value.hashCode(), so the hashes are the same + return ImmutableMap.this.hashCode(); + } + + @Override + boolean isHashCodeFast() { + return ImmutableMap.this.isHashCodeFast(); + } + + @Override + UnmodifiableIterator>> entryIterator() { + final Iterator> backingIterator = ImmutableMap.this.entrySet().iterator(); + return new UnmodifiableIterator>>() { + @Override + public boolean hasNext() { + return backingIterator.hasNext(); + } + + @Override + public Entry> next() { + final Entry backingEntry = backingIterator.next(); + return new AbstractMapEntry>() { + @Override + public K getKey() { + return backingEntry.getKey(); + } + + @Override + public ImmutableSet getValue() { + return ImmutableSet.of(backingEntry.getValue()); + } + }; + } + }; + } + } + + @Override + public boolean equals(Object object) { + return Maps.equalsImpl(this, object); + } + + abstract boolean isPartialView(); + + @Override + public int hashCode() { + return Sets.hashCodeImpl(entrySet()); + } + + boolean isHashCodeFast() { + return false; + } + + @Override + public String toString() { + return Maps.toStringImpl(this); + } + + /** + * Serialized type for all ImmutableMap instances. It captures the logical contents and they are + * reconstructed using public factory methods. This ensures that the implementation types remain + * as implementation details. + */ + static class SerializedForm implements Serializable { + private final Object[] keys; + private final Object[] values; + + SerializedForm(ImmutableMap map) { + keys = new Object[map.size()]; + values = new Object[map.size()]; + int i = 0; + for (Entry entry : map.entrySet()) { + keys[i] = entry.getKey(); + values[i] = entry.getValue(); + i++; + } + } + + Object readResolve() { + Builder builder = new Builder<>(keys.length); + return createMap(builder); + } + + Object createMap(Builder builder) { + for (int i = 0; i < keys.length; i++) { + builder.put(keys[i], values[i]); + } + return builder.build(); + } + + private static final long serialVersionUID = 0; + } + + Object writeReplace() { + return new SerializedForm(this); + } +} diff --git a/src/main/java/com/google/common/collect/ImmutableMapEntry.java b/src/main/java/com/google/common/collect/ImmutableMapEntry.java new file mode 100644 index 0000000..68407f0 --- /dev/null +++ b/src/main/java/com/google/common/collect/ImmutableMapEntry.java @@ -0,0 +1,112 @@ +/* + * Copyright (C) 2013 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import static com.google.common.collect.CollectPreconditions.checkEntryNotNull; + +import com.google.common.annotations.GwtIncompatible; + + +/** + * Implementation of {@code Entry} for {@link ImmutableMap} that adds extra methods to traverse hash + * buckets for the key and the value. This allows reuse in {@link RegularImmutableMap} and {@link + * RegularImmutableBiMap}, which don't have to recopy the entries created by their {@code Builder} + * implementations. + * + *

This base implementation has no key or value pointers, so instances of ImmutableMapEntry (but + * not its subclasses) can be reused when copied from one ImmutableMap to another. + * + * @author Louis Wasserman + */ +@GwtIncompatible // unnecessary +class ImmutableMapEntry extends ImmutableEntry { + /** + * Creates an {@code ImmutableMapEntry} array to hold parameterized entries. The result must never + * be upcast back to ImmutableMapEntry[] (or Object[], etc.), or allowed to escape the class. + */ + @SuppressWarnings("unchecked") // Safe as long as the javadocs are followed + static ImmutableMapEntry[] createEntryArray(int size) { + return new ImmutableMapEntry[size]; + } + + ImmutableMapEntry(K key, V value) { + super(key, value); + checkEntryNotNull(key, value); + } + + ImmutableMapEntry(ImmutableMapEntry contents) { + super(contents.getKey(), contents.getValue()); + // null check would be redundant + } + + + ImmutableMapEntry getNextInKeyBucket() { + return null; + } + + + ImmutableMapEntry getNextInValueBucket() { + return null; + } + + /** + * Returns true if this entry has no bucket links and can safely be reused as a terminal entry in + * a bucket in another map. + */ + boolean isReusable() { + return true; + } + + static class NonTerminalImmutableMapEntry extends ImmutableMapEntry { + private final transient ImmutableMapEntry nextInKeyBucket; + + NonTerminalImmutableMapEntry(K key, V value, ImmutableMapEntry nextInKeyBucket) { + super(key, value); + this.nextInKeyBucket = nextInKeyBucket; + } + + @Override + final ImmutableMapEntry getNextInKeyBucket() { + return nextInKeyBucket; + } + + @Override + final boolean isReusable() { + return false; + } + } + + static final class NonTerminalImmutableBiMapEntry + extends NonTerminalImmutableMapEntry { + private final transient ImmutableMapEntry nextInValueBucket; + + NonTerminalImmutableBiMapEntry( + K key, + V value, + ImmutableMapEntry nextInKeyBucket, + ImmutableMapEntry nextInValueBucket) { + super(key, value, nextInKeyBucket); + this.nextInValueBucket = nextInValueBucket; + } + + @Override + + ImmutableMapEntry getNextInValueBucket() { + return nextInValueBucket; + } + } +} diff --git a/src/main/java/com/google/common/collect/ImmutableMapEntrySet.java b/src/main/java/com/google/common/collect/ImmutableMapEntrySet.java new file mode 100644 index 0000000..35a66f5 --- /dev/null +++ b/src/main/java/com/google/common/collect/ImmutableMapEntrySet.java @@ -0,0 +1,135 @@ +/* + * Copyright (C) 2008 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import com.google.common.annotations.GwtCompatible; +import com.google.common.annotations.GwtIncompatible; +import java.io.Serializable; +import java.util.Map.Entry; +import java.util.Spliterator; +import java.util.function.Consumer; + + +/** + * {@code entrySet()} implementation for {@link ImmutableMap}. + * + * @author Jesse Wilson + * @author Kevin Bourrillion + */ +@GwtCompatible(emulated = true) +abstract class ImmutableMapEntrySet extends ImmutableSet> { + static final class RegularEntrySet extends ImmutableMapEntrySet { + private final transient ImmutableMap map; + private final transient ImmutableList> entries; + + RegularEntrySet(ImmutableMap map, Entry[] entries) { + this(map, ImmutableList.>asImmutableList(entries)); + } + + RegularEntrySet(ImmutableMap map, ImmutableList> entries) { + this.map = map; + this.entries = entries; + } + + @Override + ImmutableMap map() { + return map; + } + + @Override + @GwtIncompatible("not used in GWT") + int copyIntoArray(Object[] dst, int offset) { + return entries.copyIntoArray(dst, offset); + } + + @Override + public UnmodifiableIterator> iterator() { + return entries.iterator(); + } + + @Override + public Spliterator> spliterator() { + return entries.spliterator(); + } + + @Override + public void forEach(Consumer> action) { + entries.forEach(action); + } + + @Override + ImmutableList> createAsList() { + return new RegularImmutableAsList<>(this, entries); + } + } + + ImmutableMapEntrySet() {} + + abstract ImmutableMap map(); + + @Override + public int size() { + return map().size(); + } + + @Override + public boolean contains(Object object) { + if (object instanceof Entry) { + Entry entry = (Entry) object; + V value = map().get(entry.getKey()); + return value != null && value.equals(entry.getValue()); + } + return false; + } + + @Override + boolean isPartialView() { + return map().isPartialView(); + } + + @Override + @GwtIncompatible // not used in GWT + boolean isHashCodeFast() { + return map().isHashCodeFast(); + } + + @Override + public int hashCode() { + return map().hashCode(); + } + + @GwtIncompatible // serialization + @Override + Object writeReplace() { + return new EntrySetSerializedForm<>(map()); + } + + @GwtIncompatible // serialization + private static class EntrySetSerializedForm implements Serializable { + final ImmutableMap map; + + EntrySetSerializedForm(ImmutableMap map) { + this.map = map; + } + + Object readResolve() { + return map.entrySet(); + } + + private static final long serialVersionUID = 0; + } +} diff --git a/src/main/java/com/google/common/collect/ImmutableMapKeySet.java b/src/main/java/com/google/common/collect/ImmutableMapKeySet.java new file mode 100644 index 0000000..3aa0bfa --- /dev/null +++ b/src/main/java/com/google/common/collect/ImmutableMapKeySet.java @@ -0,0 +1,98 @@ +/* + * Copyright (C) 2008 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.annotations.GwtCompatible; +import com.google.common.annotations.GwtIncompatible; +import java.io.Serializable; +import java.util.Spliterator; +import java.util.function.Consumer; + + +/** + * {@code keySet()} implementation for {@link ImmutableMap}. + * + * @author Jesse Wilson + * @author Kevin Bourrillion + */ +@GwtCompatible(emulated = true) +final class ImmutableMapKeySet extends IndexedImmutableSet { + private final ImmutableMap map; + + ImmutableMapKeySet(ImmutableMap map) { + this.map = map; + } + + @Override + public int size() { + return map.size(); + } + + @Override + public UnmodifiableIterator iterator() { + return map.keyIterator(); + } + + @Override + public Spliterator spliterator() { + return map.keySpliterator(); + } + + @Override + public boolean contains(Object object) { + return map.containsKey(object); + } + + @Override + K get(int index) { + return map.entrySet().asList().get(index).getKey(); + } + + @Override + public void forEach(Consumer action) { + checkNotNull(action); + map.forEach((k, v) -> action.accept(k)); + } + + @Override + boolean isPartialView() { + return true; + } + + @GwtIncompatible // serialization + @Override + Object writeReplace() { + return new KeySetSerializedForm(map); + } + + @GwtIncompatible // serialization + private static class KeySetSerializedForm implements Serializable { + final ImmutableMap map; + + KeySetSerializedForm(ImmutableMap map) { + this.map = map; + } + + Object readResolve() { + return map.keySet(); + } + + private static final long serialVersionUID = 0; + } +} diff --git a/src/main/java/com/google/common/collect/ImmutableMapValues.java b/src/main/java/com/google/common/collect/ImmutableMapValues.java new file mode 100644 index 0000000..f641895 --- /dev/null +++ b/src/main/java/com/google/common/collect/ImmutableMapValues.java @@ -0,0 +1,123 @@ +/* + * Copyright (C) 2008 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.annotations.GwtCompatible; +import com.google.common.annotations.GwtIncompatible; +import java.io.Serializable; +import java.util.Map.Entry; +import java.util.Spliterator; +import java.util.function.Consumer; + + +/** + * {@code values()} implementation for {@link ImmutableMap}. + * + * @author Jesse Wilson + * @author Kevin Bourrillion + */ +@GwtCompatible(emulated = true) +final class ImmutableMapValues extends ImmutableCollection { + private final ImmutableMap map; + + ImmutableMapValues(ImmutableMap map) { + this.map = map; + } + + @Override + public int size() { + return map.size(); + } + + @Override + public UnmodifiableIterator iterator() { + return new UnmodifiableIterator() { + final UnmodifiableIterator> entryItr = map.entrySet().iterator(); + + @Override + public boolean hasNext() { + return entryItr.hasNext(); + } + + @Override + public V next() { + return entryItr.next().getValue(); + } + }; + } + + @Override + public Spliterator spliterator() { + return CollectSpliterators.map(map.entrySet().spliterator(), Entry::getValue); + } + + @Override + public boolean contains(Object object) { + return object != null && Iterators.contains(iterator(), object); + } + + @Override + boolean isPartialView() { + return true; + } + + @Override + public ImmutableList asList() { + final ImmutableList> entryList = map.entrySet().asList(); + return new ImmutableAsList() { + @Override + public V get(int index) { + return entryList.get(index).getValue(); + } + + @Override + ImmutableCollection delegateCollection() { + return ImmutableMapValues.this; + } + }; + } + + @GwtIncompatible // serialization + @Override + public void forEach(Consumer action) { + checkNotNull(action); + map.forEach((k, v) -> action.accept(v)); + } + + @GwtIncompatible // serialization + @Override + Object writeReplace() { + return new SerializedForm(map); + } + + @GwtIncompatible // serialization + private static class SerializedForm implements Serializable { + final ImmutableMap map; + + SerializedForm(ImmutableMap map) { + this.map = map; + } + + Object readResolve() { + return map.values(); + } + + private static final long serialVersionUID = 0; + } +} diff --git a/src/main/java/com/google/common/collect/ImmutableMultimap.java b/src/main/java/com/google/common/collect/ImmutableMultimap.java new file mode 100644 index 0000000..50cc044 --- /dev/null +++ b/src/main/java/com/google/common/collect/ImmutableMultimap.java @@ -0,0 +1,752 @@ +/* + * Copyright (C) 2008 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.collect.CollectPreconditions.checkEntryNotNull; + +import com.google.common.annotations.Beta; +import com.google.common.annotations.GwtCompatible; +import com.google.common.annotations.GwtIncompatible; + + + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Comparator; +import java.util.Iterator; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; +import java.util.Spliterator; +import java.util.function.BiConsumer; + + + +/** + * A {@link Multimap} whose contents will never change, with many other important properties + * detailed at {@link ImmutableCollection}. + * + *

Warning: avoid direct usage of {@link ImmutableMultimap} as a type (as with + * {@link Multimap} itself). Prefer subtypes such as {@link ImmutableSetMultimap} or {@link + * ImmutableListMultimap}, which have well-defined {@link #equals} semantics, thus avoiding a common + * source of bugs and confusion. + * + *

Note: every {@link ImmutableMultimap} offers an {@link #inverse} view, so there is no + * need for a distinct {@code ImmutableBiMultimap} type. + * + *

+ * + *

Key-grouped iteration. All view collections follow the same iteration order. In all + * current implementations, the iteration order always keeps multiple entries with the same key + * together. Any creation method that would customarily respect insertion order (such as {@link + * #copyOf(Multimap)}) instead preserves key-grouped order by inserting entries for an existing key + * immediately after the last entry having that key. + * + *

See the Guava User Guide article on immutable collections. + * + * @author Jared Levy + * @since 2.0 + */ +@GwtCompatible(emulated = true) +public abstract class ImmutableMultimap extends BaseImmutableMultimap + implements Serializable { + + /** Returns an empty multimap. */ + public static ImmutableMultimap of() { + return ImmutableListMultimap.of(); + } + + /** Returns an immutable multimap containing a single entry. */ + public static ImmutableMultimap of(K k1, V v1) { + return ImmutableListMultimap.of(k1, v1); + } + + /** Returns an immutable multimap containing the given entries, in order. */ + public static ImmutableMultimap of(K k1, V v1, K k2, V v2) { + return ImmutableListMultimap.of(k1, v1, k2, v2); + } + + /** + * Returns an immutable multimap containing the given entries, in the "key-grouped" insertion + * order described in the class documentation. + */ + public static ImmutableMultimap of(K k1, V v1, K k2, V v2, K k3, V v3) { + return ImmutableListMultimap.of(k1, v1, k2, v2, k3, v3); + } + + /** + * Returns an immutable multimap containing the given entries, in the "key-grouped" insertion + * order described in the class documentation. + */ + public static ImmutableMultimap of(K k1, V v1, K k2, V v2, K k3, V v3, K k4, V v4) { + return ImmutableListMultimap.of(k1, v1, k2, v2, k3, v3, k4, v4); + } + + /** + * Returns an immutable multimap containing the given entries, in the "key-grouped" insertion + * order described in the class documentation. + */ + public static ImmutableMultimap of( + K k1, V v1, K k2, V v2, K k3, V v3, K k4, V v4, K k5, V v5) { + return ImmutableListMultimap.of(k1, v1, k2, v2, k3, v3, k4, v4, k5, v5); + } + + // looking for of() with > 5 entries? Use the builder instead. + + /** + * Returns a new builder. The generated builder is equivalent to the builder created by the {@link + * Builder} constructor. + */ + public static Builder builder() { + return new Builder<>(); + } + + /** + * A builder for creating immutable multimap instances, especially {@code public static final} + * multimaps ("constant multimaps"). Example: + * + *

{@code
+   * static final Multimap STRING_TO_INTEGER_MULTIMAP =
+   *     new ImmutableMultimap.Builder()
+   *         .put("one", 1)
+   *         .putAll("several", 1, 2, 3)
+   *         .putAll("many", 1, 2, 3, 4, 5)
+   *         .build();
+   * }
+ * + *

Builder instances can be reused; it is safe to call {@link #build} multiple times to build + * multiple multimaps in series. Each multimap contains the key-value mappings in the previously + * created multimaps. + * + * @since 2.0 + */ + public static class Builder { + Map> builderMap; + Comparator keyComparator; + Comparator valueComparator; + + /** + * Creates a new builder. The returned builder is equivalent to the builder generated by {@link + * ImmutableMultimap#builder}. + */ + public Builder() { + this.builderMap = Platform.preservesInsertionOrderOnPutsMap(); + } + + Collection newMutableValueCollection() { + return new ArrayList<>(); + } + + /** Adds a key-value mapping to the built multimap. */ + + public Builder put(K key, V value) { + checkEntryNotNull(key, value); + Collection valueCollection = builderMap.get(key); + if (valueCollection == null) { + builderMap.put(key, valueCollection = newMutableValueCollection()); + } + valueCollection.add(value); + return this; + } + + /** + * Adds an entry to the built multimap. + * + * @since 11.0 + */ + + public Builder put(Entry entry) { + return put(entry.getKey(), entry.getValue()); + } + + /** + * Adds entries to the built multimap. + * + * @since 19.0 + */ + + @Beta + public Builder putAll(Iterable> entries) { + for (Entry entry : entries) { + put(entry); + } + return this; + } + + /** + * Stores a collection of values with the same key in the built multimap. + * + * @throws NullPointerException if {@code key}, {@code values}, or any element in {@code values} + * is null. The builder is left in an invalid state. + */ + + public Builder putAll(K key, Iterable values) { + if (key == null) { + throw new NullPointerException("null key in entry: null=" + Iterables.toString(values)); + } + Collection valueCollection = builderMap.get(key); + if (valueCollection != null) { + for (V value : values) { + checkEntryNotNull(key, value); + valueCollection.add(value); + } + return this; + } + Iterator valuesItr = values.iterator(); + if (!valuesItr.hasNext()) { + return this; + } + valueCollection = newMutableValueCollection(); + while (valuesItr.hasNext()) { + V value = valuesItr.next(); + checkEntryNotNull(key, value); + valueCollection.add(value); + } + builderMap.put(key, valueCollection); + return this; + } + + /** + * Stores an array of values with the same key in the built multimap. + * + * @throws NullPointerException if the key or any value is null. The builder is left in an + * invalid state. + */ + + public Builder putAll(K key, V... values) { + return putAll(key, Arrays.asList(values)); + } + + /** + * Stores another multimap's entries in the built multimap. The generated multimap's key and + * value orderings correspond to the iteration ordering of the {@code multimap.asMap()} view, + * with new keys and values following any existing keys and values. + * + * @throws NullPointerException if any key or value in {@code multimap} is null. The builder is + * left in an invalid state. + */ + + public Builder putAll(Multimap multimap) { + for (Entry> entry : + multimap.asMap().entrySet()) { + putAll(entry.getKey(), entry.getValue()); + } + return this; + } + + /** + * Specifies the ordering of the generated multimap's keys. + * + * @since 8.0 + */ + + public Builder orderKeysBy(Comparator keyComparator) { + this.keyComparator = checkNotNull(keyComparator); + return this; + } + + /** + * Specifies the ordering of the generated multimap's values for each key. + * + * @since 8.0 + */ + + public Builder orderValuesBy(Comparator valueComparator) { + this.valueComparator = checkNotNull(valueComparator); + return this; + } + + + Builder combine(Builder other) { + for (Map.Entry> entry : other.builderMap.entrySet()) { + putAll(entry.getKey(), entry.getValue()); + } + return this; + } + + /** Returns a newly-created immutable multimap. */ + public ImmutableMultimap build() { + Collection>> mapEntries = builderMap.entrySet(); + if (keyComparator != null) { + mapEntries = Ordering.from(keyComparator).onKeys().immutableSortedCopy(mapEntries); + } + return ImmutableListMultimap.fromMapEntries(mapEntries, valueComparator); + } + } + + /** + * Returns an immutable multimap containing the same mappings as {@code multimap}, in the + * "key-grouped" iteration order described in the class documentation. + * + *

Despite the method name, this method attempts to avoid actually copying the data when it is + * safe to do so. The exact circumstances under which a copy will or will not be performed are + * undocumented and subject to change. + * + * @throws NullPointerException if any key or value in {@code multimap} is null + */ + public static ImmutableMultimap copyOf(Multimap multimap) { + if (multimap instanceof ImmutableMultimap) { + @SuppressWarnings("unchecked") // safe since multimap is not writable + ImmutableMultimap kvMultimap = (ImmutableMultimap) multimap; + if (!kvMultimap.isPartialView()) { + return kvMultimap; + } + } + return ImmutableListMultimap.copyOf(multimap); + } + + /** + * Returns an immutable multimap containing the specified entries. The returned multimap iterates + * over keys in the order they were first encountered in the input, and the values for each key + * are iterated in the order they were encountered. + * + * @throws NullPointerException if any key, value, or entry is null + * @since 19.0 + */ + @Beta + public static ImmutableMultimap copyOf( + Iterable> entries) { + return ImmutableListMultimap.copyOf(entries); + } + + final transient ImmutableMap> map; + final transient int size; + + // These constants allow the deserialization code to set final fields. This + // holder class makes sure they are not initialized unless an instance is + // deserialized. + @GwtIncompatible // java serialization is not supported + static class FieldSettersHolder { + static final Serialization.FieldSetter MAP_FIELD_SETTER = + Serialization.getFieldSetter(ImmutableMultimap.class, "map"); + static final Serialization.FieldSetter SIZE_FIELD_SETTER = + Serialization.getFieldSetter(ImmutableMultimap.class, "size"); + } + + ImmutableMultimap(ImmutableMap> map, int size) { + this.map = map; + this.size = size; + } + + // mutators (not supported) + + /** + * Guaranteed to throw an exception and leave the multimap unmodified. + * + * @throws UnsupportedOperationException always + * @deprecated Unsupported operation. + */ + + @Deprecated + @Override + public ImmutableCollection removeAll(Object key) { + throw new UnsupportedOperationException(); + } + + /** + * Guaranteed to throw an exception and leave the multimap unmodified. + * + * @throws UnsupportedOperationException always + * @deprecated Unsupported operation. + */ + + @Deprecated + @Override + public ImmutableCollection replaceValues(K key, Iterable values) { + throw new UnsupportedOperationException(); + } + + /** + * Guaranteed to throw an exception and leave the multimap unmodified. + * + * @throws UnsupportedOperationException always + * @deprecated Unsupported operation. + */ + @Deprecated + @Override + public void clear() { + throw new UnsupportedOperationException(); + } + + /** + * Returns an immutable collection of the values for the given key. If no mappings in the multimap + * have the provided key, an empty immutable collection is returned. The values are in the same + * order as the parameters used to build this multimap. + */ + @Override + public abstract ImmutableCollection get(K key); + + /** + * Returns an immutable multimap which is the inverse of this one. For every key-value mapping in + * the original, the result will have a mapping with key and value reversed. + * + * @since 11.0 + */ + public abstract ImmutableMultimap inverse(); + + /** + * Guaranteed to throw an exception and leave the multimap unmodified. + * + * @throws UnsupportedOperationException always + * @deprecated Unsupported operation. + */ + + @Deprecated + @Override + public boolean put(K key, V value) { + throw new UnsupportedOperationException(); + } + + /** + * Guaranteed to throw an exception and leave the multimap unmodified. + * + * @throws UnsupportedOperationException always + * @deprecated Unsupported operation. + */ + + @Deprecated + @Override + public boolean putAll(K key, Iterable values) { + throw new UnsupportedOperationException(); + } + + /** + * Guaranteed to throw an exception and leave the multimap unmodified. + * + * @throws UnsupportedOperationException always + * @deprecated Unsupported operation. + */ + + @Deprecated + @Override + public boolean putAll(Multimap multimap) { + throw new UnsupportedOperationException(); + } + + /** + * Guaranteed to throw an exception and leave the multimap unmodified. + * + * @throws UnsupportedOperationException always + * @deprecated Unsupported operation. + */ + + @Deprecated + @Override + public boolean remove(Object key, Object value) { + throw new UnsupportedOperationException(); + } + + /** + * Returns {@code true} if this immutable multimap's implementation contains references to + * user-created objects that aren't accessible via this multimap's methods. This is generally used + * to determine whether {@code copyOf} implementations should make an explicit copy to avoid + * memory leaks. + */ + boolean isPartialView() { + return map.isPartialView(); + } + + // accessors + + @Override + public boolean containsKey(Object key) { + return map.containsKey(key); + } + + @Override + public boolean containsValue(Object value) { + return value != null && super.containsValue(value); + } + + @Override + public int size() { + return size; + } + + // views + + /** + * Returns an immutable set of the distinct keys in this multimap, in the same order as they + * appear in this multimap. + */ + @Override + public ImmutableSet keySet() { + return map.keySet(); + } + + @Override + Set createKeySet() { + throw new AssertionError("unreachable"); + } + + /** + * Returns an immutable map that associates each key with its corresponding values in the + * multimap. Keys and values appear in the same order as in this multimap. + */ + @Override + @SuppressWarnings("unchecked") // a widening cast + public ImmutableMap> asMap() { + return (ImmutableMap) map; + } + + @Override + Map> createAsMap() { + throw new AssertionError("should never be called"); + } + + /** Returns an immutable collection of all key-value pairs in the multimap. */ + @Override + public ImmutableCollection> entries() { + return (ImmutableCollection>) super.entries(); + } + + @Override + ImmutableCollection> createEntries() { + return new EntryCollection<>(this); + } + + private static class EntryCollection extends ImmutableCollection> { + final ImmutableMultimap multimap; + + EntryCollection(ImmutableMultimap multimap) { + this.multimap = multimap; + } + + @Override + public UnmodifiableIterator> iterator() { + return multimap.entryIterator(); + } + + @Override + boolean isPartialView() { + return multimap.isPartialView(); + } + + @Override + public int size() { + return multimap.size(); + } + + @Override + public boolean contains(Object object) { + if (object instanceof Entry) { + Entry entry = (Entry) object; + return multimap.containsEntry(entry.getKey(), entry.getValue()); + } + return false; + } + + private static final long serialVersionUID = 0; + } + + @Override + UnmodifiableIterator> entryIterator() { + return new UnmodifiableIterator>() { + final Iterator>> asMapItr = + map.entrySet().iterator(); + K currentKey = null; + Iterator valueItr = Iterators.emptyIterator(); + + @Override + public boolean hasNext() { + return valueItr.hasNext() || asMapItr.hasNext(); + } + + @Override + public Entry next() { + if (!valueItr.hasNext()) { + Entry> entry = asMapItr.next(); + currentKey = entry.getKey(); + valueItr = entry.getValue().iterator(); + } + return Maps.immutableEntry(currentKey, valueItr.next()); + } + }; + } + + @Override + Spliterator> entrySpliterator() { + return CollectSpliterators.flatMap( + asMap().entrySet().spliterator(), + keyToValueCollectionEntry -> { + K key = keyToValueCollectionEntry.getKey(); + Collection valueCollection = keyToValueCollectionEntry.getValue(); + return CollectSpliterators.map( + valueCollection.spliterator(), (V value) -> Maps.immutableEntry(key, value)); + }, + Spliterator.SIZED | (this instanceof SetMultimap ? Spliterator.DISTINCT : 0), + size()); + } + + @Override + public void forEach(BiConsumer action) { + checkNotNull(action); + asMap() + .forEach( + (key, valueCollection) -> valueCollection.forEach(value -> action.accept(key, value))); + } + + /** + * Returns an immutable multiset containing all the keys in this multimap, in the same order and + * with the same frequencies as they appear in this multimap; to get only a single occurrence of + * each key, use {@link #keySet}. + */ + @Override + public ImmutableMultiset keys() { + return (ImmutableMultiset) super.keys(); + } + + @Override + ImmutableMultiset createKeys() { + return new Keys(); + } + + @SuppressWarnings("serial") // Uses writeReplace, not default serialization + + class Keys extends ImmutableMultiset { + @Override + public boolean contains(Object object) { + return containsKey(object); + } + + @Override + public int count(Object element) { + Collection values = map.get(element); + return (values == null) ? 0 : values.size(); + } + + @Override + public ImmutableSet elementSet() { + return keySet(); + } + + @Override + public int size() { + return ImmutableMultimap.this.size(); + } + + @Override + Multiset.Entry getEntry(int index) { + Map.Entry> entry = map.entrySet().asList().get(index); + return Multisets.immutableEntry(entry.getKey(), entry.getValue().size()); + } + + @Override + boolean isPartialView() { + return true; + } + + @GwtIncompatible + @Override + Object writeReplace() { + return new KeysSerializedForm(ImmutableMultimap.this); + } + } + + @GwtIncompatible + private static final class KeysSerializedForm implements Serializable { + final ImmutableMultimap multimap; + + KeysSerializedForm(ImmutableMultimap multimap) { + this.multimap = multimap; + } + + Object readResolve() { + return multimap.keys(); + } + } + + /** + * Returns an immutable collection of the values in this multimap. Its iterator traverses the + * values for the first key, the values for the second key, and so on. + */ + @Override + public ImmutableCollection values() { + return (ImmutableCollection) super.values(); + } + + @Override + ImmutableCollection createValues() { + return new Values<>(this); + } + + @Override + UnmodifiableIterator valueIterator() { + return new UnmodifiableIterator() { + Iterator> valueCollectionItr = map.values().iterator(); + Iterator valueItr = Iterators.emptyIterator(); + + @Override + public boolean hasNext() { + return valueItr.hasNext() || valueCollectionItr.hasNext(); + } + + @Override + public V next() { + if (!valueItr.hasNext()) { + valueItr = valueCollectionItr.next().iterator(); + } + return valueItr.next(); + } + }; + } + + private static final class Values extends ImmutableCollection { + private final transient ImmutableMultimap multimap; + + Values(ImmutableMultimap multimap) { + this.multimap = multimap; + } + + @Override + public boolean contains(Object object) { + return multimap.containsValue(object); + } + + @Override + public UnmodifiableIterator iterator() { + return multimap.valueIterator(); + } + + @GwtIncompatible // not present in emulated superclass + @Override + int copyIntoArray(Object[] dst, int offset) { + for (ImmutableCollection valueCollection : multimap.map.values()) { + offset = valueCollection.copyIntoArray(dst, offset); + } + return offset; + } + + @Override + public int size() { + return multimap.size(); + } + + @Override + boolean isPartialView() { + return true; + } + + private static final long serialVersionUID = 0; + } + + private static final long serialVersionUID = 0; +} diff --git a/src/main/java/com/google/common/collect/ImmutableMultiset.java b/src/main/java/com/google/common/collect/ImmutableMultiset.java new file mode 100644 index 0000000..391cbd2 --- /dev/null +++ b/src/main/java/com/google/common/collect/ImmutableMultiset.java @@ -0,0 +1,642 @@ +/* + * Copyright (C) 2008 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.annotations.GwtCompatible; +import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.VisibleForTesting; + + + +import java.io.Serializable; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.function.Function; +import java.util.function.ToIntFunction; +import java.util.stream.Collector; + + + +/** + * A {@link Multiset} whose contents will never change, with many other important properties + * detailed at {@link ImmutableCollection}. + * + *

Grouped iteration. In all current implementations, duplicate elements always appear + * consecutively when iterating. Elements iterate in order by the first appearance of that + * element when the multiset was created. + * + *

See the Guava User Guide article on immutable collections. + * + * @author Jared Levy + * @author Louis Wasserman + * @since 2.0 + */ +@GwtCompatible(serializable = true, emulated = true) +@SuppressWarnings("serial") // we're overriding default serialization +public abstract class ImmutableMultiset extends ImmutableMultisetGwtSerializationDependencies + implements Multiset { + + /** + * Returns a {@code Collector} that accumulates the input elements into a new {@code + * ImmutableMultiset}. Elements iterate in order by the first appearance of that element in + * encounter order. + * + * @since 21.0 + */ + public static Collector> toImmutableMultiset() { + return toImmutableMultiset(Function.identity(), e -> 1); + } + + /** + * Returns a {@code Collector} that accumulates elements into an {@code ImmutableMultiset} whose + * elements are the result of applying {@code elementFunction} to the inputs, with counts equal to + * the result of applying {@code countFunction} to the inputs. + * + *

If the mapped elements contain duplicates (according to {@link Object#equals}), the first + * occurrence in encounter order appears in the resulting multiset, with count equal to the sum of + * the outputs of {@code countFunction.applyAsInt(t)} for each {@code t} mapped to that element. + * + * @since 22.0 + */ + public static Collector> toImmutableMultiset( + Function elementFunction, ToIntFunction countFunction) { + checkNotNull(elementFunction); + checkNotNull(countFunction); + return Collector.of( + LinkedHashMultiset::create, + (multiset, t) -> + multiset.add(checkNotNull(elementFunction.apply(t)), countFunction.applyAsInt(t)), + (multiset1, multiset2) -> { + multiset1.addAll(multiset2); + return multiset1; + }, + (Multiset multiset) -> copyFromEntries(multiset.entrySet())); + } + + /** Returns the empty immutable multiset. */ + @SuppressWarnings("unchecked") // all supported methods are covariant + public static ImmutableMultiset of() { + return (ImmutableMultiset) RegularImmutableMultiset.EMPTY; + } + + /** + * Returns an immutable multiset containing a single element. + * + * @throws NullPointerException if {@code element} is null + * @since 6.0 (source-compatible since 2.0) + */ + @SuppressWarnings("unchecked") // generic array created but never written + public static ImmutableMultiset of(E element) { + return copyFromElements(element); + } + + /** + * Returns an immutable multiset containing the given elements, in order. + * + * @throws NullPointerException if any element is null + * @since 6.0 (source-compatible since 2.0) + */ + @SuppressWarnings("unchecked") // + public static ImmutableMultiset of(E e1, E e2) { + return copyFromElements(e1, e2); + } + + /** + * Returns an immutable multiset containing the given elements, in the "grouped iteration order" + * described in the class documentation. + * + * @throws NullPointerException if any element is null + * @since 6.0 (source-compatible since 2.0) + */ + @SuppressWarnings("unchecked") // + public static ImmutableMultiset of(E e1, E e2, E e3) { + return copyFromElements(e1, e2, e3); + } + + /** + * Returns an immutable multiset containing the given elements, in the "grouped iteration order" + * described in the class documentation. + * + * @throws NullPointerException if any element is null + * @since 6.0 (source-compatible since 2.0) + */ + @SuppressWarnings("unchecked") // + public static ImmutableMultiset of(E e1, E e2, E e3, E e4) { + return copyFromElements(e1, e2, e3, e4); + } + + /** + * Returns an immutable multiset containing the given elements, in the "grouped iteration order" + * described in the class documentation. + * + * @throws NullPointerException if any element is null + * @since 6.0 (source-compatible since 2.0) + */ + @SuppressWarnings("unchecked") // + public static ImmutableMultiset of(E e1, E e2, E e3, E e4, E e5) { + return copyFromElements(e1, e2, e3, e4, e5); + } + + /** + * Returns an immutable multiset containing the given elements, in the "grouped iteration order" + * described in the class documentation. + * + * @throws NullPointerException if any element is null + * @since 6.0 (source-compatible since 2.0) + */ + @SuppressWarnings("unchecked") // + public static ImmutableMultiset of(E e1, E e2, E e3, E e4, E e5, E e6, E... others) { + return new Builder().add(e1).add(e2).add(e3).add(e4).add(e5).add(e6).add(others).build(); + } + + /** + * Returns an immutable multiset containing the given elements, in the "grouped iteration order" + * described in the class documentation. + * + * @throws NullPointerException if any of {@code elements} is null + * @since 6.0 + */ + public static ImmutableMultiset copyOf(E[] elements) { + return copyFromElements(elements); + } + + /** + * Returns an immutable multiset containing the given elements, in the "grouped iteration order" + * described in the class documentation. + * + * @throws NullPointerException if any of {@code elements} is null + */ + public static ImmutableMultiset copyOf(Iterable elements) { + if (elements instanceof ImmutableMultiset) { + @SuppressWarnings("unchecked") // all supported methods are covariant + ImmutableMultiset result = (ImmutableMultiset) elements; + if (!result.isPartialView()) { + return result; + } + } + + Multiset multiset = + (elements instanceof Multiset) + ? Multisets.cast(elements) + : LinkedHashMultiset.create(elements); + + return copyFromEntries(multiset.entrySet()); + } + + /** + * Returns an immutable multiset containing the given elements, in the "grouped iteration order" + * described in the class documentation. + * + * @throws NullPointerException if any of {@code elements} is null + */ + public static ImmutableMultiset copyOf(Iterator elements) { + Multiset multiset = LinkedHashMultiset.create(); + Iterators.addAll(multiset, elements); + return copyFromEntries(multiset.entrySet()); + } + + private static ImmutableMultiset copyFromElements(E... elements) { + Multiset multiset = LinkedHashMultiset.create(); + Collections.addAll(multiset, elements); + return copyFromEntries(multiset.entrySet()); + } + + static ImmutableMultiset copyFromEntries( + Collection> entries) { + if (entries.isEmpty()) { + return of(); + } else { + return RegularImmutableMultiset.create(entries); + } + } + + ImmutableMultiset() {} + + @Override + public UnmodifiableIterator iterator() { + final Iterator> entryIterator = entrySet().iterator(); + return new UnmodifiableIterator() { + int remaining; + E element; + + @Override + public boolean hasNext() { + return (remaining > 0) || entryIterator.hasNext(); + } + + @Override + public E next() { + if (remaining <= 0) { + Entry entry = entryIterator.next(); + element = entry.getElement(); + remaining = entry.getCount(); + } + remaining--; + return element; + } + }; + } + + private transient ImmutableList asList; + + @Override + public ImmutableList asList() { + ImmutableList result = asList; + return (result == null) ? asList = super.asList() : result; + } + + @Override + public boolean contains(Object object) { + return count(object) > 0; + } + + /** + * Guaranteed to throw an exception and leave the collection unmodified. + * + * @throws UnsupportedOperationException always + * @deprecated Unsupported operation. + */ + + @Deprecated + @Override + public final int add(E element, int occurrences) { + throw new UnsupportedOperationException(); + } + + /** + * Guaranteed to throw an exception and leave the collection unmodified. + * + * @throws UnsupportedOperationException always + * @deprecated Unsupported operation. + */ + + @Deprecated + @Override + public final int remove(Object element, int occurrences) { + throw new UnsupportedOperationException(); + } + + /** + * Guaranteed to throw an exception and leave the collection unmodified. + * + * @throws UnsupportedOperationException always + * @deprecated Unsupported operation. + */ + + @Deprecated + @Override + public final int setCount(E element, int count) { + throw new UnsupportedOperationException(); + } + + /** + * Guaranteed to throw an exception and leave the collection unmodified. + * + * @throws UnsupportedOperationException always + * @deprecated Unsupported operation. + */ + + @Deprecated + @Override + public final boolean setCount(E element, int oldCount, int newCount) { + throw new UnsupportedOperationException(); + } + + @GwtIncompatible // not present in emulated superclass + @Override + int copyIntoArray(Object[] dst, int offset) { + for (Multiset.Entry entry : entrySet()) { + Arrays.fill(dst, offset, offset + entry.getCount(), entry.getElement()); + offset += entry.getCount(); + } + return offset; + } + + @Override + public boolean equals(Object object) { + return Multisets.equalsImpl(this, object); + } + + @Override + public int hashCode() { + return Sets.hashCodeImpl(entrySet()); + } + + @Override + public String toString() { + return entrySet().toString(); + } + + /** @since 21.0 (present with return type {@code Set} since 2.0) */ + @Override + public abstract ImmutableSet elementSet(); + + private transient ImmutableSet> entrySet; + + @Override + public ImmutableSet> entrySet() { + ImmutableSet> es = entrySet; + return (es == null) ? (entrySet = createEntrySet()) : es; + } + + private ImmutableSet> createEntrySet() { + return isEmpty() ? ImmutableSet.>of() : new EntrySet(); + } + + abstract Entry getEntry(int index); + + + private final class EntrySet extends IndexedImmutableSet> { + @Override + boolean isPartialView() { + return ImmutableMultiset.this.isPartialView(); + } + + @Override + Entry get(int index) { + return getEntry(index); + } + + @Override + public int size() { + return elementSet().size(); + } + + @Override + public boolean contains(Object o) { + if (o instanceof Entry) { + Entry entry = (Entry) o; + if (entry.getCount() <= 0) { + return false; + } + int count = count(entry.getElement()); + return count == entry.getCount(); + } + return false; + } + + @Override + public int hashCode() { + return ImmutableMultiset.this.hashCode(); + } + + @GwtIncompatible + @Override + Object writeReplace() { + return new EntrySetSerializedForm(ImmutableMultiset.this); + } + + private static final long serialVersionUID = 0; + } + + @GwtIncompatible + static class EntrySetSerializedForm implements Serializable { + final ImmutableMultiset multiset; + + EntrySetSerializedForm(ImmutableMultiset multiset) { + this.multiset = multiset; + } + + Object readResolve() { + return multiset.entrySet(); + } + } + + @GwtIncompatible + @Override + Object writeReplace() { + return new SerializedForm(this); + } + + /** + * Returns a new builder. The generated builder is equivalent to the builder created by the {@link + * Builder} constructor. + */ + public static Builder builder() { + return new Builder(); + } + + /** + * A builder for creating immutable multiset instances, especially {@code public static final} + * multisets ("constant multisets"). Example: + * + *

{@code
+   * public static final ImmutableMultiset BEANS =
+   *     new ImmutableMultiset.Builder()
+   *         .addCopies(Bean.COCOA, 4)
+   *         .addCopies(Bean.GARDEN, 6)
+   *         .addCopies(Bean.RED, 8)
+   *         .addCopies(Bean.BLACK_EYED, 10)
+   *         .build();
+   * }
+ * + *

Builder instances can be reused; it is safe to call {@link #build} multiple times to build + * multiple multisets in series. + * + * @since 2.0 + */ + public static class Builder extends ImmutableCollection.Builder { + final Multiset contents; + + /** + * Creates a new builder. The returned builder is equivalent to the builder generated by {@link + * ImmutableMultiset#builder}. + */ + public Builder() { + this(LinkedHashMultiset.create()); + } + + Builder(Multiset contents) { + this.contents = contents; + } + + /** + * Adds {@code element} to the {@code ImmutableMultiset}. + * + * @param element the element to add + * @return this {@code Builder} object + * @throws NullPointerException if {@code element} is null + */ + + @Override + public Builder add(E element) { + contents.add(checkNotNull(element)); + return this; + } + + /** + * Adds each element of {@code elements} to the {@code ImmutableMultiset}. + * + * @param elements the elements to add + * @return this {@code Builder} object + * @throws NullPointerException if {@code elements} is null or contains a null element + */ + + @Override + public Builder add(E... elements) { + super.add(elements); + return this; + } + + /** + * Adds a number of occurrences of an element to this {@code ImmutableMultiset}. + * + * @param element the element to add + * @param occurrences the number of occurrences of the element to add. May be zero, in which + * case no change will be made. + * @return this {@code Builder} object + * @throws NullPointerException if {@code element} is null + * @throws IllegalArgumentException if {@code occurrences} is negative, or if this operation + * would result in more than {@link Integer#MAX_VALUE} occurrences of the element + */ + + public Builder addCopies(E element, int occurrences) { + contents.add(checkNotNull(element), occurrences); + return this; + } + + /** + * Adds or removes the necessary occurrences of an element such that the element attains the + * desired count. + * + * @param element the element to add or remove occurrences of + * @param count the desired count of the element in this multiset + * @return this {@code Builder} object + * @throws NullPointerException if {@code element} is null + * @throws IllegalArgumentException if {@code count} is negative + */ + + public Builder setCount(E element, int count) { + contents.setCount(checkNotNull(element), count); + return this; + } + + /** + * Adds each element of {@code elements} to the {@code ImmutableMultiset}. + * + * @param elements the {@code Iterable} to add to the {@code ImmutableMultiset} + * @return this {@code Builder} object + * @throws NullPointerException if {@code elements} is null or contains a null element + */ + + @Override + public Builder addAll(Iterable elements) { + if (elements instanceof Multiset) { + Multiset multiset = Multisets.cast(elements); + multiset.forEachEntry((e, n) -> contents.add(checkNotNull(e), n)); + } else { + super.addAll(elements); + } + return this; + } + + /** + * Adds each element of {@code elements} to the {@code ImmutableMultiset}. + * + * @param elements the elements to add to the {@code ImmutableMultiset} + * @return this {@code Builder} object + * @throws NullPointerException if {@code elements} is null or contains a null element + */ + + @Override + public Builder addAll(Iterator elements) { + super.addAll(elements); + return this; + } + + /** + * Returns a newly-created {@code ImmutableMultiset} based on the contents of the {@code + * Builder}. + */ + @Override + public ImmutableMultiset build() { + return copyOf(contents); + } + + @VisibleForTesting + ImmutableMultiset buildJdkBacked() { + if (contents.isEmpty()) { + return of(); + } + return JdkBackedImmutableMultiset.create(contents.entrySet()); + } + } + + static final class ElementSet extends ImmutableSet.Indexed { + private final List> entries; + // TODO(cpovirk): @Weak? + private final Multiset delegate; + + ElementSet(List> entries, Multiset delegate) { + this.entries = entries; + this.delegate = delegate; + } + + @Override + E get(int index) { + return entries.get(index).getElement(); + } + + @Override + public boolean contains(Object object) { + return delegate.contains(object); + } + + @Override + boolean isPartialView() { + return true; + } + + @Override + public int size() { + return entries.size(); + } + } + + static final class SerializedForm implements Serializable { + final Object[] elements; + final int[] counts; + + SerializedForm(Multiset multiset) { + int distinct = multiset.entrySet().size(); + elements = new Object[distinct]; + counts = new int[distinct]; + int i = 0; + for (Entry entry : multiset.entrySet()) { + elements[i] = entry.getElement(); + counts[i] = entry.getCount(); + i++; + } + } + + Object readResolve() { + LinkedHashMultiset multiset = LinkedHashMultiset.create(elements.length); + for (int i = 0; i < elements.length; i++) { + multiset.add(elements[i], counts[i]); + } + return ImmutableMultiset.copyOf(multiset); + } + + private static final long serialVersionUID = 0; + } +} diff --git a/src/main/java/com/google/common/collect/ImmutableMultisetGwtSerializationDependencies.java b/src/main/java/com/google/common/collect/ImmutableMultisetGwtSerializationDependencies.java new file mode 100644 index 0000000..a8b1899 --- /dev/null +++ b/src/main/java/com/google/common/collect/ImmutableMultisetGwtSerializationDependencies.java @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2016 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import com.google.common.annotations.GwtCompatible; + +/** + * A dummy superclass to support GWT serialization of the element type of an {@link + * ImmutableMultiset}. The GWT supersource for this class contains a field of type {@code E}. + * + *

For details about this hack, see {@code GwtSerializationDependencies}, which takes the same + * approach but with a subclass rather than a superclass. + * + *

TODO(cpovirk): Consider applying this subclass approach to our other types. + * + *

For {@code ImmutableMultiset} in particular, I ran into a problem with the {@code + * GwtSerializationDependencies} approach: When autogenerating a serializer for the new class, GWT + * tries to refer to our dummy serializer for the superclass, + * ImmutableMultiset_CustomFieldSerializer. But that type has no methods (since it's never actually + * used). We could probably fix the problem by adding dummy methods to that class, but that is + * starting to sound harder than taking the superclass approach, which I've been coming to like, + * anyway, since it doesn't require us to declare dummy methods (though occasionally constructors) + * and make types non-final. + */ +@GwtCompatible(emulated = true) +abstract class ImmutableMultisetGwtSerializationDependencies extends ImmutableCollection {} diff --git a/src/main/java/com/google/common/collect/ImmutableRangeMap.java b/src/main/java/com/google/common/collect/ImmutableRangeMap.java new file mode 100644 index 0000000..2a11531 --- /dev/null +++ b/src/main/java/com/google/common/collect/ImmutableRangeMap.java @@ -0,0 +1,426 @@ +/* + * Copyright (C) 2012 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.collect; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkElementIndex; +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.annotations.Beta; +import com.google.common.annotations.GwtIncompatible; +import com.google.common.collect.SortedLists.KeyAbsentBehavior; +import com.google.common.collect.SortedLists.KeyPresentBehavior; + +import java.io.Serializable; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.NoSuchElementException; +import java.util.function.BiFunction; +import java.util.function.Function; +import java.util.stream.Collector; + + +/** + * A {@link RangeMap} whose contents will never change, with many other important properties + * detailed at {@link ImmutableCollection}. + * + * @author Louis Wasserman + * @since 14.0 + */ +@Beta +@GwtIncompatible // NavigableMap +public class ImmutableRangeMap, V> implements RangeMap, Serializable { + + private static final ImmutableRangeMap, Object> EMPTY = + new ImmutableRangeMap<>(ImmutableList.>>of(), ImmutableList.of()); + + /** + * Returns a {@code Collector} that accumulates the input elements into a new {@code + * ImmutableRangeMap}. As in {@link Builder}, overlapping ranges are not permitted. + * + * @since 23.1 + */ + public static , V> + Collector> toImmutableRangeMap( + Function> keyFunction, + Function valueFunction) { + return CollectCollectors.toImmutableRangeMap(keyFunction, valueFunction); + } + + /** Returns an empty immutable range map. */ + @SuppressWarnings("unchecked") + public static , V> ImmutableRangeMap of() { + return (ImmutableRangeMap) EMPTY; + } + + /** Returns an immutable range map mapping a single range to a single value. */ + public static , V> ImmutableRangeMap of(Range range, V value) { + return new ImmutableRangeMap<>(ImmutableList.of(range), ImmutableList.of(value)); + } + + @SuppressWarnings("unchecked") + public static , V> ImmutableRangeMap copyOf( + RangeMap rangeMap) { + if (rangeMap instanceof ImmutableRangeMap) { + return (ImmutableRangeMap) rangeMap; + } + Map, ? extends V> map = rangeMap.asMapOfRanges(); + ImmutableList.Builder> rangesBuilder = new ImmutableList.Builder<>(map.size()); + ImmutableList.Builder valuesBuilder = new ImmutableList.Builder(map.size()); + for (Entry, ? extends V> entry : map.entrySet()) { + rangesBuilder.add(entry.getKey()); + valuesBuilder.add(entry.getValue()); + } + return new ImmutableRangeMap<>(rangesBuilder.build(), valuesBuilder.build()); + } + + /** Returns a new builder for an immutable range map. */ + public static , V> Builder builder() { + return new Builder<>(); + } + + /** + * A builder for immutable range maps. Overlapping ranges are prohibited. + * + * @since 14.0 + */ + public static final class Builder, V> { + private final List, V>> entries; + + public Builder() { + this.entries = Lists.newArrayList(); + } + + /** + * Associates the specified range with the specified value. + * + * @throws IllegalArgumentException if {@code range} is empty + */ + + public Builder put(Range range, V value) { + checkNotNull(range); + checkNotNull(value); + checkArgument(!range.isEmpty(), "Range must not be empty, but was %s", range); + entries.add(Maps.immutableEntry(range, value)); + return this; + } + + /** Copies all associations from the specified range map into this builder. */ + + public Builder putAll(RangeMap rangeMap) { + for (Entry, ? extends V> entry : rangeMap.asMapOfRanges().entrySet()) { + put(entry.getKey(), entry.getValue()); + } + return this; + } + + + Builder combine(Builder builder) { + entries.addAll(builder.entries); + return this; + } + + /** + * Returns an {@code ImmutableRangeMap} containing the associations previously added to this + * builder. + * + * @throws IllegalArgumentException if any two ranges inserted into this builder overlap + */ + public ImmutableRangeMap build() { + Collections.sort(entries, Range.rangeLexOrdering().onKeys()); + ImmutableList.Builder> rangesBuilder = new ImmutableList.Builder<>(entries.size()); + ImmutableList.Builder valuesBuilder = new ImmutableList.Builder(entries.size()); + for (int i = 0; i < entries.size(); i++) { + Range range = entries.get(i).getKey(); + if (i > 0) { + Range prevRange = entries.get(i - 1).getKey(); + if (range.isConnected(prevRange) && !range.intersection(prevRange).isEmpty()) { + throw new IllegalArgumentException( + "Overlapping ranges: range " + prevRange + " overlaps with entry " + range); + } + } + rangesBuilder.add(range); + valuesBuilder.add(entries.get(i).getValue()); + } + return new ImmutableRangeMap<>(rangesBuilder.build(), valuesBuilder.build()); + } + } + + private final transient ImmutableList> ranges; + private final transient ImmutableList values; + + ImmutableRangeMap(ImmutableList> ranges, ImmutableList values) { + this.ranges = ranges; + this.values = values; + } + + @Override + public V get(K key) { + int index = + SortedLists.binarySearch( + ranges, + Range.lowerBoundFn(), + Cut.belowValue(key), + KeyPresentBehavior.ANY_PRESENT, + KeyAbsentBehavior.NEXT_LOWER); + if (index == -1) { + return null; + } else { + Range range = ranges.get(index); + return range.contains(key) ? values.get(index) : null; + } + } + + @Override + public Entry, V> getEntry(K key) { + int index = + SortedLists.binarySearch( + ranges, + Range.lowerBoundFn(), + Cut.belowValue(key), + KeyPresentBehavior.ANY_PRESENT, + KeyAbsentBehavior.NEXT_LOWER); + if (index == -1) { + return null; + } else { + Range range = ranges.get(index); + return range.contains(key) ? Maps.immutableEntry(range, values.get(index)) : null; + } + } + + @Override + public Range span() { + if (ranges.isEmpty()) { + throw new NoSuchElementException(); + } + Range firstRange = ranges.get(0); + Range lastRange = ranges.get(ranges.size() - 1); + return Range.create(firstRange.lowerBound, lastRange.upperBound); + } + + /** + * Guaranteed to throw an exception and leave the {@code RangeMap} unmodified. + * + * @throws UnsupportedOperationException always + * @deprecated Unsupported operation. + */ + @Deprecated + @Override + public void put(Range range, V value) { + throw new UnsupportedOperationException(); + } + + /** + * Guaranteed to throw an exception and leave the {@code RangeMap} unmodified. + * + * @throws UnsupportedOperationException always + * @deprecated Unsupported operation. + */ + @Deprecated + @Override + public void putCoalescing(Range range, V value) { + throw new UnsupportedOperationException(); + } + + /** + * Guaranteed to throw an exception and leave the {@code RangeMap} unmodified. + * + * @throws UnsupportedOperationException always + * @deprecated Unsupported operation. + */ + @Deprecated + @Override + public void putAll(RangeMap rangeMap) { + throw new UnsupportedOperationException(); + } + + /** + * Guaranteed to throw an exception and leave the {@code RangeMap} unmodified. + * + * @throws UnsupportedOperationException always + * @deprecated Unsupported operation. + */ + @Deprecated + @Override + public void clear() { + throw new UnsupportedOperationException(); + } + + /** + * Guaranteed to throw an exception and leave the {@code RangeMap} unmodified. + * + * @throws UnsupportedOperationException always + * @deprecated Unsupported operation. + */ + @Deprecated + @Override + public void remove(Range range) { + throw new UnsupportedOperationException(); + } + + /** + * Guaranteed to throw an exception and leave the {@code RangeMap} unmodified. + * + * @throws UnsupportedOperationException always + * @deprecated Unsupported operation. + */ + @Deprecated + @Override + public void merge( + Range range, + V value, + BiFunction remappingFunction) { + throw new UnsupportedOperationException(); + } + + @Override + public ImmutableMap, V> asMapOfRanges() { + if (ranges.isEmpty()) { + return ImmutableMap.of(); + } + RegularImmutableSortedSet> rangeSet = + new RegularImmutableSortedSet<>(ranges, Range.rangeLexOrdering()); + return new ImmutableSortedMap<>(rangeSet, values); + } + + @Override + public ImmutableMap, V> asDescendingMapOfRanges() { + if (ranges.isEmpty()) { + return ImmutableMap.of(); + } + RegularImmutableSortedSet> rangeSet = + new RegularImmutableSortedSet<>(ranges.reverse(), Range.rangeLexOrdering().reverse()); + return new ImmutableSortedMap<>(rangeSet, values.reverse()); + } + + @Override + public ImmutableRangeMap subRangeMap(final Range range) { + if (checkNotNull(range).isEmpty()) { + return ImmutableRangeMap.of(); + } else if (ranges.isEmpty() || range.encloses(span())) { + return this; + } + int lowerIndex = + SortedLists.binarySearch( + ranges, + Range.upperBoundFn(), + range.lowerBound, + KeyPresentBehavior.FIRST_AFTER, + KeyAbsentBehavior.NEXT_HIGHER); + int upperIndex = + SortedLists.binarySearch( + ranges, + Range.lowerBoundFn(), + range.upperBound, + KeyPresentBehavior.ANY_PRESENT, + KeyAbsentBehavior.NEXT_HIGHER); + if (lowerIndex >= upperIndex) { + return ImmutableRangeMap.of(); + } + final int off = lowerIndex; + final int len = upperIndex - lowerIndex; + ImmutableList> subRanges = + new ImmutableList>() { + @Override + public int size() { + return len; + } + + @Override + public Range get(int index) { + checkElementIndex(index, len); + if (index == 0 || index == len - 1) { + return ranges.get(index + off).intersection(range); + } else { + return ranges.get(index + off); + } + } + + @Override + boolean isPartialView() { + return true; + } + }; + final ImmutableRangeMap outer = this; + return new ImmutableRangeMap(subRanges, values.subList(lowerIndex, upperIndex)) { + @Override + public ImmutableRangeMap subRangeMap(Range subRange) { + if (range.isConnected(subRange)) { + return outer.subRangeMap(subRange.intersection(range)); + } else { + return ImmutableRangeMap.of(); + } + } + }; + } + + @Override + public int hashCode() { + return asMapOfRanges().hashCode(); + } + + @Override + public boolean equals(Object o) { + if (o instanceof RangeMap) { + RangeMap rangeMap = (RangeMap) o; + return asMapOfRanges().equals(rangeMap.asMapOfRanges()); + } + return false; + } + + @Override + public String toString() { + return asMapOfRanges().toString(); + } + + /** + * This class is used to serialize ImmutableRangeMap instances. Serializes the {@link + * #asMapOfRanges()} form. + */ + private static class SerializedForm, V> implements Serializable { + + private final ImmutableMap, V> mapOfRanges; + + SerializedForm(ImmutableMap, V> mapOfRanges) { + this.mapOfRanges = mapOfRanges; + } + + Object readResolve() { + if (mapOfRanges.isEmpty()) { + return of(); + } else { + return createRangeMap(); + } + } + + Object createRangeMap() { + Builder builder = new Builder<>(); + for (Entry, V> entry : mapOfRanges.entrySet()) { + builder.put(entry.getKey(), entry.getValue()); + } + return builder.build(); + } + + private static final long serialVersionUID = 0; + } + + Object writeReplace() { + return new SerializedForm<>(asMapOfRanges()); + } + + private static final long serialVersionUID = 0; +} diff --git a/src/main/java/com/google/common/collect/ImmutableRangeSet.java b/src/main/java/com/google/common/collect/ImmutableRangeSet.java new file mode 100644 index 0000000..9ffb6e3 --- /dev/null +++ b/src/main/java/com/google/common/collect/ImmutableRangeSet.java @@ -0,0 +1,830 @@ +/* + * Copyright (C) 2012 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.collect; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkElementIndex; +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.collect.SortedLists.KeyAbsentBehavior.NEXT_HIGHER; +import static com.google.common.collect.SortedLists.KeyAbsentBehavior.NEXT_LOWER; +import static com.google.common.collect.SortedLists.KeyPresentBehavior.ANY_PRESENT; + +import com.google.common.annotations.Beta; +import com.google.common.annotations.GwtIncompatible; +import com.google.common.collect.SortedLists.KeyAbsentBehavior; +import com.google.common.collect.SortedLists.KeyPresentBehavior; +import com.google.common.primitives.Ints; + + +import java.io.Serializable; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.NoSuchElementException; +import java.util.Set; +import java.util.stream.Collector; + + + +/** + * A {@link RangeSet} whose contents will never change, with many other important properties + * detailed at {@link ImmutableCollection}. + * + * @author Louis Wasserman + * @since 14.0 + */ +@Beta +@GwtIncompatible +public final class ImmutableRangeSet extends AbstractRangeSet + implements Serializable { + + private static final ImmutableRangeSet> EMPTY = + new ImmutableRangeSet<>(ImmutableList.>>of()); + + private static final ImmutableRangeSet> ALL = + new ImmutableRangeSet<>(ImmutableList.of(Range.>all())); + + /** + * Returns a {@code Collector} that accumulates the input elements into a new {@code + * ImmutableRangeSet}. As in {@link Builder}, overlapping ranges are not permitted and adjacent + * ranges will be merged. + * + * @since 23.1 + */ + public static > + Collector, ?, ImmutableRangeSet> toImmutableRangeSet() { + return CollectCollectors.toImmutableRangeSet(); + } + + /** Returns an empty immutable range set. */ + @SuppressWarnings("unchecked") + public static ImmutableRangeSet of() { + return (ImmutableRangeSet) EMPTY; + } + + /** + * Returns an immutable range set containing the specified single range. If {@link Range#isEmpty() + * range.isEmpty()}, this is equivalent to {@link ImmutableRangeSet#of()}. + */ + public static ImmutableRangeSet of(Range range) { + checkNotNull(range); + if (range.isEmpty()) { + return of(); + } else if (range.equals(Range.all())) { + return all(); + } else { + return new ImmutableRangeSet(ImmutableList.of(range)); + } + } + + /** Returns an immutable range set containing the single range {@link Range#all()}. */ + @SuppressWarnings("unchecked") + static ImmutableRangeSet all() { + return (ImmutableRangeSet) ALL; + } + + /** Returns an immutable copy of the specified {@code RangeSet}. */ + public static ImmutableRangeSet copyOf(RangeSet rangeSet) { + checkNotNull(rangeSet); + if (rangeSet.isEmpty()) { + return of(); + } else if (rangeSet.encloses(Range.all())) { + return all(); + } + + if (rangeSet instanceof ImmutableRangeSet) { + ImmutableRangeSet immutableRangeSet = (ImmutableRangeSet) rangeSet; + if (!immutableRangeSet.isPartialView()) { + return immutableRangeSet; + } + } + return new ImmutableRangeSet(ImmutableList.copyOf(rangeSet.asRanges())); + } + + /** + * Returns an {@code ImmutableRangeSet} containing each of the specified disjoint ranges. + * Overlapping ranges and empty ranges are forbidden, though adjacent ranges are permitted and + * will be merged. + * + * @throws IllegalArgumentException if any ranges overlap or are empty + * @since 21.0 + */ + public static > ImmutableRangeSet copyOf(Iterable> ranges) { + return new ImmutableRangeSet.Builder().addAll(ranges).build(); + } + + /** + * Returns an {@code ImmutableRangeSet} representing the union of the specified ranges. + * + *

This is the smallest {@code RangeSet} which encloses each of the specified ranges. Duplicate + * or connected ranges are permitted, and will be coalesced in the result. + * + * @since 21.0 + */ + public static > ImmutableRangeSet unionOf(Iterable> ranges) { + return copyOf(TreeRangeSet.create(ranges)); + } + + ImmutableRangeSet(ImmutableList> ranges) { + this.ranges = ranges; + } + + private ImmutableRangeSet(ImmutableList> ranges, ImmutableRangeSet complement) { + this.ranges = ranges; + this.complement = complement; + } + + private final transient ImmutableList> ranges; + + @Override + public boolean intersects(Range otherRange) { + int ceilingIndex = + SortedLists.binarySearch( + ranges, + Range.lowerBoundFn(), + otherRange.lowerBound, + Ordering.natural(), + ANY_PRESENT, + NEXT_HIGHER); + if (ceilingIndex < ranges.size() + && ranges.get(ceilingIndex).isConnected(otherRange) + && !ranges.get(ceilingIndex).intersection(otherRange).isEmpty()) { + return true; + } + return ceilingIndex > 0 + && ranges.get(ceilingIndex - 1).isConnected(otherRange) + && !ranges.get(ceilingIndex - 1).intersection(otherRange).isEmpty(); + } + + @Override + public boolean encloses(Range otherRange) { + int index = + SortedLists.binarySearch( + ranges, + Range.lowerBoundFn(), + otherRange.lowerBound, + Ordering.natural(), + ANY_PRESENT, + NEXT_LOWER); + return index != -1 && ranges.get(index).encloses(otherRange); + } + + @Override + public Range rangeContaining(C value) { + int index = + SortedLists.binarySearch( + ranges, + Range.lowerBoundFn(), + Cut.belowValue(value), + Ordering.natural(), + ANY_PRESENT, + NEXT_LOWER); + if (index != -1) { + Range range = ranges.get(index); + return range.contains(value) ? range : null; + } + return null; + } + + @Override + public Range span() { + if (ranges.isEmpty()) { + throw new NoSuchElementException(); + } + return Range.create(ranges.get(0).lowerBound, ranges.get(ranges.size() - 1).upperBound); + } + + @Override + public boolean isEmpty() { + return ranges.isEmpty(); + } + + /** + * Guaranteed to throw an exception and leave the {@code RangeSet} unmodified. + * + * @throws UnsupportedOperationException always + * @deprecated Unsupported operation. + */ + @Deprecated + @Override + public void add(Range range) { + throw new UnsupportedOperationException(); + } + + /** + * Guaranteed to throw an exception and leave the {@code RangeSet} unmodified. + * + * @throws UnsupportedOperationException always + * @deprecated Unsupported operation. + */ + @Deprecated + @Override + public void addAll(RangeSet other) { + throw new UnsupportedOperationException(); + } + + /** + * Guaranteed to throw an exception and leave the {@code RangeSet} unmodified. + * + * @throws UnsupportedOperationException always + * @deprecated Unsupported operation. + */ + @Deprecated + @Override + public void addAll(Iterable> other) { + throw new UnsupportedOperationException(); + } + + /** + * Guaranteed to throw an exception and leave the {@code RangeSet} unmodified. + * + * @throws UnsupportedOperationException always + * @deprecated Unsupported operation. + */ + @Deprecated + @Override + public void remove(Range range) { + throw new UnsupportedOperationException(); + } + + /** + * Guaranteed to throw an exception and leave the {@code RangeSet} unmodified. + * + * @throws UnsupportedOperationException always + * @deprecated Unsupported operation. + */ + @Deprecated + @Override + public void removeAll(RangeSet other) { + throw new UnsupportedOperationException(); + } + + /** + * Guaranteed to throw an exception and leave the {@code RangeSet} unmodified. + * + * @throws UnsupportedOperationException always + * @deprecated Unsupported operation. + */ + @Deprecated + @Override + public void removeAll(Iterable> other) { + throw new UnsupportedOperationException(); + } + + @Override + public ImmutableSet> asRanges() { + if (ranges.isEmpty()) { + return ImmutableSet.of(); + } + return new RegularImmutableSortedSet<>(ranges, Range.rangeLexOrdering()); + } + + @Override + public ImmutableSet> asDescendingSetOfRanges() { + if (ranges.isEmpty()) { + return ImmutableSet.of(); + } + return new RegularImmutableSortedSet<>(ranges.reverse(), Range.rangeLexOrdering().reverse()); + } + + private transient ImmutableRangeSet complement; + + private final class ComplementRanges extends ImmutableList> { + // True if the "positive" range set is empty or bounded below. + private final boolean positiveBoundedBelow; + + // True if the "positive" range set is empty or bounded above. + private final boolean positiveBoundedAbove; + + private final int size; + + ComplementRanges() { + this.positiveBoundedBelow = ranges.get(0).hasLowerBound(); + this.positiveBoundedAbove = Iterables.getLast(ranges).hasUpperBound(); + + int size = ranges.size() - 1; + if (positiveBoundedBelow) { + size++; + } + if (positiveBoundedAbove) { + size++; + } + this.size = size; + } + + @Override + public int size() { + return size; + } + + @Override + public Range get(int index) { + checkElementIndex(index, size); + + Cut lowerBound; + if (positiveBoundedBelow) { + lowerBound = (index == 0) ? Cut.belowAll() : ranges.get(index - 1).upperBound; + } else { + lowerBound = ranges.get(index).upperBound; + } + + Cut upperBound; + if (positiveBoundedAbove && index == size - 1) { + upperBound = Cut.aboveAll(); + } else { + upperBound = ranges.get(index + (positiveBoundedBelow ? 0 : 1)).lowerBound; + } + + return Range.create(lowerBound, upperBound); + } + + @Override + boolean isPartialView() { + return true; + } + } + + @Override + public ImmutableRangeSet complement() { + ImmutableRangeSet result = complement; + if (result != null) { + return result; + } else if (ranges.isEmpty()) { + return complement = all(); + } else if (ranges.size() == 1 && ranges.get(0).equals(Range.all())) { + return complement = of(); + } else { + ImmutableList> complementRanges = new ComplementRanges(); + result = complement = new ImmutableRangeSet(complementRanges, this); + } + return result; + } + + /** + * Returns a new range set consisting of the union of this range set and {@code other}. + * + *

This is essentially the same as {@code TreeRangeSet.create(this).addAll(other)} except it + * returns an {@code ImmutableRangeSet}. + * + * @since 21.0 + */ + public ImmutableRangeSet union(RangeSet other) { + return unionOf(Iterables.concat(asRanges(), other.asRanges())); + } + + /** + * Returns a new range set consisting of the intersection of this range set and {@code other}. + * + *

This is essentially the same as {@code + * TreeRangeSet.create(this).removeAll(other.complement())} except it returns an {@code + * ImmutableRangeSet}. + * + * @since 21.0 + */ + public ImmutableRangeSet intersection(RangeSet other) { + RangeSet copy = TreeRangeSet.create(this); + copy.removeAll(other.complement()); + return copyOf(copy); + } + + /** + * Returns a new range set consisting of the difference of this range set and {@code other}. + * + *

This is essentially the same as {@code TreeRangeSet.create(this).removeAll(other)} except it + * returns an {@code ImmutableRangeSet}. + * + * @since 21.0 + */ + public ImmutableRangeSet difference(RangeSet other) { + RangeSet copy = TreeRangeSet.create(this); + copy.removeAll(other); + return copyOf(copy); + } + + /** + * Returns a list containing the nonempty intersections of {@code range} with the ranges in this + * range set. + */ + private ImmutableList> intersectRanges(final Range range) { + if (ranges.isEmpty() || range.isEmpty()) { + return ImmutableList.of(); + } else if (range.encloses(span())) { + return ranges; + } + + final int fromIndex; + if (range.hasLowerBound()) { + fromIndex = + SortedLists.binarySearch( + ranges, + Range.upperBoundFn(), + range.lowerBound, + KeyPresentBehavior.FIRST_AFTER, + KeyAbsentBehavior.NEXT_HIGHER); + } else { + fromIndex = 0; + } + + int toIndex; + if (range.hasUpperBound()) { + toIndex = + SortedLists.binarySearch( + ranges, + Range.lowerBoundFn(), + range.upperBound, + KeyPresentBehavior.FIRST_PRESENT, + KeyAbsentBehavior.NEXT_HIGHER); + } else { + toIndex = ranges.size(); + } + final int length = toIndex - fromIndex; + if (length == 0) { + return ImmutableList.of(); + } else { + return new ImmutableList>() { + @Override + public int size() { + return length; + } + + @Override + public Range get(int index) { + checkElementIndex(index, length); + if (index == 0 || index == length - 1) { + return ranges.get(index + fromIndex).intersection(range); + } else { + return ranges.get(index + fromIndex); + } + } + + @Override + boolean isPartialView() { + return true; + } + }; + } + } + + /** Returns a view of the intersection of this range set with the given range. */ + @Override + public ImmutableRangeSet subRangeSet(Range range) { + if (!isEmpty()) { + Range span = span(); + if (range.encloses(span)) { + return this; + } else if (range.isConnected(span)) { + return new ImmutableRangeSet(intersectRanges(range)); + } + } + return of(); + } + + /** + * Returns an {@link ImmutableSortedSet} containing the same values in the given domain + * {@linkplain RangeSet#contains contained} by this range set. + * + *

Note: {@code a.asSet(d).equals(b.asSet(d))} does not imply {@code a.equals(b)}! For + * example, {@code a} and {@code b} could be {@code [2..4]} and {@code (1..5)}, or the empty + * ranges {@code [3..3)} and {@code [4..4)}. + * + *

Warning: Be extremely careful what you do with the {@code asSet} view of a large + * range set (such as {@code ImmutableRangeSet.of(Range.greaterThan(0))}). Certain operations on + * such a set can be performed efficiently, but others (such as {@link Set#hashCode} or {@link + * Collections#frequency}) can cause major performance problems. + * + *

The returned set's {@link Object#toString} method returns a short-hand form of the set's + * contents, such as {@code "[1..100]}"}. + * + * @throws IllegalArgumentException if neither this range nor the domain has a lower bound, or if + * neither has an upper bound + */ + public ImmutableSortedSet asSet(DiscreteDomain domain) { + checkNotNull(domain); + if (isEmpty()) { + return ImmutableSortedSet.of(); + } + Range span = span().canonical(domain); + if (!span.hasLowerBound()) { + // according to the spec of canonical, neither this ImmutableRangeSet nor + // the range have a lower bound + throw new IllegalArgumentException( + "Neither the DiscreteDomain nor this range set are bounded below"); + } else if (!span.hasUpperBound()) { + try { + domain.maxValue(); + } catch (NoSuchElementException e) { + throw new IllegalArgumentException( + "Neither the DiscreteDomain nor this range set are bounded above"); + } + } + + return new AsSet(domain); + } + + private final class AsSet extends ImmutableSortedSet { + private final DiscreteDomain domain; + + AsSet(DiscreteDomain domain) { + super(Ordering.natural()); + this.domain = domain; + } + + private transient Integer size; + + @Override + public int size() { + // racy single-check idiom + Integer result = size; + if (result == null) { + long total = 0; + for (Range range : ranges) { + total += ContiguousSet.create(range, domain).size(); + if (total >= Integer.MAX_VALUE) { + break; + } + } + result = size = Ints.saturatedCast(total); + } + return result.intValue(); + } + + @Override + public UnmodifiableIterator iterator() { + return new AbstractIterator() { + final Iterator> rangeItr = ranges.iterator(); + Iterator elemItr = Iterators.emptyIterator(); + + @Override + protected C computeNext() { + while (!elemItr.hasNext()) { + if (rangeItr.hasNext()) { + elemItr = ContiguousSet.create(rangeItr.next(), domain).iterator(); + } else { + return endOfData(); + } + } + return elemItr.next(); + } + }; + } + + @Override + @GwtIncompatible("NavigableSet") + public UnmodifiableIterator descendingIterator() { + return new AbstractIterator() { + final Iterator> rangeItr = ranges.reverse().iterator(); + Iterator elemItr = Iterators.emptyIterator(); + + @Override + protected C computeNext() { + while (!elemItr.hasNext()) { + if (rangeItr.hasNext()) { + elemItr = ContiguousSet.create(rangeItr.next(), domain).descendingIterator(); + } else { + return endOfData(); + } + } + return elemItr.next(); + } + }; + } + + ImmutableSortedSet subSet(Range range) { + return subRangeSet(range).asSet(domain); + } + + @Override + ImmutableSortedSet headSetImpl(C toElement, boolean inclusive) { + return subSet(Range.upTo(toElement, BoundType.forBoolean(inclusive))); + } + + @Override + ImmutableSortedSet subSetImpl( + C fromElement, boolean fromInclusive, C toElement, boolean toInclusive) { + if (!fromInclusive && !toInclusive && Range.compareOrThrow(fromElement, toElement) == 0) { + return ImmutableSortedSet.of(); + } + return subSet( + Range.range( + fromElement, BoundType.forBoolean(fromInclusive), + toElement, BoundType.forBoolean(toInclusive))); + } + + @Override + ImmutableSortedSet tailSetImpl(C fromElement, boolean inclusive) { + return subSet(Range.downTo(fromElement, BoundType.forBoolean(inclusive))); + } + + @Override + public boolean contains(Object o) { + if (o == null) { + return false; + } + try { + @SuppressWarnings("unchecked") // we catch CCE's + C c = (C) o; + return ImmutableRangeSet.this.contains(c); + } catch (ClassCastException e) { + return false; + } + } + + @Override + int indexOf(Object target) { + if (contains(target)) { + @SuppressWarnings("unchecked") // if it's contained, it's definitely a C + C c = (C) target; + long total = 0; + for (Range range : ranges) { + if (range.contains(c)) { + return Ints.saturatedCast(total + ContiguousSet.create(range, domain).indexOf(c)); + } else { + total += ContiguousSet.create(range, domain).size(); + } + } + throw new AssertionError("impossible"); + } + return -1; + } + + @Override + ImmutableSortedSet createDescendingSet() { + return new DescendingImmutableSortedSet(this); + } + + @Override + boolean isPartialView() { + return ranges.isPartialView(); + } + + @Override + public String toString() { + return ranges.toString(); + } + + @Override + Object writeReplace() { + return new AsSetSerializedForm(ranges, domain); + } + } + + private static class AsSetSerializedForm implements Serializable { + private final ImmutableList> ranges; + private final DiscreteDomain domain; + + AsSetSerializedForm(ImmutableList> ranges, DiscreteDomain domain) { + this.ranges = ranges; + this.domain = domain; + } + + Object readResolve() { + return new ImmutableRangeSet(ranges).asSet(domain); + } + } + + /** + * Returns {@code true} if this immutable range set's implementation contains references to + * user-created objects that aren't accessible via this range set's methods. This is generally + * used to determine whether {@code copyOf} implementations should make an explicit copy to avoid + * memory leaks. + */ + boolean isPartialView() { + return ranges.isPartialView(); + } + + /** Returns a new builder for an immutable range set. */ + public static > Builder builder() { + return new Builder(); + } + + /** + * A builder for immutable range sets. + * + * @since 14.0 + */ + public static class Builder> { + private final List> ranges; + + public Builder() { + this.ranges = Lists.newArrayList(); + } + + // TODO(lowasser): consider adding union, in addition to add, that does allow overlap + + /** + * Add the specified range to this builder. Adjacent ranges are permitted and will be merged, + * but overlapping ranges will cause an exception when {@link #build()} is called. + * + * @throws IllegalArgumentException if {@code range} is empty + */ + + public Builder add(Range range) { + checkArgument(!range.isEmpty(), "range must not be empty, but was %s", range); + ranges.add(range); + return this; + } + + /** + * Add all ranges from the specified range set to this builder. Adjacent ranges are permitted + * and will be merged, but overlapping ranges will cause an exception when {@link #build()} is + * called. + */ + + public Builder addAll(RangeSet ranges) { + return addAll(ranges.asRanges()); + } + + /** + * Add all of the specified ranges to this builder. Adjacent ranges are permitted and will be + * merged, but overlapping ranges will cause an exception when {@link #build()} is called. + * + * @throws IllegalArgumentException if any inserted ranges are empty + * @since 21.0 + */ + + public Builder addAll(Iterable> ranges) { + for (Range range : ranges) { + add(range); + } + return this; + } + + + Builder combine(Builder builder) { + addAll(builder.ranges); + return this; + } + + /** + * Returns an {@code ImmutableRangeSet} containing the ranges added to this builder. + * + * @throws IllegalArgumentException if any input ranges have nonempty overlap + */ + public ImmutableRangeSet build() { + ImmutableList.Builder> mergedRangesBuilder = + new ImmutableList.Builder<>(ranges.size()); + Collections.sort(ranges, Range.rangeLexOrdering()); + PeekingIterator> peekingItr = Iterators.peekingIterator(ranges.iterator()); + while (peekingItr.hasNext()) { + Range range = peekingItr.next(); + while (peekingItr.hasNext()) { + Range nextRange = peekingItr.peek(); + if (range.isConnected(nextRange)) { + checkArgument( + range.intersection(nextRange).isEmpty(), + "Overlapping ranges not permitted but found %s overlapping %s", + range, + nextRange); + range = range.span(peekingItr.next()); + } else { + break; + } + } + mergedRangesBuilder.add(range); + } + ImmutableList> mergedRanges = mergedRangesBuilder.build(); + if (mergedRanges.isEmpty()) { + return of(); + } else if (mergedRanges.size() == 1 + && Iterables.getOnlyElement(mergedRanges).equals(Range.all())) { + return all(); + } else { + return new ImmutableRangeSet(mergedRanges); + } + } + } + + private static final class SerializedForm implements Serializable { + private final ImmutableList> ranges; + + SerializedForm(ImmutableList> ranges) { + this.ranges = ranges; + } + + Object readResolve() { + if (ranges.isEmpty()) { + return of(); + } else if (ranges.equals(ImmutableList.of(Range.all()))) { + return all(); + } else { + return new ImmutableRangeSet(ranges); + } + } + } + + Object writeReplace() { + return new SerializedForm(ranges); + } +} diff --git a/src/main/java/com/google/common/collect/ImmutableSet.java b/src/main/java/com/google/common/collect/ImmutableSet.java new file mode 100644 index 0000000..9926dc4 --- /dev/null +++ b/src/main/java/com/google/common/collect/ImmutableSet.java @@ -0,0 +1,880 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.collect.CollectPreconditions.checkNonnegative; + +import com.google.common.annotations.Beta; +import com.google.common.annotations.GwtCompatible; +import com.google.common.annotations.VisibleForTesting; +import com.google.common.math.IntMath; +import com.google.common.primitives.Ints; + + + +import java.io.Serializable; +import java.math.RoundingMode; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.EnumSet; +import java.util.Iterator; +import java.util.Set; +import java.util.SortedSet; +import java.util.Spliterator; +import java.util.function.Consumer; +import java.util.stream.Collector; + + +/** + * A {@link Set} whose contents will never change, with many other important properties detailed at + * {@link ImmutableCollection}. + * + * @since 2.0 + */ +@GwtCompatible(serializable = true, emulated = true) +@SuppressWarnings("serial") // we're overriding default serialization +public abstract class ImmutableSet extends ImmutableCollection implements Set { + static final int SPLITERATOR_CHARACTERISTICS = + ImmutableCollection.SPLITERATOR_CHARACTERISTICS | Spliterator.DISTINCT; + + /** + * Returns a {@code Collector} that accumulates the input elements into a new {@code + * ImmutableSet}. Elements appear in the resulting set in the encounter order of the stream; if + * the stream contains duplicates (according to {@link Object#equals(Object)}), only the first + * duplicate in encounter order will appear in the result. + * + * @since 21.0 + */ + public static Collector> toImmutableSet() { + return CollectCollectors.toImmutableSet(); + } + + /** + * Returns the empty immutable set. Preferred over {@link Collections#emptySet} for code + * consistency, and because the return type conveys the immutability guarantee. + */ + @SuppressWarnings({"unchecked"}) // fully variant implementation (never actually produces any Es) + public static ImmutableSet of() { + return (ImmutableSet) RegularImmutableSet.EMPTY; + } + + /** + * Returns an immutable set containing {@code element}. Preferred over {@link + * Collections#singleton} for code consistency, {@code null} rejection, and because the return + * type conveys the immutability guarantee. + */ + public static ImmutableSet of(E element) { + return new SingletonImmutableSet(element); + } + + /** + * Returns an immutable set containing the given elements, minus duplicates, in the order each was + * first specified. That is, if multiple elements are {@linkplain Object#equals equal}, all except + * the first are ignored. + */ + public static ImmutableSet of(E e1, E e2) { + return construct(2, 2, e1, e2); + } + + /** + * Returns an immutable set containing the given elements, minus duplicates, in the order each was + * first specified. That is, if multiple elements are {@linkplain Object#equals equal}, all except + * the first are ignored. + */ + public static ImmutableSet of(E e1, E e2, E e3) { + return construct(3, 3, e1, e2, e3); + } + + /** + * Returns an immutable set containing the given elements, minus duplicates, in the order each was + * first specified. That is, if multiple elements are {@linkplain Object#equals equal}, all except + * the first are ignored. + */ + public static ImmutableSet of(E e1, E e2, E e3, E e4) { + return construct(4, 4, e1, e2, e3, e4); + } + + /** + * Returns an immutable set containing the given elements, minus duplicates, in the order each was + * first specified. That is, if multiple elements are {@linkplain Object#equals equal}, all except + * the first are ignored. + */ + public static ImmutableSet of(E e1, E e2, E e3, E e4, E e5) { + return construct(5, 5, e1, e2, e3, e4, e5); + } + + /** + * Returns an immutable set containing the given elements, minus duplicates, in the order each was + * first specified. That is, if multiple elements are {@linkplain Object#equals equal}, all except + * the first are ignored. + * + *

The array {@code others} must not be longer than {@code Integer.MAX_VALUE - 6}. + * + * @since 3.0 (source-compatible since 2.0) + */ + @SafeVarargs // For Eclipse. For internal javac we have disabled this pointless type of warning. + public static ImmutableSet of(E e1, E e2, E e3, E e4, E e5, E e6, E... others) { + checkArgument( + others.length <= Integer.MAX_VALUE - 6, "the total number of elements must fit in an int"); + final int paramCount = 6; + Object[] elements = new Object[paramCount + others.length]; + elements[0] = e1; + elements[1] = e2; + elements[2] = e3; + elements[3] = e4; + elements[4] = e5; + elements[5] = e6; + System.arraycopy(others, 0, elements, paramCount, others.length); + return construct(elements.length, elements.length, elements); + } + + /** + * Constructs an {@code ImmutableSet} from the first {@code n} elements of the specified array, + * which we have no particular reason to believe does or does not contain duplicates. If {@code k} + * is the size of the returned {@code ImmutableSet}, then the unique elements of {@code elements} + * will be in the first {@code k} positions, and {@code elements[i] == null} for {@code k <= i < + * n}. + * + *

This may modify {@code elements}. Additionally, if {@code n == elements.length} and {@code + * elements} contains no duplicates, {@code elements} may be used without copying in the returned + * {@code ImmutableSet}, in which case the caller must not modify it. + * + *

{@code elements} may contain only values of type {@code E}. + * + * @throws NullPointerException if any of the first {@code n} elements of {@code elements} is null + */ + private static ImmutableSet constructUnknownDuplication(int n, Object... elements) { + // Guess the size is "halfway between" all duplicates and no duplicates, on a log scale. + return construct( + n, + Math.max( + ImmutableCollection.Builder.DEFAULT_INITIAL_CAPACITY, + IntMath.sqrt(n, RoundingMode.CEILING)), + elements); + } + + /** + * Constructs an {@code ImmutableSet} from the first {@code n} elements of the specified array. If + * {@code k} is the size of the returned {@code ImmutableSet}, then the unique elements of {@code + * elements} will be in the first {@code k} positions, and {@code elements[i] == null} for {@code + * k <= i < n}. + * + *

This may modify {@code elements}. Additionally, if {@code n == elements.length} and {@code + * elements} contains no duplicates, {@code elements} may be used without copying in the returned + * {@code ImmutableSet}, in which case it may no longer be modified. + * + *

{@code elements} may contain only values of type {@code E}. + * + * @throws NullPointerException if any of the first {@code n} elements of {@code elements} is null + */ + private static ImmutableSet construct(int n, int expectedSize, Object... elements) { + switch (n) { + case 0: + return of(); + case 1: + @SuppressWarnings("unchecked") // safe; elements contains only E's + E elem = (E) elements[0]; + return of(elem); + default: + SetBuilderImpl builder = new RegularSetBuilderImpl(expectedSize); + for (int i = 0; i < n; i++) { + @SuppressWarnings("unchecked") + E e = (E) checkNotNull(elements[i]); + builder = builder.add(e); + } + return builder.review().build(); + } + } + + /** + * Returns an immutable set containing each of {@code elements}, minus duplicates, in the order + * each appears first in the source collection. + * + *

Performance note: This method will sometimes recognize that the actual copy operation + * is unnecessary; for example, {@code copyOf(copyOf(anArrayList))} will copy the data only once. + * This reduces the expense of habitually making defensive copies at API boundaries. However, the + * precise conditions for skipping the copy operation are undefined. + * + * @throws NullPointerException if any of {@code elements} is null + * @since 7.0 (source-compatible since 2.0) + */ + public static ImmutableSet copyOf(Collection elements) { + /* + * TODO(lowasser): consider checking for ImmutableAsList here + * TODO(lowasser): consider checking for Multiset here + */ + // Don't refer to ImmutableSortedSet by name so it won't pull in all that code + if (elements instanceof ImmutableSet && !(elements instanceof SortedSet)) { + @SuppressWarnings("unchecked") // all supported methods are covariant + ImmutableSet set = (ImmutableSet) elements; + if (!set.isPartialView()) { + return set; + } + } else if (elements instanceof EnumSet) { + return copyOfEnumSet((EnumSet) elements); + } + Object[] array = elements.toArray(); + if (elements instanceof Set) { + // assume probably no duplicates (though it might be using different equality semantics) + return construct(array.length, array.length, array); + } else { + return constructUnknownDuplication(array.length, array); + } + } + + /** + * Returns an immutable set containing each of {@code elements}, minus duplicates, in the order + * each appears first in the source iterable. This method iterates over {@code elements} only + * once. + * + *

Performance note: This method will sometimes recognize that the actual copy operation + * is unnecessary; for example, {@code copyOf(copyOf(anArrayList))} should copy the data only + * once. This reduces the expense of habitually making defensive copies at API boundaries. + * However, the precise conditions for skipping the copy operation are undefined. + * + * @throws NullPointerException if any of {@code elements} is null + */ + public static ImmutableSet copyOf(Iterable elements) { + return (elements instanceof Collection) + ? copyOf((Collection) elements) + : copyOf(elements.iterator()); + } + + /** + * Returns an immutable set containing each of {@code elements}, minus duplicates, in the order + * each appears first in the source iterator. + * + * @throws NullPointerException if any of {@code elements} is null + */ + public static ImmutableSet copyOf(Iterator elements) { + // We special-case for 0 or 1 elements, but anything further is madness. + if (!elements.hasNext()) { + return of(); + } + E first = elements.next(); + if (!elements.hasNext()) { + return of(first); + } else { + return new ImmutableSet.Builder().add(first).addAll(elements).build(); + } + } + + /** + * Returns an immutable set containing each of {@code elements}, minus duplicates, in the order + * each appears first in the source array. + * + * @throws NullPointerException if any of {@code elements} is null + * @since 3.0 + */ + public static ImmutableSet copyOf(E[] elements) { + switch (elements.length) { + case 0: + return of(); + case 1: + return of(elements[0]); + default: + return constructUnknownDuplication(elements.length, elements.clone()); + } + } + + @SuppressWarnings("rawtypes") // necessary to compile against Java 8 + private static ImmutableSet copyOfEnumSet(EnumSet enumSet) { + return ImmutableEnumSet.asImmutable(EnumSet.copyOf(enumSet)); + } + + ImmutableSet() {} + + /** Returns {@code true} if the {@code hashCode()} method runs quickly. */ + boolean isHashCodeFast() { + return false; + } + + @Override + public boolean equals(Object object) { + if (object == this) { + return true; + } else if (object instanceof ImmutableSet + && isHashCodeFast() + && ((ImmutableSet) object).isHashCodeFast() + && hashCode() != object.hashCode()) { + return false; + } + return Sets.equalsImpl(this, object); + } + + @Override + public int hashCode() { + return Sets.hashCodeImpl(this); + } + + // This declaration is needed to make Set.iterator() and + // ImmutableCollection.iterator() consistent. + @Override + public abstract UnmodifiableIterator iterator(); + + private transient ImmutableList asList; + + @Override + public ImmutableList asList() { + ImmutableList result = asList; + return (result == null) ? asList = createAsList() : result; + } + + ImmutableList createAsList() { + return new RegularImmutableAsList(this, toArray()); + } + + abstract static class Indexed extends ImmutableSet { + abstract E get(int index); + + @Override + public UnmodifiableIterator iterator() { + return asList().iterator(); + } + + @Override + public Spliterator spliterator() { + return CollectSpliterators.indexed(size(), SPLITERATOR_CHARACTERISTICS, this::get); + } + + @Override + public void forEach(Consumer consumer) { + checkNotNull(consumer); + int n = size(); + for (int i = 0; i < n; i++) { + consumer.accept(get(i)); + } + } + + @Override + int copyIntoArray(Object[] dst, int offset) { + return asList().copyIntoArray(dst, offset); + } + + @Override + ImmutableList createAsList() { + return new ImmutableAsList() { + @Override + public E get(int index) { + return Indexed.this.get(index); + } + + @Override + Indexed delegateCollection() { + return Indexed.this; + } + }; + } + } + + /* + * This class is used to serialize all ImmutableSet instances, except for + * ImmutableEnumSet/ImmutableSortedSet, regardless of implementation type. It + * captures their "logical contents" and they are reconstructed using public + * static factories. This is necessary to ensure that the existence of a + * particular implementation type is an implementation detail. + */ + private static class SerializedForm implements Serializable { + final Object[] elements; + + SerializedForm(Object[] elements) { + this.elements = elements; + } + + Object readResolve() { + return copyOf(elements); + } + + private static final long serialVersionUID = 0; + } + + @Override + Object writeReplace() { + return new SerializedForm(toArray()); + } + + /** + * Returns a new builder. The generated builder is equivalent to the builder created by the {@link + * Builder} constructor. + */ + public static Builder builder() { + return new Builder(); + } + + /** + * Returns a new builder, expecting the specified number of distinct elements to be added. + * + *

If {@code expectedSize} is exactly the number of distinct elements added to the builder + * before {@link Builder#build} is called, the builder is likely to perform better than an unsized + * {@link #builder()} would have. + * + *

It is not specified if any performance benefits apply if {@code expectedSize} is close to, + * but not exactly, the number of distinct elements added to the builder. + * + * @since 23.1 + */ + @Beta + public static Builder builderWithExpectedSize(int expectedSize) { + checkNonnegative(expectedSize, "expectedSize"); + return new Builder(expectedSize); + } + + /** Builds a new open-addressed hash table from the first n objects in elements. */ + static Object[] rebuildHashTable(int newTableSize, Object[] elements, int n) { + Object[] hashTable = new Object[newTableSize]; + int mask = hashTable.length - 1; + for (int i = 0; i < n; i++) { + Object e = elements[i]; + int j0 = Hashing.smear(e.hashCode()); + for (int j = j0; ; j++) { + int index = j & mask; + if (hashTable[index] == null) { + hashTable[index] = e; + break; + } + } + } + return hashTable; + } + + /** + * A builder for creating {@code ImmutableSet} instances. Example: + * + *

{@code
+   * static final ImmutableSet GOOGLE_COLORS =
+   *     ImmutableSet.builder()
+   *         .addAll(WEBSAFE_COLORS)
+   *         .add(new Color(0, 191, 255))
+   *         .build();
+   * }
+ * + *

Elements appear in the resulting set in the same order they were first added to the builder. + * + *

Building does not change the state of the builder, so it is still possible to add more + * elements and to build again. + * + * @since 2.0 + */ + public static class Builder extends ImmutableCollection.Builder { + private SetBuilderImpl impl; + boolean forceCopy; + + public Builder() { + this(DEFAULT_INITIAL_CAPACITY); + } + + Builder(int capacity) { + impl = new RegularSetBuilderImpl(capacity); + } + + Builder(@SuppressWarnings("unused") boolean subclass) { + this.impl = null; // unused + } + + @VisibleForTesting + void forceJdk() { + this.impl = new JdkBackedSetBuilderImpl(impl); + } + + final void copyIfNecessary() { + if (forceCopy) { + copy(); + forceCopy = false; + } + } + + void copy() { + impl = impl.copy(); + } + + @Override + + public Builder add(E element) { + checkNotNull(element); + copyIfNecessary(); + impl = impl.add(element); + return this; + } + + @Override + + public Builder add(E... elements) { + super.add(elements); + return this; + } + + @Override + /** + * Adds each element of {@code elements} to the {@code ImmutableSet}, ignoring duplicate + * elements (only the first duplicate element is added). + * + * @param elements the elements to add + * @return this {@code Builder} object + * @throws NullPointerException if {@code elements} is null or contains a null element + */ + + public Builder addAll(Iterable elements) { + super.addAll(elements); + return this; + } + + @Override + + public Builder addAll(Iterator elements) { + super.addAll(elements); + return this; + } + + Builder combine(Builder other) { + copyIfNecessary(); + this.impl = this.impl.combine(other.impl); + return this; + } + + @Override + public ImmutableSet build() { + forceCopy = true; + impl = impl.review(); + return impl.build(); + } + } + + /** Swappable internal implementation of an ImmutableSet.Builder. */ + private abstract static class SetBuilderImpl { + E[] dedupedElements; + int distinct; + + @SuppressWarnings("unchecked") + SetBuilderImpl(int expectedCapacity) { + this.dedupedElements = (E[]) new Object[expectedCapacity]; + this.distinct = 0; + } + + /** Initializes this SetBuilderImpl with a copy of the deduped elements array from toCopy. */ + SetBuilderImpl(SetBuilderImpl toCopy) { + this.dedupedElements = Arrays.copyOf(toCopy.dedupedElements, toCopy.dedupedElements.length); + this.distinct = toCopy.distinct; + } + + /** + * Resizes internal data structures if necessary to store the specified number of distinct + * elements. + */ + private void ensureCapacity(int minCapacity) { + if (minCapacity > dedupedElements.length) { + int newCapacity = + ImmutableCollection.Builder.expandedCapacity(dedupedElements.length, minCapacity); + dedupedElements = Arrays.copyOf(dedupedElements, newCapacity); + } + } + + /** Adds e to the insertion-order array of deduplicated elements. Calls ensureCapacity. */ + final void addDedupedElement(E e) { + ensureCapacity(distinct + 1); + dedupedElements[distinct++] = e; + } + + /** + * Adds e to this SetBuilderImpl, returning the updated result. Only use the returned + * SetBuilderImpl, since we may switch implementations if e.g. hash flooding is detected. + */ + abstract SetBuilderImpl add(E e); + + /** Adds all the elements from the specified SetBuilderImpl to this SetBuilderImpl. */ + final SetBuilderImpl combine(SetBuilderImpl other) { + SetBuilderImpl result = this; + for (int i = 0; i < other.distinct; i++) { + result = result.add(other.dedupedElements[i]); + } + return result; + } + + /** + * Creates a new copy of this SetBuilderImpl. Modifications to that SetBuilderImpl will not + * affect this SetBuilderImpl or sets constructed from this SetBuilderImpl via build(). + */ + abstract SetBuilderImpl copy(); + + /** + * Call this before build(). Does a final check on the internal data structures, e.g. shrinking + * unnecessarily large structures or detecting previously unnoticed hash flooding. + */ + SetBuilderImpl review() { + return this; + } + + abstract ImmutableSet build(); + } + + // We use power-of-2 tables, and this is the highest int that's a power of 2 + static final int MAX_TABLE_SIZE = Ints.MAX_POWER_OF_TWO; + + // Represents how tightly we can pack things, as a maximum. + private static final double DESIRED_LOAD_FACTOR = 0.7; + + // If the set has this many elements, it will "max out" the table size + private static final int CUTOFF = (int) (MAX_TABLE_SIZE * DESIRED_LOAD_FACTOR); + + /** + * Returns an array size suitable for the backing array of a hash table that uses open addressing + * with linear probing in its implementation. The returned size is the smallest power of two that + * can hold setSize elements with the desired load factor. Always returns at least setSize + 2. + */ + @VisibleForTesting + static int chooseTableSize(int setSize) { + setSize = Math.max(setSize, 2); + // Correct the size for open addressing to match desired load factor. + if (setSize < CUTOFF) { + // Round up to the next highest power of 2. + int tableSize = Integer.highestOneBit(setSize - 1) << 1; + while (tableSize * DESIRED_LOAD_FACTOR < setSize) { + tableSize <<= 1; + } + return tableSize; + } + + // The table can't be completely full or we'll get infinite reprobes + checkArgument(setSize < MAX_TABLE_SIZE, "collection too large"); + return MAX_TABLE_SIZE; + } + + /** + * We attempt to detect deliberate hash flooding attempts, and if one is detected, fall back to a + * wrapper around j.u.HashSet, which has built in flooding protection. HASH_FLOODING_FPP is the + * maximum allowed probability of falsely detecting a hash flooding attack if the input is + * randomly generated. + * + *

MAX_RUN_MULTIPLIER was determined experimentally to match this FPP. + */ + static final double HASH_FLOODING_FPP = 0.001; + + // NB: yes, this is surprisingly high, but that's what the experiments said was necessary + // The higher it is, the worse constant factors we are willing to accept. + static final int MAX_RUN_MULTIPLIER = 13; + + /** + * Checks the whole hash table for poor hash distribution. Takes O(n) in the worst case, O(n / log + * n) on average. + * + *

The online hash flooding detecting in RegularSetBuilderImpl.add can detect e.g. many exactly + * matching hash codes, which would cause construction to take O(n^2), but can't detect e.g. hash + * codes adversarially designed to go into ascending table locations, which keeps construction + * O(n) (as desired) but then can have O(n) queries later. + * + *

If this returns false, then no query can take more than O(log n). + * + *

Note that for a RegularImmutableSet with elements with truly random hash codes, contains + * operations take expected O(1) time but with high probability take O(log n) for at least some + * element. (https://en.wikipedia.org/wiki/Linear_probing#Analysis) + * + *

This method may return {@code true} up to {@link #HASH_FLOODING_FPP} of the time even on + * truly random input. + * + *

If this method returns false, there are definitely no runs of length at least {@code + * maxRunBeforeFallback(hashTable.length)} nonnull elements. If there are no runs of length at + * least {@code maxRunBeforeFallback(hashTable.length) / 2} nonnull elements, this method + * definitely returns false. In between those constraints, the result of this method is undefined, + * subject to the above {@link #HASH_FLOODING_FPP} constraint. + */ + static boolean hashFloodingDetected(Object[] hashTable) { + int maxRunBeforeFallback = maxRunBeforeFallback(hashTable.length); + + // Test for a run wrapping around the end of the table of length at least maxRunBeforeFallback. + int endOfStartRun; + for (endOfStartRun = 0; endOfStartRun < hashTable.length; ) { + if (hashTable[endOfStartRun] == null) { + break; + } + endOfStartRun++; + if (endOfStartRun > maxRunBeforeFallback) { + return true; + } + } + int startOfEndRun; + for (startOfEndRun = hashTable.length - 1; startOfEndRun > endOfStartRun; startOfEndRun--) { + if (hashTable[startOfEndRun] == null) { + break; + } + if (endOfStartRun + (hashTable.length - 1 - startOfEndRun) > maxRunBeforeFallback) { + return true; + } + } + + // Now, break the remainder of the table into blocks of maxRunBeforeFallback/2 elements and + // check that each has at least one null. + int testBlockSize = maxRunBeforeFallback / 2; + blockLoop: + for (int i = endOfStartRun + 1; i + testBlockSize <= startOfEndRun; i += testBlockSize) { + for (int j = 0; j < testBlockSize; j++) { + if (hashTable[i + j] == null) { + continue blockLoop; + } + } + return true; + } + return false; + } + + /** + * If more than this many consecutive positions are filled in a table of the specified size, + * report probable hash flooding. ({@link #hashFloodingDetected} may also report hash flooding if + * fewer consecutive positions are filled; see that method for details.) + */ + private static int maxRunBeforeFallback(int tableSize) { + return MAX_RUN_MULTIPLIER * IntMath.log2(tableSize, RoundingMode.UNNECESSARY); + } + + /** + * Default implementation of the guts of ImmutableSet.Builder, creating an open-addressed hash + * table and deduplicating elements as they come, so it only allocates O(max(distinct, + * expectedCapacity)) rather than O(calls to add). + * + *

This implementation attempts to detect hash flooding, and if it's identified, falls back to + * JdkBackedSetBuilderImpl. + */ + private static final class RegularSetBuilderImpl extends SetBuilderImpl { + private Object[] hashTable; + private int maxRunBeforeFallback; + private int expandTableThreshold; + private int hashCode; + + RegularSetBuilderImpl(int expectedCapacity) { + super(expectedCapacity); + int tableSize = chooseTableSize(expectedCapacity); + this.hashTable = new Object[tableSize]; + this.maxRunBeforeFallback = maxRunBeforeFallback(tableSize); + this.expandTableThreshold = (int) (DESIRED_LOAD_FACTOR * tableSize); + } + + RegularSetBuilderImpl(RegularSetBuilderImpl toCopy) { + super(toCopy); + this.hashTable = Arrays.copyOf(toCopy.hashTable, toCopy.hashTable.length); + this.maxRunBeforeFallback = toCopy.maxRunBeforeFallback; + this.expandTableThreshold = toCopy.expandTableThreshold; + this.hashCode = toCopy.hashCode; + } + + void ensureTableCapacity(int minCapacity) { + if (minCapacity > expandTableThreshold && hashTable.length < MAX_TABLE_SIZE) { + int newTableSize = hashTable.length * 2; + hashTable = rebuildHashTable(newTableSize, dedupedElements, distinct); + maxRunBeforeFallback = maxRunBeforeFallback(newTableSize); + expandTableThreshold = (int) (DESIRED_LOAD_FACTOR * newTableSize); + } + } + + @Override + SetBuilderImpl add(E e) { + checkNotNull(e); + int eHash = e.hashCode(); + int i0 = Hashing.smear(eHash); + int mask = hashTable.length - 1; + for (int i = i0; i - i0 < maxRunBeforeFallback; i++) { + int index = i & mask; + Object tableEntry = hashTable[index]; + if (tableEntry == null) { + addDedupedElement(e); + hashTable[index] = e; + hashCode += eHash; + ensureTableCapacity(distinct); // rebuilds table if necessary + return this; + } else if (tableEntry.equals(e)) { // not a new element, ignore + return this; + } + } + // we fell out of the loop due to a long run; fall back to JDK impl + return new JdkBackedSetBuilderImpl(this).add(e); + } + + @Override + SetBuilderImpl copy() { + return new RegularSetBuilderImpl(this); + } + + @Override + SetBuilderImpl review() { + int targetTableSize = chooseTableSize(distinct); + if (targetTableSize * 2 < hashTable.length) { + hashTable = rebuildHashTable(targetTableSize, dedupedElements, distinct); + maxRunBeforeFallback = maxRunBeforeFallback(targetTableSize); + expandTableThreshold = (int) (DESIRED_LOAD_FACTOR * targetTableSize); + } + return hashFloodingDetected(hashTable) ? new JdkBackedSetBuilderImpl(this) : this; + } + + @Override + ImmutableSet build() { + switch (distinct) { + case 0: + return of(); + case 1: + return of(dedupedElements[0]); + default: + Object[] elements = + (distinct == dedupedElements.length) + ? dedupedElements + : Arrays.copyOf(dedupedElements, distinct); + return new RegularImmutableSet(elements, hashCode, hashTable, hashTable.length - 1); + } + } + } + + /** + * SetBuilderImpl version that uses a JDK HashSet, which has built in hash flooding protection. + */ + private static final class JdkBackedSetBuilderImpl extends SetBuilderImpl { + private final Set delegate; + + JdkBackedSetBuilderImpl(SetBuilderImpl toCopy) { + super(toCopy); // initializes dedupedElements and distinct + delegate = Sets.newHashSetWithExpectedSize(distinct); + for (int i = 0; i < distinct; i++) { + delegate.add(dedupedElements[i]); + } + } + + @Override + SetBuilderImpl add(E e) { + checkNotNull(e); + if (delegate.add(e)) { + addDedupedElement(e); + } + return this; + } + + @Override + SetBuilderImpl copy() { + return new JdkBackedSetBuilderImpl<>(this); + } + + @Override + ImmutableSet build() { + switch (distinct) { + case 0: + return of(); + case 1: + return of(dedupedElements[0]); + default: + return new JdkBackedImmutableSet( + delegate, ImmutableList.asImmutableList(dedupedElements, distinct)); + } + } + } +} diff --git a/src/main/java/com/google/common/collect/ImmutableSetMultimap.java b/src/main/java/com/google/common/collect/ImmutableSetMultimap.java new file mode 100644 index 0000000..f424c14 --- /dev/null +++ b/src/main/java/com/google/common/collect/ImmutableSetMultimap.java @@ -0,0 +1,650 @@ +/* + * Copyright (C) 2009 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.annotations.Beta; +import com.google.common.annotations.GwtCompatible; +import com.google.common.annotations.GwtIncompatible; +import com.google.common.base.MoreObjects; +import com.google.common.base.Preconditions; + + + + +import java.io.IOException; +import java.io.InvalidObjectException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.util.Arrays; +import java.util.Collection; +import java.util.Comparator; +import java.util.Map; +import java.util.Map.Entry; +import java.util.function.Function; +import java.util.stream.Collector; +import java.util.stream.Collectors; +import java.util.stream.Stream; + + + +/** + * A {@link SetMultimap} whose contents will never change, with many other important properties + * detailed at {@link ImmutableCollection}. + * + *

See the Guava User Guide article on immutable collections. + * + * @author Mike Ward + * @since 2.0 + */ +@GwtCompatible(serializable = true, emulated = true) +public class ImmutableSetMultimap extends ImmutableMultimap + implements SetMultimap { + /** + * Returns a {@link Collector} that accumulates elements into an {@code ImmutableSetMultimap} + * whose keys and values are the result of applying the provided mapping functions to the input + * elements. + * + *

For streams with defined encounter order (as defined in the Ordering section of the {@link + * java.util.stream} Javadoc), that order is preserved, but entries are grouped by key. + * + *

Example: + * + *

{@code
+   * static final Multimap FIRST_LETTER_MULTIMAP =
+   *     Stream.of("banana", "apple", "carrot", "asparagus", "cherry")
+   *         .collect(toImmutableSetMultimap(str -> str.charAt(0), str -> str.substring(1)));
+   *
+   * // is equivalent to
+   *
+   * static final Multimap FIRST_LETTER_MULTIMAP =
+   *     new ImmutableSetMultimap.Builder()
+   *         .put('b', "anana")
+   *         .putAll('a', "pple", "sparagus")
+   *         .putAll('c', "arrot", "herry")
+   *         .build();
+   * }
+ * + * @since 21.0 + */ + public static Collector> toImmutableSetMultimap( + Function keyFunction, + Function valueFunction) { + checkNotNull(keyFunction, "keyFunction"); + checkNotNull(valueFunction, "valueFunction"); + return Collector.of( + ImmutableSetMultimap::builder, + (builder, t) -> builder.put(keyFunction.apply(t), valueFunction.apply(t)), + ImmutableSetMultimap.Builder::combine, + ImmutableSetMultimap.Builder::build); + } + + /** + * Returns a {@code Collector} accumulating entries into an {@code ImmutableSetMultimap}. Each + * input element is mapped to a key and a stream of values, each of which are put into the + * resulting {@code Multimap}, in the encounter order of the stream and the encounter order of the + * streams of values. + * + *

Example: + * + *

{@code
+   * static final ImmutableSetMultimap FIRST_LETTER_MULTIMAP =
+   *     Stream.of("banana", "apple", "carrot", "asparagus", "cherry")
+   *         .collect(
+   *             flatteningToImmutableSetMultimap(
+   *                  str -> str.charAt(0),
+   *                  str -> str.substring(1).chars().mapToObj(c -> (char) c));
+   *
+   * // is equivalent to
+   *
+   * static final ImmutableSetMultimap FIRST_LETTER_MULTIMAP =
+   *     ImmutableSetMultimap.builder()
+   *         .putAll('b', Arrays.asList('a', 'n', 'a', 'n', 'a'))
+   *         .putAll('a', Arrays.asList('p', 'p', 'l', 'e'))
+   *         .putAll('c', Arrays.asList('a', 'r', 'r', 'o', 't'))
+   *         .putAll('a', Arrays.asList('s', 'p', 'a', 'r', 'a', 'g', 'u', 's'))
+   *         .putAll('c', Arrays.asList('h', 'e', 'r', 'r', 'y'))
+   *         .build();
+   *
+   * // after deduplication, the resulting multimap is equivalent to
+   *
+   * static final ImmutableSetMultimap FIRST_LETTER_MULTIMAP =
+   *     ImmutableSetMultimap.builder()
+   *         .putAll('b', Arrays.asList('a', 'n'))
+   *         .putAll('a', Arrays.asList('p', 'l', 'e', 's', 'a', 'r', 'g', 'u'))
+   *         .putAll('c', Arrays.asList('a', 'r', 'o', 't', 'h', 'e', 'y'))
+   *         .build();
+   * }
+   * }
+ * + * @since 21.0 + */ + public static + Collector> flatteningToImmutableSetMultimap( + Function keyFunction, + Function> valuesFunction) { + checkNotNull(keyFunction); + checkNotNull(valuesFunction); + return Collectors.collectingAndThen( + Multimaps.flatteningToMultimap( + input -> checkNotNull(keyFunction.apply(input)), + input -> valuesFunction.apply(input).peek(Preconditions::checkNotNull), + MultimapBuilder.linkedHashKeys().linkedHashSetValues()::build), + ImmutableSetMultimap::copyOf); + } + + /** Returns the empty multimap. */ + // Casting is safe because the multimap will never hold any elements. + @SuppressWarnings("unchecked") + public static ImmutableSetMultimap of() { + return (ImmutableSetMultimap) EmptyImmutableSetMultimap.INSTANCE; + } + + /** Returns an immutable multimap containing a single entry. */ + public static ImmutableSetMultimap of(K k1, V v1) { + ImmutableSetMultimap.Builder builder = ImmutableSetMultimap.builder(); + builder.put(k1, v1); + return builder.build(); + } + + /** + * Returns an immutable multimap containing the given entries, in order. Repeated occurrences of + * an entry (according to {@link Object#equals}) after the first are ignored. + */ + public static ImmutableSetMultimap of(K k1, V v1, K k2, V v2) { + ImmutableSetMultimap.Builder builder = ImmutableSetMultimap.builder(); + builder.put(k1, v1); + builder.put(k2, v2); + return builder.build(); + } + + /** + * Returns an immutable multimap containing the given entries, in order. Repeated occurrences of + * an entry (according to {@link Object#equals}) after the first are ignored. + */ + public static ImmutableSetMultimap of(K k1, V v1, K k2, V v2, K k3, V v3) { + ImmutableSetMultimap.Builder builder = ImmutableSetMultimap.builder(); + builder.put(k1, v1); + builder.put(k2, v2); + builder.put(k3, v3); + return builder.build(); + } + + /** + * Returns an immutable multimap containing the given entries, in order. Repeated occurrences of + * an entry (according to {@link Object#equals}) after the first are ignored. + */ + public static ImmutableSetMultimap of( + K k1, V v1, K k2, V v2, K k3, V v3, K k4, V v4) { + ImmutableSetMultimap.Builder builder = ImmutableSetMultimap.builder(); + builder.put(k1, v1); + builder.put(k2, v2); + builder.put(k3, v3); + builder.put(k4, v4); + return builder.build(); + } + + /** + * Returns an immutable multimap containing the given entries, in order. Repeated occurrences of + * an entry (according to {@link Object#equals}) after the first are ignored. + */ + public static ImmutableSetMultimap of( + K k1, V v1, K k2, V v2, K k3, V v3, K k4, V v4, K k5, V v5) { + ImmutableSetMultimap.Builder builder = ImmutableSetMultimap.builder(); + builder.put(k1, v1); + builder.put(k2, v2); + builder.put(k3, v3); + builder.put(k4, v4); + builder.put(k5, v5); + return builder.build(); + } + + // looking for of() with > 5 entries? Use the builder instead. + + /** Returns a new {@link Builder}. */ + public static Builder builder() { + return new Builder<>(); + } + + /** + * A builder for creating immutable {@code SetMultimap} instances, especially {@code public static + * final} multimaps ("constant multimaps"). Example: + * + *
{@code
+   * static final Multimap STRING_TO_INTEGER_MULTIMAP =
+   *     new ImmutableSetMultimap.Builder()
+   *         .put("one", 1)
+   *         .putAll("several", 1, 2, 3)
+   *         .putAll("many", 1, 2, 3, 4, 5)
+   *         .build();
+   * }
+ * + *

Builder instances can be reused; it is safe to call {@link #build} multiple times to build + * multiple multimaps in series. Each multimap contains the key-value mappings in the previously + * created multimaps. + * + * @since 2.0 + */ + public static final class Builder extends ImmutableMultimap.Builder { + /** + * Creates a new builder. The returned builder is equivalent to the builder generated by {@link + * ImmutableSetMultimap#builder}. + */ + public Builder() { + super(); + } + + @Override + Collection newMutableValueCollection() { + return Platform.preservesInsertionOrderOnAddsSet(); + } + + /** Adds a key-value mapping to the built multimap if it is not already present. */ + + @Override + public Builder put(K key, V value) { + super.put(key, value); + return this; + } + + /** + * Adds an entry to the built multimap if it is not already present. + * + * @since 11.0 + */ + + @Override + public Builder put(Entry entry) { + super.put(entry); + return this; + } + + /** + * {@inheritDoc} + * + * @since 19.0 + */ + + @Beta + @Override + public Builder putAll(Iterable> entries) { + super.putAll(entries); + return this; + } + + + @Override + public Builder putAll(K key, Iterable values) { + super.putAll(key, values); + return this; + } + + + @Override + public Builder putAll(K key, V... values) { + return putAll(key, Arrays.asList(values)); + } + + + @Override + public Builder putAll(Multimap multimap) { + for (Entry> entry : + multimap.asMap().entrySet()) { + putAll(entry.getKey(), entry.getValue()); + } + return this; + } + + + @Override + Builder combine(ImmutableMultimap.Builder other) { + super.combine(other); + return this; + } + + /** + * {@inheritDoc} + * + * @since 8.0 + */ + + @Override + public Builder orderKeysBy(Comparator keyComparator) { + super.orderKeysBy(keyComparator); + return this; + } + + /** + * Specifies the ordering of the generated multimap's values for each key. + * + *

If this method is called, the sets returned by the {@code get()} method of the generated + * multimap and its {@link Multimap#asMap()} view are {@link ImmutableSortedSet} instances. + * However, serialization does not preserve that property, though it does maintain the key and + * value ordering. + * + * @since 8.0 + */ + // TODO: Make serialization behavior consistent. + + @Override + public Builder orderValuesBy(Comparator valueComparator) { + super.orderValuesBy(valueComparator); + return this; + } + + /** Returns a newly-created immutable set multimap. */ + @Override + public ImmutableSetMultimap build() { + Collection>> mapEntries = builderMap.entrySet(); + if (keyComparator != null) { + mapEntries = Ordering.from(keyComparator).onKeys().immutableSortedCopy(mapEntries); + } + return fromMapEntries(mapEntries, valueComparator); + } + } + + /** + * Returns an immutable set multimap containing the same mappings as {@code multimap}. The + * generated multimap's key and value orderings correspond to the iteration ordering of the {@code + * multimap.asMap()} view. Repeated occurrences of an entry in the multimap after the first are + * ignored. + * + *

Despite the method name, this method attempts to avoid actually copying the data when it is + * safe to do so. The exact circumstances under which a copy will or will not be performed are + * undocumented and subject to change. + * + * @throws NullPointerException if any key or value in {@code multimap} is null + */ + public static ImmutableSetMultimap copyOf( + Multimap multimap) { + return copyOf(multimap, null); + } + + private static ImmutableSetMultimap copyOf( + Multimap multimap, Comparator valueComparator) { + checkNotNull(multimap); // eager for GWT + if (multimap.isEmpty() && valueComparator == null) { + return of(); + } + + if (multimap instanceof ImmutableSetMultimap) { + @SuppressWarnings("unchecked") // safe since multimap is not writable + ImmutableSetMultimap kvMultimap = (ImmutableSetMultimap) multimap; + if (!kvMultimap.isPartialView()) { + return kvMultimap; + } + } + + return fromMapEntries(multimap.asMap().entrySet(), valueComparator); + } + + /** + * Returns an immutable multimap containing the specified entries. The returned multimap iterates + * over keys in the order they were first encountered in the input, and the values for each key + * are iterated in the order they were encountered. If two values for the same key are {@linkplain + * Object#equals equal}, the first value encountered is used. + * + * @throws NullPointerException if any key, value, or entry is null + * @since 19.0 + */ + @Beta + public static ImmutableSetMultimap copyOf( + Iterable> entries) { + return new Builder().putAll(entries).build(); + } + + /** Creates an ImmutableSetMultimap from an asMap.entrySet. */ + static ImmutableSetMultimap fromMapEntries( + Collection>> mapEntries, + Comparator valueComparator) { + if (mapEntries.isEmpty()) { + return of(); + } + ImmutableMap.Builder> builder = + new ImmutableMap.Builder<>(mapEntries.size()); + int size = 0; + + for (Entry> entry : mapEntries) { + K key = entry.getKey(); + Collection values = entry.getValue(); + ImmutableSet set = valueSet(valueComparator, values); + if (!set.isEmpty()) { + builder.put(key, set); + size += set.size(); + } + } + + return new ImmutableSetMultimap<>(builder.build(), size, valueComparator); + } + + /** + * Returned by get() when a missing key is provided. Also holds the comparator, if any, used for + * values. + */ + private final transient ImmutableSet emptySet; + + ImmutableSetMultimap( + ImmutableMap> map, + int size, + Comparator valueComparator) { + super(map, size); + this.emptySet = emptySet(valueComparator); + } + + // views + + /** + * Returns an immutable set of the values for the given key. If no mappings in the multimap have + * the provided key, an empty immutable set is returned. The values are in the same order as the + * parameters used to build this multimap. + */ + @Override + public ImmutableSet get(K key) { + // This cast is safe as its type is known in constructor. + ImmutableSet set = (ImmutableSet) map.get(key); + return MoreObjects.firstNonNull(set, emptySet); + } + + private transient ImmutableSetMultimap inverse; + + /** + * {@inheritDoc} + * + *

Because an inverse of a set multimap cannot contain multiple pairs with the same key and + * value, this method returns an {@code ImmutableSetMultimap} rather than the {@code + * ImmutableMultimap} specified in the {@code ImmutableMultimap} class. + */ + @Override + public ImmutableSetMultimap inverse() { + ImmutableSetMultimap result = inverse; + return (result == null) ? (inverse = invert()) : result; + } + + private ImmutableSetMultimap invert() { + Builder builder = builder(); + for (Entry entry : entries()) { + builder.put(entry.getValue(), entry.getKey()); + } + ImmutableSetMultimap invertedMultimap = builder.build(); + invertedMultimap.inverse = this; + return invertedMultimap; + } + + /** + * Guaranteed to throw an exception and leave the multimap unmodified. + * + * @throws UnsupportedOperationException always + * @deprecated Unsupported operation. + */ + + @Deprecated + @Override + public ImmutableSet removeAll(Object key) { + throw new UnsupportedOperationException(); + } + + /** + * Guaranteed to throw an exception and leave the multimap unmodified. + * + * @throws UnsupportedOperationException always + * @deprecated Unsupported operation. + */ + + @Deprecated + @Override + public ImmutableSet replaceValues(K key, Iterable values) { + throw new UnsupportedOperationException(); + } + + private transient ImmutableSet> entries; + + /** + * Returns an immutable collection of all key-value pairs in the multimap. Its iterator traverses + * the values for the first key, the values for the second key, and so on. + */ + @Override + public ImmutableSet> entries() { + ImmutableSet> result = entries; + return result == null ? (entries = new EntrySet<>(this)) : result; + } + + private static final class EntrySet extends ImmutableSet> { + private final transient ImmutableSetMultimap multimap; + + EntrySet(ImmutableSetMultimap multimap) { + this.multimap = multimap; + } + + @Override + public boolean contains(Object object) { + if (object instanceof Entry) { + Entry entry = (Entry) object; + return multimap.containsEntry(entry.getKey(), entry.getValue()); + } + return false; + } + + @Override + public int size() { + return multimap.size(); + } + + @Override + public UnmodifiableIterator> iterator() { + return multimap.entryIterator(); + } + + @Override + boolean isPartialView() { + return false; + } + } + + private static ImmutableSet valueSet( + Comparator valueComparator, Collection values) { + return (valueComparator == null) + ? ImmutableSet.copyOf(values) + : ImmutableSortedSet.copyOf(valueComparator, values); + } + + private static ImmutableSet emptySet(Comparator valueComparator) { + return (valueComparator == null) + ? ImmutableSet.of() + : ImmutableSortedSet.emptySet(valueComparator); + } + + private static ImmutableSet.Builder valuesBuilder( + Comparator valueComparator) { + return (valueComparator == null) + ? new ImmutableSet.Builder() + : new ImmutableSortedSet.Builder(valueComparator); + } + + /** + * @serialData number of distinct keys, and then for each distinct key: the key, the number of + * values for that key, and the key's values + */ + @GwtIncompatible // java.io.ObjectOutputStream + private void writeObject(ObjectOutputStream stream) throws IOException { + stream.defaultWriteObject(); + stream.writeObject(valueComparator()); + Serialization.writeMultimap(this, stream); + } + + + Comparator valueComparator() { + return emptySet instanceof ImmutableSortedSet + ? ((ImmutableSortedSet) emptySet).comparator() + : null; + } + + @GwtIncompatible // java serialization + private static final class SetFieldSettersHolder { + static final Serialization.FieldSetter EMPTY_SET_FIELD_SETTER = + Serialization.getFieldSetter(ImmutableSetMultimap.class, "emptySet"); + } + + @GwtIncompatible // java.io.ObjectInputStream + // Serialization type safety is at the caller's mercy. + @SuppressWarnings("unchecked") + private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException { + stream.defaultReadObject(); + Comparator valueComparator = (Comparator) stream.readObject(); + int keyCount = stream.readInt(); + if (keyCount < 0) { + throw new InvalidObjectException("Invalid key count " + keyCount); + } + ImmutableMap.Builder> builder = ImmutableMap.builder(); + int tmpSize = 0; + + for (int i = 0; i < keyCount; i++) { + Object key = stream.readObject(); + int valueCount = stream.readInt(); + if (valueCount <= 0) { + throw new InvalidObjectException("Invalid value count " + valueCount); + } + + ImmutableSet.Builder valuesBuilder = valuesBuilder(valueComparator); + for (int j = 0; j < valueCount; j++) { + valuesBuilder.add(stream.readObject()); + } + ImmutableSet valueSet = valuesBuilder.build(); + if (valueSet.size() != valueCount) { + throw new InvalidObjectException("Duplicate key-value pairs exist for key " + key); + } + builder.put(key, valueSet); + tmpSize += valueCount; + } + + ImmutableMap> tmpMap; + try { + tmpMap = builder.build(); + } catch (IllegalArgumentException e) { + throw (InvalidObjectException) new InvalidObjectException(e.getMessage()).initCause(e); + } + + FieldSettersHolder.MAP_FIELD_SETTER.set(this, tmpMap); + FieldSettersHolder.SIZE_FIELD_SETTER.set(this, tmpSize); + SetFieldSettersHolder.EMPTY_SET_FIELD_SETTER.set(this, emptySet(valueComparator)); + } + + @GwtIncompatible // not needed in emulated source. + private static final long serialVersionUID = 0; +} diff --git a/src/main/java/com/google/common/collect/ImmutableSortedAsList.java b/src/main/java/com/google/common/collect/ImmutableSortedAsList.java new file mode 100644 index 0000000..2f23bd7 --- /dev/null +++ b/src/main/java/com/google/common/collect/ImmutableSortedAsList.java @@ -0,0 +1,95 @@ +/* + * Copyright (C) 2009 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the + * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import com.google.common.annotations.GwtCompatible; +import com.google.common.annotations.GwtIncompatible; +import java.util.Comparator; +import java.util.Spliterator; + + +/** + * List returned by {@code ImmutableSortedSet.asList()} when the set isn't empty. + * + * @author Jared Levy + * @author Louis Wasserman + */ +@GwtCompatible(emulated = true) +@SuppressWarnings("serial") +final class ImmutableSortedAsList extends RegularImmutableAsList + implements SortedIterable { + ImmutableSortedAsList(ImmutableSortedSet backingSet, ImmutableList backingList) { + super(backingSet, backingList); + } + + @Override + ImmutableSortedSet delegateCollection() { + return (ImmutableSortedSet) super.delegateCollection(); + } + + @Override + public Comparator comparator() { + return delegateCollection().comparator(); + } + + // Override indexOf() and lastIndexOf() to be O(log N) instead of O(N). + + @GwtIncompatible // ImmutableSortedSet.indexOf + // TODO(cpovirk): consider manual binary search under GWT to preserve O(log N) lookup + @Override + public int indexOf(Object target) { + int index = delegateCollection().indexOf(target); + + // TODO(kevinb): reconsider if it's really worth making feeble attempts at + // sanity for inconsistent comparators. + + // The equals() check is needed when the comparator isn't compatible with + // equals(). + return (index >= 0 && get(index).equals(target)) ? index : -1; + } + + @GwtIncompatible // ImmutableSortedSet.indexOf + @Override + public int lastIndexOf(Object target) { + return indexOf(target); + } + + @Override + public boolean contains(Object target) { + // Necessary for ISS's with comparators inconsistent with equals. + return indexOf(target) >= 0; + } + + @GwtIncompatible // super.subListUnchecked does not exist; inherited subList is valid if slow + /* + * TODO(cpovirk): if we start to override indexOf/lastIndexOf under GWT, we'll want some way to + * override subList to return an ImmutableSortedAsList for better performance. Right now, I'm not + * sure there's any performance hit from our failure to override subListUnchecked under GWT + */ + @Override + ImmutableList subListUnchecked(int fromIndex, int toIndex) { + ImmutableList parentSubList = super.subListUnchecked(fromIndex, toIndex); + return new RegularImmutableSortedSet(parentSubList, comparator()).asList(); + } + + @Override + public Spliterator spliterator() { + return CollectSpliterators.indexed( + size(), + ImmutableList.SPLITERATOR_CHARACTERISTICS | Spliterator.SORTED | Spliterator.DISTINCT, + delegateList()::get, + comparator()); + } +} diff --git a/src/main/java/com/google/common/collect/ImmutableSortedMap.java b/src/main/java/com/google/common/collect/ImmutableSortedMap.java new file mode 100644 index 0000000..a8d893b --- /dev/null +++ b/src/main/java/com/google/common/collect/ImmutableSortedMap.java @@ -0,0 +1,945 @@ +/* + * Copyright (C) 2009 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.collect.CollectPreconditions.checkEntryNotNull; +import static com.google.common.collect.Maps.keyOrNull; + +import com.google.common.annotations.Beta; +import com.google.common.annotations.GwtCompatible; + +import java.util.AbstractMap; +import java.util.Arrays; +import java.util.Comparator; +import java.util.Map; +import java.util.NavigableMap; +import java.util.SortedMap; +import java.util.Spliterator; +import java.util.TreeMap; +import java.util.function.BiConsumer; +import java.util.function.BinaryOperator; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.stream.Collector; +import java.util.stream.Collectors; + + +/** + * A {@link NavigableMap} whose contents will never change, with many other important properties + * detailed at {@link ImmutableCollection}. + * + *

Warning: as with any sorted collection, you are strongly advised not to use a {@link + * Comparator} or {@link Comparable} type whose comparison behavior is inconsistent with + * equals. That is, {@code a.compareTo(b)} or {@code comparator.compare(a, b)} should equal zero + * if and only if {@code a.equals(b)}. If this advice is not followed, the resulting map will + * not correctly obey its specification. + * + *

See the Guava User Guide article on immutable collections. + * + * @author Jared Levy + * @author Louis Wasserman + * @since 2.0 (implements {@code NavigableMap} since 12.0) + */ +@GwtCompatible(serializable = true, emulated = true) +public final class ImmutableSortedMap extends ImmutableSortedMapFauxverideShim + implements NavigableMap { + /** + * Returns a {@link Collector} that accumulates elements into an {@code ImmutableSortedMap} whose + * keys and values are the result of applying the provided mapping functions to the input + * elements. The generated map is sorted by the specified comparator. + * + *

If the mapped keys contain duplicates (according to the specified comparator), an {@code + * IllegalArgumentException} is thrown when the collection operation is performed. (This differs + * from the {@code Collector} returned by {@link Collectors#toMap(Function, Function)}, which + * throws an {@code IllegalStateException}.) + * + * @since 21.0 + */ + public static Collector> toImmutableSortedMap( + Comparator comparator, + Function keyFunction, + Function valueFunction) { + return CollectCollectors.toImmutableSortedMap(comparator, keyFunction, valueFunction); + } + + /** + * Returns a {@link Collector} that accumulates elements into an {@code ImmutableSortedMap} whose + * keys and values are the result of applying the provided mapping functions to the input + * elements. + * + *

If the mapped keys contain duplicates (according to the comparator), the the values are + * merged using the specified merging function. Entries will appear in the encounter order of the + * first occurrence of the key. + * + * @since 21.0 + */ + public static Collector> toImmutableSortedMap( + Comparator comparator, + Function keyFunction, + Function valueFunction, + BinaryOperator mergeFunction) { + checkNotNull(comparator); + checkNotNull(keyFunction); + checkNotNull(valueFunction); + checkNotNull(mergeFunction); + return Collectors.collectingAndThen( + Collectors.toMap( + keyFunction, valueFunction, mergeFunction, () -> new TreeMap(comparator)), + ImmutableSortedMap::copyOfSorted); + } + + /* + * TODO(kevinb): Confirm that ImmutableSortedMap is faster to construct and + * uses less memory than TreeMap; then say so in the class Javadoc. + */ + private static final Comparator NATURAL_ORDER = Ordering.natural(); + + private static final ImmutableSortedMap NATURAL_EMPTY_MAP = + new ImmutableSortedMap<>( + ImmutableSortedSet.emptySet(Ordering.natural()), ImmutableList.of()); + + static ImmutableSortedMap emptyMap(Comparator comparator) { + if (Ordering.natural().equals(comparator)) { + return of(); + } else { + return new ImmutableSortedMap<>( + ImmutableSortedSet.emptySet(comparator), ImmutableList.of()); + } + } + + /** Returns the empty sorted map. */ + @SuppressWarnings("unchecked") + // unsafe, comparator() returns a comparator on the specified type + // TODO(kevinb): evaluate whether or not of().comparator() should return null + public static ImmutableSortedMap of() { + return (ImmutableSortedMap) NATURAL_EMPTY_MAP; + } + + /** Returns an immutable map containing a single entry. */ + public static , V> ImmutableSortedMap of(K k1, V v1) { + return of(Ordering.natural(), k1, v1); + } + + /** Returns an immutable map containing a single entry. */ + private static ImmutableSortedMap of(Comparator comparator, K k1, V v1) { + return new ImmutableSortedMap<>( + new RegularImmutableSortedSet(ImmutableList.of(k1), checkNotNull(comparator)), + ImmutableList.of(v1)); + } + + /** + * Returns an immutable sorted map containing the given entries, sorted by the natural ordering of + * their keys. + * + * @throws IllegalArgumentException if the two keys are equal according to their natural ordering + */ + @SuppressWarnings("unchecked") + public static , V> ImmutableSortedMap of( + K k1, V v1, K k2, V v2) { + return ofEntries(entryOf(k1, v1), entryOf(k2, v2)); + } + + /** + * Returns an immutable sorted map containing the given entries, sorted by the natural ordering of + * their keys. + * + * @throws IllegalArgumentException if any two keys are equal according to their natural ordering + */ + @SuppressWarnings("unchecked") + public static , V> ImmutableSortedMap of( + K k1, V v1, K k2, V v2, K k3, V v3) { + return ofEntries(entryOf(k1, v1), entryOf(k2, v2), entryOf(k3, v3)); + } + + /** + * Returns an immutable sorted map containing the given entries, sorted by the natural ordering of + * their keys. + * + * @throws IllegalArgumentException if any two keys are equal according to their natural ordering + */ + @SuppressWarnings("unchecked") + public static , V> ImmutableSortedMap of( + K k1, V v1, K k2, V v2, K k3, V v3, K k4, V v4) { + return ofEntries(entryOf(k1, v1), entryOf(k2, v2), entryOf(k3, v3), entryOf(k4, v4)); + } + + /** + * Returns an immutable sorted map containing the given entries, sorted by the natural ordering of + * their keys. + * + * @throws IllegalArgumentException if any two keys are equal according to their natural ordering + */ + @SuppressWarnings("unchecked") + public static , V> ImmutableSortedMap of( + K k1, V v1, K k2, V v2, K k3, V v3, K k4, V v4, K k5, V v5) { + return ofEntries( + entryOf(k1, v1), entryOf(k2, v2), entryOf(k3, v3), entryOf(k4, v4), entryOf(k5, v5)); + } + + private static , V> ImmutableSortedMap ofEntries( + Entry... entries) { + return fromEntries(Ordering.natural(), false, entries, entries.length); + } + + /** + * Returns an immutable map containing the same entries as {@code map}, sorted by the natural + * ordering of the keys. + * + *

Despite the method name, this method attempts to avoid actually copying the data when it is + * safe to do so. The exact circumstances under which a copy will or will not be performed are + * undocumented and subject to change. + * + *

This method is not type-safe, as it may be called on a map with keys that are not mutually + * comparable. + * + * @throws ClassCastException if the keys in {@code map} are not mutually comparable + * @throws NullPointerException if any key or value in {@code map} is null + * @throws IllegalArgumentException if any two keys are equal according to their natural ordering + */ + public static ImmutableSortedMap copyOf(Map map) { + // Hack around K not being a subtype of Comparable. + // Unsafe, see ImmutableSortedSetFauxverideShim. + @SuppressWarnings("unchecked") + Ordering naturalOrder = (Ordering) NATURAL_ORDER; + return copyOfInternal(map, naturalOrder); + } + + /** + * Returns an immutable map containing the same entries as {@code map}, with keys sorted by the + * provided comparator. + * + *

Despite the method name, this method attempts to avoid actually copying the data when it is + * safe to do so. The exact circumstances under which a copy will or will not be performed are + * undocumented and subject to change. + * + * @throws NullPointerException if any key or value in {@code map} is null + * @throws IllegalArgumentException if any two keys are equal according to the comparator + */ + public static ImmutableSortedMap copyOf( + Map map, Comparator comparator) { + return copyOfInternal(map, checkNotNull(comparator)); + } + + /** + * Returns an immutable map containing the given entries, with keys sorted by the provided + * comparator. + * + *

This method is not type-safe, as it may be called on a map with keys that are not mutually + * comparable. + * + * @throws NullPointerException if any key or value in {@code map} is null + * @throws IllegalArgumentException if any two keys are equal according to the comparator + * @since 19.0 + */ + @Beta + public static ImmutableSortedMap copyOf( + Iterable> entries) { + // Hack around K not being a subtype of Comparable. + // Unsafe, see ImmutableSortedSetFauxverideShim. + @SuppressWarnings("unchecked") + Ordering naturalOrder = (Ordering) NATURAL_ORDER; + return copyOf(entries, naturalOrder); + } + + /** + * Returns an immutable map containing the given entries, with keys sorted by the provided + * comparator. + * + * @throws NullPointerException if any key or value in {@code map} is null + * @throws IllegalArgumentException if any two keys are equal according to the comparator + * @since 19.0 + */ + @Beta + public static ImmutableSortedMap copyOf( + Iterable> entries, + Comparator comparator) { + return fromEntries(checkNotNull(comparator), false, entries); + } + + /** + * Returns an immutable map containing the same entries as the provided sorted map, with the same + * ordering. + * + *

Despite the method name, this method attempts to avoid actually copying the data when it is + * safe to do so. The exact circumstances under which a copy will or will not be performed are + * undocumented and subject to change. + * + * @throws NullPointerException if any key or value in {@code map} is null + */ + @SuppressWarnings("unchecked") + public static ImmutableSortedMap copyOfSorted(SortedMap map) { + Comparator comparator = map.comparator(); + if (comparator == null) { + // If map has a null comparator, the keys should have a natural ordering, + // even though K doesn't explicitly implement Comparable. + comparator = (Comparator) NATURAL_ORDER; + } + if (map instanceof ImmutableSortedMap) { + // TODO(kevinb): Prove that this cast is safe, even though + // Collections.unmodifiableSortedMap requires the same key type. + @SuppressWarnings("unchecked") + ImmutableSortedMap kvMap = (ImmutableSortedMap) map; + if (!kvMap.isPartialView()) { + return kvMap; + } + } + return fromEntries(comparator, true, map.entrySet()); + } + + private static ImmutableSortedMap copyOfInternal( + Map map, Comparator comparator) { + boolean sameComparator = false; + if (map instanceof SortedMap) { + SortedMap sortedMap = (SortedMap) map; + Comparator comparator2 = sortedMap.comparator(); + sameComparator = + (comparator2 == null) ? comparator == NATURAL_ORDER : comparator.equals(comparator2); + } + + if (sameComparator && (map instanceof ImmutableSortedMap)) { + // TODO(kevinb): Prove that this cast is safe, even though + // Collections.unmodifiableSortedMap requires the same key type. + @SuppressWarnings("unchecked") + ImmutableSortedMap kvMap = (ImmutableSortedMap) map; + if (!kvMap.isPartialView()) { + return kvMap; + } + } + return fromEntries(comparator, sameComparator, map.entrySet()); + } + + /** + * Accepts a collection of possibly-null entries. If {@code sameComparator}, then it is assumed + * that they do not need to be sorted or checked for dupes. + */ + private static ImmutableSortedMap fromEntries( + Comparator comparator, + boolean sameComparator, + Iterable> entries) { + // "adding" type params to an array of a raw type should be safe as + // long as no one can ever cast that same array instance back to a + // raw type. + @SuppressWarnings("unchecked") + Entry[] entryArray = (Entry[]) Iterables.toArray(entries, EMPTY_ENTRY_ARRAY); + return fromEntries(comparator, sameComparator, entryArray, entryArray.length); + } + + private static ImmutableSortedMap fromEntries( + final Comparator comparator, + boolean sameComparator, + Entry[] entryArray, + int size) { + switch (size) { + case 0: + return emptyMap(comparator); + case 1: + return ImmutableSortedMap.of( + comparator, entryArray[0].getKey(), entryArray[0].getValue()); + default: + Object[] keys = new Object[size]; + Object[] values = new Object[size]; + if (sameComparator) { + // Need to check for nulls, but don't need to sort or validate. + for (int i = 0; i < size; i++) { + Object key = entryArray[i].getKey(); + Object value = entryArray[i].getValue(); + checkEntryNotNull(key, value); + keys[i] = key; + values[i] = value; + } + } else { + // Need to sort and check for nulls and dupes. + // Inline the Comparator implementation rather than transforming with a Function + // to save code size. + Arrays.sort( + entryArray, + 0, + size, + new Comparator>() { + @Override + public int compare(Entry e1, Entry e2) { + return comparator.compare(e1.getKey(), e2.getKey()); + } + }); + K prevKey = entryArray[0].getKey(); + keys[0] = prevKey; + values[0] = entryArray[0].getValue(); + checkEntryNotNull(keys[0], values[0]); + for (int i = 1; i < size; i++) { + K key = entryArray[i].getKey(); + V value = entryArray[i].getValue(); + checkEntryNotNull(key, value); + keys[i] = key; + values[i] = value; + checkNoConflict( + comparator.compare(prevKey, key) != 0, "key", entryArray[i - 1], entryArray[i]); + prevKey = key; + } + } + return new ImmutableSortedMap<>( + new RegularImmutableSortedSet(new RegularImmutableList(keys), comparator), + new RegularImmutableList(values)); + } + } + + /** + * Returns a builder that creates immutable sorted maps whose keys are ordered by their natural + * ordering. The sorted maps use {@link Ordering#natural()} as the comparator. + */ + public static , V> Builder naturalOrder() { + return new Builder<>(Ordering.natural()); + } + + /** + * Returns a builder that creates immutable sorted maps with an explicit comparator. If the + * comparator has a more general type than the map's keys, such as creating a {@code + * SortedMap} with a {@code Comparator}, use the {@link Builder} + * constructor instead. + * + * @throws NullPointerException if {@code comparator} is null + */ + public static Builder orderedBy(Comparator comparator) { + return new Builder<>(comparator); + } + + /** + * Returns a builder that creates immutable sorted maps whose keys are ordered by the reverse of + * their natural ordering. + */ + public static , V> Builder reverseOrder() { + return new Builder<>(Ordering.natural().reverse()); + } + + /** + * A builder for creating immutable sorted map instances, especially {@code public static final} + * maps ("constant maps"). Example: + * + *

{@code
+   * static final ImmutableSortedMap INT_TO_WORD =
+   *     new ImmutableSortedMap.Builder(Ordering.natural())
+   *         .put(1, "one")
+   *         .put(2, "two")
+   *         .put(3, "three")
+   *         .build();
+   * }
+ * + *

For small immutable sorted maps, the {@code ImmutableSortedMap.of()} methods are even + * more convenient. + * + *

Builder instances can be reused - it is safe to call {@link #build} multiple times to build + * multiple maps in series. Each map is a superset of the maps created before it. + * + * @since 2.0 + */ + public static class Builder extends ImmutableMap.Builder { + private final Comparator comparator; + + /** + * Creates a new builder. The returned builder is equivalent to the builder generated by {@link + * ImmutableSortedMap#orderedBy}. + */ + @SuppressWarnings("unchecked") + public Builder(Comparator comparator) { + this.comparator = checkNotNull(comparator); + } + + /** + * Associates {@code key} with {@code value} in the built map. Duplicate keys, according to the + * comparator (which might be the keys' natural order), are not allowed, and will cause {@link + * #build} to fail. + */ + + @Override + public Builder put(K key, V value) { + super.put(key, value); + return this; + } + + /** + * Adds the given {@code entry} to the map, making it immutable if necessary. Duplicate keys, + * according to the comparator (which might be the keys' natural order), are not allowed, and + * will cause {@link #build} to fail. + * + * @since 11.0 + */ + + @Override + public Builder put(Entry entry) { + super.put(entry); + return this; + } + + /** + * Associates all of the given map's keys and values in the built map. Duplicate keys, according + * to the comparator (which might be the keys' natural order), are not allowed, and will cause + * {@link #build} to fail. + * + * @throws NullPointerException if any key or value in {@code map} is null + */ + + @Override + public Builder putAll(Map map) { + super.putAll(map); + return this; + } + + /** + * Adds all the given entries to the built map. Duplicate keys, according to the comparator + * (which might be the keys' natural order), are not allowed, and will cause {@link #build} to + * fail. + * + * @throws NullPointerException if any key, value, or entry is null + * @since 19.0 + */ + + @Beta + @Override + public Builder putAll(Iterable> entries) { + super.putAll(entries); + return this; + } + + /** + * Throws an {@code UnsupportedOperationException}. + * + * @since 19.0 + * @deprecated Unsupported by ImmutableSortedMap.Builder. + */ + + @Beta + @Override + @Deprecated + public Builder orderEntriesByValue(Comparator valueComparator) { + throw new UnsupportedOperationException("Not available on ImmutableSortedMap.Builder"); + } + + @Override + Builder combine(ImmutableMap.Builder other) { + super.combine(other); + return this; + } + + /** + * Returns a newly-created immutable sorted map. + * + * @throws IllegalArgumentException if any two keys are equal according to the comparator (which + * might be the keys' natural order) + */ + @Override + public ImmutableSortedMap build() { + switch (size) { + case 0: + return emptyMap(comparator); + case 1: + return of(comparator, entries[0].getKey(), entries[0].getValue()); + default: + return fromEntries(comparator, false, entries, size); + } + } + } + + private final transient RegularImmutableSortedSet keySet; + private final transient ImmutableList valueList; + private transient ImmutableSortedMap descendingMap; + + ImmutableSortedMap(RegularImmutableSortedSet keySet, ImmutableList valueList) { + this(keySet, valueList, null); + } + + ImmutableSortedMap( + RegularImmutableSortedSet keySet, + ImmutableList valueList, + ImmutableSortedMap descendingMap) { + this.keySet = keySet; + this.valueList = valueList; + this.descendingMap = descendingMap; + } + + @Override + public int size() { + return valueList.size(); + } + + @Override + public void forEach(BiConsumer action) { + checkNotNull(action); + ImmutableList keyList = keySet.asList(); + for (int i = 0; i < size(); i++) { + action.accept(keyList.get(i), valueList.get(i)); + } + } + + @Override + public V get(Object key) { + int index = keySet.indexOf(key); + return (index == -1) ? null : valueList.get(index); + } + + @Override + boolean isPartialView() { + return keySet.isPartialView() || valueList.isPartialView(); + } + + /** Returns an immutable set of the mappings in this map, sorted by the key ordering. */ + @Override + public ImmutableSet> entrySet() { + return super.entrySet(); + } + + @Override + ImmutableSet> createEntrySet() { + class EntrySet extends ImmutableMapEntrySet { + @Override + public UnmodifiableIterator> iterator() { + return asList().iterator(); + } + + @Override + public Spliterator> spliterator() { + return asList().spliterator(); + } + + @Override + public void forEach(Consumer> action) { + asList().forEach(action); + } + + @Override + ImmutableList> createAsList() { + return new ImmutableAsList>() { + @Override + public Entry get(int index) { + return new AbstractMap.SimpleImmutableEntry<>( + keySet.asList().get(index), valueList.get(index)); + } + + @Override + public Spliterator> spliterator() { + return CollectSpliterators.indexed( + size(), ImmutableSet.SPLITERATOR_CHARACTERISTICS, this::get); + } + + @Override + ImmutableCollection> delegateCollection() { + return EntrySet.this; + } + }; + } + + @Override + ImmutableMap map() { + return ImmutableSortedMap.this; + } + } + return isEmpty() ? ImmutableSet.>of() : new EntrySet(); + } + + /** Returns an immutable sorted set of the keys in this map. */ + @Override + public ImmutableSortedSet keySet() { + return keySet; + } + + @Override + ImmutableSet createKeySet() { + throw new AssertionError("should never be called"); + } + + /** + * Returns an immutable collection of the values in this map, sorted by the ordering of the + * corresponding keys. + */ + @Override + public ImmutableCollection values() { + return valueList; + } + + @Override + ImmutableCollection createValues() { + throw new AssertionError("should never be called"); + } + + /** + * Returns the comparator that orders the keys, which is {@link Ordering#natural()} when the + * natural ordering of the keys is used. Note that its behavior is not consistent with {@link + * TreeMap#comparator()}, which returns {@code null} to indicate natural ordering. + */ + @Override + public Comparator comparator() { + return keySet().comparator(); + } + + @Override + public K firstKey() { + return keySet().first(); + } + + @Override + public K lastKey() { + return keySet().last(); + } + + private ImmutableSortedMap getSubMap(int fromIndex, int toIndex) { + if (fromIndex == 0 && toIndex == size()) { + return this; + } else if (fromIndex == toIndex) { + return emptyMap(comparator()); + } else { + return new ImmutableSortedMap<>( + keySet.getSubSet(fromIndex, toIndex), valueList.subList(fromIndex, toIndex)); + } + } + + /** + * This method returns a {@code ImmutableSortedMap}, consisting of the entries whose keys are less + * than {@code toKey}. + * + *

The {@link SortedMap#headMap} documentation states that a submap of a submap throws an + * {@link IllegalArgumentException} if passed a {@code toKey} greater than an earlier {@code + * toKey}. However, this method doesn't throw an exception in that situation, but instead keeps + * the original {@code toKey}. + */ + @Override + public ImmutableSortedMap headMap(K toKey) { + return headMap(toKey, false); + } + + /** + * This method returns a {@code ImmutableSortedMap}, consisting of the entries whose keys are less + * than (or equal to, if {@code inclusive}) {@code toKey}. + * + *

The {@link SortedMap#headMap} documentation states that a submap of a submap throws an + * {@link IllegalArgumentException} if passed a {@code toKey} greater than an earlier {@code + * toKey}. However, this method doesn't throw an exception in that situation, but instead keeps + * the original {@code toKey}. + * + * @since 12.0 + */ + @Override + public ImmutableSortedMap headMap(K toKey, boolean inclusive) { + return getSubMap(0, keySet.headIndex(checkNotNull(toKey), inclusive)); + } + + /** + * This method returns a {@code ImmutableSortedMap}, consisting of the entries whose keys ranges + * from {@code fromKey}, inclusive, to {@code toKey}, exclusive. + * + *

The {@link SortedMap#subMap} documentation states that a submap of a submap throws an {@link + * IllegalArgumentException} if passed a {@code fromKey} less than an earlier {@code fromKey}. + * However, this method doesn't throw an exception in that situation, but instead keeps the + * original {@code fromKey}. Similarly, this method keeps the original {@code toKey}, instead of + * throwing an exception, if passed a {@code toKey} greater than an earlier {@code toKey}. + */ + @Override + public ImmutableSortedMap subMap(K fromKey, K toKey) { + return subMap(fromKey, true, toKey, false); + } + + /** + * This method returns a {@code ImmutableSortedMap}, consisting of the entries whose keys ranges + * from {@code fromKey} to {@code toKey}, inclusive or exclusive as indicated by the boolean + * flags. + * + *

The {@link SortedMap#subMap} documentation states that a submap of a submap throws an {@link + * IllegalArgumentException} if passed a {@code fromKey} less than an earlier {@code fromKey}. + * However, this method doesn't throw an exception in that situation, but instead keeps the + * original {@code fromKey}. Similarly, this method keeps the original {@code toKey}, instead of + * throwing an exception, if passed a {@code toKey} greater than an earlier {@code toKey}. + * + * @since 12.0 + */ + @Override + public ImmutableSortedMap subMap( + K fromKey, boolean fromInclusive, K toKey, boolean toInclusive) { + checkNotNull(fromKey); + checkNotNull(toKey); + checkArgument( + comparator().compare(fromKey, toKey) <= 0, + "expected fromKey <= toKey but %s > %s", + fromKey, + toKey); + return headMap(toKey, toInclusive).tailMap(fromKey, fromInclusive); + } + + /** + * This method returns a {@code ImmutableSortedMap}, consisting of the entries whose keys are + * greater than or equals to {@code fromKey}. + * + *

The {@link SortedMap#tailMap} documentation states that a submap of a submap throws an + * {@link IllegalArgumentException} if passed a {@code fromKey} less than an earlier {@code + * fromKey}. However, this method doesn't throw an exception in that situation, but instead keeps + * the original {@code fromKey}. + */ + @Override + public ImmutableSortedMap tailMap(K fromKey) { + return tailMap(fromKey, true); + } + + /** + * This method returns a {@code ImmutableSortedMap}, consisting of the entries whose keys are + * greater than (or equal to, if {@code inclusive}) {@code fromKey}. + * + *

The {@link SortedMap#tailMap} documentation states that a submap of a submap throws an + * {@link IllegalArgumentException} if passed a {@code fromKey} less than an earlier {@code + * fromKey}. However, this method doesn't throw an exception in that situation, but instead keeps + * the original {@code fromKey}. + * + * @since 12.0 + */ + @Override + public ImmutableSortedMap tailMap(K fromKey, boolean inclusive) { + return getSubMap(keySet.tailIndex(checkNotNull(fromKey), inclusive), size()); + } + + @Override + public Entry lowerEntry(K key) { + return headMap(key, false).lastEntry(); + } + + @Override + public K lowerKey(K key) { + return keyOrNull(lowerEntry(key)); + } + + @Override + public Entry floorEntry(K key) { + return headMap(key, true).lastEntry(); + } + + @Override + public K floorKey(K key) { + return keyOrNull(floorEntry(key)); + } + + @Override + public Entry ceilingEntry(K key) { + return tailMap(key, true).firstEntry(); + } + + @Override + public K ceilingKey(K key) { + return keyOrNull(ceilingEntry(key)); + } + + @Override + public Entry higherEntry(K key) { + return tailMap(key, false).firstEntry(); + } + + @Override + public K higherKey(K key) { + return keyOrNull(higherEntry(key)); + } + + @Override + public Entry firstEntry() { + return isEmpty() ? null : entrySet().asList().get(0); + } + + @Override + public Entry lastEntry() { + return isEmpty() ? null : entrySet().asList().get(size() - 1); + } + + /** + * Guaranteed to throw an exception and leave the map unmodified. + * + * @throws UnsupportedOperationException always + * @deprecated Unsupported operation. + */ + + @Deprecated + @Override + public final Entry pollFirstEntry() { + throw new UnsupportedOperationException(); + } + + /** + * Guaranteed to throw an exception and leave the map unmodified. + * + * @throws UnsupportedOperationException always + * @deprecated Unsupported operation. + */ + + @Deprecated + @Override + public final Entry pollLastEntry() { + throw new UnsupportedOperationException(); + } + + @Override + public ImmutableSortedMap descendingMap() { + // TODO(kevinb): the descendingMap is never actually cached at all. Either it should be or the + // code below simplified. + ImmutableSortedMap result = descendingMap; + if (result == null) { + if (isEmpty()) { + return result = emptyMap(Ordering.from(comparator()).reverse()); + } else { + return result = + new ImmutableSortedMap<>( + (RegularImmutableSortedSet) keySet.descendingSet(), valueList.reverse(), this); + } + } + return result; + } + + @Override + public ImmutableSortedSet navigableKeySet() { + return keySet; + } + + @Override + public ImmutableSortedSet descendingKeySet() { + return keySet.descendingSet(); + } + + /** + * Serialized type for all ImmutableSortedMap instances. It captures the logical contents and they + * are reconstructed using public factory methods. This ensures that the implementation types + * remain as implementation details. + */ + private static class SerializedForm extends ImmutableMap.SerializedForm { + private final Comparator comparator; + + @SuppressWarnings("unchecked") + SerializedForm(ImmutableSortedMap sortedMap) { + super(sortedMap); + comparator = (Comparator) sortedMap.comparator(); + } + + @Override + Object readResolve() { + Builder builder = new Builder<>(comparator); + return createMap(builder); + } + + private static final long serialVersionUID = 0; + } + + @Override + Object writeReplace() { + return new SerializedForm(this); + } + + // This class is never actually serialized directly, but we have to make the + // warning go away (and suppressing would suppress for all nested classes too) + private static final long serialVersionUID = 0; +} diff --git a/src/main/java/com/google/common/collect/ImmutableSortedMapFauxverideShim.java b/src/main/java/com/google/common/collect/ImmutableSortedMapFauxverideShim.java new file mode 100644 index 0000000..87b8351 --- /dev/null +++ b/src/main/java/com/google/common/collect/ImmutableSortedMapFauxverideShim.java @@ -0,0 +1,162 @@ +/* + * Copyright (C) 2009 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import com.google.common.annotations.GwtIncompatible; +import java.util.function.BinaryOperator; +import java.util.function.Function; +import java.util.stream.Collector; + +/** + * "Overrides" the {@link ImmutableMap} static methods that lack {@link ImmutableSortedMap} + * equivalents with deprecated, exception-throwing versions. See {@link + * ImmutableSortedSetFauxverideShim} for details. + * + * @author Chris Povirk + */ +@GwtIncompatible +abstract class ImmutableSortedMapFauxverideShim extends ImmutableMap { + /** + * Not supported. Use {@link ImmutableSortedMap#toImmutableSortedMap}, which offers better + * type-safety, instead. This method exists only to hide {@link ImmutableMap#toImmutableMap} from + * consumers of {@code ImmutableSortedMap}. + * + * @throws UnsupportedOperationException always + * @deprecated Use {@link ImmutableSortedMap#toImmutableSortedMap}. + */ + @Deprecated + public static Collector> toImmutableMap( + Function keyFunction, + Function valueFunction) { + throw new UnsupportedOperationException(); + } + + /** + * Not supported. Use {@link ImmutableSortedMap#toImmutableSortedMap}, which offers better + * type-safety, instead. This method exists only to hide {@link ImmutableMap#toImmutableMap} from + * consumers of {@code ImmutableSortedMap}. + * + * @throws UnsupportedOperationException always + * @deprecated Use {@link ImmutableSortedMap#toImmutableSortedMap}. + */ + @Deprecated + public static Collector> toImmutableMap( + Function keyFunction, + Function valueFunction, + BinaryOperator mergeFunction) { + throw new UnsupportedOperationException(); + } + + /** + * Not supported. Use {@link ImmutableSortedMap#naturalOrder}, which offers better type-safety, + * instead. This method exists only to hide {@link ImmutableMap#builder} from consumers of {@code + * ImmutableSortedMap}. + * + * @throws UnsupportedOperationException always + * @deprecated Use {@link ImmutableSortedMap#naturalOrder}, which offers better type-safety. + */ + @Deprecated + public static ImmutableSortedMap.Builder builder() { + throw new UnsupportedOperationException(); + } + + /** + * Not supported for ImmutableSortedMap. + * + * @throws UnsupportedOperationException always + * @deprecated Not supported for ImmutableSortedMap. + */ + @Deprecated + public static ImmutableSortedMap.Builder builderWithExpectedSize(int expectedSize) { + throw new UnsupportedOperationException(); + } + + /** + * Not supported. You are attempting to create a map that may contain a non-{@code Comparable} + * key. Proper calls will resolve to the version in {@code ImmutableSortedMap}, not this dummy + * version. + * + * @throws UnsupportedOperationException always + * @deprecated Pass a key of type {@code Comparable} to use {@link + * ImmutableSortedMap#of(Comparable, Object)}. + */ + @Deprecated + public static ImmutableSortedMap of(K k1, V v1) { + throw new UnsupportedOperationException(); + } + + /** + * Not supported. You are attempting to create a map that may contain non-{@code Comparable} + * keys. Proper calls will resolve to the version in {@code ImmutableSortedMap}, not this + * dummy version. + * + * @throws UnsupportedOperationException always + * @deprecated Pass keys of type {@code Comparable} to use {@link + * ImmutableSortedMap#of(Comparable, Object, Comparable, Object)}. + */ + @Deprecated + public static ImmutableSortedMap of(K k1, V v1, K k2, V v2) { + throw new UnsupportedOperationException(); + } + + /** + * Not supported. You are attempting to create a map that may contain non-{@code Comparable} + * keys. Proper calls to will resolve to the version in {@code ImmutableSortedMap}, not this + * dummy version. + * + * @throws UnsupportedOperationException always + * @deprecated Pass keys of type {@code Comparable} to use {@link + * ImmutableSortedMap#of(Comparable, Object, Comparable, Object, Comparable, Object)}. + */ + @Deprecated + public static ImmutableSortedMap of(K k1, V v1, K k2, V v2, K k3, V v3) { + throw new UnsupportedOperationException(); + } + + /** + * Not supported. You are attempting to create a map that may contain non-{@code Comparable} + * keys. Proper calls will resolve to the version in {@code ImmutableSortedMap}, not this + * dummy version. + * + * @throws UnsupportedOperationException always + * @deprecated Pass keys of type {@code Comparable} to use {@link + * ImmutableSortedMap#of(Comparable, Object, Comparable, Object, Comparable, Object, + * Comparable, Object)}. + */ + @Deprecated + public static ImmutableSortedMap of(K k1, V v1, K k2, V v2, K k3, V v3, K k4, V v4) { + throw new UnsupportedOperationException(); + } + + /** + * Not supported. You are attempting to create a map that may contain non-{@code Comparable} + * keys. Proper calls will resolve to the version in {@code ImmutableSortedMap}, not this + * dummy version. + * + * @throws UnsupportedOperationException always + * @deprecated Pass keys of type {@code Comparable} to use {@link + * ImmutableSortedMap#of(Comparable, Object, Comparable, Object, Comparable, Object, + * Comparable, Object, Comparable, Object)}. + */ + @Deprecated + public static ImmutableSortedMap of( + K k1, V v1, K k2, V v2, K k3, V v3, K k4, V v4, K k5, V v5) { + throw new UnsupportedOperationException(); + } + + // No copyOf() fauxveride; see ImmutableSortedSetFauxverideShim. +} diff --git a/src/main/java/com/google/common/collect/ImmutableSortedMultiset.java b/src/main/java/com/google/common/collect/ImmutableSortedMultiset.java new file mode 100644 index 0000000..c132e7e --- /dev/null +++ b/src/main/java/com/google/common/collect/ImmutableSortedMultiset.java @@ -0,0 +1,591 @@ +/* + * Copyright (C) 2011 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the + * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.annotations.GwtIncompatible; + + +import java.io.Serializable; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.Iterator; +import java.util.List; +import java.util.function.Function; +import java.util.function.ToIntFunction; +import java.util.stream.Collector; + +/** + * A {@link SortedMultiset} whose contents will never change, with many other important properties + * detailed at {@link ImmutableCollection}. + * + *

Warning: as with any sorted collection, you are strongly advised not to use a {@link + * Comparator} or {@link Comparable} type whose comparison behavior is inconsistent with + * equals. That is, {@code a.compareTo(b)} or {@code comparator.compare(a, b)} should equal zero + * if and only if {@code a.equals(b)}. If this advice is not followed, the resulting + * collection will not correctly obey its specification. + * + *

See the Guava User Guide article on immutable collections. + * + * @author Louis Wasserman + * @since 12.0 + */ +@GwtIncompatible // hasn't been tested yet +public abstract class ImmutableSortedMultiset extends ImmutableSortedMultisetFauxverideShim + implements SortedMultiset { + // TODO(lowasser): GWT compatibility + + /** + * Returns a {@code Collector} that accumulates the input elements into a new {@code + * ImmutableMultiset}. Elements are sorted by the specified comparator. + * + *

Warning: {@code comparator} should be consistent with {@code equals} as + * explained in the {@link Comparator} documentation. + * + * @since 21.0 + */ + public static Collector> toImmutableSortedMultiset( + Comparator comparator) { + return toImmutableSortedMultiset(comparator, Function.identity(), e -> 1); + } + + /** + * Returns a {@code Collector} that accumulates elements into an {@code ImmutableSortedMultiset} + * whose elements are the result of applying {@code elementFunction} to the inputs, with counts + * equal to the result of applying {@code countFunction} to the inputs. + * + *

If the mapped elements contain duplicates (according to {@code comparator}), the first + * occurrence in encounter order appears in the resulting multiset, with count equal to the sum of + * the outputs of {@code countFunction.applyAsInt(t)} for each {@code t} mapped to that element. + * + * @since 22.0 + */ + public static Collector> toImmutableSortedMultiset( + Comparator comparator, + Function elementFunction, + ToIntFunction countFunction) { + checkNotNull(comparator); + checkNotNull(elementFunction); + checkNotNull(countFunction); + return Collector.of( + () -> TreeMultiset.create(comparator), + (multiset, t) -> + multiset.add(checkNotNull(elementFunction.apply(t)), countFunction.applyAsInt(t)), + (multiset1, multiset2) -> { + multiset1.addAll(multiset2); + return multiset1; + }, + (Multiset multiset) -> copyOfSortedEntries(comparator, multiset.entrySet())); + } + + /** Returns the empty immutable sorted multiset. */ + @SuppressWarnings("unchecked") + public static ImmutableSortedMultiset of() { + return (ImmutableSortedMultiset) RegularImmutableSortedMultiset.NATURAL_EMPTY_MULTISET; + } + + /** Returns an immutable sorted multiset containing a single element. */ + public static > ImmutableSortedMultiset of(E element) { + RegularImmutableSortedSet elementSet = + (RegularImmutableSortedSet) ImmutableSortedSet.of(element); + long[] cumulativeCounts = {0, 1}; + return new RegularImmutableSortedMultiset(elementSet, cumulativeCounts, 0, 1); + } + + /** + * Returns an immutable sorted multiset containing the given elements sorted by their natural + * ordering. + * + * @throws NullPointerException if any element is null + */ + @SuppressWarnings("unchecked") + public static > ImmutableSortedMultiset of(E e1, E e2) { + return copyOf(Ordering.natural(), Arrays.asList(e1, e2)); + } + + /** + * Returns an immutable sorted multiset containing the given elements sorted by their natural + * ordering. + * + * @throws NullPointerException if any element is null + */ + @SuppressWarnings("unchecked") + public static > ImmutableSortedMultiset of(E e1, E e2, E e3) { + return copyOf(Ordering.natural(), Arrays.asList(e1, e2, e3)); + } + + /** + * Returns an immutable sorted multiset containing the given elements sorted by their natural + * ordering. + * + * @throws NullPointerException if any element is null + */ + @SuppressWarnings("unchecked") + public static > ImmutableSortedMultiset of( + E e1, E e2, E e3, E e4) { + return copyOf(Ordering.natural(), Arrays.asList(e1, e2, e3, e4)); + } + + /** + * Returns an immutable sorted multiset containing the given elements sorted by their natural + * ordering. + * + * @throws NullPointerException if any element is null + */ + @SuppressWarnings("unchecked") + public static > ImmutableSortedMultiset of( + E e1, E e2, E e3, E e4, E e5) { + return copyOf(Ordering.natural(), Arrays.asList(e1, e2, e3, e4, e5)); + } + + /** + * Returns an immutable sorted multiset containing the given elements sorted by their natural + * ordering. + * + * @throws NullPointerException if any element is null + */ + @SuppressWarnings("unchecked") + public static > ImmutableSortedMultiset of( + E e1, E e2, E e3, E e4, E e5, E e6, E... remaining) { + int size = remaining.length + 6; + List all = Lists.newArrayListWithCapacity(size); + Collections.addAll(all, e1, e2, e3, e4, e5, e6); + Collections.addAll(all, remaining); + return copyOf(Ordering.natural(), all); + } + + /** + * Returns an immutable sorted multiset containing the given elements sorted by their natural + * ordering. + * + * @throws NullPointerException if any of {@code elements} is null + */ + public static > ImmutableSortedMultiset copyOf(E[] elements) { + return copyOf(Ordering.natural(), Arrays.asList(elements)); + } + + /** + * Returns an immutable sorted multiset containing the given elements sorted by their natural + * ordering. To create a copy of a {@code SortedMultiset} that preserves the comparator, call + * {@link #copyOfSorted} instead. This method iterates over {@code elements} at most once. + * + *

Note that if {@code s} is a {@code Multiset}, then {@code + * ImmutableSortedMultiset.copyOf(s)} returns an {@code ImmutableSortedMultiset} + * containing each of the strings in {@code s}, while {@code ImmutableSortedMultiset.of(s)} + * returns an {@code ImmutableSortedMultiset>} containing one element (the given + * multiset itself). + * + *

Despite the method name, this method attempts to avoid actually copying the data when it is + * safe to do so. The exact circumstances under which a copy will or will not be performed are + * undocumented and subject to change. + * + *

This method is not type-safe, as it may be called on elements that are not mutually + * comparable. + * + * @throws ClassCastException if the elements are not mutually comparable + * @throws NullPointerException if any of {@code elements} is null + */ + public static ImmutableSortedMultiset copyOf(Iterable elements) { + // Hack around E not being a subtype of Comparable. + // Unsafe, see ImmutableSortedMultisetFauxverideShim. + @SuppressWarnings("unchecked") + Ordering naturalOrder = (Ordering) Ordering.natural(); + return copyOf(naturalOrder, elements); + } + + /** + * Returns an immutable sorted multiset containing the given elements sorted by their natural + * ordering. + * + *

This method is not type-safe, as it may be called on elements that are not mutually + * comparable. + * + * @throws ClassCastException if the elements are not mutually comparable + * @throws NullPointerException if any of {@code elements} is null + */ + public static ImmutableSortedMultiset copyOf(Iterator elements) { + // Hack around E not being a subtype of Comparable. + // Unsafe, see ImmutableSortedMultisetFauxverideShim. + @SuppressWarnings("unchecked") + Ordering naturalOrder = (Ordering) Ordering.natural(); + return copyOf(naturalOrder, elements); + } + + /** + * Returns an immutable sorted multiset containing the given elements sorted by the given {@code + * Comparator}. + * + * @throws NullPointerException if {@code comparator} or any of {@code elements} is null + */ + public static ImmutableSortedMultiset copyOf( + Comparator comparator, Iterator elements) { + checkNotNull(comparator); + return new Builder(comparator).addAll(elements).build(); + } + + /** + * Returns an immutable sorted multiset containing the given elements sorted by the given {@code + * Comparator}. This method iterates over {@code elements} at most once. + * + *

Despite the method name, this method attempts to avoid actually copying the data when it is + * safe to do so. The exact circumstances under which a copy will or will not be performed are + * undocumented and subject to change. + * + * @throws NullPointerException if {@code comparator} or any of {@code elements} is null + */ + public static ImmutableSortedMultiset copyOf( + Comparator comparator, Iterable elements) { + if (elements instanceof ImmutableSortedMultiset) { + @SuppressWarnings("unchecked") // immutable collections are always safe for covariant casts + ImmutableSortedMultiset multiset = (ImmutableSortedMultiset) elements; + if (comparator.equals(multiset.comparator())) { + if (multiset.isPartialView()) { + return copyOfSortedEntries(comparator, multiset.entrySet().asList()); + } else { + return multiset; + } + } + } + elements = Lists.newArrayList(elements); // defensive copy + TreeMultiset sortedCopy = TreeMultiset.create(checkNotNull(comparator)); + Iterables.addAll(sortedCopy, elements); + return copyOfSortedEntries(comparator, sortedCopy.entrySet()); + } + + /** + * Returns an immutable sorted multiset containing the elements of a sorted multiset, sorted by + * the same {@code Comparator}. That behavior differs from {@link #copyOf(Iterable)}, which always + * uses the natural ordering of the elements. + * + *

Despite the method name, this method attempts to avoid actually copying the data when it is + * safe to do so. The exact circumstances under which a copy will or will not be performed are + * undocumented and subject to change. + * + *

This method is safe to use even when {@code sortedMultiset} is a synchronized or concurrent + * collection that is currently being modified by another thread. + * + * @throws NullPointerException if {@code sortedMultiset} or any of its elements is null + */ + public static ImmutableSortedMultiset copyOfSorted(SortedMultiset sortedMultiset) { + return copyOfSortedEntries( + sortedMultiset.comparator(), Lists.newArrayList(sortedMultiset.entrySet())); + } + + private static ImmutableSortedMultiset copyOfSortedEntries( + Comparator comparator, Collection> entries) { + if (entries.isEmpty()) { + return emptyMultiset(comparator); + } + ImmutableList.Builder elementsBuilder = new ImmutableList.Builder(entries.size()); + long[] cumulativeCounts = new long[entries.size() + 1]; + int i = 0; + for (Entry entry : entries) { + elementsBuilder.add(entry.getElement()); + cumulativeCounts[i + 1] = cumulativeCounts[i] + entry.getCount(); + i++; + } + return new RegularImmutableSortedMultiset( + new RegularImmutableSortedSet(elementsBuilder.build(), comparator), + cumulativeCounts, + 0, + entries.size()); + } + + @SuppressWarnings("unchecked") + static ImmutableSortedMultiset emptyMultiset(Comparator comparator) { + if (Ordering.natural().equals(comparator)) { + return (ImmutableSortedMultiset) RegularImmutableSortedMultiset.NATURAL_EMPTY_MULTISET; + } else { + return new RegularImmutableSortedMultiset(comparator); + } + } + + ImmutableSortedMultiset() {} + + @Override + public final Comparator comparator() { + return elementSet().comparator(); + } + + @Override + public abstract ImmutableSortedSet elementSet(); + + transient ImmutableSortedMultiset descendingMultiset; + + @Override + public ImmutableSortedMultiset descendingMultiset() { + ImmutableSortedMultiset result = descendingMultiset; + if (result == null) { + return descendingMultiset = + this.isEmpty() + ? emptyMultiset(Ordering.from(comparator()).reverse()) + : new DescendingImmutableSortedMultiset(this); + } + return result; + } + + /** + * {@inheritDoc} + * + *

This implementation is guaranteed to throw an {@link UnsupportedOperationException}. + * + * @throws UnsupportedOperationException always + * @deprecated Unsupported operation. + */ + + @Deprecated + @Override + public final Entry pollFirstEntry() { + throw new UnsupportedOperationException(); + } + + /** + * {@inheritDoc} + * + *

This implementation is guaranteed to throw an {@link UnsupportedOperationException}. + * + * @throws UnsupportedOperationException always + * @deprecated Unsupported operation. + */ + + @Deprecated + @Override + public final Entry pollLastEntry() { + throw new UnsupportedOperationException(); + } + + @Override + public abstract ImmutableSortedMultiset headMultiset(E upperBound, BoundType boundType); + + @Override + public ImmutableSortedMultiset subMultiset( + E lowerBound, BoundType lowerBoundType, E upperBound, BoundType upperBoundType) { + checkArgument( + comparator().compare(lowerBound, upperBound) <= 0, + "Expected lowerBound <= upperBound but %s > %s", + lowerBound, + upperBound); + return tailMultiset(lowerBound, lowerBoundType).headMultiset(upperBound, upperBoundType); + } + + @Override + public abstract ImmutableSortedMultiset tailMultiset(E lowerBound, BoundType boundType); + + /** + * Returns a builder that creates immutable sorted multisets with an explicit comparator. If the + * comparator has a more general type than the set being generated, such as creating a {@code + * SortedMultiset} with a {@code Comparator}, use the {@link Builder} constructor + * instead. + * + * @throws NullPointerException if {@code comparator} is null + */ + public static Builder orderedBy(Comparator comparator) { + return new Builder(comparator); + } + + /** + * Returns a builder that creates immutable sorted multisets whose elements are ordered by the + * reverse of their natural ordering. + * + *

Note: the type parameter {@code E} extends {@code Comparable} rather than {@code + * Comparable} as a workaround for javac bug 6468354. + */ + public static > Builder reverseOrder() { + return new Builder(Ordering.natural().reverse()); + } + + /** + * Returns a builder that creates immutable sorted multisets whose elements are ordered by their + * natural ordering. The sorted multisets use {@link Ordering#natural()} as the comparator. This + * method provides more type-safety than {@link #builder}, as it can be called only for classes + * that implement {@link Comparable}. + * + *

Note: the type parameter {@code E} extends {@code Comparable} rather than {@code + * Comparable} as a workaround for javac bug 6468354. + */ + public static > Builder naturalOrder() { + return new Builder(Ordering.natural()); + } + + /** + * A builder for creating immutable multiset instances, especially {@code public static final} + * multisets ("constant multisets"). Example: + * + *

{@code
+   * public static final ImmutableSortedMultiset BEANS =
+   *     new ImmutableSortedMultiset.Builder(colorComparator())
+   *         .addCopies(Bean.COCOA, 4)
+   *         .addCopies(Bean.GARDEN, 6)
+   *         .addCopies(Bean.RED, 8)
+   *         .addCopies(Bean.BLACK_EYED, 10)
+   *         .build();
+   * }
+ * + *

Builder instances can be reused; it is safe to call {@link #build} multiple times to build + * multiple multisets in series. + * + * @since 12.0 + */ + public static class Builder extends ImmutableMultiset.Builder { + /** + * Creates a new builder. The returned builder is equivalent to the builder generated by {@link + * ImmutableSortedMultiset#orderedBy(Comparator)}. + */ + public Builder(Comparator comparator) { + super(TreeMultiset.create(checkNotNull(comparator))); + } + + /** + * Adds {@code element} to the {@code ImmutableSortedMultiset}. + * + * @param element the element to add + * @return this {@code Builder} object + * @throws NullPointerException if {@code element} is null + */ + + @Override + public Builder add(E element) { + super.add(element); + return this; + } + + /** + * Adds each element of {@code elements} to the {@code ImmutableSortedMultiset}. + * + * @param elements the elements to add + * @return this {@code Builder} object + * @throws NullPointerException if {@code elements} is null or contains a null element + */ + + @Override + public Builder add(E... elements) { + super.add(elements); + return this; + } + + /** + * Adds a number of occurrences of an element to this {@code ImmutableSortedMultiset}. + * + * @param element the element to add + * @param occurrences the number of occurrences of the element to add. May be zero, in which + * case no change will be made. + * @return this {@code Builder} object + * @throws NullPointerException if {@code element} is null + * @throws IllegalArgumentException if {@code occurrences} is negative, or if this operation + * would result in more than {@link Integer#MAX_VALUE} occurrences of the element + */ + + @Override + public Builder addCopies(E element, int occurrences) { + super.addCopies(element, occurrences); + return this; + } + + /** + * Adds or removes the necessary occurrences of an element such that the element attains the + * desired count. + * + * @param element the element to add or remove occurrences of + * @param count the desired count of the element in this multiset + * @return this {@code Builder} object + * @throws NullPointerException if {@code element} is null + * @throws IllegalArgumentException if {@code count} is negative + */ + + @Override + public Builder setCount(E element, int count) { + super.setCount(element, count); + return this; + } + + /** + * Adds each element of {@code elements} to the {@code ImmutableSortedMultiset}. + * + * @param elements the {@code Iterable} to add to the {@code ImmutableSortedMultiset} + * @return this {@code Builder} object + * @throws NullPointerException if {@code elements} is null or contains a null element + */ + + @Override + public Builder addAll(Iterable elements) { + super.addAll(elements); + return this; + } + + /** + * Adds each element of {@code elements} to the {@code ImmutableSortedMultiset}. + * + * @param elements the elements to add to the {@code ImmutableSortedMultiset} + * @return this {@code Builder} object + * @throws NullPointerException if {@code elements} is null or contains a null element + */ + + @Override + public Builder addAll(Iterator elements) { + super.addAll(elements); + return this; + } + + /** + * Returns a newly-created {@code ImmutableSortedMultiset} based on the contents of the {@code + * Builder}. + */ + @Override + public ImmutableSortedMultiset build() { + return copyOfSorted((SortedMultiset) contents); + } + } + + private static final class SerializedForm implements Serializable { + final Comparator comparator; + final E[] elements; + final int[] counts; + + @SuppressWarnings("unchecked") + SerializedForm(SortedMultiset multiset) { + this.comparator = multiset.comparator(); + int n = multiset.entrySet().size(); + elements = (E[]) new Object[n]; + counts = new int[n]; + int i = 0; + for (Entry entry : multiset.entrySet()) { + elements[i] = entry.getElement(); + counts[i] = entry.getCount(); + i++; + } + } + + Object readResolve() { + int n = elements.length; + Builder builder = new Builder<>(comparator); + for (int i = 0; i < n; i++) { + builder.addCopies(elements[i], counts[i]); + } + return builder.build(); + } + } + + @Override + Object writeReplace() { + return new SerializedForm(this); + } +} diff --git a/src/main/java/com/google/common/collect/ImmutableSortedMultisetFauxverideShim.java b/src/main/java/com/google/common/collect/ImmutableSortedMultisetFauxverideShim.java new file mode 100644 index 0000000..e3f45f7 --- /dev/null +++ b/src/main/java/com/google/common/collect/ImmutableSortedMultisetFauxverideShim.java @@ -0,0 +1,199 @@ +/* + * Copyright (C) 2011 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the + * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import com.google.common.annotations.GwtIncompatible; +import java.util.function.Function; +import java.util.function.ToIntFunction; +import java.util.stream.Collector; + +/** + * "Overrides" the {@link ImmutableMultiset} static methods that lack {@link + * ImmutableSortedMultiset} equivalents with deprecated, exception-throwing versions. This prevents + * accidents like the following: + * + *

{@code
+ * List objects = ...;
+ * // Sort them:
+ * Set sorted = ImmutableSortedMultiset.copyOf(objects);
+ * // BAD CODE! The returned multiset is actually an unsorted ImmutableMultiset!
+ * }
+ *
+ * 

While we could put the overrides in {@link ImmutableSortedMultiset} itself, it seems clearer + * to separate these "do not call" methods from those intended for normal use. + * + * @author Louis Wasserman + */ +@GwtIncompatible +abstract class ImmutableSortedMultisetFauxverideShim extends ImmutableMultiset { + /** + * Not supported. Use {@link ImmutableSortedMultiset#toImmutableSortedMultiset} instead. This + * method exists only to hide {@link ImmutableMultiset#toImmutableMultiset} from consumers of + * {@code ImmutableSortedMultiset}. + * + * @throws UnsupportedOperationException always + * @deprecated Use {@link ImmutableSortedMultiset#toImmutableSortedMultiset}. + * @since 21.0 + */ + @Deprecated + public static Collector> toImmutableMultiset() { + throw new UnsupportedOperationException(); + } + + /** + * Not supported. Use {@link ImmutableSortedMultiset#toImmutableSortedMultiset} instead. This + * method exists only to hide {@link ImmutableMultiset#toImmutableMultiset} from consumers of + * {@code ImmutableSortedMultiset}. + * + * @throws UnsupportedOperationException always + * @deprecated Use {@link ImmutableSortedMultiset#toImmutableSortedMultiset}. + * @since 22.0 + */ + @Deprecated + public static Collector> toImmutableMultiset( + Function elementFunction, ToIntFunction countFunction) { + throw new UnsupportedOperationException(); + } + + /** + * Not supported. Use {@link ImmutableSortedMultiset#naturalOrder}, which offers better + * type-safety, instead. This method exists only to hide {@link ImmutableMultiset#builder} from + * consumers of {@code ImmutableSortedMultiset}. + * + * @throws UnsupportedOperationException always + * @deprecated Use {@link ImmutableSortedMultiset#naturalOrder}, which offers better type-safety. + */ + @Deprecated + public static ImmutableSortedMultiset.Builder builder() { + throw new UnsupportedOperationException(); + } + + /** + * Not supported. You are attempting to create a multiset that may contain a non-{@code + * Comparable} element. Proper calls will resolve to the version in {@code + * ImmutableSortedMultiset}, not this dummy version. + * + * @throws UnsupportedOperationException always + * @deprecated Pass a parameter of type {@code Comparable} to use {@link + * ImmutableSortedMultiset#of(Comparable)}. + */ + @Deprecated + public static ImmutableSortedMultiset of(E element) { + throw new UnsupportedOperationException(); + } + + /** + * Not supported. You are attempting to create a multiset that may contain a non-{@code + * Comparable} element. Proper calls will resolve to the version in {@code + * ImmutableSortedMultiset}, not this dummy version. + * + * @throws UnsupportedOperationException always + * @deprecated Pass the parameters of type {@code Comparable} to use {@link + * ImmutableSortedMultiset#of(Comparable, Comparable)}. + */ + @Deprecated + public static ImmutableSortedMultiset of(E e1, E e2) { + throw new UnsupportedOperationException(); + } + + /** + * Not supported. You are attempting to create a multiset that may contain a non-{@code + * Comparable} element. Proper calls will resolve to the version in {@code + * ImmutableSortedMultiset}, not this dummy version. + * + * @throws UnsupportedOperationException always + * @deprecated Pass the parameters of type {@code Comparable} to use {@link + * ImmutableSortedMultiset#of(Comparable, Comparable, Comparable)}. + */ + @Deprecated + public static ImmutableSortedMultiset of(E e1, E e2, E e3) { + throw new UnsupportedOperationException(); + } + + /** + * Not supported. You are attempting to create a multiset that may contain a non-{@code + * Comparable} element. Proper calls will resolve to the version in {@code + * ImmutableSortedMultiset}, not this dummy version. + * + * @throws UnsupportedOperationException always + * @deprecated Pass the parameters of type {@code Comparable} to use {@link + * ImmutableSortedMultiset#of(Comparable, Comparable, Comparable, Comparable)}. + */ + @Deprecated + public static ImmutableSortedMultiset of(E e1, E e2, E e3, E e4) { + throw new UnsupportedOperationException(); + } + + /** + * Not supported. You are attempting to create a multiset that may contain a non-{@code + * Comparable} element. Proper calls will resolve to the version in {@code + * ImmutableSortedMultiset}, not this dummy version. + * + * @throws UnsupportedOperationException always + * @deprecated Pass the parameters of type {@code Comparable} to use {@link + * ImmutableSortedMultiset#of(Comparable, Comparable, Comparable, Comparable, Comparable)} . + * + */ + @Deprecated + public static ImmutableSortedMultiset of(E e1, E e2, E e3, E e4, E e5) { + throw new UnsupportedOperationException(); + } + + /** + * Not supported. You are attempting to create a multiset that may contain a non-{@code + * Comparable} element. Proper calls will resolve to the version in {@code + * ImmutableSortedMultiset}, not this dummy version. + * + * @throws UnsupportedOperationException always + * @deprecated Pass the parameters of type {@code Comparable} to use {@link + * ImmutableSortedMultiset#of(Comparable, Comparable, Comparable, Comparable, Comparable, + * Comparable, Comparable...)} . + */ + @Deprecated + public static ImmutableSortedMultiset of( + E e1, E e2, E e3, E e4, E e5, E e6, E... remaining) { + throw new UnsupportedOperationException(); + } + + /** + * Not supported. You are attempting to create a multiset that may contain non-{@code + * Comparable} elements. Proper calls will resolve to the version in {@code + * ImmutableSortedMultiset}, not this dummy version. + * + * @throws UnsupportedOperationException always + * @deprecated Pass parameters of type {@code Comparable} to use {@link + * ImmutableSortedMultiset#copyOf(Comparable[])}. + */ + @Deprecated + public static ImmutableSortedMultiset copyOf(E[] elements) { + throw new UnsupportedOperationException(); + } + + /* + * We would like to include an unsupported " copyOf(Iterable)" here, providing only the + * properly typed "> copyOf(Iterable)" in ImmutableSortedMultiset (and + * likewise for the Iterator equivalent). However, due to a change in Sun's interpretation of the + * JLS (as described at http://bugs.sun.com/view_bug.do?bug_id=6182950), the OpenJDK 7 compiler + * available as of this writing rejects our attempts. To maintain compatibility with that version + * and with any other compilers that interpret the JLS similarly, there is no definition of + * copyOf() here, and the definition in ImmutableSortedMultiset matches that in + * ImmutableMultiset. + * + * The result is that ImmutableSortedMultiset.copyOf() may be called on non-Comparable elements. + * We have not discovered a better solution. In retrospect, the static factory methods should + * have gone in a separate class so that ImmutableSortedMultiset wouldn't "inherit" + * too-permissive factory methods from ImmutableMultiset. + */ +} diff --git a/src/main/java/com/google/common/collect/ImmutableSortedSet.java b/src/main/java/com/google/common/collect/ImmutableSortedSet.java new file mode 100644 index 0000000..6cdb7c7 --- /dev/null +++ b/src/main/java/com/google/common/collect/ImmutableSortedSet.java @@ -0,0 +1,840 @@ +/* + * Copyright (C) 2008 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.collect.ObjectArrays.checkElementsNotNull; + +import com.google.common.annotations.GwtCompatible; +import com.google.common.annotations.GwtIncompatible; + + +import java.io.InvalidObjectException; +import java.io.ObjectInputStream; +import java.io.Serializable; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.Iterator; +import java.util.NavigableSet; +import java.util.SortedSet; +import java.util.Spliterator; +import java.util.Spliterators; +import java.util.function.Consumer; +import java.util.stream.Collector; + + +/** + * A {@link NavigableSet} whose contents will never change, with many other important properties + * detailed at {@link ImmutableCollection}. + * + *

Warning: as with any sorted collection, you are strongly advised not to use a {@link + * Comparator} or {@link Comparable} type whose comparison behavior is inconsistent with + * equals. That is, {@code a.compareTo(b)} or {@code comparator.compare(a, b)} should equal zero + * if and only if {@code a.equals(b)}. If this advice is not followed, the resulting + * collection will not correctly obey its specification. + * + *

See the Guava User Guide article on immutable collections. + * + * @author Jared Levy + * @author Louis Wasserman + * @since 2.0 (implements {@code NavigableSet} since 12.0) + */ +// TODO(benyu): benchmark and optimize all creation paths, which are a mess now +@GwtCompatible(serializable = true, emulated = true) +@SuppressWarnings("serial") // we're overriding default serialization +public abstract class ImmutableSortedSet extends ImmutableSortedSetFauxverideShim + implements NavigableSet, SortedIterable { + static final int SPLITERATOR_CHARACTERISTICS = + ImmutableSet.SPLITERATOR_CHARACTERISTICS | Spliterator.SORTED; + + /** + * Returns a {@code Collector} that accumulates the input elements into a new {@code + * ImmutableSortedSet}, ordered by the specified comparator. + * + *

If the elements contain duplicates (according to the comparator), only the first duplicate + * in encounter order will appear in the result. + * + * @since 21.0 + */ + public static Collector> toImmutableSortedSet( + Comparator comparator) { + return CollectCollectors.toImmutableSortedSet(comparator); + } + + static RegularImmutableSortedSet emptySet(Comparator comparator) { + if (Ordering.natural().equals(comparator)) { + return (RegularImmutableSortedSet) RegularImmutableSortedSet.NATURAL_EMPTY_SET; + } else { + return new RegularImmutableSortedSet(ImmutableList.of(), comparator); + } + } + + /** Returns the empty immutable sorted set. */ + public static ImmutableSortedSet of() { + return (ImmutableSortedSet) RegularImmutableSortedSet.NATURAL_EMPTY_SET; + } + + /** Returns an immutable sorted set containing a single element. */ + public static > ImmutableSortedSet of(E element) { + return new RegularImmutableSortedSet(ImmutableList.of(element), Ordering.natural()); + } + + /** + * Returns an immutable sorted set containing the given elements sorted by their natural ordering. + * When multiple elements are equivalent according to {@link Comparable#compareTo}, only the first + * one specified is included. + * + * @throws NullPointerException if any element is null + */ + @SuppressWarnings("unchecked") + public static > ImmutableSortedSet of(E e1, E e2) { + return construct(Ordering.natural(), 2, e1, e2); + } + + /** + * Returns an immutable sorted set containing the given elements sorted by their natural ordering. + * When multiple elements are equivalent according to {@link Comparable#compareTo}, only the first + * one specified is included. + * + * @throws NullPointerException if any element is null + */ + @SuppressWarnings("unchecked") + public static > ImmutableSortedSet of(E e1, E e2, E e3) { + return construct(Ordering.natural(), 3, e1, e2, e3); + } + + /** + * Returns an immutable sorted set containing the given elements sorted by their natural ordering. + * When multiple elements are equivalent according to {@link Comparable#compareTo}, only the first + * one specified is included. + * + * @throws NullPointerException if any element is null + */ + @SuppressWarnings("unchecked") + public static > ImmutableSortedSet of(E e1, E e2, E e3, E e4) { + return construct(Ordering.natural(), 4, e1, e2, e3, e4); + } + + /** + * Returns an immutable sorted set containing the given elements sorted by their natural ordering. + * When multiple elements are equivalent according to {@link Comparable#compareTo}, only the first + * one specified is included. + * + * @throws NullPointerException if any element is null + */ + @SuppressWarnings("unchecked") + public static > ImmutableSortedSet of( + E e1, E e2, E e3, E e4, E e5) { + return construct(Ordering.natural(), 5, e1, e2, e3, e4, e5); + } + + /** + * Returns an immutable sorted set containing the given elements sorted by their natural ordering. + * When multiple elements are equivalent according to {@link Comparable#compareTo}, only the first + * one specified is included. + * + * @throws NullPointerException if any element is null + * @since 3.0 (source-compatible since 2.0) + */ + @SuppressWarnings("unchecked") + public static > ImmutableSortedSet of( + E e1, E e2, E e3, E e4, E e5, E e6, E... remaining) { + Comparable[] contents = new Comparable[6 + remaining.length]; + contents[0] = e1; + contents[1] = e2; + contents[2] = e3; + contents[3] = e4; + contents[4] = e5; + contents[5] = e6; + System.arraycopy(remaining, 0, contents, 6, remaining.length); + return construct(Ordering.natural(), contents.length, (E[]) contents); + } + + // TODO(kevinb): Consider factory methods that reject duplicates + + /** + * Returns an immutable sorted set containing the given elements sorted by their natural ordering. + * When multiple elements are equivalent according to {@link Comparable#compareTo}, only the first + * one specified is included. + * + * @throws NullPointerException if any of {@code elements} is null + * @since 3.0 + */ + public static > ImmutableSortedSet copyOf(E[] elements) { + return construct(Ordering.natural(), elements.length, elements.clone()); + } + + /** + * Returns an immutable sorted set containing the given elements sorted by their natural ordering. + * When multiple elements are equivalent according to {@code compareTo()}, only the first one + * specified is included. To create a copy of a {@code SortedSet} that preserves the comparator, + * call {@link #copyOfSorted} instead. This method iterates over {@code elements} at most once. + * + *

Note that if {@code s} is a {@code Set}, then {@code ImmutableSortedSet.copyOf(s)} + * returns an {@code ImmutableSortedSet} containing each of the strings in {@code s}, + * while {@code ImmutableSortedSet.of(s)} returns an {@code ImmutableSortedSet>} + * containing one element (the given set itself). + * + *

Despite the method name, this method attempts to avoid actually copying the data when it is + * safe to do so. The exact circumstances under which a copy will or will not be performed are + * undocumented and subject to change. + * + *

This method is not type-safe, as it may be called on elements that are not mutually + * comparable. + * + * @throws ClassCastException if the elements are not mutually comparable + * @throws NullPointerException if any of {@code elements} is null + */ + public static ImmutableSortedSet copyOf(Iterable elements) { + // Hack around E not being a subtype of Comparable. + // Unsafe, see ImmutableSortedSetFauxverideShim. + @SuppressWarnings("unchecked") + Ordering naturalOrder = (Ordering) Ordering.natural(); + return copyOf(naturalOrder, elements); + } + + /** + * Returns an immutable sorted set containing the given elements sorted by their natural ordering. + * When multiple elements are equivalent according to {@code compareTo()}, only the first one + * specified is included. To create a copy of a {@code SortedSet} that preserves the comparator, + * call {@link #copyOfSorted} instead. This method iterates over {@code elements} at most once. + * + *

Note that if {@code s} is a {@code Set}, then {@code ImmutableSortedSet.copyOf(s)} + * returns an {@code ImmutableSortedSet} containing each of the strings in {@code s}, + * while {@code ImmutableSortedSet.of(s)} returns an {@code ImmutableSortedSet>} + * containing one element (the given set itself). + * + *

Note: Despite what the method name suggests, if {@code elements} is an {@code + * ImmutableSortedSet}, it may be returned instead of a copy. + * + *

This method is not type-safe, as it may be called on elements that are not mutually + * comparable. + * + *

This method is safe to use even when {@code elements} is a synchronized or concurrent + * collection that is currently being modified by another thread. + * + * @throws ClassCastException if the elements are not mutually comparable + * @throws NullPointerException if any of {@code elements} is null + * @since 7.0 (source-compatible since 2.0) + */ + public static ImmutableSortedSet copyOf(Collection elements) { + // Hack around E not being a subtype of Comparable. + // Unsafe, see ImmutableSortedSetFauxverideShim. + @SuppressWarnings("unchecked") + Ordering naturalOrder = (Ordering) Ordering.natural(); + return copyOf(naturalOrder, elements); + } + + /** + * Returns an immutable sorted set containing the given elements sorted by their natural ordering. + * When multiple elements are equivalent according to {@code compareTo()}, only the first one + * specified is included. + * + *

This method is not type-safe, as it may be called on elements that are not mutually + * comparable. + * + * @throws ClassCastException if the elements are not mutually comparable + * @throws NullPointerException if any of {@code elements} is null + */ + public static ImmutableSortedSet copyOf(Iterator elements) { + // Hack around E not being a subtype of Comparable. + // Unsafe, see ImmutableSortedSetFauxverideShim. + @SuppressWarnings("unchecked") + Ordering naturalOrder = (Ordering) Ordering.natural(); + return copyOf(naturalOrder, elements); + } + + /** + * Returns an immutable sorted set containing the given elements sorted by the given {@code + * Comparator}. When multiple elements are equivalent according to {@code compareTo()}, only the + * first one specified is included. + * + * @throws NullPointerException if {@code comparator} or any of {@code elements} is null + */ + public static ImmutableSortedSet copyOf( + Comparator comparator, Iterator elements) { + return new Builder(comparator).addAll(elements).build(); + } + + /** + * Returns an immutable sorted set containing the given elements sorted by the given {@code + * Comparator}. When multiple elements are equivalent according to {@code compare()}, only the + * first one specified is included. This method iterates over {@code elements} at most once. + * + *

Despite the method name, this method attempts to avoid actually copying the data when it is + * safe to do so. The exact circumstances under which a copy will or will not be performed are + * undocumented and subject to change. + * + * @throws NullPointerException if {@code comparator} or any of {@code elements} is null + */ + public static ImmutableSortedSet copyOf( + Comparator comparator, Iterable elements) { + checkNotNull(comparator); + boolean hasSameComparator = SortedIterables.hasSameComparator(comparator, elements); + + if (hasSameComparator && (elements instanceof ImmutableSortedSet)) { + @SuppressWarnings("unchecked") + ImmutableSortedSet original = (ImmutableSortedSet) elements; + if (!original.isPartialView()) { + return original; + } + } + @SuppressWarnings("unchecked") // elements only contains E's; it's safe. + E[] array = (E[]) Iterables.toArray(elements); + return construct(comparator, array.length, array); + } + + /** + * Returns an immutable sorted set containing the given elements sorted by the given {@code + * Comparator}. When multiple elements are equivalent according to {@code compareTo()}, only the + * first one specified is included. + * + *

Despite the method name, this method attempts to avoid actually copying the data when it is + * safe to do so. The exact circumstances under which a copy will or will not be performed are + * undocumented and subject to change. + * + *

This method is safe to use even when {@code elements} is a synchronized or concurrent + * collection that is currently being modified by another thread. + * + * @throws NullPointerException if {@code comparator} or any of {@code elements} is null + * @since 7.0 (source-compatible since 2.0) + */ + public static ImmutableSortedSet copyOf( + Comparator comparator, Collection elements) { + return copyOf(comparator, (Iterable) elements); + } + + /** + * Returns an immutable sorted set containing the elements of a sorted set, sorted by the same + * {@code Comparator}. That behavior differs from {@link #copyOf(Iterable)}, which always uses the + * natural ordering of the elements. + * + *

Despite the method name, this method attempts to avoid actually copying the data when it is + * safe to do so. The exact circumstances under which a copy will or will not be performed are + * undocumented and subject to change. + * + *

This method is safe to use even when {@code sortedSet} is a synchronized or concurrent + * collection that is currently being modified by another thread. + * + * @throws NullPointerException if {@code sortedSet} or any of its elements is null + */ + public static ImmutableSortedSet copyOfSorted(SortedSet sortedSet) { + Comparator comparator = SortedIterables.comparator(sortedSet); + ImmutableList list = ImmutableList.copyOf(sortedSet); + if (list.isEmpty()) { + return emptySet(comparator); + } else { + return new RegularImmutableSortedSet(list, comparator); + } + } + + /** + * Constructs an {@code ImmutableSortedSet} from the first {@code n} elements of {@code contents}. + * If {@code k} is the size of the returned {@code ImmutableSortedSet}, then the sorted unique + * elements are in the first {@code k} positions of {@code contents}, and {@code contents[i] == + * null} for {@code k <= i < n}. + * + *

If {@code k == contents.length}, then {@code contents} may no longer be safe for + * modification. + * + * @throws NullPointerException if any of the first {@code n} elements of {@code contents} is null + */ + static ImmutableSortedSet construct( + Comparator comparator, int n, E... contents) { + if (n == 0) { + return emptySet(comparator); + } + checkElementsNotNull(contents, n); + Arrays.sort(contents, 0, n, comparator); + int uniques = 1; + for (int i = 1; i < n; i++) { + E cur = contents[i]; + E prev = contents[uniques - 1]; + if (comparator.compare(cur, prev) != 0) { + contents[uniques++] = cur; + } + } + Arrays.fill(contents, uniques, n, null); + return new RegularImmutableSortedSet( + ImmutableList.asImmutableList(contents, uniques), comparator); + } + + /** + * Returns a builder that creates immutable sorted sets with an explicit comparator. If the + * comparator has a more general type than the set being generated, such as creating a {@code + * SortedSet} with a {@code Comparator}, use the {@link Builder} constructor + * instead. + * + * @throws NullPointerException if {@code comparator} is null + */ + public static Builder orderedBy(Comparator comparator) { + return new Builder(comparator); + } + + /** + * Returns a builder that creates immutable sorted sets whose elements are ordered by the reverse + * of their natural ordering. + */ + public static > Builder reverseOrder() { + return new Builder(Collections.reverseOrder()); + } + + /** + * Returns a builder that creates immutable sorted sets whose elements are ordered by their + * natural ordering. The sorted sets use {@link Ordering#natural()} as the comparator. This method + * provides more type-safety than {@link #builder}, as it can be called only for classes that + * implement {@link Comparable}. + */ + public static > Builder naturalOrder() { + return new Builder(Ordering.natural()); + } + + /** + * A builder for creating immutable sorted set instances, especially {@code public static final} + * sets ("constant sets"), with a given comparator. Example: + * + *

{@code
+   * public static final ImmutableSortedSet LUCKY_NUMBERS =
+   *     new ImmutableSortedSet.Builder(ODDS_FIRST_COMPARATOR)
+   *         .addAll(SINGLE_DIGIT_PRIMES)
+   *         .add(42)
+   *         .build();
+   * }
+ * + *

Builder instances can be reused; it is safe to call {@link #build} multiple times to build + * multiple sets in series. Each set is a superset of the set created before it. + * + * @since 2.0 + */ + public static final class Builder extends ImmutableSet.Builder { + private final Comparator comparator; + private E[] elements; + private int n; + + /** + * Creates a new builder. The returned builder is equivalent to the builder generated by {@link + * ImmutableSortedSet#orderedBy}. + */ + public Builder(Comparator comparator) { + super(true); // don't construct guts of hash-based set builder + this.comparator = checkNotNull(comparator); + this.elements = (E[]) new Object[ImmutableCollection.Builder.DEFAULT_INITIAL_CAPACITY]; + this.n = 0; + } + + @Override + void copy() { + elements = Arrays.copyOf(elements, elements.length); + } + + private void sortAndDedup() { + if (n == 0) { + return; + } + Arrays.sort(elements, 0, n, comparator); + int unique = 1; + for (int i = 1; i < n; i++) { + int cmp = comparator.compare(elements[unique - 1], elements[i]); + if (cmp < 0) { + elements[unique++] = elements[i]; + } else if (cmp > 0) { + throw new AssertionError( + "Comparator " + comparator + " compare method violates its contract"); + } + } + Arrays.fill(elements, unique, n, null); + n = unique; + } + + /** + * Adds {@code element} to the {@code ImmutableSortedSet}. If the {@code ImmutableSortedSet} + * already contains {@code element}, then {@code add} has no effect. (only the previously added + * element is retained). + * + * @param element the element to add + * @return this {@code Builder} object + * @throws NullPointerException if {@code element} is null + */ + + @Override + public Builder add(E element) { + checkNotNull(element); + copyIfNecessary(); + if (n == elements.length) { + sortAndDedup(); + /* + * Sorting operations can only be allowed to occur once every O(n) operations to keep + * amortized O(n log n) performance. Therefore, ensure there are at least O(n) *unused* + * spaces in the builder array. + */ + int newLength = ImmutableCollection.Builder.expandedCapacity(n, n + 1); + if (newLength > elements.length) { + elements = Arrays.copyOf(elements, newLength); + } + } + elements[n++] = element; + return this; + } + + /** + * Adds each element of {@code elements} to the {@code ImmutableSortedSet}, ignoring duplicate + * elements (only the first duplicate element is added). + * + * @param elements the elements to add + * @return this {@code Builder} object + * @throws NullPointerException if {@code elements} contains a null element + */ + + @Override + public Builder add(E... elements) { + checkElementsNotNull(elements); + for (E e : elements) { + add(e); + } + return this; + } + + /** + * Adds each element of {@code elements} to the {@code ImmutableSortedSet}, ignoring duplicate + * elements (only the first duplicate element is added). + * + * @param elements the elements to add to the {@code ImmutableSortedSet} + * @return this {@code Builder} object + * @throws NullPointerException if {@code elements} contains a null element + */ + + @Override + public Builder addAll(Iterable elements) { + super.addAll(elements); + return this; + } + + /** + * Adds each element of {@code elements} to the {@code ImmutableSortedSet}, ignoring duplicate + * elements (only the first duplicate element is added). + * + * @param elements the elements to add to the {@code ImmutableSortedSet} + * @return this {@code Builder} object + * @throws NullPointerException if {@code elements} contains a null element + */ + + @Override + public Builder addAll(Iterator elements) { + super.addAll(elements); + return this; + } + + + @Override + Builder combine(ImmutableSet.Builder builder) { + copyIfNecessary(); + Builder other = (Builder) builder; + for (int i = 0; i < other.n; i++) { + add(other.elements[i]); + } + return this; + } + + /** + * Returns a newly-created {@code ImmutableSortedSet} based on the contents of the {@code + * Builder} and its comparator. + */ + @Override + public ImmutableSortedSet build() { + sortAndDedup(); + if (n == 0) { + return emptySet(comparator); + } else { + forceCopy = true; + return new RegularImmutableSortedSet( + ImmutableList.asImmutableList(elements, n), comparator); + } + } + } + + int unsafeCompare(Object a, Object b) { + return unsafeCompare(comparator, a, b); + } + + static int unsafeCompare(Comparator comparator, Object a, Object b) { + // Pretend the comparator can compare anything. If it turns out it can't + // compare a and b, we should get a CCE on the subsequent line. Only methods + // that are spec'd to throw CCE should call this. + @SuppressWarnings("unchecked") + Comparator unsafeComparator = (Comparator) comparator; + return unsafeComparator.compare(a, b); + } + + final transient Comparator comparator; + + ImmutableSortedSet(Comparator comparator) { + this.comparator = comparator; + } + + /** + * Returns the comparator that orders the elements, which is {@link Ordering#natural()} when the + * natural ordering of the elements is used. Note that its behavior is not consistent with {@link + * SortedSet#comparator()}, which returns {@code null} to indicate natural ordering. + */ + @Override + public Comparator comparator() { + return comparator; + } + + @Override // needed to unify the iterator() methods in Collection and SortedIterable + public abstract UnmodifiableIterator iterator(); + + /** + * {@inheritDoc} + * + *

This method returns a serializable {@code ImmutableSortedSet}. + * + *

The {@link SortedSet#headSet} documentation states that a subset of a subset throws an + * {@link IllegalArgumentException} if passed a {@code toElement} greater than an earlier {@code + * toElement}. However, this method doesn't throw an exception in that situation, but instead + * keeps the original {@code toElement}. + */ + @Override + public ImmutableSortedSet headSet(E toElement) { + return headSet(toElement, false); + } + + /** @since 12.0 */ + @GwtIncompatible // NavigableSet + @Override + public ImmutableSortedSet headSet(E toElement, boolean inclusive) { + return headSetImpl(checkNotNull(toElement), inclusive); + } + + /** + * {@inheritDoc} + * + *

This method returns a serializable {@code ImmutableSortedSet}. + * + *

The {@link SortedSet#subSet} documentation states that a subset of a subset throws an {@link + * IllegalArgumentException} if passed a {@code fromElement} smaller than an earlier {@code + * fromElement}. However, this method doesn't throw an exception in that situation, but instead + * keeps the original {@code fromElement}. Similarly, this method keeps the original {@code + * toElement}, instead of throwing an exception, if passed a {@code toElement} greater than an + * earlier {@code toElement}. + */ + @Override + public ImmutableSortedSet subSet(E fromElement, E toElement) { + return subSet(fromElement, true, toElement, false); + } + + /** @since 12.0 */ + @GwtIncompatible // NavigableSet + @Override + public ImmutableSortedSet subSet( + E fromElement, boolean fromInclusive, E toElement, boolean toInclusive) { + checkNotNull(fromElement); + checkNotNull(toElement); + checkArgument(comparator.compare(fromElement, toElement) <= 0); + return subSetImpl(fromElement, fromInclusive, toElement, toInclusive); + } + + /** + * {@inheritDoc} + * + *

This method returns a serializable {@code ImmutableSortedSet}. + * + *

The {@link SortedSet#tailSet} documentation states that a subset of a subset throws an + * {@link IllegalArgumentException} if passed a {@code fromElement} smaller than an earlier {@code + * fromElement}. However, this method doesn't throw an exception in that situation, but instead + * keeps the original {@code fromElement}. + */ + @Override + public ImmutableSortedSet tailSet(E fromElement) { + return tailSet(fromElement, true); + } + + /** @since 12.0 */ + @GwtIncompatible // NavigableSet + @Override + public ImmutableSortedSet tailSet(E fromElement, boolean inclusive) { + return tailSetImpl(checkNotNull(fromElement), inclusive); + } + + /* + * These methods perform most headSet, subSet, and tailSet logic, besides + * parameter validation. + */ + abstract ImmutableSortedSet headSetImpl(E toElement, boolean inclusive); + + abstract ImmutableSortedSet subSetImpl( + E fromElement, boolean fromInclusive, E toElement, boolean toInclusive); + + abstract ImmutableSortedSet tailSetImpl(E fromElement, boolean inclusive); + + /** @since 12.0 */ + @GwtIncompatible // NavigableSet + @Override + public E lower(E e) { + return Iterators.getNext(headSet(e, false).descendingIterator(), null); + } + + /** @since 12.0 */ + @GwtIncompatible // NavigableSet + @Override + public E floor(E e) { + return Iterators.getNext(headSet(e, true).descendingIterator(), null); + } + + /** @since 12.0 */ + @GwtIncompatible // NavigableSet + @Override + public E ceiling(E e) { + return Iterables.getFirst(tailSet(e, true), null); + } + + /** @since 12.0 */ + @GwtIncompatible // NavigableSet + @Override + public E higher(E e) { + return Iterables.getFirst(tailSet(e, false), null); + } + + @Override + public E first() { + return iterator().next(); + } + + @Override + public E last() { + return descendingIterator().next(); + } + + /** + * Guaranteed to throw an exception and leave the set unmodified. + * + * @since 12.0 + * @throws UnsupportedOperationException always + * @deprecated Unsupported operation. + */ + + @Deprecated + @GwtIncompatible // NavigableSet + @Override + public final E pollFirst() { + throw new UnsupportedOperationException(); + } + + /** + * Guaranteed to throw an exception and leave the set unmodified. + * + * @since 12.0 + * @throws UnsupportedOperationException always + * @deprecated Unsupported operation. + */ + + @Deprecated + @GwtIncompatible // NavigableSet + @Override + public final E pollLast() { + throw new UnsupportedOperationException(); + } + + @GwtIncompatible // NavigableSet + transient ImmutableSortedSet descendingSet; + + /** @since 12.0 */ + @GwtIncompatible // NavigableSet + @Override + public ImmutableSortedSet descendingSet() { + // racy single-check idiom + ImmutableSortedSet result = descendingSet; + if (result == null) { + result = descendingSet = createDescendingSet(); + result.descendingSet = this; + } + return result; + } + + // Most classes should implement this as new DescendingImmutableSortedSet(this), + // but we push down that implementation because ProGuard can't eliminate it even when it's always + // overridden. + @GwtIncompatible // NavigableSet + abstract ImmutableSortedSet createDescendingSet(); + + @Override + public Spliterator spliterator() { + return new Spliterators.AbstractSpliterator( + size(), SPLITERATOR_CHARACTERISTICS | Spliterator.SIZED) { + final UnmodifiableIterator iterator = iterator(); + + @Override + public boolean tryAdvance(Consumer action) { + if (iterator.hasNext()) { + action.accept(iterator.next()); + return true; + } else { + return false; + } + } + + @Override + public Comparator getComparator() { + return comparator; + } + }; + } + + /** @since 12.0 */ + @GwtIncompatible // NavigableSet + @Override + public abstract UnmodifiableIterator descendingIterator(); + + /** Returns the position of an element within the set, or -1 if not present. */ + abstract int indexOf(Object target); + + /* + * This class is used to serialize all ImmutableSortedSet instances, + * regardless of implementation type. It captures their "logical contents" + * only. This is necessary to ensure that the existence of a particular + * implementation type is an implementation detail. + */ + private static class SerializedForm implements Serializable { + final Comparator comparator; + final Object[] elements; + + public SerializedForm(Comparator comparator, Object[] elements) { + this.comparator = comparator; + this.elements = elements; + } + + @SuppressWarnings("unchecked") + Object readResolve() { + return new Builder(comparator).add((E[]) elements).build(); + } + + private static final long serialVersionUID = 0; + } + + private void readObject(ObjectInputStream unused) throws InvalidObjectException { + throw new InvalidObjectException("Use SerializedForm"); + } + + @Override + Object writeReplace() { + return new SerializedForm(comparator, toArray()); + } +} diff --git a/src/main/java/com/google/common/collect/ImmutableSortedSetFauxverideShim.java b/src/main/java/com/google/common/collect/ImmutableSortedSetFauxverideShim.java new file mode 100644 index 0000000..9a49fcb --- /dev/null +++ b/src/main/java/com/google/common/collect/ImmutableSortedSetFauxverideShim.java @@ -0,0 +1,196 @@ +/* + * Copyright (C) 2009 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import com.google.common.annotations.GwtIncompatible; +import java.util.stream.Collector; + +/** + * "Overrides" the {@link ImmutableSet} static methods that lack {@link ImmutableSortedSet} + * equivalents with deprecated, exception-throwing versions. This prevents accidents like the + * following: + * + *

{@code
+ * List objects = ...;
+ * // Sort them:
+ * Set sorted = ImmutableSortedSet.copyOf(objects);
+ * // BAD CODE! The returned set is actually an unsorted ImmutableSet!
+ * }
+ *
+ * 

While we could put the overrides in {@link ImmutableSortedSet} itself, it seems clearer to + * separate these "do not call" methods from those intended for normal use. + * + * @author Chris Povirk + */ +@GwtIncompatible +abstract class ImmutableSortedSetFauxverideShim extends ImmutableSet { + /** + * Not supported. Use {@link ImmutableSortedSet#toImmutableSortedSet} instead. This method exists + * only to hide {@link ImmutableSet#toImmutableSet} from consumers of {@code ImmutableSortedSet}. + * + * @throws UnsupportedOperationException always + * @deprecated Use {@link ImmutableSortedSet#toImmutableSortedSet}. + * @since 21.0 + */ + @Deprecated + public static Collector> toImmutableSet() { + throw new UnsupportedOperationException(); + } + + /** + * Not supported. Use {@link ImmutableSortedSet#naturalOrder}, which offers better type-safety, + * instead. This method exists only to hide {@link ImmutableSet#builder} from consumers of {@code + * ImmutableSortedSet}. + * + * @throws UnsupportedOperationException always + * @deprecated Use {@link ImmutableSortedSet#naturalOrder}, which offers better type-safety. + */ + @Deprecated + public static ImmutableSortedSet.Builder builder() { + throw new UnsupportedOperationException(); + } + + /** + * Not supported. This method exists only to hide {@link ImmutableSet#builderWithExpectedSize} + * from consumers of {@code ImmutableSortedSet}. + * + * @throws UnsupportedOperationException always + * @deprecated Not supported by ImmutableSortedSet. + */ + @Deprecated + public static ImmutableSortedSet.Builder builderWithExpectedSize(int expectedSize) { + throw new UnsupportedOperationException(); + } + + /** + * Not supported. You are attempting to create a set that may contain a non-{@code Comparable} + * element. Proper calls will resolve to the version in {@code ImmutableSortedSet}, not this + * dummy version. + * + * @throws UnsupportedOperationException always + * @deprecated Pass a parameter of type {@code Comparable} to use {@link + * ImmutableSortedSet#of(Comparable)}. + */ + @Deprecated + public static ImmutableSortedSet of(E element) { + throw new UnsupportedOperationException(); + } + + /** + * Not supported. You are attempting to create a set that may contain a non-{@code Comparable} + * element. Proper calls will resolve to the version in {@code ImmutableSortedSet}, not this + * dummy version. + * + * @throws UnsupportedOperationException always + * @deprecated Pass the parameters of type {@code Comparable} to use {@link + * ImmutableSortedSet#of(Comparable, Comparable)}. + */ + @Deprecated + public static ImmutableSortedSet of(E e1, E e2) { + throw new UnsupportedOperationException(); + } + + /** + * Not supported. You are attempting to create a set that may contain a non-{@code Comparable} + * element. Proper calls will resolve to the version in {@code ImmutableSortedSet}, not this + * dummy version. + * + * @throws UnsupportedOperationException always + * @deprecated Pass the parameters of type {@code Comparable} to use {@link + * ImmutableSortedSet#of(Comparable, Comparable, Comparable)}. + */ + @Deprecated + public static ImmutableSortedSet of(E e1, E e2, E e3) { + throw new UnsupportedOperationException(); + } + + /** + * Not supported. You are attempting to create a set that may contain a non-{@code Comparable} + * element. Proper calls will resolve to the version in {@code ImmutableSortedSet}, not this + * dummy version. + * + * @throws UnsupportedOperationException always + * @deprecated Pass the parameters of type {@code Comparable} to use {@link + * ImmutableSortedSet#of(Comparable, Comparable, Comparable, Comparable)}. + */ + @Deprecated + public static ImmutableSortedSet of(E e1, E e2, E e3, E e4) { + throw new UnsupportedOperationException(); + } + + /** + * Not supported. You are attempting to create a set that may contain a non-{@code Comparable} + * element. Proper calls will resolve to the version in {@code ImmutableSortedSet}, not this + * dummy version. + * + * @throws UnsupportedOperationException always + * @deprecated Pass the parameters of type {@code Comparable} to use {@link + * ImmutableSortedSet#of( Comparable, Comparable, Comparable, Comparable, Comparable)}. + */ + @Deprecated + public static ImmutableSortedSet of(E e1, E e2, E e3, E e4, E e5) { + throw new UnsupportedOperationException(); + } + + /** + * Not supported. You are attempting to create a set that may contain a non-{@code Comparable} + * element. Proper calls will resolve to the version in {@code ImmutableSortedSet}, not this + * dummy version. + * + * @throws UnsupportedOperationException always + * @deprecated Pass the parameters of type {@code Comparable} to use {@link + * ImmutableSortedSet#of(Comparable, Comparable, Comparable, Comparable, Comparable, + * Comparable, Comparable...)}. + */ + @Deprecated + public static ImmutableSortedSet of(E e1, E e2, E e3, E e4, E e5, E e6, E... remaining) { + throw new UnsupportedOperationException(); + } + + /** + * Not supported. You are attempting to create a set that may contain non-{@code Comparable} + * elements. Proper calls will resolve to the version in {@code ImmutableSortedSet}, not this + * dummy version. + * + * @throws UnsupportedOperationException always + * @deprecated Pass parameters of type {@code Comparable} to use {@link + * ImmutableSortedSet#copyOf(Comparable[])}. + */ + @Deprecated + public static ImmutableSortedSet copyOf(E[] elements) { + throw new UnsupportedOperationException(); + } + + /* + * We would like to include an unsupported " copyOf(Iterable)" here, + * providing only the properly typed + * "> copyOf(Iterable)" in ImmutableSortedSet (and + * likewise for the Iterator equivalent). However, due to a change in Sun's + * interpretation of the JLS (as described at + * http://bugs.sun.com/view_bug.do?bug_id=6182950), the OpenJDK 7 compiler + * available as of this writing rejects our attempts. To maintain + * compatibility with that version and with any other compilers that interpret + * the JLS similarly, there is no definition of copyOf() here, and the + * definition in ImmutableSortedSet matches that in ImmutableSet. + * + * The result is that ImmutableSortedSet.copyOf() may be called on + * non-Comparable elements. We have not discovered a better solution. In + * retrospect, the static factory methods should have gone in a separate class + * so that ImmutableSortedSet wouldn't "inherit" too-permissive factory + * methods from ImmutableSet. + */ +} diff --git a/src/main/java/com/google/common/collect/ImmutableTable.java b/src/main/java/com/google/common/collect/ImmutableTable.java new file mode 100644 index 0000000..3beb9f9 --- /dev/null +++ b/src/main/java/com/google/common/collect/ImmutableTable.java @@ -0,0 +1,563 @@ +/* + * Copyright (C) 2009 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.annotations.GwtCompatible; +import com.google.common.base.MoreObjects; +import com.google.common.collect.Tables.AbstractCell; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Spliterator; +import java.util.function.BinaryOperator; +import java.util.function.Function; +import java.util.stream.Collector; + + + +/** + * A {@link Table} whose contents will never change, with many other important properties detailed + * at {@link ImmutableCollection}. + * + *

See the Guava User Guide article on immutable collections. + * + * @author Gregory Kick + * @since 11.0 + */ +@GwtCompatible +public abstract class ImmutableTable extends AbstractTable + implements Serializable { + + /** + * Returns a {@code Collector} that accumulates elements into an {@code ImmutableTable}. Each + * input element is mapped to one cell in the returned table, with the rows, columns, and values + * generated by applying the specified functions. + * + *

The returned {@code Collector} will throw a {@code NullPointerException} at collection time + * if the row, column, or value functions return null on any input. + * + * @since 21.0 + */ + public static Collector> toImmutableTable( + Function rowFunction, + Function columnFunction, + Function valueFunction) { + checkNotNull(rowFunction, "rowFunction"); + checkNotNull(columnFunction, "columnFunction"); + checkNotNull(valueFunction, "valueFunction"); + return Collector.of( + () -> new ImmutableTable.Builder(), + (builder, t) -> + builder.put(rowFunction.apply(t), columnFunction.apply(t), valueFunction.apply(t)), + (b1, b2) -> b1.combine(b2), + b -> b.build()); + } + + /** + * Returns a {@code Collector} that accumulates elements into an {@code ImmutableTable}. Each + * input element is mapped to one cell in the returned table, with the rows, columns, and values + * generated by applying the specified functions. If multiple inputs are mapped to the same row + * and column pair, they will be combined with the specified merging function in encounter order. + * + *

The returned {@code Collector} will throw a {@code NullPointerException} at collection time + * if the row, column, value, or merging functions return null on any input. + * + * @since 21.0 + */ + public static Collector> toImmutableTable( + Function rowFunction, + Function columnFunction, + Function valueFunction, + BinaryOperator mergeFunction) { + + checkNotNull(rowFunction, "rowFunction"); + checkNotNull(columnFunction, "columnFunction"); + checkNotNull(valueFunction, "valueFunction"); + checkNotNull(mergeFunction, "mergeFunction"); + + /* + * No mutable Table exactly matches the insertion order behavior of ImmutableTable.Builder, but + * the Builder can't efficiently support merging of duplicate values. Getting around this + * requires some work. + */ + + return Collector.of( + () -> new CollectorState() + /* GWT isn't currently playing nicely with constructor references? */ , + (state, input) -> + state.put( + rowFunction.apply(input), + columnFunction.apply(input), + valueFunction.apply(input), + mergeFunction), + (s1, s2) -> s1.combine(s2, mergeFunction), + state -> state.toTable()); + } + + private static final class CollectorState { + final List> insertionOrder = new ArrayList<>(); + final Table> table = HashBasedTable.create(); + + void put(R row, C column, V value, BinaryOperator merger) { + MutableCell oldCell = table.get(row, column); + if (oldCell == null) { + MutableCell cell = new MutableCell<>(row, column, value); + insertionOrder.add(cell); + table.put(row, column, cell); + } else { + oldCell.merge(value, merger); + } + } + + CollectorState combine(CollectorState other, BinaryOperator merger) { + for (MutableCell cell : other.insertionOrder) { + put(cell.getRowKey(), cell.getColumnKey(), cell.getValue(), merger); + } + return this; + } + + ImmutableTable toTable() { + return copyOf(insertionOrder); + } + } + + private static final class MutableCell extends AbstractCell { + private final R row; + private final C column; + private V value; + + MutableCell(R row, C column, V value) { + this.row = checkNotNull(row, "row"); + this.column = checkNotNull(column, "column"); + this.value = checkNotNull(value, "value"); + } + + @Override + public R getRowKey() { + return row; + } + + @Override + public C getColumnKey() { + return column; + } + + @Override + public V getValue() { + return value; + } + + void merge(V value, BinaryOperator mergeFunction) { + checkNotNull(value, "value"); + this.value = checkNotNull(mergeFunction.apply(this.value, value), "mergeFunction.apply"); + } + } + + /** Returns an empty immutable table. */ + @SuppressWarnings("unchecked") + public static ImmutableTable of() { + return (ImmutableTable) SparseImmutableTable.EMPTY; + } + + /** Returns an immutable table containing a single cell. */ + public static ImmutableTable of(R rowKey, C columnKey, V value) { + return new SingletonImmutableTable<>(rowKey, columnKey, value); + } + + /** + * Returns an immutable copy of the provided table. + * + *

The {@link Table#cellSet()} iteration order of the provided table determines the iteration + * ordering of all views in the returned table. Note that some views of the original table and the + * copied table may have different iteration orders. For more control over the ordering, create a + * {@link Builder} and call {@link Builder#orderRowsBy}, {@link Builder#orderColumnsBy}, and + * {@link Builder#putAll} + * + *

Despite the method name, this method attempts to avoid actually copying the data when it is + * safe to do so. The exact circumstances under which a copy will or will not be performed are + * undocumented and subject to change. + */ + public static ImmutableTable copyOf( + Table table) { + if (table instanceof ImmutableTable) { + @SuppressWarnings("unchecked") + ImmutableTable parameterizedTable = (ImmutableTable) table; + return parameterizedTable; + } else { + return copyOf(table.cellSet()); + } + } + + private static ImmutableTable copyOf( + Iterable> cells) { + ImmutableTable.Builder builder = ImmutableTable.builder(); + for (Cell cell : cells) { + builder.put(cell); + } + return builder.build(); + } + + /** + * Returns a new builder. The generated builder is equivalent to the builder created by the {@link + * Builder#Builder() ImmutableTable.Builder()} constructor. + */ + public static Builder builder() { + return new Builder<>(); + } + + /** + * Verifies that {@code rowKey}, {@code columnKey} and {@code value} are non-null, and returns a + * new entry with those values. + */ + static Cell cellOf(R rowKey, C columnKey, V value) { + return Tables.immutableCell( + checkNotNull(rowKey, "rowKey"), + checkNotNull(columnKey, "columnKey"), + checkNotNull(value, "value")); + } + + /** + * A builder for creating immutable table instances, especially {@code public static final} tables + * ("constant tables"). Example: + * + *

{@code
+   * static final ImmutableTable SPREADSHEET =
+   *     new ImmutableTable.Builder()
+   *         .put(1, 'A', "foo")
+   *         .put(1, 'B', "bar")
+   *         .put(2, 'A', "baz")
+   *         .build();
+   * }
+ * + *

By default, the order in which cells are added to the builder determines the iteration + * ordering of all views in the returned table, with {@link #putAll} following the {@link + * Table#cellSet()} iteration order. However, if {@link #orderRowsBy} or {@link #orderColumnsBy} + * is called, the views are sorted by the supplied comparators. + * + *

For empty or single-cell immutable tables, {@link #of()} and {@link #of(Object, Object, + * Object)} are even more convenient. + * + *

Builder instances can be reused - it is safe to call {@link #build} multiple times to build + * multiple tables in series. Each table is a superset of the tables created before it. + * + * @since 11.0 + */ + public static final class Builder { + private final List> cells = Lists.newArrayList(); + private Comparator rowComparator; + private Comparator columnComparator; + + /** + * Creates a new builder. The returned builder is equivalent to the builder generated by {@link + * ImmutableTable#builder}. + */ + public Builder() {} + + /** Specifies the ordering of the generated table's rows. */ + + public Builder orderRowsBy(Comparator rowComparator) { + this.rowComparator = checkNotNull(rowComparator, "rowComparator"); + return this; + } + + /** Specifies the ordering of the generated table's columns. */ + + public Builder orderColumnsBy(Comparator columnComparator) { + this.columnComparator = checkNotNull(columnComparator, "columnComparator"); + return this; + } + + /** + * Associates the ({@code rowKey}, {@code columnKey}) pair with {@code value} in the built + * table. Duplicate key pairs are not allowed and will cause {@link #build} to fail. + */ + + public Builder put(R rowKey, C columnKey, V value) { + cells.add(cellOf(rowKey, columnKey, value)); + return this; + } + + /** + * Adds the given {@code cell} to the table, making it immutable if necessary. Duplicate key + * pairs are not allowed and will cause {@link #build} to fail. + */ + + public Builder put(Cell cell) { + if (cell instanceof Tables.ImmutableCell) { + checkNotNull(cell.getRowKey(), "row"); + checkNotNull(cell.getColumnKey(), "column"); + checkNotNull(cell.getValue(), "value"); + @SuppressWarnings("unchecked") // all supported methods are covariant + Cell immutableCell = (Cell) cell; + cells.add(immutableCell); + } else { + put(cell.getRowKey(), cell.getColumnKey(), cell.getValue()); + } + return this; + } + + /** + * Associates all of the given table's keys and values in the built table. Duplicate row key + * column key pairs are not allowed, and will cause {@link #build} to fail. + * + * @throws NullPointerException if any key or value in {@code table} is null + */ + + public Builder putAll(Table table) { + for (Cell cell : table.cellSet()) { + put(cell); + } + return this; + } + + Builder combine(Builder other) { + this.cells.addAll(other.cells); + return this; + } + + /** + * Returns a newly-created immutable table. + * + * @throws IllegalArgumentException if duplicate key pairs were added + */ + public ImmutableTable build() { + int size = cells.size(); + switch (size) { + case 0: + return of(); + case 1: + return new SingletonImmutableTable<>(Iterables.getOnlyElement(cells)); + default: + return RegularImmutableTable.forCells(cells, rowComparator, columnComparator); + } + } + } + + ImmutableTable() {} + + @Override + public ImmutableSet> cellSet() { + return (ImmutableSet>) super.cellSet(); + } + + @Override + abstract ImmutableSet> createCellSet(); + + @Override + final UnmodifiableIterator> cellIterator() { + throw new AssertionError("should never be called"); + } + + @Override + final Spliterator> cellSpliterator() { + throw new AssertionError("should never be called"); + } + + @Override + public ImmutableCollection values() { + return (ImmutableCollection) super.values(); + } + + @Override + abstract ImmutableCollection createValues(); + + @Override + final Iterator valuesIterator() { + throw new AssertionError("should never be called"); + } + + /** + * {@inheritDoc} + * + * @throws NullPointerException if {@code columnKey} is {@code null} + */ + @Override + public ImmutableMap column(C columnKey) { + checkNotNull(columnKey, "columnKey"); + return MoreObjects.firstNonNull( + (ImmutableMap) columnMap().get(columnKey), ImmutableMap.of()); + } + + @Override + public ImmutableSet columnKeySet() { + return columnMap().keySet(); + } + + /** + * {@inheritDoc} + * + *

The value {@code Map} instances in the returned map are {@link ImmutableMap} instances + * as well. + */ + @Override + public abstract ImmutableMap> columnMap(); + + /** + * {@inheritDoc} + * + * @throws NullPointerException if {@code rowKey} is {@code null} + */ + @Override + public ImmutableMap row(R rowKey) { + checkNotNull(rowKey, "rowKey"); + return MoreObjects.firstNonNull( + (ImmutableMap) rowMap().get(rowKey), ImmutableMap.of()); + } + + @Override + public ImmutableSet rowKeySet() { + return rowMap().keySet(); + } + + /** + * {@inheritDoc} + * + *

The value {@code Map} instances in the returned map are {@link ImmutableMap} instances + * as well. + */ + @Override + public abstract ImmutableMap> rowMap(); + + @Override + public boolean contains(Object rowKey, Object columnKey) { + return get(rowKey, columnKey) != null; + } + + @Override + public boolean containsValue(Object value) { + return values().contains(value); + } + + /** + * Guaranteed to throw an exception and leave the table unmodified. + * + * @throws UnsupportedOperationException always + * @deprecated Unsupported operation. + */ + @Deprecated + @Override + public final void clear() { + throw new UnsupportedOperationException(); + } + + /** + * Guaranteed to throw an exception and leave the table unmodified. + * + * @throws UnsupportedOperationException always + * @deprecated Unsupported operation. + */ + + @Deprecated + @Override + public final V put(R rowKey, C columnKey, V value) { + throw new UnsupportedOperationException(); + } + + /** + * Guaranteed to throw an exception and leave the table unmodified. + * + * @throws UnsupportedOperationException always + * @deprecated Unsupported operation. + */ + @Deprecated + @Override + public final void putAll(Table table) { + throw new UnsupportedOperationException(); + } + + /** + * Guaranteed to throw an exception and leave the table unmodified. + * + * @throws UnsupportedOperationException always + * @deprecated Unsupported operation. + */ + + @Deprecated + @Override + public final V remove(Object rowKey, Object columnKey) { + throw new UnsupportedOperationException(); + } + + /** Creates the common serialized form for this table. */ + abstract SerializedForm createSerializedForm(); + + /** + * Serialized type for all ImmutableTable instances. It captures the logical contents and + * preserves iteration order of all views. + */ + static final class SerializedForm implements Serializable { + private final Object[] rowKeys; + private final Object[] columnKeys; + + private final Object[] cellValues; + private final int[] cellRowIndices; + private final int[] cellColumnIndices; + + private SerializedForm( + Object[] rowKeys, + Object[] columnKeys, + Object[] cellValues, + int[] cellRowIndices, + int[] cellColumnIndices) { + this.rowKeys = rowKeys; + this.columnKeys = columnKeys; + this.cellValues = cellValues; + this.cellRowIndices = cellRowIndices; + this.cellColumnIndices = cellColumnIndices; + } + + static SerializedForm create( + ImmutableTable table, int[] cellRowIndices, int[] cellColumnIndices) { + return new SerializedForm( + table.rowKeySet().toArray(), + table.columnKeySet().toArray(), + table.values().toArray(), + cellRowIndices, + cellColumnIndices); + } + + Object readResolve() { + if (cellValues.length == 0) { + return of(); + } + if (cellValues.length == 1) { + return of(rowKeys[0], columnKeys[0], cellValues[0]); + } + ImmutableList.Builder> cellListBuilder = + new ImmutableList.Builder<>(cellValues.length); + for (int i = 0; i < cellValues.length; i++) { + cellListBuilder.add( + cellOf(rowKeys[cellRowIndices[i]], columnKeys[cellColumnIndices[i]], cellValues[i])); + } + return RegularImmutableTable.forOrderedComponents( + cellListBuilder.build(), ImmutableSet.copyOf(rowKeys), ImmutableSet.copyOf(columnKeys)); + } + + private static final long serialVersionUID = 0; + } + + final Object writeReplace() { + return createSerializedForm(); + } +} diff --git a/src/main/java/com/google/common/collect/IndexedImmutableSet.java b/src/main/java/com/google/common/collect/IndexedImmutableSet.java new file mode 100644 index 0000000..0168913 --- /dev/null +++ b/src/main/java/com/google/common/collect/IndexedImmutableSet.java @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2018 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.annotations.GwtCompatible; +import com.google.common.annotations.GwtIncompatible; +import java.util.Spliterator; +import java.util.function.Consumer; + +@GwtCompatible(emulated = true) +abstract class IndexedImmutableSet extends ImmutableSet { + abstract E get(int index); + + @Override + public UnmodifiableIterator iterator() { + return asList().iterator(); + } + + @Override + public Spliterator spliterator() { + return CollectSpliterators.indexed(size(), SPLITERATOR_CHARACTERISTICS, this::get); + } + + @Override + public void forEach(Consumer consumer) { + checkNotNull(consumer); + int n = size(); + for (int i = 0; i < n; i++) { + consumer.accept(get(i)); + } + } + + @Override + @GwtIncompatible + int copyIntoArray(Object[] dst, int offset) { + return asList().copyIntoArray(dst, offset); + } + + @Override + ImmutableList createAsList() { + return new ImmutableAsList() { + @Override + public E get(int index) { + return IndexedImmutableSet.this.get(index); + } + + @Override + boolean isPartialView() { + return IndexedImmutableSet.this.isPartialView(); + } + + @Override + public int size() { + return IndexedImmutableSet.this.size(); + } + + @Override + ImmutableCollection delegateCollection() { + return IndexedImmutableSet.this; + } + }; + } +} diff --git a/src/main/java/com/google/common/collect/Interner.java b/src/main/java/com/google/common/collect/Interner.java new file mode 100644 index 0000000..a72a1dd --- /dev/null +++ b/src/main/java/com/google/common/collect/Interner.java @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import com.google.common.annotations.Beta; +import com.google.common.annotations.GwtIncompatible; + + +/** + * Provides equivalent behavior to {@link String#intern} for other immutable types. Common + * implementations are available from the {@link Interners} class. + * + * @author Kevin Bourrillion + * @since 3.0 + */ +@Beta +@GwtIncompatible +public interface Interner { + /** + * Chooses and returns the representative instance for any of a collection of instances that are + * equal to each other. If two {@linkplain Object#equals equal} inputs are given to this method, + * both calls will return the same instance. That is, {@code intern(a).equals(a)} always holds, + * and {@code intern(a) == intern(b)} if and only if {@code a.equals(b)}. Note that {@code + * intern(a)} is permitted to return one instance now and a different instance later if the + * original interned instance was garbage-collected. + * + *

Warning: do not use with mutable objects. + * + * @throws NullPointerException if {@code sample} is null + */ + // TODO(cpovirk): Consider removing this? + E intern(E sample); +} diff --git a/src/main/java/com/google/common/collect/Interners.java b/src/main/java/com/google/common/collect/Interners.java new file mode 100644 index 0000000..061a1cf --- /dev/null +++ b/src/main/java/com/google/common/collect/Interners.java @@ -0,0 +1,188 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.collect; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.annotations.Beta; +import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.Equivalence; +import com.google.common.base.Function; +import com.google.common.collect.MapMaker.Dummy; +import com.google.common.collect.MapMakerInternalMap.InternalEntry; + +/** + * Contains static methods pertaining to instances of {@link Interner}. + * + * @author Kevin Bourrillion + * @since 3.0 + */ +@Beta +@GwtIncompatible +public final class Interners { + private Interners() {} + + /** + * Builder for {@link Interner} instances. + * + * @since 21.0 + */ + public static class InternerBuilder { + private final MapMaker mapMaker = new MapMaker(); + private boolean strong = true; + + private InternerBuilder() {} + + /** + * Instructs the {@link InternerBuilder} to build a strong interner. + * + * @see Interners#newStrongInterner() + */ + public InternerBuilder strong() { + this.strong = true; + return this; + } + + /** + * Instructs the {@link InternerBuilder} to build a weak interner. + * + * @see Interners#newWeakInterner() + */ + @GwtIncompatible("java.lang.ref.WeakReference") + public InternerBuilder weak() { + this.strong = false; + return this; + } + + /** + * Sets the concurrency level that will be used by the to-be-built {@link Interner}. + * + * @see MapMaker#concurrencyLevel(int) + */ + public InternerBuilder concurrencyLevel(int concurrencyLevel) { + this.mapMaker.concurrencyLevel(concurrencyLevel); + return this; + } + + public Interner build() { + if (!strong) { + mapMaker.weakKeys(); + } + return new InternerImpl(mapMaker); + } + } + + /** Returns a fresh {@link InternerBuilder} instance. */ + public static InternerBuilder newBuilder() { + return new InternerBuilder(); + } + + /** + * Returns a new thread-safe interner which retains a strong reference to each instance it has + * interned, thus preventing these instances from being garbage-collected. If this retention is + * acceptable, this implementation may perform better than {@link #newWeakInterner}. + */ + public static Interner newStrongInterner() { + return newBuilder().strong().build(); + } + + /** + * Returns a new thread-safe interner which retains a weak reference to each instance it has + * interned, and so does not prevent these instances from being garbage-collected. This most + * likely does not perform as well as {@link #newStrongInterner}, but is the best alternative when + * the memory usage of that implementation is unacceptable. + */ + @GwtIncompatible("java.lang.ref.WeakReference") + public static Interner newWeakInterner() { + return newBuilder().weak().build(); + } + + @VisibleForTesting + static final class InternerImpl implements Interner { + // MapMaker is our friend, we know about this type + @VisibleForTesting final MapMakerInternalMap map; + + private InternerImpl(MapMaker mapMaker) { + this.map = + MapMakerInternalMap.createWithDummyValues(mapMaker.keyEquivalence(Equivalence.equals())); + } + + @Override + public E intern(E sample) { + while (true) { + // trying to read the canonical... + InternalEntry entry = map.getEntry(sample); + if (entry != null) { + E canonical = entry.getKey(); + if (canonical != null) { // only matters if weak/soft keys are used + return canonical; + } + } + + // didn't see it, trying to put it instead... + Dummy sneaky = map.putIfAbsent(sample, Dummy.VALUE); + if (sneaky == null) { + return sample; + } else { + /* Someone beat us to it! Trying again... + * + * Technically this loop not guaranteed to terminate, so theoretically (extremely + * unlikely) this thread might starve, but even then, there is always going to be another + * thread doing progress here. + */ + } + } + } + } + + /** + * Returns a function that delegates to the {@link Interner#intern} method of the given interner. + * + * @since 8.0 + */ + public static Function asFunction(Interner interner) { + return new InternerFunction(checkNotNull(interner)); + } + + private static class InternerFunction implements Function { + + private final Interner interner; + + public InternerFunction(Interner interner) { + this.interner = interner; + } + + @Override + public E apply(E input) { + return interner.intern(input); + } + + @Override + public int hashCode() { + return interner.hashCode(); + } + + @Override + public boolean equals(Object other) { + if (other instanceof InternerFunction) { + InternerFunction that = (InternerFunction) other; + return interner.equals(that.interner); + } + + return false; + } + } +} diff --git a/src/main/java/com/google/common/collect/Iterables.java b/src/main/java/com/google/common/collect/Iterables.java new file mode 100644 index 0000000..8c185dd --- /dev/null +++ b/src/main/java/com/google/common/collect/Iterables.java @@ -0,0 +1,1030 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.collect.CollectPreconditions.checkRemove; + +import com.google.common.annotations.Beta; +import com.google.common.annotations.GwtCompatible; +import com.google.common.annotations.GwtIncompatible; +import com.google.common.base.Function; +import com.google.common.base.Optional; +import com.google.common.base.Predicate; +import com.google.common.base.Predicates; + +import java.util.Collection; +import java.util.Comparator; +import java.util.Iterator; +import java.util.List; +import java.util.NoSuchElementException; +import java.util.Queue; +import java.util.RandomAccess; +import java.util.Set; +import java.util.Spliterator; +import java.util.function.Consumer; +import java.util.stream.Stream; + + +/** + * An assortment of mainly legacy static utility methods that operate on or return objects of type + * {@code Iterable}. Except as noted, each method has a corresponding {@link Iterator}-based method + * in the {@link Iterators} class. + * + *

Java 8 users: several common uses for this class are now more comprehensively addressed + * by the new {@link java.util.stream.Stream} library. Read the method documentation below for + * comparisons. This class is not being deprecated, but we gently encourage you to migrate to + * streams. + * + *

Performance notes: Unless otherwise noted, all of the iterables produced in this class + * are lazy, which means that their iterators only advance the backing iteration when + * absolutely necessary. + * + *

See the Guava User Guide article on {@code + * Iterables}. + * + * @author Kevin Bourrillion + * @author Jared Levy + * @since 2.0 + */ +@GwtCompatible(emulated = true) +public final class Iterables { + private Iterables() {} + + /** Returns an unmodifiable view of {@code iterable}. */ + public static Iterable unmodifiableIterable(final Iterable iterable) { + checkNotNull(iterable); + if (iterable instanceof UnmodifiableIterable || iterable instanceof ImmutableCollection) { + @SuppressWarnings("unchecked") // Since it's unmodifiable, the covariant cast is safe + Iterable result = (Iterable) iterable; + return result; + } + return new UnmodifiableIterable<>(iterable); + } + + /** + * Simply returns its argument. + * + * @deprecated no need to use this + * @since 10.0 + */ + @Deprecated + public static Iterable unmodifiableIterable(ImmutableCollection iterable) { + return checkNotNull(iterable); + } + + private static final class UnmodifiableIterable extends FluentIterable { + private final Iterable iterable; + + private UnmodifiableIterable(Iterable iterable) { + this.iterable = iterable; + } + + @Override + public Iterator iterator() { + return Iterators.unmodifiableIterator(iterable.iterator()); + } + + @Override + public void forEach(Consumer action) { + iterable.forEach(action); + } + + @SuppressWarnings("unchecked") // safe upcast, assuming no one has a crazy Spliterator subclass + @Override + public Spliterator spliterator() { + return (Spliterator) iterable.spliterator(); + } + + @Override + public String toString() { + return iterable.toString(); + } + // no equals and hashCode; it would break the contract! + } + + /** Returns the number of elements in {@code iterable}. */ + public static int size(Iterable iterable) { + return (iterable instanceof Collection) + ? ((Collection) iterable).size() + : Iterators.size(iterable.iterator()); + } + + /** + * Returns {@code true} if {@code iterable} contains any element {@code o} for which {@code + * Objects.equals(o, element)} would return {@code true}. Otherwise returns {@code false}, even in + * cases where {@link Collection#contains} might throw {@link NullPointerException} or {@link + * ClassCastException}. + */ + public static boolean contains(Iterable iterable, Object element) { + if (iterable instanceof Collection) { + Collection collection = (Collection) iterable; + return Collections2.safeContains(collection, element); + } + return Iterators.contains(iterable.iterator(), element); + } + + /** + * Removes, from an iterable, every element that belongs to the provided collection. + * + *

This method calls {@link Collection#removeAll} if {@code iterable} is a collection, and + * {@link Iterators#removeAll} otherwise. + * + * @param removeFrom the iterable to (potentially) remove elements from + * @param elementsToRemove the elements to remove + * @return {@code true} if any element was removed from {@code iterable} + */ + + public static boolean removeAll(Iterable removeFrom, Collection elementsToRemove) { + return (removeFrom instanceof Collection) + ? ((Collection) removeFrom).removeAll(checkNotNull(elementsToRemove)) + : Iterators.removeAll(removeFrom.iterator(), elementsToRemove); + } + + /** + * Removes, from an iterable, every element that does not belong to the provided collection. + * + *

This method calls {@link Collection#retainAll} if {@code iterable} is a collection, and + * {@link Iterators#retainAll} otherwise. + * + * @param removeFrom the iterable to (potentially) remove elements from + * @param elementsToRetain the elements to retain + * @return {@code true} if any element was removed from {@code iterable} + */ + + public static boolean retainAll(Iterable removeFrom, Collection elementsToRetain) { + return (removeFrom instanceof Collection) + ? ((Collection) removeFrom).retainAll(checkNotNull(elementsToRetain)) + : Iterators.retainAll(removeFrom.iterator(), elementsToRetain); + } + + /** + * Removes, from an iterable, every element that satisfies the provided predicate. + * + *

Removals may or may not happen immediately as each element is tested against the predicate. + * The behavior of this method is not specified if {@code predicate} is dependent on {@code + * removeFrom}. + * + *

Java 8 users: if {@code removeFrom} is a {@link Collection}, use {@code + * removeFrom.removeIf(predicate)} instead. + * + * @param removeFrom the iterable to (potentially) remove elements from + * @param predicate a predicate that determines whether an element should be removed + * @return {@code true} if any elements were removed from the iterable + * @throws UnsupportedOperationException if the iterable does not support {@code remove()}. + * @since 2.0 + */ + + public static boolean removeIf(Iterable removeFrom, Predicate predicate) { + if (removeFrom instanceof Collection) { + return ((Collection) removeFrom).removeIf(predicate); + } + return Iterators.removeIf(removeFrom.iterator(), predicate); + } + + /** Removes and returns the first matching element, or returns {@code null} if there is none. */ + static T removeFirstMatching( + Iterable removeFrom, Predicate predicate) { + checkNotNull(predicate); + Iterator iterator = removeFrom.iterator(); + while (iterator.hasNext()) { + T next = iterator.next(); + if (predicate.apply(next)) { + iterator.remove(); + return next; + } + } + return null; + } + + /** + * Determines whether two iterables contain equal elements in the same order. More specifically, + * this method returns {@code true} if {@code iterable1} and {@code iterable2} contain the same + * number of elements and every element of {@code iterable1} is equal to the corresponding element + * of {@code iterable2}. + */ + public static boolean elementsEqual(Iterable iterable1, Iterable iterable2) { + if (iterable1 instanceof Collection && iterable2 instanceof Collection) { + Collection collection1 = (Collection) iterable1; + Collection collection2 = (Collection) iterable2; + if (collection1.size() != collection2.size()) { + return false; + } + } + return Iterators.elementsEqual(iterable1.iterator(), iterable2.iterator()); + } + + /** + * Returns a string representation of {@code iterable}, with the format {@code [e1, e2, ..., en]} + * (that is, identical to {@link java.util.Arrays Arrays}{@code + * .toString(Iterables.toArray(iterable))}). Note that for most implementations of {@link + * Collection}, {@code collection.toString()} also gives the same result, but that behavior is not + * generally guaranteed. + */ + public static String toString(Iterable iterable) { + return Iterators.toString(iterable.iterator()); + } + + /** + * Returns the single element contained in {@code iterable}. + * + *

Java 8 users: the {@code Stream} equivalent to this method is {@code + * stream.collect(MoreCollectors.onlyElement())}. + * + * @throws NoSuchElementException if the iterable is empty + * @throws IllegalArgumentException if the iterable contains multiple elements + */ + public static T getOnlyElement(Iterable iterable) { + return Iterators.getOnlyElement(iterable.iterator()); + } + + /** + * Returns the single element contained in {@code iterable}, or {@code defaultValue} if the + * iterable is empty. + * + *

Java 8 users: the {@code Stream} equivalent to this method is {@code + * stream.collect(MoreCollectors.toOptional()).orElse(defaultValue)}. + * + * @throws IllegalArgumentException if the iterator contains multiple elements + */ + public static T getOnlyElement( + Iterable iterable, T defaultValue) { + return Iterators.getOnlyElement(iterable.iterator(), defaultValue); + } + + /** + * Copies an iterable's elements into an array. + * + * @param iterable the iterable to copy + * @param type the type of the elements + * @return a newly-allocated array into which all the elements of the iterable have been copied + */ + @GwtIncompatible // Array.newInstance(Class, int) + public static T[] toArray(Iterable iterable, Class type) { + return toArray(iterable, ObjectArrays.newArray(type, 0)); + } + + static T[] toArray(Iterable iterable, T[] array) { + Collection collection = castOrCopyToCollection(iterable); + return collection.toArray(array); + } + + /** + * Copies an iterable's elements into an array. + * + * @param iterable the iterable to copy + * @return a newly-allocated array into which all the elements of the iterable have been copied + */ + static Object[] toArray(Iterable iterable) { + return castOrCopyToCollection(iterable).toArray(); + } + + /** + * Converts an iterable into a collection. If the iterable is already a collection, it is + * returned. Otherwise, an {@link java.util.ArrayList} is created with the contents of the + * iterable in the same iteration order. + */ + private static Collection castOrCopyToCollection(Iterable iterable) { + return (iterable instanceof Collection) + ? (Collection) iterable + : Lists.newArrayList(iterable.iterator()); + } + + /** + * Adds all elements in {@code iterable} to {@code collection}. + * + * @return {@code true} if {@code collection} was modified as a result of this operation. + */ + + public static boolean addAll(Collection addTo, Iterable elementsToAdd) { + if (elementsToAdd instanceof Collection) { + Collection c = Collections2.cast(elementsToAdd); + return addTo.addAll(c); + } + return Iterators.addAll(addTo, checkNotNull(elementsToAdd).iterator()); + } + + /** + * Returns the number of elements in the specified iterable that equal the specified object. This + * implementation avoids a full iteration when the iterable is a {@link Multiset} or {@link Set}. + * + *

Java 8 users: In most cases, the {@code Stream} equivalent of this method is {@code + * stream.filter(element::equals).count()}. If {@code element} might be null, use {@code + * stream.filter(Predicate.isEqual(element)).count()} instead. + * + * @see java.util.Collections#frequency(Collection, Object) Collections.frequency(Collection, + * Object) + */ + public static int frequency(Iterable iterable, Object element) { + if ((iterable instanceof Multiset)) { + return ((Multiset) iterable).count(element); + } else if ((iterable instanceof Set)) { + return ((Set) iterable).contains(element) ? 1 : 0; + } + return Iterators.frequency(iterable.iterator(), element); + } + + /** + * Returns an iterable whose iterators cycle indefinitely over the elements of {@code iterable}. + * + *

That iterator supports {@code remove()} if {@code iterable.iterator()} does. After {@code + * remove()} is called, subsequent cycles omit the removed element, which is no longer in {@code + * iterable}. The iterator's {@code hasNext()} method returns {@code true} until {@code iterable} + * is empty. + * + *

Warning: Typical uses of the resulting iterator may produce an infinite loop. You + * should use an explicit {@code break} or be certain that you will eventually remove all the + * elements. + * + *

To cycle over the iterable {@code n} times, use the following: {@code + * Iterables.concat(Collections.nCopies(n, iterable))} + * + *

Java 8 users: The {@code Stream} equivalent of this method is {@code + * Stream.generate(() -> iterable).flatMap(Streams::stream)}. + */ + public static Iterable cycle(final Iterable iterable) { + checkNotNull(iterable); + return new FluentIterable() { + @Override + public Iterator iterator() { + return Iterators.cycle(iterable); + } + + @Override + public Spliterator spliterator() { + return Stream.generate(() -> iterable).flatMap(Streams::stream).spliterator(); + } + + @Override + public String toString() { + return iterable.toString() + " (cycled)"; + } + }; + } + + /** + * Returns an iterable whose iterators cycle indefinitely over the provided elements. + * + *

After {@code remove} is invoked on a generated iterator, the removed element will no longer + * appear in either that iterator or any other iterator created from the same source iterable. + * That is, this method behaves exactly as {@code Iterables.cycle(Lists.newArrayList(elements))}. + * The iterator's {@code hasNext} method returns {@code true} until all of the original elements + * have been removed. + * + *

Warning: Typical uses of the resulting iterator may produce an infinite loop. You + * should use an explicit {@code break} or be certain that you will eventually remove all the + * elements. + * + *

To cycle over the elements {@code n} times, use the following: {@code + * Iterables.concat(Collections.nCopies(n, Arrays.asList(elements)))} + * + *

Java 8 users: If passing a single element {@code e}, the {@code Stream} equivalent of + * this method is {@code Stream.generate(() -> e)}. Otherwise, put the elements in a collection + * and use {@code Stream.generate(() -> collection).flatMap(Collection::stream)}. + */ + @SafeVarargs + public static Iterable cycle(T... elements) { + return cycle(Lists.newArrayList(elements)); + } + + /** + * Combines two iterables into a single iterable. The returned iterable has an iterator that + * traverses the elements in {@code a}, followed by the elements in {@code b}. The source + * iterators are not polled until necessary. + * + *

The returned iterable's iterator supports {@code remove()} when the corresponding input + * iterator supports it. + * + *

Java 8 users: The {@code Stream} equivalent of this method is {@code Stream.concat(a, + * b)}. + */ + public static Iterable concat(Iterable a, Iterable b) { + return FluentIterable.concat(a, b); + } + + /** + * Combines three iterables into a single iterable. The returned iterable has an iterator that + * traverses the elements in {@code a}, followed by the elements in {@code b}, followed by the + * elements in {@code c}. The source iterators are not polled until necessary. + * + *

The returned iterable's iterator supports {@code remove()} when the corresponding input + * iterator supports it. + * + *

Java 8 users: The {@code Stream} equivalent of this method is {@code + * Streams.concat(a, b, c)}. + */ + public static Iterable concat( + Iterable a, Iterable b, Iterable c) { + return FluentIterable.concat(a, b, c); + } + + /** + * Combines four iterables into a single iterable. The returned iterable has an iterator that + * traverses the elements in {@code a}, followed by the elements in {@code b}, followed by the + * elements in {@code c}, followed by the elements in {@code d}. The source iterators are not + * polled until necessary. + * + *

The returned iterable's iterator supports {@code remove()} when the corresponding input + * iterator supports it. + * + *

Java 8 users: The {@code Stream} equivalent of this method is {@code + * Streams.concat(a, b, c, d)}. + */ + public static Iterable concat( + Iterable a, + Iterable b, + Iterable c, + Iterable d) { + return FluentIterable.concat(a, b, c, d); + } + + /** + * Combines multiple iterables into a single iterable. The returned iterable has an iterator that + * traverses the elements of each iterable in {@code inputs}. The input iterators are not polled + * until necessary. + * + *

The returned iterable's iterator supports {@code remove()} when the corresponding input + * iterator supports it. + * + *

Java 8 users: The {@code Stream} equivalent of this method is {@code + * Streams.concat(...)}. + * + * @throws NullPointerException if any of the provided iterables is null + */ + @SafeVarargs + public static Iterable concat(Iterable... inputs) { + return FluentIterable.concat(inputs); + } + + /** + * Combines multiple iterables into a single iterable. The returned iterable has an iterator that + * traverses the elements of each iterable in {@code inputs}. The input iterators are not polled + * until necessary. + * + *

The returned iterable's iterator supports {@code remove()} when the corresponding input + * iterator supports it. The methods of the returned iterable may throw {@code + * NullPointerException} if any of the input iterators is null. + * + *

Java 8 users: The {@code Stream} equivalent of this method is {@code + * streamOfStreams.flatMap(s -> s)}. + */ + public static Iterable concat(Iterable> inputs) { + return FluentIterable.concat(inputs); + } + + /** + * Divides an iterable into unmodifiable sublists of the given size (the final iterable may be + * smaller). For example, partitioning an iterable containing {@code [a, b, c, d, e]} with a + * partition size of 3 yields {@code [[a, b, c], [d, e]]} -- an outer iterable containing two + * inner lists of three and two elements, all in the original order. + * + *

Iterators returned by the returned iterable do not support the {@link Iterator#remove()} + * method. The returned lists implement {@link RandomAccess}, whether or not the input list does. + * + *

Note: if {@code iterable} is a {@link List}, use {@link Lists#partition(List, int)} + * instead. + * + * @param iterable the iterable to return a partitioned view of + * @param size the desired size of each partition (the last may be smaller) + * @return an iterable of unmodifiable lists containing the elements of {@code iterable} divided + * into partitions + * @throws IllegalArgumentException if {@code size} is nonpositive + */ + public static Iterable> partition(final Iterable iterable, final int size) { + checkNotNull(iterable); + checkArgument(size > 0); + return new FluentIterable>() { + @Override + public Iterator> iterator() { + return Iterators.partition(iterable.iterator(), size); + } + }; + } + + /** + * Divides an iterable into unmodifiable sublists of the given size, padding the final iterable + * with null values if necessary. For example, partitioning an iterable containing {@code [a, b, + * c, d, e]} with a partition size of 3 yields {@code [[a, b, c], [d, e, null]]} -- an outer + * iterable containing two inner lists of three elements each, all in the original order. + * + *

Iterators returned by the returned iterable do not support the {@link Iterator#remove()} + * method. + * + * @param iterable the iterable to return a partitioned view of + * @param size the desired size of each partition + * @return an iterable of unmodifiable lists containing the elements of {@code iterable} divided + * into partitions (the final iterable may have trailing null elements) + * @throws IllegalArgumentException if {@code size} is nonpositive + */ + public static Iterable> paddedPartition(final Iterable iterable, final int size) { + checkNotNull(iterable); + checkArgument(size > 0); + return new FluentIterable>() { + @Override + public Iterator> iterator() { + return Iterators.paddedPartition(iterable.iterator(), size); + } + }; + } + + /** + * Returns a view of {@code unfiltered} containing all elements that satisfy the input predicate + * {@code retainIfTrue}. The returned iterable's iterator does not support {@code remove()}. + * + *

{@code Stream} equivalent: {@link Stream#filter}. + */ + public static Iterable filter( + final Iterable unfiltered, final Predicate retainIfTrue) { + checkNotNull(unfiltered); + checkNotNull(retainIfTrue); + return new FluentIterable() { + @Override + public Iterator iterator() { + return Iterators.filter(unfiltered.iterator(), retainIfTrue); + } + + @Override + public void forEach(Consumer action) { + checkNotNull(action); + unfiltered.forEach( + (T a) -> { + if (retainIfTrue.test(a)) { + action.accept(a); + } + }); + } + + @Override + public Spliterator spliterator() { + return CollectSpliterators.filter(unfiltered.spliterator(), retainIfTrue); + } + }; + } + + /** + * Returns a view of {@code unfiltered} containing all elements that are of the type {@code + * desiredType}. The returned iterable's iterator does not support {@code remove()}. + * + *

{@code Stream} equivalent: {@code stream.filter(type::isInstance).map(type::cast)}. + * This does perform a little more work than necessary, so another option is to insert an + * unchecked cast at some later point: + * + *

+   * {@code @SuppressWarnings("unchecked") // safe because of ::isInstance check
+   * ImmutableList result =
+   *     (ImmutableList) stream.filter(NewType.class::isInstance).collect(toImmutableList());}
+   * 
+ */ + @SuppressWarnings("unchecked") + @GwtIncompatible // Class.isInstance + public static Iterable filter(final Iterable unfiltered, final Class desiredType) { + checkNotNull(unfiltered); + checkNotNull(desiredType); + return (Iterable) filter(unfiltered, Predicates.instanceOf(desiredType)); + } + + /** + * Returns {@code true} if any element in {@code iterable} satisfies the predicate. + * + *

{@code Stream} equivalent: {@link Stream#anyMatch}. + */ + public static boolean any(Iterable iterable, Predicate predicate) { + return Iterators.any(iterable.iterator(), predicate); + } + + /** + * Returns {@code true} if every element in {@code iterable} satisfies the predicate. If {@code + * iterable} is empty, {@code true} is returned. + * + *

{@code Stream} equivalent: {@link Stream#allMatch}. + */ + public static boolean all(Iterable iterable, Predicate predicate) { + return Iterators.all(iterable.iterator(), predicate); + } + + /** + * Returns the first element in {@code iterable} that satisfies the given predicate; use this + * method only when such an element is known to exist. If it is possible that no element + * will match, use {@link #tryFind} or {@link #find(Iterable, Predicate, Object)} instead. + * + *

{@code Stream} equivalent: {@code stream.filter(predicate).findFirst().get()} + * + * @throws NoSuchElementException if no element in {@code iterable} matches the given predicate + */ + public static T find(Iterable iterable, Predicate predicate) { + return Iterators.find(iterable.iterator(), predicate); + } + + /** + * Returns the first element in {@code iterable} that satisfies the given predicate, or {@code + * defaultValue} if none found. Note that this can usually be handled more naturally using {@code + * tryFind(iterable, predicate).or(defaultValue)}. + * + *

{@code Stream} equivalent: {@code + * stream.filter(predicate).findFirst().orElse(defaultValue)} + * + * @since 7.0 + */ + public static T find( + Iterable iterable, Predicate predicate, T defaultValue) { + return Iterators.find(iterable.iterator(), predicate, defaultValue); + } + + /** + * Returns an {@link Optional} containing the first element in {@code iterable} that satisfies the + * given predicate, if such an element exists. + * + *

Warning: avoid using a {@code predicate} that matches {@code null}. If {@code null} + * is matched in {@code iterable}, a NullPointerException will be thrown. + * + *

{@code Stream} equivalent: {@code stream.filter(predicate).findFirst()} + * + * @since 11.0 + */ + public static Optional tryFind(Iterable iterable, Predicate predicate) { + return Iterators.tryFind(iterable.iterator(), predicate); + } + + /** + * Returns the index in {@code iterable} of the first element that satisfies the provided {@code + * predicate}, or {@code -1} if the Iterable has no such elements. + * + *

More formally, returns the lowest index {@code i} such that {@code + * predicate.apply(Iterables.get(iterable, i))} returns {@code true}, or {@code -1} if there is no + * such index. + * + * @since 2.0 + */ + public static int indexOf(Iterable iterable, Predicate predicate) { + return Iterators.indexOf(iterable.iterator(), predicate); + } + + /** + * Returns a view containing the result of applying {@code function} to each element of {@code + * fromIterable}. + * + *

The returned iterable's iterator supports {@code remove()} if {@code fromIterable}'s + * iterator does. After a successful {@code remove()} call, {@code fromIterable} no longer + * contains the corresponding element. + * + *

If the input {@code Iterable} is known to be a {@code List} or other {@code Collection}, + * consider {@link Lists#transform} and {@link Collections2#transform}. + * + *

{@code Stream} equivalent: {@link Stream#map} + */ + public static Iterable transform( + final Iterable fromIterable, final Function function) { + checkNotNull(fromIterable); + checkNotNull(function); + return new FluentIterable() { + @Override + public Iterator iterator() { + return Iterators.transform(fromIterable.iterator(), function); + } + + @Override + public void forEach(Consumer action) { + checkNotNull(action); + fromIterable.forEach((F f) -> action.accept(function.apply(f))); + } + + @Override + public Spliterator spliterator() { + return CollectSpliterators.map(fromIterable.spliterator(), function); + } + }; + } + + /** + * Returns the element at the specified position in an iterable. + * + *

{@code Stream} equivalent: {@code stream.skip(position).findFirst().get()} (throws + * {@code NoSuchElementException} if out of bounds) + * + * @param position position of the element to return + * @return the element at the specified position in {@code iterable} + * @throws IndexOutOfBoundsException if {@code position} is negative or greater than or equal to + * the size of {@code iterable} + */ + public static T get(Iterable iterable, int position) { + checkNotNull(iterable); + return (iterable instanceof List) + ? ((List) iterable).get(position) + : Iterators.get(iterable.iterator(), position); + } + + /** + * Returns the element at the specified position in an iterable or a default value otherwise. + * + *

{@code Stream} equivalent: {@code + * stream.skip(position).findFirst().orElse(defaultValue)} (returns the default value if the index + * is out of bounds) + * + * @param position position of the element to return + * @param defaultValue the default value to return if {@code position} is greater than or equal to + * the size of the iterable + * @return the element at the specified position in {@code iterable} or {@code defaultValue} if + * {@code iterable} contains fewer than {@code position + 1} elements. + * @throws IndexOutOfBoundsException if {@code position} is negative + * @since 4.0 + */ + public static T get( + Iterable iterable, int position, T defaultValue) { + checkNotNull(iterable); + Iterators.checkNonnegative(position); + if (iterable instanceof List) { + List list = Lists.cast(iterable); + return (position < list.size()) ? list.get(position) : defaultValue; + } else { + Iterator iterator = iterable.iterator(); + Iterators.advance(iterator, position); + return Iterators.getNext(iterator, defaultValue); + } + } + + /** + * Returns the first element in {@code iterable} or {@code defaultValue} if the iterable is empty. + * The {@link Iterators} analog to this method is {@link Iterators#getNext}. + * + *

If no default value is desired (and the caller instead wants a {@link + * NoSuchElementException} to be thrown), it is recommended that {@code + * iterable.iterator().next()} is used instead. + * + *

To get the only element in a single-element {@code Iterable}, consider using {@link + * #getOnlyElement(Iterable)} or {@link #getOnlyElement(Iterable, Object)} instead. + * + *

{@code Stream} equivalent: {@code stream.findFirst().orElse(defaultValue)} + * + * @param defaultValue the default value to return if the iterable is empty + * @return the first element of {@code iterable} or the default value + * @since 7.0 + */ + public static T getFirst(Iterable iterable, T defaultValue) { + return Iterators.getNext(iterable.iterator(), defaultValue); + } + + /** + * Returns the last element of {@code iterable}. If {@code iterable} is a {@link List} with {@link + * RandomAccess} support, then this operation is guaranteed to be {@code O(1)}. + * + *

{@code Stream} equivalent: {@link Streams#findLast Streams.findLast(stream).get()} + * + * @return the last element of {@code iterable} + * @throws NoSuchElementException if the iterable is empty + */ + public static T getLast(Iterable iterable) { + // TODO(kevinb): Support a concurrently modified collection? + if (iterable instanceof List) { + List list = (List) iterable; + if (list.isEmpty()) { + throw new NoSuchElementException(); + } + return getLastInNonemptyList(list); + } + + return Iterators.getLast(iterable.iterator()); + } + + /** + * Returns the last element of {@code iterable} or {@code defaultValue} if the iterable is empty. + * If {@code iterable} is a {@link List} with {@link RandomAccess} support, then this operation is + * guaranteed to be {@code O(1)}. + * + *

{@code Stream} equivalent: {@code Streams.findLast(stream).orElse(defaultValue)} + * + * @param defaultValue the value to return if {@code iterable} is empty + * @return the last element of {@code iterable} or the default value + * @since 3.0 + */ + public static T getLast(Iterable iterable, T defaultValue) { + if (iterable instanceof Collection) { + Collection c = Collections2.cast(iterable); + if (c.isEmpty()) { + return defaultValue; + } else if (iterable instanceof List) { + return getLastInNonemptyList(Lists.cast(iterable)); + } + } + + return Iterators.getLast(iterable.iterator(), defaultValue); + } + + private static T getLastInNonemptyList(List list) { + return list.get(list.size() - 1); + } + + /** + * Returns a view of {@code iterable} that skips its first {@code numberToSkip} elements. If + * {@code iterable} contains fewer than {@code numberToSkip} elements, the returned iterable skips + * all of its elements. + * + *

Modifications to the underlying {@link Iterable} before a call to {@code iterator()} are + * reflected in the returned iterator. That is, the iterator skips the first {@code numberToSkip} + * elements that exist when the {@code Iterator} is created, not when {@code skip()} is called. + * + *

The returned iterable's iterator supports {@code remove()} if the iterator of the underlying + * iterable supports it. Note that it is not possible to delete the last skipped element by + * immediately calling {@code remove()} on that iterator, as the {@code Iterator} contract states + * that a call to {@code remove()} before a call to {@code next()} will throw an {@link + * IllegalStateException}. + * + *

{@code Stream} equivalent: {@link Stream#skip} + * + * @since 3.0 + */ + public static Iterable skip(final Iterable iterable, final int numberToSkip) { + checkNotNull(iterable); + checkArgument(numberToSkip >= 0, "number to skip cannot be negative"); + + return new FluentIterable() { + @Override + public Iterator iterator() { + if (iterable instanceof List) { + final List list = (List) iterable; + int toSkip = Math.min(list.size(), numberToSkip); + return list.subList(toSkip, list.size()).iterator(); + } + final Iterator iterator = iterable.iterator(); + + Iterators.advance(iterator, numberToSkip); + + /* + * We can't just return the iterator because an immediate call to its + * remove() method would remove one of the skipped elements instead of + * throwing an IllegalStateException. + */ + return new Iterator() { + boolean atStart = true; + + @Override + public boolean hasNext() { + return iterator.hasNext(); + } + + @Override + public T next() { + T result = iterator.next(); + atStart = false; // not called if next() fails + return result; + } + + @Override + public void remove() { + checkRemove(!atStart); + iterator.remove(); + } + }; + } + + @Override + public Spliterator spliterator() { + if (iterable instanceof List) { + final List list = (List) iterable; + int toSkip = Math.min(list.size(), numberToSkip); + return list.subList(toSkip, list.size()).spliterator(); + } else { + return Streams.stream(iterable).skip(numberToSkip).spliterator(); + } + } + }; + } + + /** + * Returns a view of {@code iterable} containing its first {@code limitSize} elements. If {@code + * iterable} contains fewer than {@code limitSize} elements, the returned view contains all of its + * elements. The returned iterable's iterator supports {@code remove()} if {@code iterable}'s + * iterator does. + * + *

{@code Stream} equivalent: {@link Stream#limit} + * + * @param iterable the iterable to limit + * @param limitSize the maximum number of elements in the returned iterable + * @throws IllegalArgumentException if {@code limitSize} is negative + * @since 3.0 + */ + public static Iterable limit(final Iterable iterable, final int limitSize) { + checkNotNull(iterable); + checkArgument(limitSize >= 0, "limit is negative"); + return new FluentIterable() { + @Override + public Iterator iterator() { + return Iterators.limit(iterable.iterator(), limitSize); + } + + @Override + public Spliterator spliterator() { + return Streams.stream(iterable).limit(limitSize).spliterator(); + } + }; + } + + /** + * Returns a view of the supplied iterable that wraps each generated {@link Iterator} through + * {@link Iterators#consumingIterator(Iterator)}. + * + *

Note: If {@code iterable} is a {@link Queue}, the returned iterable will get entries from + * {@link Queue#remove()} since {@link Queue}'s iteration order is undefined. Calling {@link + * Iterator#hasNext()} on a generated iterator from the returned iterable may cause an item to be + * immediately dequeued for return on a subsequent call to {@link Iterator#next()}. + * + * @param iterable the iterable to wrap + * @return a view of the supplied iterable that wraps each generated iterator through {@link + * Iterators#consumingIterator(Iterator)}; for queues, an iterable that generates iterators + * that return and consume the queue's elements in queue order + * @see Iterators#consumingIterator(Iterator) + * @since 2.0 + */ + public static Iterable consumingIterable(final Iterable iterable) { + checkNotNull(iterable); + + return new FluentIterable() { + @Override + public Iterator iterator() { + return (iterable instanceof Queue) + ? new ConsumingQueueIterator<>((Queue) iterable) + : Iterators.consumingIterator(iterable.iterator()); + } + + @Override + public String toString() { + return "Iterables.consumingIterable(...)"; + } + }; + } + + // Methods only in Iterables, not in Iterators + + /** + * Determines if the given iterable contains no elements. + * + *

There is no precise {@link Iterator} equivalent to this method, since one can only ask an + * iterator whether it has any elements remaining (which one does using {@link + * Iterator#hasNext}). + * + *

{@code Stream} equivalent: {@code !stream.findAny().isPresent()} + * + * @return {@code true} if the iterable contains no elements + */ + public static boolean isEmpty(Iterable iterable) { + if (iterable instanceof Collection) { + return ((Collection) iterable).isEmpty(); + } + return !iterable.iterator().hasNext(); + } + + /** + * Returns an iterable over the merged contents of all given {@code iterables}. Equivalent entries + * will not be de-duplicated. + * + *

Callers must ensure that the source {@code iterables} are in non-descending order as this + * method does not sort its input. + * + *

For any equivalent elements across all {@code iterables}, it is undefined which element is + * returned first. + * + * @since 11.0 + */ + @Beta + public static Iterable mergeSorted( + final Iterable> iterables, + final Comparator comparator) { + checkNotNull(iterables, "iterables"); + checkNotNull(comparator, "comparator"); + Iterable iterable = + new FluentIterable() { + @Override + public Iterator iterator() { + return Iterators.mergeSorted( + Iterables.transform(iterables, Iterables.toIterator()), comparator); + } + }; + return new UnmodifiableIterable<>(iterable); + } + + // TODO(user): Is this the best place for this? Move to fluent functions? + // Useful as a public method? + static Function, Iterator> toIterator() { + return new Function, Iterator>() { + @Override + public Iterator apply(Iterable iterable) { + return iterable.iterator(); + } + }; + } +} diff --git a/src/main/java/com/google/common/collect/Iterators.java b/src/main/java/com/google/common/collect/Iterators.java new file mode 100644 index 0000000..f98e314 --- /dev/null +++ b/src/main/java/com/google/common/collect/Iterators.java @@ -0,0 +1,1382 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.base.Preconditions.checkState; +import static com.google.common.base.Predicates.instanceOf; +import static com.google.common.collect.CollectPreconditions.checkRemove; + +import com.google.common.annotations.Beta; +import com.google.common.annotations.GwtCompatible; +import com.google.common.annotations.GwtIncompatible; +import com.google.common.base.Function; +import com.google.common.base.Objects; +import com.google.common.base.Optional; +import com.google.common.base.Preconditions; +import com.google.common.base.Predicate; +import com.google.common.primitives.Ints; + +import java.util.ArrayDeque; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.Deque; +import java.util.Enumeration; +import java.util.Iterator; +import java.util.List; +import java.util.ListIterator; +import java.util.NoSuchElementException; +import java.util.PriorityQueue; +import java.util.Queue; + + +/** + * This class contains static utility methods that operate on or return objects of type {@link + * Iterator}. Except as noted, each method has a corresponding {@link Iterable}-based method in the + * {@link Iterables} class. + * + *

Performance notes: Unless otherwise noted, all of the iterators produced in this class + * are lazy, which means that they only advance the backing iteration when absolutely + * necessary. + * + *

See the Guava User Guide section on {@code + * Iterators}. + * + * @author Kevin Bourrillion + * @author Jared Levy + * @since 2.0 + */ +@GwtCompatible(emulated = true) +public final class Iterators { + private Iterators() {} + + /** + * Returns the empty iterator. + * + *

The {@link Iterable} equivalent of this method is {@link ImmutableSet#of()}. + */ + static UnmodifiableIterator emptyIterator() { + return emptyListIterator(); + } + + /** + * Returns the empty iterator. + * + *

The {@link Iterable} equivalent of this method is {@link ImmutableSet#of()}. + */ + // Casting to any type is safe since there are no actual elements. + @SuppressWarnings("unchecked") + static UnmodifiableListIterator emptyListIterator() { + return (UnmodifiableListIterator) ArrayItr.EMPTY; + } + + /** + * This is an enum singleton rather than an anonymous class so ProGuard can figure out it's only + * referenced by emptyModifiableIterator(). + */ + private enum EmptyModifiableIterator implements Iterator { + INSTANCE; + + @Override + public boolean hasNext() { + return false; + } + + @Override + public Object next() { + throw new NoSuchElementException(); + } + + @Override + public void remove() { + checkRemove(false); + } + } + + /** + * Returns the empty {@code Iterator} that throws {@link IllegalStateException} instead of {@link + * UnsupportedOperationException} on a call to {@link Iterator#remove()}. + */ + // Casting to any type is safe since there are no actual elements. + @SuppressWarnings("unchecked") + static Iterator emptyModifiableIterator() { + return (Iterator) EmptyModifiableIterator.INSTANCE; + } + + /** Returns an unmodifiable view of {@code iterator}. */ + public static UnmodifiableIterator unmodifiableIterator( + final Iterator iterator) { + checkNotNull(iterator); + if (iterator instanceof UnmodifiableIterator) { + @SuppressWarnings("unchecked") // Since it's unmodifiable, the covariant cast is safe + UnmodifiableIterator result = (UnmodifiableIterator) iterator; + return result; + } + return new UnmodifiableIterator() { + @Override + public boolean hasNext() { + return iterator.hasNext(); + } + + @Override + public T next() { + return iterator.next(); + } + }; + } + + /** + * Simply returns its argument. + * + * @deprecated no need to use this + * @since 10.0 + */ + @Deprecated + public static UnmodifiableIterator unmodifiableIterator(UnmodifiableIterator iterator) { + return checkNotNull(iterator); + } + + /** + * Returns the number of elements remaining in {@code iterator}. The iterator will be left + * exhausted: its {@code hasNext()} method will return {@code false}. + */ + public static int size(Iterator iterator) { + long count = 0L; + while (iterator.hasNext()) { + iterator.next(); + count++; + } + return Ints.saturatedCast(count); + } + + /** Returns {@code true} if {@code iterator} contains {@code element}. */ + public static boolean contains(Iterator iterator, Object element) { + if (element == null) { + while (iterator.hasNext()) { + if (iterator.next() == null) { + return true; + } + } + } else { + while (iterator.hasNext()) { + if (element.equals(iterator.next())) { + return true; + } + } + } + return false; + } + + /** + * Traverses an iterator and removes every element that belongs to the provided collection. The + * iterator will be left exhausted: its {@code hasNext()} method will return {@code false}. + * + * @param removeFrom the iterator to (potentially) remove elements from + * @param elementsToRemove the elements to remove + * @return {@code true} if any element was removed from {@code iterator} + */ + + public static boolean removeAll(Iterator removeFrom, Collection elementsToRemove) { + checkNotNull(elementsToRemove); + boolean result = false; + while (removeFrom.hasNext()) { + if (elementsToRemove.contains(removeFrom.next())) { + removeFrom.remove(); + result = true; + } + } + return result; + } + + /** + * Removes every element that satisfies the provided predicate from the iterator. The iterator + * will be left exhausted: its {@code hasNext()} method will return {@code false}. + * + * @param removeFrom the iterator to (potentially) remove elements from + * @param predicate a predicate that determines whether an element should be removed + * @return {@code true} if any elements were removed from the iterator + * @since 2.0 + */ + + public static boolean removeIf(Iterator removeFrom, Predicate predicate) { + checkNotNull(predicate); + boolean modified = false; + while (removeFrom.hasNext()) { + if (predicate.apply(removeFrom.next())) { + removeFrom.remove(); + modified = true; + } + } + return modified; + } + + /** + * Traverses an iterator and removes every element that does not belong to the provided + * collection. The iterator will be left exhausted: its {@code hasNext()} method will return + * {@code false}. + * + * @param removeFrom the iterator to (potentially) remove elements from + * @param elementsToRetain the elements to retain + * @return {@code true} if any element was removed from {@code iterator} + */ + + public static boolean retainAll(Iterator removeFrom, Collection elementsToRetain) { + checkNotNull(elementsToRetain); + boolean result = false; + while (removeFrom.hasNext()) { + if (!elementsToRetain.contains(removeFrom.next())) { + removeFrom.remove(); + result = true; + } + } + return result; + } + + /** + * Determines whether two iterators contain equal elements in the same order. More specifically, + * this method returns {@code true} if {@code iterator1} and {@code iterator2} contain the same + * number of elements and every element of {@code iterator1} is equal to the corresponding element + * of {@code iterator2}. + * + *

Note that this will modify the supplied iterators, since they will have been advanced some + * number of elements forward. + */ + public static boolean elementsEqual(Iterator iterator1, Iterator iterator2) { + while (iterator1.hasNext()) { + if (!iterator2.hasNext()) { + return false; + } + Object o1 = iterator1.next(); + Object o2 = iterator2.next(); + if (!Objects.equal(o1, o2)) { + return false; + } + } + return !iterator2.hasNext(); + } + + /** + * Returns a string representation of {@code iterator}, with the format {@code [e1, e2, ..., en]}. + * The iterator will be left exhausted: its {@code hasNext()} method will return {@code false}. + */ + public static String toString(Iterator iterator) { + StringBuilder sb = new StringBuilder().append('['); + boolean first = true; + while (iterator.hasNext()) { + if (!first) { + sb.append(", "); + } + first = false; + sb.append(iterator.next()); + } + return sb.append(']').toString(); + } + + /** + * Returns the single element contained in {@code iterator}. + * + * @throws NoSuchElementException if the iterator is empty + * @throws IllegalArgumentException if the iterator contains multiple elements. The state of the + * iterator is unspecified. + */ + public static T getOnlyElement(Iterator iterator) { + T first = iterator.next(); + if (!iterator.hasNext()) { + return first; + } + + StringBuilder sb = new StringBuilder().append("expected one element but was: <").append(first); + for (int i = 0; i < 4 && iterator.hasNext(); i++) { + sb.append(", ").append(iterator.next()); + } + if (iterator.hasNext()) { + sb.append(", ..."); + } + sb.append('>'); + + throw new IllegalArgumentException(sb.toString()); + } + + /** + * Returns the single element contained in {@code iterator}, or {@code defaultValue} if the + * iterator is empty. + * + * @throws IllegalArgumentException if the iterator contains multiple elements. The state of the + * iterator is unspecified. + */ + public static T getOnlyElement( + Iterator iterator, T defaultValue) { + return iterator.hasNext() ? getOnlyElement(iterator) : defaultValue; + } + + /** + * Copies an iterator's elements into an array. The iterator will be left exhausted: its {@code + * hasNext()} method will return {@code false}. + * + * @param iterator the iterator to copy + * @param type the type of the elements + * @return a newly-allocated array into which all the elements of the iterator have been copied + */ + @GwtIncompatible // Array.newInstance(Class, int) + public static T[] toArray(Iterator iterator, Class type) { + List list = Lists.newArrayList(iterator); + return Iterables.toArray(list, type); + } + + /** + * Adds all elements in {@code iterator} to {@code collection}. The iterator will be left + * exhausted: its {@code hasNext()} method will return {@code false}. + * + * @return {@code true} if {@code collection} was modified as a result of this operation + */ + + public static boolean addAll(Collection addTo, Iterator iterator) { + checkNotNull(addTo); + checkNotNull(iterator); + boolean wasModified = false; + while (iterator.hasNext()) { + wasModified |= addTo.add(iterator.next()); + } + return wasModified; + } + + /** + * Returns the number of elements in the specified iterator that equal the specified object. The + * iterator will be left exhausted: its {@code hasNext()} method will return {@code false}. + * + * @see Collections#frequency + */ + public static int frequency(Iterator iterator, Object element) { + int count = 0; + while (contains(iterator, element)) { + // Since it lives in the same class, we know contains gets to the element and then stops, + // though that isn't currently publicly documented. + count++; + } + return count; + } + + /** + * Returns an iterator that cycles indefinitely over the elements of {@code iterable}. + * + *

The returned iterator supports {@code remove()} if the provided iterator does. After {@code + * remove()} is called, subsequent cycles omit the removed element, which is no longer in {@code + * iterable}. The iterator's {@code hasNext()} method returns {@code true} until {@code iterable} + * is empty. + * + *

Warning: Typical uses of the resulting iterator may produce an infinite loop. You + * should use an explicit {@code break} or be certain that you will eventually remove all the + * elements. + */ + public static Iterator cycle(final Iterable iterable) { + checkNotNull(iterable); + return new Iterator() { + Iterator iterator = emptyModifiableIterator(); + + @Override + public boolean hasNext() { + /* + * Don't store a new Iterator until we know the user can't remove() the last returned + * element anymore. Otherwise, when we remove from the old iterator, we may be invalidating + * the new one. The result is a ConcurrentModificationException or other bad behavior. + * + * (If we decide that we really, really hate allocating two Iterators per cycle instead of + * one, we can optimistically store the new Iterator and then be willing to throw it out if + * the user calls remove().) + */ + return iterator.hasNext() || iterable.iterator().hasNext(); + } + + @Override + public T next() { + if (!iterator.hasNext()) { + iterator = iterable.iterator(); + if (!iterator.hasNext()) { + throw new NoSuchElementException(); + } + } + return iterator.next(); + } + + @Override + public void remove() { + iterator.remove(); + } + }; + } + + /** + * Returns an iterator that cycles indefinitely over the provided elements. + * + *

The returned iterator supports {@code remove()}. After {@code remove()} is called, + * subsequent cycles omit the removed element, but {@code elements} does not change. The + * iterator's {@code hasNext()} method returns {@code true} until all of the original elements + * have been removed. + * + *

Warning: Typical uses of the resulting iterator may produce an infinite loop. You + * should use an explicit {@code break} or be certain that you will eventually remove all the + * elements. + */ + @SafeVarargs + public static Iterator cycle(T... elements) { + return cycle(Lists.newArrayList(elements)); + } + + /** + * Returns an Iterator that walks the specified array, nulling out elements behind it. This can + * avoid memory leaks when an element is no longer necessary. + * + *

This is mainly just to avoid the intermediate ArrayDeque in ConsumingQueueIterator. + */ + private static Iterator consumingForArray(final T... elements) { + return new UnmodifiableIterator() { + int index = 0; + + @Override + public boolean hasNext() { + return index < elements.length; + } + + @Override + public T next() { + if (!hasNext()) { + throw new NoSuchElementException(); + } + T result = elements[index]; + elements[index] = null; + index++; + return result; + } + }; + } + + /** + * Combines two iterators into a single iterator. The returned iterator iterates across the + * elements in {@code a}, followed by the elements in {@code b}. The source iterators are not + * polled until necessary. + * + *

The returned iterator supports {@code remove()} when the corresponding input iterator + * supports it. + */ + public static Iterator concat(Iterator a, Iterator b) { + checkNotNull(a); + checkNotNull(b); + return concat(consumingForArray(a, b)); + } + + /** + * Combines three iterators into a single iterator. The returned iterator iterates across the + * elements in {@code a}, followed by the elements in {@code b}, followed by the elements in + * {@code c}. The source iterators are not polled until necessary. + * + *

The returned iterator supports {@code remove()} when the corresponding input iterator + * supports it. + */ + public static Iterator concat( + Iterator a, Iterator b, Iterator c) { + checkNotNull(a); + checkNotNull(b); + checkNotNull(c); + return concat(consumingForArray(a, b, c)); + } + + /** + * Combines four iterators into a single iterator. The returned iterator iterates across the + * elements in {@code a}, followed by the elements in {@code b}, followed by the elements in + * {@code c}, followed by the elements in {@code d}. The source iterators are not polled until + * necessary. + * + *

The returned iterator supports {@code remove()} when the corresponding input iterator + * supports it. + */ + public static Iterator concat( + Iterator a, + Iterator b, + Iterator c, + Iterator d) { + checkNotNull(a); + checkNotNull(b); + checkNotNull(c); + checkNotNull(d); + return concat(consumingForArray(a, b, c, d)); + } + + /** + * Combines multiple iterators into a single iterator. The returned iterator iterates across the + * elements of each iterator in {@code inputs}. The input iterators are not polled until + * necessary. + * + *

The returned iterator supports {@code remove()} when the corresponding input iterator + * supports it. + * + * @throws NullPointerException if any of the provided iterators is null + */ + public static Iterator concat(Iterator... inputs) { + return concatNoDefensiveCopy(Arrays.copyOf(inputs, inputs.length)); + } + + /** + * Combines multiple iterators into a single iterator. The returned iterator iterates across the + * elements of each iterator in {@code inputs}. The input iterators are not polled until + * necessary. + * + *

The returned iterator supports {@code remove()} when the corresponding input iterator + * supports it. The methods of the returned iterator may throw {@code NullPointerException} if any + * of the input iterators is null. + */ + public static Iterator concat(Iterator> inputs) { + return new ConcatenatedIterator(inputs); + } + + /** Concats a varargs array of iterators without making a defensive copy of the array. */ + static Iterator concatNoDefensiveCopy(Iterator... inputs) { + for (Iterator input : checkNotNull(inputs)) { + checkNotNull(input); + } + return concat(consumingForArray(inputs)); + } + + /** + * Divides an iterator into unmodifiable sublists of the given size (the final list may be + * smaller). For example, partitioning an iterator containing {@code [a, b, c, d, e]} with a + * partition size of 3 yields {@code [[a, b, c], [d, e]]} -- an outer iterator containing two + * inner lists of three and two elements, all in the original order. + * + *

The returned lists implement {@link java.util.RandomAccess}. + * + * @param iterator the iterator to return a partitioned view of + * @param size the desired size of each partition (the last may be smaller) + * @return an iterator of immutable lists containing the elements of {@code iterator} divided into + * partitions + * @throws IllegalArgumentException if {@code size} is nonpositive + */ + public static UnmodifiableIterator> partition(Iterator iterator, int size) { + return partitionImpl(iterator, size, false); + } + + /** + * Divides an iterator into unmodifiable sublists of the given size, padding the final iterator + * with null values if necessary. For example, partitioning an iterator containing {@code [a, b, + * c, d, e]} with a partition size of 3 yields {@code [[a, b, c], [d, e, null]]} -- an outer + * iterator containing two inner lists of three elements each, all in the original order. + * + *

The returned lists implement {@link java.util.RandomAccess}. + * + * @param iterator the iterator to return a partitioned view of + * @param size the desired size of each partition + * @return an iterator of immutable lists containing the elements of {@code iterator} divided into + * partitions (the final iterable may have trailing null elements) + * @throws IllegalArgumentException if {@code size} is nonpositive + */ + public static UnmodifiableIterator> paddedPartition(Iterator iterator, int size) { + return partitionImpl(iterator, size, true); + } + + private static UnmodifiableIterator> partitionImpl( + final Iterator iterator, final int size, final boolean pad) { + checkNotNull(iterator); + checkArgument(size > 0); + return new UnmodifiableIterator>() { + @Override + public boolean hasNext() { + return iterator.hasNext(); + } + + @Override + public List next() { + if (!hasNext()) { + throw new NoSuchElementException(); + } + Object[] array = new Object[size]; + int count = 0; + for (; count < size && iterator.hasNext(); count++) { + array[count] = iterator.next(); + } + for (int i = count; i < size; i++) { + array[i] = null; // for GWT + } + + @SuppressWarnings("unchecked") // we only put Ts in it + List list = Collections.unmodifiableList((List) Arrays.asList(array)); + return (pad || count == size) ? list : list.subList(0, count); + } + }; + } + + /** + * Returns a view of {@code unfiltered} containing all elements that satisfy the input predicate + * {@code retainIfTrue}. + */ + public static UnmodifiableIterator filter( + final Iterator unfiltered, final Predicate retainIfTrue) { + checkNotNull(unfiltered); + checkNotNull(retainIfTrue); + return new AbstractIterator() { + @Override + protected T computeNext() { + while (unfiltered.hasNext()) { + T element = unfiltered.next(); + if (retainIfTrue.apply(element)) { + return element; + } + } + return endOfData(); + } + }; + } + + /** + * Returns a view of {@code unfiltered} containing all elements that are of the type {@code + * desiredType}. + */ + @SuppressWarnings("unchecked") // can cast to because non-Ts are removed + @GwtIncompatible // Class.isInstance + public static UnmodifiableIterator filter(Iterator unfiltered, Class desiredType) { + return (UnmodifiableIterator) filter(unfiltered, instanceOf(desiredType)); + } + + /** + * Returns {@code true} if one or more elements returned by {@code iterator} satisfy the given + * predicate. + */ + public static boolean any(Iterator iterator, Predicate predicate) { + return indexOf(iterator, predicate) != -1; + } + + /** + * Returns {@code true} if every element returned by {@code iterator} satisfies the given + * predicate. If {@code iterator} is empty, {@code true} is returned. + */ + public static boolean all(Iterator iterator, Predicate predicate) { + checkNotNull(predicate); + while (iterator.hasNext()) { + T element = iterator.next(); + if (!predicate.apply(element)) { + return false; + } + } + return true; + } + + /** + * Returns the first element in {@code iterator} that satisfies the given predicate; use this + * method only when such an element is known to exist. If no such element is found, the iterator + * will be left exhausted: its {@code hasNext()} method will return {@code false}. If it is + * possible that no element will match, use {@link #tryFind} or {@link #find(Iterator, + * Predicate, Object)} instead. + * + * @throws NoSuchElementException if no element in {@code iterator} matches the given predicate + */ + public static T find(Iterator iterator, Predicate predicate) { + checkNotNull(iterator); + checkNotNull(predicate); + while (iterator.hasNext()) { + T t = iterator.next(); + if (predicate.apply(t)) { + return t; + } + } + throw new NoSuchElementException(); + } + + /** + * Returns the first element in {@code iterator} that satisfies the given predicate. If no such + * element is found, {@code defaultValue} will be returned from this method and the iterator will + * be left exhausted: its {@code hasNext()} method will return {@code false}. Note that this can + * usually be handled more naturally using {@code tryFind(iterator, predicate).or(defaultValue)}. + * + * @since 7.0 + */ + public static T find( + Iterator iterator, Predicate predicate, T defaultValue) { + checkNotNull(iterator); + checkNotNull(predicate); + while (iterator.hasNext()) { + T t = iterator.next(); + if (predicate.apply(t)) { + return t; + } + } + return defaultValue; + } + + /** + * Returns an {@link Optional} containing the first element in {@code iterator} that satisfies the + * given predicate, if such an element exists. If no such element is found, an empty {@link + * Optional} will be returned from this method and the iterator will be left exhausted: its {@code + * hasNext()} method will return {@code false}. + * + *

Warning: avoid using a {@code predicate} that matches {@code null}. If {@code null} + * is matched in {@code iterator}, a NullPointerException will be thrown. + * + * @since 11.0 + */ + public static Optional tryFind(Iterator iterator, Predicate predicate) { + checkNotNull(iterator); + checkNotNull(predicate); + while (iterator.hasNext()) { + T t = iterator.next(); + if (predicate.apply(t)) { + return Optional.of(t); + } + } + return Optional.absent(); + } + + /** + * Returns the index in {@code iterator} of the first element that satisfies the provided {@code + * predicate}, or {@code -1} if the Iterator has no such elements. + * + *

More formally, returns the lowest index {@code i} such that {@code + * predicate.apply(Iterators.get(iterator, i))} returns {@code true}, or {@code -1} if there is no + * such index. + * + *

If -1 is returned, the iterator will be left exhausted: its {@code hasNext()} method will + * return {@code false}. Otherwise, the iterator will be set to the element which satisfies the + * {@code predicate}. + * + * @since 2.0 + */ + public static int indexOf(Iterator iterator, Predicate predicate) { + checkNotNull(predicate, "predicate"); + for (int i = 0; iterator.hasNext(); i++) { + T current = iterator.next(); + if (predicate.apply(current)) { + return i; + } + } + return -1; + } + + /** + * Returns a view containing the result of applying {@code function} to each element of {@code + * fromIterator}. + * + *

The returned iterator supports {@code remove()} if {@code fromIterator} does. After a + * successful {@code remove()} call, {@code fromIterator} no longer contains the corresponding + * element. + */ + public static Iterator transform( + final Iterator fromIterator, final Function function) { + checkNotNull(function); + return new TransformedIterator(fromIterator) { + @Override + T transform(F from) { + return function.apply(from); + } + }; + } + + /** + * Advances {@code iterator} {@code position + 1} times, returning the element at the {@code + * position}th position. + * + * @param position position of the element to return + * @return the element at the specified position in {@code iterator} + * @throws IndexOutOfBoundsException if {@code position} is negative or greater than or equal to + * the number of elements remaining in {@code iterator} + */ + public static T get(Iterator iterator, int position) { + checkNonnegative(position); + int skipped = advance(iterator, position); + if (!iterator.hasNext()) { + throw new IndexOutOfBoundsException( + "position (" + + position + + ") must be less than the number of elements that remained (" + + skipped + + ")"); + } + return iterator.next(); + } + + /** + * Advances {@code iterator} {@code position + 1} times, returning the element at the {@code + * position}th position or {@code defaultValue} otherwise. + * + * @param position position of the element to return + * @param defaultValue the default value to return if the iterator is empty or if {@code position} + * is greater than the number of elements remaining in {@code iterator} + * @return the element at the specified position in {@code iterator} or {@code defaultValue} if + * {@code iterator} produces fewer than {@code position + 1} elements. + * @throws IndexOutOfBoundsException if {@code position} is negative + * @since 4.0 + */ + public static T get( + Iterator iterator, int position, T defaultValue) { + checkNonnegative(position); + advance(iterator, position); + return getNext(iterator, defaultValue); + } + + static void checkNonnegative(int position) { + if (position < 0) { + throw new IndexOutOfBoundsException("position (" + position + ") must not be negative"); + } + } + + /** + * Returns the next element in {@code iterator} or {@code defaultValue} if the iterator is empty. + * The {@link Iterables} analog to this method is {@link Iterables#getFirst}. + * + * @param defaultValue the default value to return if the iterator is empty + * @return the next element of {@code iterator} or the default value + * @since 7.0 + */ + public static T getNext(Iterator iterator, T defaultValue) { + return iterator.hasNext() ? iterator.next() : defaultValue; + } + + /** + * Advances {@code iterator} to the end, returning the last element. + * + * @return the last element of {@code iterator} + * @throws NoSuchElementException if the iterator is empty + */ + public static T getLast(Iterator iterator) { + while (true) { + T current = iterator.next(); + if (!iterator.hasNext()) { + return current; + } + } + } + + /** + * Advances {@code iterator} to the end, returning the last element or {@code defaultValue} if the + * iterator is empty. + * + * @param defaultValue the default value to return if the iterator is empty + * @return the last element of {@code iterator} + * @since 3.0 + */ + public static T getLast(Iterator iterator, T defaultValue) { + return iterator.hasNext() ? getLast(iterator) : defaultValue; + } + + /** + * Calls {@code next()} on {@code iterator}, either {@code numberToAdvance} times or until {@code + * hasNext()} returns {@code false}, whichever comes first. + * + * @return the number of elements the iterator was advanced + * @since 13.0 (since 3.0 as {@code Iterators.skip}) + */ + + public static int advance(Iterator iterator, int numberToAdvance) { + checkNotNull(iterator); + checkArgument(numberToAdvance >= 0, "numberToAdvance must be nonnegative"); + + int i; + for (i = 0; i < numberToAdvance && iterator.hasNext(); i++) { + iterator.next(); + } + return i; + } + + /** + * Returns a view containing the first {@code limitSize} elements of {@code iterator}. If {@code + * iterator} contains fewer than {@code limitSize} elements, the returned view contains all of its + * elements. The returned iterator supports {@code remove()} if {@code iterator} does. + * + * @param iterator the iterator to limit + * @param limitSize the maximum number of elements in the returned iterator + * @throws IllegalArgumentException if {@code limitSize} is negative + * @since 3.0 + */ + public static Iterator limit(final Iterator iterator, final int limitSize) { + checkNotNull(iterator); + checkArgument(limitSize >= 0, "limit is negative"); + return new Iterator() { + private int count; + + @Override + public boolean hasNext() { + return count < limitSize && iterator.hasNext(); + } + + @Override + public T next() { + if (!hasNext()) { + throw new NoSuchElementException(); + } + count++; + return iterator.next(); + } + + @Override + public void remove() { + iterator.remove(); + } + }; + } + + /** + * Returns a view of the supplied {@code iterator} that removes each element from the supplied + * {@code iterator} as it is returned. + * + *

The provided iterator must support {@link Iterator#remove()} or else the returned iterator + * will fail on the first call to {@code next}. + * + * @param iterator the iterator to remove and return elements from + * @return an iterator that removes and returns elements from the supplied iterator + * @since 2.0 + */ + public static Iterator consumingIterator(final Iterator iterator) { + checkNotNull(iterator); + return new UnmodifiableIterator() { + @Override + public boolean hasNext() { + return iterator.hasNext(); + } + + @Override + public T next() { + T next = iterator.next(); + iterator.remove(); + return next; + } + + @Override + public String toString() { + return "Iterators.consumingIterator(...)"; + } + }; + } + + /** + * Deletes and returns the next value from the iterator, or returns {@code null} if there is no + * such value. + */ + static T pollNext(Iterator iterator) { + if (iterator.hasNext()) { + T result = iterator.next(); + iterator.remove(); + return result; + } else { + return null; + } + } + + // Methods only in Iterators, not in Iterables + + /** Clears the iterator using its remove method. */ + static void clear(Iterator iterator) { + checkNotNull(iterator); + while (iterator.hasNext()) { + iterator.next(); + iterator.remove(); + } + } + + /** + * Returns an iterator containing the elements of {@code array} in order. The returned iterator is + * a view of the array; subsequent changes to the array will be reflected in the iterator. + * + *

Note: It is often preferable to represent your data using a collection type, for + * example using {@link Arrays#asList(Object[])}, making this method unnecessary. + * + *

The {@code Iterable} equivalent of this method is either {@link Arrays#asList(Object[])}, + * {@link ImmutableList#copyOf(Object[])}}, or {@link ImmutableList#of}. + */ + @SafeVarargs + public static UnmodifiableIterator forArray(final T... array) { + return forArray(array, 0, array.length, 0); + } + + /** + * Returns a list iterator containing the elements in the specified range of {@code array} in + * order, starting at the specified index. + * + *

The {@code Iterable} equivalent of this method is {@code + * Arrays.asList(array).subList(offset, offset + length).listIterator(index)}. + */ + static UnmodifiableListIterator forArray( + final T[] array, final int offset, int length, int index) { + checkArgument(length >= 0); + int end = offset + length; + + // Technically we should give a slightly more descriptive error on overflow + Preconditions.checkPositionIndexes(offset, end, array.length); + Preconditions.checkPositionIndex(index, length); + if (length == 0) { + return emptyListIterator(); + } + return new ArrayItr(array, offset, length, index); + } + + private static final class ArrayItr extends AbstractIndexedListIterator { + static final UnmodifiableListIterator EMPTY = new ArrayItr<>(new Object[0], 0, 0, 0); + + private final T[] array; + private final int offset; + + ArrayItr(T[] array, int offset, int length, int index) { + super(length, index); + this.array = array; + this.offset = offset; + } + + @Override + protected T get(int index) { + return array[offset + index]; + } + } + + /** + * Returns an iterator containing only {@code value}. + * + *

The {@link Iterable} equivalent of this method is {@link Collections#singleton}. + */ + public static UnmodifiableIterator singletonIterator(final T value) { + return new UnmodifiableIterator() { + boolean done; + + @Override + public boolean hasNext() { + return !done; + } + + @Override + public T next() { + if (done) { + throw new NoSuchElementException(); + } + done = true; + return value; + } + }; + } + + /** + * Adapts an {@code Enumeration} to the {@code Iterator} interface. + * + *

This method has no equivalent in {@link Iterables} because viewing an {@code Enumeration} as + * an {@code Iterable} is impossible. However, the contents can be copied into a collection + * using {@link Collections#list}. + * + *

Java 9 users: use {@code enumeration.asIterator()} instead, unless it is important to + * return an {@code UnmodifiableIterator} instead of a plain {@code Iterator}. + */ + public static UnmodifiableIterator forEnumeration(final Enumeration enumeration) { + checkNotNull(enumeration); + return new UnmodifiableIterator() { + @Override + public boolean hasNext() { + return enumeration.hasMoreElements(); + } + + @Override + public T next() { + return enumeration.nextElement(); + } + }; + } + + /** + * Adapts an {@code Iterator} to the {@code Enumeration} interface. + * + *

The {@code Iterable} equivalent of this method is either {@link Collections#enumeration} (if + * you have a {@link Collection}), or {@code Iterators.asEnumeration(collection.iterator())}. + */ + public static Enumeration asEnumeration(final Iterator iterator) { + checkNotNull(iterator); + return new Enumeration() { + @Override + public boolean hasMoreElements() { + return iterator.hasNext(); + } + + @Override + public T nextElement() { + return iterator.next(); + } + }; + } + + /** Implementation of PeekingIterator that avoids peeking unless necessary. */ + private static class PeekingImpl implements PeekingIterator { + + private final Iterator iterator; + private boolean hasPeeked; + private E peekedElement; + + public PeekingImpl(Iterator iterator) { + this.iterator = checkNotNull(iterator); + } + + @Override + public boolean hasNext() { + return hasPeeked || iterator.hasNext(); + } + + @Override + public E next() { + if (!hasPeeked) { + return iterator.next(); + } + E result = peekedElement; + hasPeeked = false; + peekedElement = null; + return result; + } + + @Override + public void remove() { + checkState(!hasPeeked, "Can't remove after you've peeked at next"); + iterator.remove(); + } + + @Override + public E peek() { + if (!hasPeeked) { + peekedElement = iterator.next(); + hasPeeked = true; + } + return peekedElement; + } + } + + /** + * Returns a {@code PeekingIterator} backed by the given iterator. + * + *

Calls to the {@code peek} method with no intervening calls to {@code next} do not affect the + * iteration, and hence return the same object each time. A subsequent call to {@code next} is + * guaranteed to return the same object again. For example: + * + *

{@code
+   * PeekingIterator peekingIterator =
+   *     Iterators.peekingIterator(Iterators.forArray("a", "b"));
+   * String a1 = peekingIterator.peek(); // returns "a"
+   * String a2 = peekingIterator.peek(); // also returns "a"
+   * String a3 = peekingIterator.next(); // also returns "a"
+   * }
+ * + *

Any structural changes to the underlying iteration (aside from those performed by the + * iterator's own {@link PeekingIterator#remove()} method) will leave the iterator in an undefined + * state. + * + *

The returned iterator does not support removal after peeking, as explained by {@link + * PeekingIterator#remove()}. + * + *

Note: If the given iterator is already a {@code PeekingIterator}, it might be + * returned to the caller, although this is neither guaranteed to occur nor required to be + * consistent. For example, this method might choose to pass through recognized + * implementations of {@code PeekingIterator} when the behavior of the implementation is known to + * meet the contract guaranteed by this method. + * + *

There is no {@link Iterable} equivalent to this method, so use this method to wrap each + * individual iterator as it is generated. + * + * @param iterator the backing iterator. The {@link PeekingIterator} assumes ownership of this + * iterator, so users should cease making direct calls to it after calling this method. + * @return a peeking iterator backed by that iterator. Apart from the additional {@link + * PeekingIterator#peek()} method, this iterator behaves exactly the same as {@code iterator}. + */ + public static PeekingIterator peekingIterator(Iterator iterator) { + if (iterator instanceof PeekingImpl) { + // Safe to cast to because PeekingImpl only uses T + // covariantly (and cannot be subclassed to add non-covariant uses). + @SuppressWarnings("unchecked") + PeekingImpl peeking = (PeekingImpl) iterator; + return peeking; + } + return new PeekingImpl(iterator); + } + + /** + * Simply returns its argument. + * + * @deprecated no need to use this + * @since 10.0 + */ + @Deprecated + public static PeekingIterator peekingIterator(PeekingIterator iterator) { + return checkNotNull(iterator); + } + + /** + * Returns an iterator over the merged contents of all given {@code iterators}, traversing every + * element of the input iterators. Equivalent entries will not be de-duplicated. + * + *

Callers must ensure that the source {@code iterators} are in non-descending order as this + * method does not sort its input. + * + *

For any equivalent elements across all {@code iterators}, it is undefined which element is + * returned first. + * + * @since 11.0 + */ + @Beta + public static UnmodifiableIterator mergeSorted( + Iterable> iterators, Comparator comparator) { + checkNotNull(iterators, "iterators"); + checkNotNull(comparator, "comparator"); + + return new MergingIterator(iterators, comparator); + } + + /** + * An iterator that performs a lazy N-way merge, calculating the next value each time the iterator + * is polled. This amortizes the sorting cost over the iteration and requires less memory than + * sorting all elements at once. + * + *

Retrieving a single element takes approximately O(log(M)) time, where M is the number of + * iterators. (Retrieving all elements takes approximately O(N*log(M)) time, where N is the total + * number of elements.) + */ + private static class MergingIterator extends UnmodifiableIterator { + final Queue> queue; + + public MergingIterator( + Iterable> iterators, + final Comparator itemComparator) { + // A comparator that's used by the heap, allowing the heap + // to be sorted based on the top of each iterator. + Comparator> heapComparator = + new Comparator>() { + @Override + public int compare(PeekingIterator o1, PeekingIterator o2) { + return itemComparator.compare(o1.peek(), o2.peek()); + } + }; + + queue = new PriorityQueue<>(2, heapComparator); + + for (Iterator iterator : iterators) { + if (iterator.hasNext()) { + queue.add(Iterators.peekingIterator(iterator)); + } + } + } + + @Override + public boolean hasNext() { + return !queue.isEmpty(); + } + + @Override + public T next() { + PeekingIterator nextIter = queue.remove(); + T next = nextIter.next(); + if (nextIter.hasNext()) { + queue.add(nextIter); + } + return next; + } + } + + private static class ConcatenatedIterator implements Iterator { + /* The last iterator to return an element. Calls to remove() go to this iterator. */ + private Iterator toRemove; + + /* The iterator currently returning elements. */ + private Iterator iterator; + + /* + * We track the "meta iterators," the iterators-of-iterators, below. Usually, topMetaIterator + * is the only one in use, but if we encounter nested concatenations, we start a deque of + * meta-iterators rather than letting the nesting get arbitrarily deep. This keeps each + * operation O(1). + */ + + private Iterator> topMetaIterator; + + // Only becomes nonnull if we encounter nested concatenations. + private Deque>> metaIterators; + + ConcatenatedIterator(Iterator> metaIterator) { + iterator = emptyIterator(); + topMetaIterator = checkNotNull(metaIterator); + } + + // Returns a nonempty meta-iterator or, if all meta-iterators are empty, null. + private Iterator> getTopMetaIterator() { + while (topMetaIterator == null || !topMetaIterator.hasNext()) { + if (metaIterators != null && !metaIterators.isEmpty()) { + topMetaIterator = metaIterators.removeFirst(); + } else { + return null; + } + } + return topMetaIterator; + } + + @Override + public boolean hasNext() { + while (!checkNotNull(iterator).hasNext()) { + // this weird checkNotNull positioning appears required by our tests, which expect + // both hasNext and next to throw NPE if an input iterator is null. + + topMetaIterator = getTopMetaIterator(); + if (topMetaIterator == null) { + return false; + } + + iterator = topMetaIterator.next(); + + if (iterator instanceof ConcatenatedIterator) { + // Instead of taking linear time in the number of nested concatenations, unpack + // them into the queue + @SuppressWarnings("unchecked") + ConcatenatedIterator topConcat = (ConcatenatedIterator) iterator; + iterator = topConcat.iterator; + + // topConcat.topMetaIterator, then topConcat.metaIterators, then this.topMetaIterator, + // then this.metaIterators + + if (this.metaIterators == null) { + this.metaIterators = new ArrayDeque<>(); + } + this.metaIterators.addFirst(this.topMetaIterator); + if (topConcat.metaIterators != null) { + while (!topConcat.metaIterators.isEmpty()) { + this.metaIterators.addFirst(topConcat.metaIterators.removeLast()); + } + } + this.topMetaIterator = topConcat.topMetaIterator; + } + } + return true; + } + + @Override + public T next() { + if (hasNext()) { + toRemove = iterator; + return iterator.next(); + } else { + throw new NoSuchElementException(); + } + } + + @Override + public void remove() { + CollectPreconditions.checkRemove(toRemove != null); + toRemove.remove(); + toRemove = null; + } + } + + /** Used to avoid http://bugs.sun.com/view_bug.do?bug_id=6558557 */ + static ListIterator cast(Iterator iterator) { + return (ListIterator) iterator; + } +} diff --git a/src/main/java/com/google/common/collect/JdkBackedImmutableBiMap.java b/src/main/java/com/google/common/collect/JdkBackedImmutableBiMap.java new file mode 100644 index 0000000..59bb437 --- /dev/null +++ b/src/main/java/com/google/common/collect/JdkBackedImmutableBiMap.java @@ -0,0 +1,121 @@ +/* + * Copyright (C) 2018 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.common.collect; + +import com.google.common.annotations.GwtCompatible; +import com.google.common.annotations.VisibleForTesting; + + + +import java.util.Map; + + +/** + * Implementation of ImmutableBiMap backed by a pair of JDK HashMaps, which have smartness + * protecting against hash flooding. + */ +@GwtCompatible(emulated = true) +final class JdkBackedImmutableBiMap extends ImmutableBiMap { + @VisibleForTesting + static ImmutableBiMap create(int n, Entry[] entryArray) { + Map forwardDelegate = Maps.newHashMapWithExpectedSize(n); + Map backwardDelegate = Maps.newHashMapWithExpectedSize(n); + for (int i = 0; i < n; i++) { + Entry e = RegularImmutableMap.makeImmutable(entryArray[i]); + entryArray[i] = e; + V oldValue = forwardDelegate.putIfAbsent(e.getKey(), e.getValue()); + if (oldValue != null) { + throw conflictException("key", e.getKey() + "=" + oldValue, entryArray[i]); + } + K oldKey = backwardDelegate.putIfAbsent(e.getValue(), e.getKey()); + if (oldKey != null) { + throw conflictException("value", oldKey + "=" + e.getValue(), entryArray[i]); + } + } + ImmutableList> entryList = ImmutableList.asImmutableList(entryArray, n); + return new JdkBackedImmutableBiMap<>(entryList, forwardDelegate, backwardDelegate); + } + + private final transient ImmutableList> entries; + private final Map forwardDelegate; + private final Map backwardDelegate; + + private JdkBackedImmutableBiMap( + ImmutableList> entries, Map forwardDelegate, Map backwardDelegate) { + this.entries = entries; + this.forwardDelegate = forwardDelegate; + this.backwardDelegate = backwardDelegate; + } + + @Override + public int size() { + return entries.size(); + } + + private transient JdkBackedImmutableBiMap inverse; + + @Override + public ImmutableBiMap inverse() { + JdkBackedImmutableBiMap result = inverse; + if (result == null) { + inverse = + result = + new JdkBackedImmutableBiMap( + new InverseEntries(), backwardDelegate, forwardDelegate); + result.inverse = this; + } + return result; + } + + + private final class InverseEntries extends ImmutableList> { + @Override + public Entry get(int index) { + Entry entry = entries.get(index); + return Maps.immutableEntry(entry.getValue(), entry.getKey()); + } + + @Override + boolean isPartialView() { + return false; + } + + @Override + public int size() { + return entries.size(); + } + } + + @Override + public V get(Object key) { + return forwardDelegate.get(key); + } + + @Override + ImmutableSet> createEntrySet() { + return new ImmutableMapEntrySet.RegularEntrySet(this, entries); + } + + @Override + ImmutableSet createKeySet() { + return new ImmutableMapKeySet(this); + } + + @Override + boolean isPartialView() { + return false; + } +} diff --git a/src/main/java/com/google/common/collect/JdkBackedImmutableMap.java b/src/main/java/com/google/common/collect/JdkBackedImmutableMap.java new file mode 100644 index 0000000..a361687 --- /dev/null +++ b/src/main/java/com/google/common/collect/JdkBackedImmutableMap.java @@ -0,0 +1,93 @@ +/* + * Copyright (C) 2018 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.collect.RegularImmutableMap.makeImmutable; + +import com.google.common.annotations.GwtCompatible; +import java.util.Map; +import java.util.function.BiConsumer; + + +/** + * Implementation of ImmutableMap backed by a JDK HashMap, which has smartness protecting against + * hash flooding. + */ +@GwtCompatible(emulated = true) +final class JdkBackedImmutableMap extends ImmutableMap { + /** + * Creates an {@code ImmutableMap} backed by a JDK HashMap. Used when probable hash flooding is + * detected. This implementation may replace the entries in entryArray with its own entry objects + * (though they will have the same key/value contents), and will take ownership of entryArray. + */ + static ImmutableMap create(int n, Entry[] entryArray) { + Map delegateMap = Maps.newHashMapWithExpectedSize(n); + for (int i = 0; i < n; i++) { + entryArray[i] = makeImmutable(entryArray[i]); + V oldValue = delegateMap.putIfAbsent(entryArray[i].getKey(), entryArray[i].getValue()); + if (oldValue != null) { + throw conflictException("key", entryArray[i], entryArray[i].getKey() + "=" + oldValue); + } + } + return new JdkBackedImmutableMap<>(delegateMap, ImmutableList.asImmutableList(entryArray, n)); + } + + private final transient Map delegateMap; + private final transient ImmutableList> entries; + + JdkBackedImmutableMap(Map delegateMap, ImmutableList> entries) { + this.delegateMap = delegateMap; + this.entries = entries; + } + + @Override + public int size() { + return entries.size(); + } + + @Override + public V get(Object key) { + return delegateMap.get(key); + } + + @Override + ImmutableSet> createEntrySet() { + return new ImmutableMapEntrySet.RegularEntrySet(this, entries); + } + + @Override + public void forEach(BiConsumer action) { + checkNotNull(action); + entries.forEach(e -> action.accept(e.getKey(), e.getValue())); + } + + @Override + ImmutableSet createKeySet() { + return new ImmutableMapKeySet(this); + } + + @Override + ImmutableCollection createValues() { + return new ImmutableMapValues(this); + } + + @Override + boolean isPartialView() { + return false; + } +} diff --git a/src/main/java/com/google/common/collect/JdkBackedImmutableMultiset.java b/src/main/java/com/google/common/collect/JdkBackedImmutableMultiset.java new file mode 100644 index 0000000..3a36625 --- /dev/null +++ b/src/main/java/com/google/common/collect/JdkBackedImmutableMultiset.java @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2018 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.collect; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.annotations.GwtCompatible; +import com.google.common.primitives.Ints; +import java.util.Collection; +import java.util.Map; + + +/** + * An implementation of ImmutableMultiset backed by a JDK Map and a list of entries. Used to protect + * against hash flooding attacks. + * + * @author Louis Wasserman + */ +@GwtCompatible +final class JdkBackedImmutableMultiset extends ImmutableMultiset { + private final Map delegateMap; + private final ImmutableList> entries; + private final long size; + + static ImmutableMultiset create(Collection> entries) { + @SuppressWarnings("unchecked") + Entry[] entriesArray = entries.toArray(new Entry[0]); + Map delegateMap = Maps.newHashMapWithExpectedSize(entriesArray.length); + long size = 0; + for (int i = 0; i < entriesArray.length; i++) { + Entry entry = entriesArray[i]; + int count = entry.getCount(); + size += count; + E element = checkNotNull(entry.getElement()); + delegateMap.put(element, count); + if (!(entry instanceof Multisets.ImmutableEntry)) { + entriesArray[i] = Multisets.immutableEntry(element, count); + } + } + return new JdkBackedImmutableMultiset<>( + delegateMap, ImmutableList.asImmutableList(entriesArray), size); + } + + private JdkBackedImmutableMultiset( + Map delegateMap, ImmutableList> entries, long size) { + this.delegateMap = delegateMap; + this.entries = entries; + this.size = size; + } + + @Override + public int count(Object element) { + return delegateMap.getOrDefault(element, 0); + } + + private transient ImmutableSet elementSet; + + @Override + public ImmutableSet elementSet() { + ImmutableSet result = elementSet; + return (result == null) ? elementSet = new ElementSet(entries, this) : result; + } + + @Override + Entry getEntry(int index) { + return entries.get(index); + } + + @Override + boolean isPartialView() { + return false; + } + + @Override + public int size() { + return Ints.saturatedCast(size); + } +} diff --git a/src/main/java/com/google/common/collect/JdkBackedImmutableSet.java b/src/main/java/com/google/common/collect/JdkBackedImmutableSet.java new file mode 100644 index 0000000..de31754 --- /dev/null +++ b/src/main/java/com/google/common/collect/JdkBackedImmutableSet.java @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2018 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.collect; + +import com.google.common.annotations.GwtCompatible; +import java.util.Set; + + +/** + * ImmutableSet implementation backed by a JDK HashSet, used to defend against apparent hash + * flooding. This implementation is never used on the GWT client side, but it must be present there + * for serialization to work. + * + * @author Louis Wasserman + */ +@GwtCompatible(serializable = true) +final class JdkBackedImmutableSet extends IndexedImmutableSet { + private final Set delegate; + private final ImmutableList delegateList; + + JdkBackedImmutableSet(Set delegate, ImmutableList delegateList) { + this.delegate = delegate; + this.delegateList = delegateList; + } + + @Override + E get(int index) { + return delegateList.get(index); + } + + @Override + public boolean contains(Object object) { + return delegate.contains(object); + } + + @Override + boolean isPartialView() { + return false; + } + + @Override + public int size() { + return delegateList.size(); + } +} diff --git a/src/main/java/com/google/common/collect/LexicographicalOrdering.java b/src/main/java/com/google/common/collect/LexicographicalOrdering.java new file mode 100644 index 0000000..0ae5c86 --- /dev/null +++ b/src/main/java/com/google/common/collect/LexicographicalOrdering.java @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import com.google.common.annotations.GwtCompatible; +import java.io.Serializable; +import java.util.Comparator; +import java.util.Iterator; + + +/** An ordering which sorts iterables by comparing corresponding elements pairwise. */ +@GwtCompatible(serializable = true) +final class LexicographicalOrdering extends Ordering> implements Serializable { + final Comparator elementOrder; + + LexicographicalOrdering(Comparator elementOrder) { + this.elementOrder = elementOrder; + } + + @Override + public int compare(Iterable leftIterable, Iterable rightIterable) { + Iterator left = leftIterable.iterator(); + Iterator right = rightIterable.iterator(); + while (left.hasNext()) { + if (!right.hasNext()) { + return LEFT_IS_GREATER; // because it's longer + } + int result = elementOrder.compare(left.next(), right.next()); + if (result != 0) { + return result; + } + } + if (right.hasNext()) { + return RIGHT_IS_GREATER; // because it's longer + } + return 0; + } + + @Override + public boolean equals(Object object) { + if (object == this) { + return true; + } + if (object instanceof LexicographicalOrdering) { + LexicographicalOrdering that = (LexicographicalOrdering) object; + return this.elementOrder.equals(that.elementOrder); + } + return false; + } + + @Override + public int hashCode() { + return elementOrder.hashCode() ^ 2075626741; // meaningless + } + + @Override + public String toString() { + return elementOrder + ".lexicographical()"; + } + + private static final long serialVersionUID = 0; +} diff --git a/src/main/java/com/google/common/collect/LinkedHashMultimap.java b/src/main/java/com/google/common/collect/LinkedHashMultimap.java new file mode 100644 index 0000000..e3e96de --- /dev/null +++ b/src/main/java/com/google/common/collect/LinkedHashMultimap.java @@ -0,0 +1,614 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.collect.CollectPreconditions.checkNonnegative; +import static com.google.common.collect.CollectPreconditions.checkRemove; + +import com.google.common.annotations.GwtCompatible; +import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.Objects; + + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.util.Arrays; +import java.util.Collection; +import java.util.ConcurrentModificationException; +import java.util.Iterator; +import java.util.Map; +import java.util.Map.Entry; +import java.util.NoSuchElementException; +import java.util.Set; +import java.util.Spliterator; +import java.util.Spliterators; +import java.util.function.Consumer; + + +/** + * Implementation of {@code Multimap} that does not allow duplicate key-value entries and that + * returns collections whose iterators follow the ordering in which the data was added to the + * multimap. + * + *

The collections returned by {@code keySet}, {@code keys}, and {@code asMap} iterate through + * the keys in the order they were first added to the multimap. Similarly, {@code get}, {@code + * removeAll}, and {@code replaceValues} return collections that iterate through the values in the + * order they were added. The collections generated by {@code entries} and {@code values} iterate + * across the key-value mappings in the order they were added to the multimap. + * + *

The iteration ordering of the collections generated by {@code keySet}, {@code keys}, and + * {@code asMap} has a few subtleties. As long as the set of keys remains unchanged, adding or + * removing mappings does not affect the key iteration order. However, if you remove all values + * associated with a key and then add the key back to the multimap, that key will come last in the + * key iteration order. + * + *

The multimap does not store duplicate key-value pairs. Adding a new key-value pair equal to an + * existing key-value pair has no effect. + * + *

Keys and values may be null. All optional multimap methods are supported, and all returned + * views are modifiable. + * + *

This class is not threadsafe when any concurrent operations update the multimap. Concurrent + * read operations will work correctly. To allow concurrent update operations, wrap your multimap + * with a call to {@link Multimaps#synchronizedSetMultimap}. + * + *

See the Guava User Guide article on {@code + * Multimap}. + * + * @author Jared Levy + * @author Louis Wasserman + * @since 2.0 + */ +@GwtCompatible(serializable = true, emulated = true) +public final class LinkedHashMultimap + extends LinkedHashMultimapGwtSerializationDependencies { + + /** Creates a new, empty {@code LinkedHashMultimap} with the default initial capacities. */ + public static LinkedHashMultimap create() { + return new LinkedHashMultimap<>(DEFAULT_KEY_CAPACITY, DEFAULT_VALUE_SET_CAPACITY); + } + + /** + * Constructs an empty {@code LinkedHashMultimap} with enough capacity to hold the specified + * numbers of keys and values without rehashing. + * + * @param expectedKeys the expected number of distinct keys + * @param expectedValuesPerKey the expected average number of values per key + * @throws IllegalArgumentException if {@code expectedKeys} or {@code expectedValuesPerKey} is + * negative + */ + public static LinkedHashMultimap create(int expectedKeys, int expectedValuesPerKey) { + return new LinkedHashMultimap<>( + Maps.capacity(expectedKeys), Maps.capacity(expectedValuesPerKey)); + } + + /** + * Constructs a {@code LinkedHashMultimap} with the same mappings as the specified multimap. If a + * key-value mapping appears multiple times in the input multimap, it only appears once in the + * constructed multimap. The new multimap has the same {@link Multimap#entries()} iteration order + * as the input multimap, except for excluding duplicate mappings. + * + * @param multimap the multimap whose contents are copied to this multimap + */ + public static LinkedHashMultimap create( + Multimap multimap) { + LinkedHashMultimap result = create(multimap.keySet().size(), DEFAULT_VALUE_SET_CAPACITY); + result.putAll(multimap); + return result; + } + + private interface ValueSetLink { + ValueSetLink getPredecessorInValueSet(); + + ValueSetLink getSuccessorInValueSet(); + + void setPredecessorInValueSet(ValueSetLink entry); + + void setSuccessorInValueSet(ValueSetLink entry); + } + + private static void succeedsInValueSet(ValueSetLink pred, ValueSetLink succ) { + pred.setSuccessorInValueSet(succ); + succ.setPredecessorInValueSet(pred); + } + + private static void succeedsInMultimap(ValueEntry pred, ValueEntry succ) { + pred.setSuccessorInMultimap(succ); + succ.setPredecessorInMultimap(pred); + } + + private static void deleteFromValueSet(ValueSetLink entry) { + succeedsInValueSet(entry.getPredecessorInValueSet(), entry.getSuccessorInValueSet()); + } + + private static void deleteFromMultimap(ValueEntry entry) { + succeedsInMultimap(entry.getPredecessorInMultimap(), entry.getSuccessorInMultimap()); + } + + /** + * LinkedHashMultimap entries are in no less than three coexisting linked lists: a bucket in the + * hash table for a {@code Set} associated with a key, the linked list of insertion-ordered + * entries in that {@code Set}, and the linked list of entries in the LinkedHashMultimap as a + * whole. + */ + @VisibleForTesting + static final class ValueEntry extends ImmutableEntry implements ValueSetLink { + final int smearedValueHash; + + ValueEntry nextInValueBucket; + + ValueSetLink predecessorInValueSet; + ValueSetLink successorInValueSet; + + ValueEntry predecessorInMultimap; + ValueEntry successorInMultimap; + + ValueEntry( + K key, + V value, + int smearedValueHash, + ValueEntry nextInValueBucket) { + super(key, value); + this.smearedValueHash = smearedValueHash; + this.nextInValueBucket = nextInValueBucket; + } + + boolean matchesValue(Object v, int smearedVHash) { + return smearedValueHash == smearedVHash && Objects.equal(getValue(), v); + } + + @Override + public ValueSetLink getPredecessorInValueSet() { + return predecessorInValueSet; + } + + @Override + public ValueSetLink getSuccessorInValueSet() { + return successorInValueSet; + } + + @Override + public void setPredecessorInValueSet(ValueSetLink entry) { + predecessorInValueSet = entry; + } + + @Override + public void setSuccessorInValueSet(ValueSetLink entry) { + successorInValueSet = entry; + } + + public ValueEntry getPredecessorInMultimap() { + return predecessorInMultimap; + } + + public ValueEntry getSuccessorInMultimap() { + return successorInMultimap; + } + + public void setSuccessorInMultimap(ValueEntry multimapSuccessor) { + this.successorInMultimap = multimapSuccessor; + } + + public void setPredecessorInMultimap(ValueEntry multimapPredecessor) { + this.predecessorInMultimap = multimapPredecessor; + } + } + + private static final int DEFAULT_KEY_CAPACITY = 16; + private static final int DEFAULT_VALUE_SET_CAPACITY = 2; + @VisibleForTesting static final double VALUE_SET_LOAD_FACTOR = 1.0; + + @VisibleForTesting transient int valueSetCapacity = DEFAULT_VALUE_SET_CAPACITY; + private transient ValueEntry multimapHeaderEntry; + + private LinkedHashMultimap(int keyCapacity, int valueSetCapacity) { + super(Platform.>newLinkedHashMapWithExpectedSize(keyCapacity)); + checkNonnegative(valueSetCapacity, "expectedValuesPerKey"); + + this.valueSetCapacity = valueSetCapacity; + this.multimapHeaderEntry = new ValueEntry<>(null, null, 0, null); + succeedsInMultimap(multimapHeaderEntry, multimapHeaderEntry); + } + + /** + * {@inheritDoc} + * + *

Creates an empty {@code LinkedHashSet} for a collection of values for one key. + * + * @return a new {@code LinkedHashSet} containing a collection of values for one key + */ + @Override + Set createCollection() { + return Platform.newLinkedHashSetWithExpectedSize(valueSetCapacity); + } + + /** + * {@inheritDoc} + * + *

Creates a decorated insertion-ordered set that also keeps track of the order in which + * key-value pairs are added to the multimap. + * + * @param key key to associate with values in the collection + * @return a new decorated set containing a collection of values for one key + */ + @Override + Collection createCollection(K key) { + return new ValueSet(key, valueSetCapacity); + } + + /** + * {@inheritDoc} + * + *

If {@code values} is not empty and the multimap already contains a mapping for {@code key}, + * the {@code keySet()} ordering is unchanged. However, the provided values always come last in + * the {@link #entries()} and {@link #values()} iteration orderings. + */ + + @Override + public Set replaceValues(K key, Iterable values) { + return super.replaceValues(key, values); + } + + /** + * Returns a set of all key-value pairs. Changes to the returned set will update the underlying + * multimap, and vice versa. The entries set does not support the {@code add} or {@code addAll} + * operations. + * + *

The iterator generated by the returned set traverses the entries in the order they were + * added to the multimap. + * + *

Each entry is an immutable snapshot of a key-value mapping in the multimap, taken at the + * time the entry is returned by a method call to the collection or its iterator. + */ + @Override + public Set> entries() { + return super.entries(); + } + + /** + * Returns a view collection of all distinct keys contained in this multimap. Note that the + * key set contains a key if and only if this multimap maps that key to at least one value. + * + *

The iterator generated by the returned set traverses the keys in the order they were first + * added to the multimap. + * + *

Changes to the returned set will update the underlying multimap, and vice versa. However, + * adding to the returned set is not possible. + */ + @Override + public Set keySet() { + return super.keySet(); + } + + /** + * Returns a collection of all values in the multimap. Changes to the returned collection will + * update the underlying multimap, and vice versa. + * + *

The iterator generated by the returned collection traverses the values in the order they + * were added to the multimap. + */ + @Override + public Collection values() { + return super.values(); + } + + @VisibleForTesting + + final class ValueSet extends Sets.ImprovedAbstractSet implements ValueSetLink { + /* + * We currently use a fixed load factor of 1.0, a bit higher than normal to reduce memory + * consumption. + */ + + private final K key; + @VisibleForTesting ValueEntry[] hashTable; + private int size = 0; + private int modCount = 0; + + // We use the set object itself as the end of the linked list, avoiding an unnecessary + // entry object per key. + private ValueSetLink firstEntry; + private ValueSetLink lastEntry; + + ValueSet(K key, int expectedValues) { + this.key = key; + this.firstEntry = this; + this.lastEntry = this; + // Round expected values up to a power of 2 to get the table size. + int tableSize = Hashing.closedTableSize(expectedValues, VALUE_SET_LOAD_FACTOR); + + @SuppressWarnings("unchecked") + ValueEntry[] hashTable = new ValueEntry[tableSize]; + this.hashTable = hashTable; + } + + private int mask() { + return hashTable.length - 1; + } + + @Override + public ValueSetLink getPredecessorInValueSet() { + return lastEntry; + } + + @Override + public ValueSetLink getSuccessorInValueSet() { + return firstEntry; + } + + @Override + public void setPredecessorInValueSet(ValueSetLink entry) { + lastEntry = entry; + } + + @Override + public void setSuccessorInValueSet(ValueSetLink entry) { + firstEntry = entry; + } + + @Override + public Iterator iterator() { + return new Iterator() { + ValueSetLink nextEntry = firstEntry; + ValueEntry toRemove; + int expectedModCount = modCount; + + private void checkForComodification() { + if (modCount != expectedModCount) { + throw new ConcurrentModificationException(); + } + } + + @Override + public boolean hasNext() { + checkForComodification(); + return nextEntry != ValueSet.this; + } + + @Override + public V next() { + if (!hasNext()) { + throw new NoSuchElementException(); + } + ValueEntry entry = (ValueEntry) nextEntry; + V result = entry.getValue(); + toRemove = entry; + nextEntry = entry.getSuccessorInValueSet(); + return result; + } + + @Override + public void remove() { + checkForComodification(); + checkRemove(toRemove != null); + ValueSet.this.remove(toRemove.getValue()); + expectedModCount = modCount; + toRemove = null; + } + }; + } + + @Override + public void forEach(Consumer action) { + checkNotNull(action); + for (ValueSetLink entry = firstEntry; + entry != ValueSet.this; + entry = entry.getSuccessorInValueSet()) { + action.accept(((ValueEntry) entry).getValue()); + } + } + + @Override + public int size() { + return size; + } + + @Override + public boolean contains(Object o) { + int smearedHash = Hashing.smearedHash(o); + for (ValueEntry entry = hashTable[smearedHash & mask()]; + entry != null; + entry = entry.nextInValueBucket) { + if (entry.matchesValue(o, smearedHash)) { + return true; + } + } + return false; + } + + @Override + public boolean add(V value) { + int smearedHash = Hashing.smearedHash(value); + int bucket = smearedHash & mask(); + ValueEntry rowHead = hashTable[bucket]; + for (ValueEntry entry = rowHead; entry != null; entry = entry.nextInValueBucket) { + if (entry.matchesValue(value, smearedHash)) { + return false; + } + } + + ValueEntry newEntry = new ValueEntry<>(key, value, smearedHash, rowHead); + succeedsInValueSet(lastEntry, newEntry); + succeedsInValueSet(newEntry, this); + succeedsInMultimap(multimapHeaderEntry.getPredecessorInMultimap(), newEntry); + succeedsInMultimap(newEntry, multimapHeaderEntry); + hashTable[bucket] = newEntry; + size++; + modCount++; + rehashIfNecessary(); + return true; + } + + private void rehashIfNecessary() { + if (Hashing.needsResizing(size, hashTable.length, VALUE_SET_LOAD_FACTOR)) { + @SuppressWarnings("unchecked") + ValueEntry[] hashTable = new ValueEntry[this.hashTable.length * 2]; + this.hashTable = hashTable; + int mask = hashTable.length - 1; + for (ValueSetLink entry = firstEntry; + entry != this; + entry = entry.getSuccessorInValueSet()) { + ValueEntry valueEntry = (ValueEntry) entry; + int bucket = valueEntry.smearedValueHash & mask; + valueEntry.nextInValueBucket = hashTable[bucket]; + hashTable[bucket] = valueEntry; + } + } + } + + + @Override + public boolean remove(Object o) { + int smearedHash = Hashing.smearedHash(o); + int bucket = smearedHash & mask(); + ValueEntry prev = null; + for (ValueEntry entry = hashTable[bucket]; + entry != null; + prev = entry, entry = entry.nextInValueBucket) { + if (entry.matchesValue(o, smearedHash)) { + if (prev == null) { + // first entry in the bucket + hashTable[bucket] = entry.nextInValueBucket; + } else { + prev.nextInValueBucket = entry.nextInValueBucket; + } + deleteFromValueSet(entry); + deleteFromMultimap(entry); + size--; + modCount++; + return true; + } + } + return false; + } + + @Override + public void clear() { + Arrays.fill(hashTable, null); + size = 0; + for (ValueSetLink entry = firstEntry; + entry != this; + entry = entry.getSuccessorInValueSet()) { + ValueEntry valueEntry = (ValueEntry) entry; + deleteFromMultimap(valueEntry); + } + succeedsInValueSet(this, this); + modCount++; + } + } + + @Override + Iterator> entryIterator() { + return new Iterator>() { + ValueEntry nextEntry = multimapHeaderEntry.successorInMultimap; + ValueEntry toRemove; + + @Override + public boolean hasNext() { + return nextEntry != multimapHeaderEntry; + } + + @Override + public Entry next() { + if (!hasNext()) { + throw new NoSuchElementException(); + } + ValueEntry result = nextEntry; + toRemove = result; + nextEntry = nextEntry.successorInMultimap; + return result; + } + + @Override + public void remove() { + checkRemove(toRemove != null); + LinkedHashMultimap.this.remove(toRemove.getKey(), toRemove.getValue()); + toRemove = null; + } + }; + } + + @Override + Spliterator> entrySpliterator() { + return Spliterators.spliterator(entries(), Spliterator.DISTINCT | Spliterator.ORDERED); + } + + @Override + Iterator valueIterator() { + return Maps.valueIterator(entryIterator()); + } + + @Override + Spliterator valueSpliterator() { + return CollectSpliterators.map(entrySpliterator(), Entry::getValue); + } + + @Override + public void clear() { + super.clear(); + succeedsInMultimap(multimapHeaderEntry, multimapHeaderEntry); + } + + /** + * @serialData the expected values per key, the number of distinct keys, the number of entries, + * and the entries in order + */ + @GwtIncompatible // java.io.ObjectOutputStream + private void writeObject(ObjectOutputStream stream) throws IOException { + stream.defaultWriteObject(); + stream.writeInt(keySet().size()); + for (K key : keySet()) { + stream.writeObject(key); + } + stream.writeInt(size()); + for (Entry entry : entries()) { + stream.writeObject(entry.getKey()); + stream.writeObject(entry.getValue()); + } + } + + @GwtIncompatible // java.io.ObjectInputStream + private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException { + stream.defaultReadObject(); + multimapHeaderEntry = new ValueEntry<>(null, null, 0, null); + succeedsInMultimap(multimapHeaderEntry, multimapHeaderEntry); + valueSetCapacity = DEFAULT_VALUE_SET_CAPACITY; + int distinctKeys = stream.readInt(); + Map> map = Platform.newLinkedHashMapWithExpectedSize(12); + for (int i = 0; i < distinctKeys; i++) { + @SuppressWarnings("unchecked") + K key = (K) stream.readObject(); + map.put(key, createCollection(key)); + } + int entries = stream.readInt(); + for (int i = 0; i < entries; i++) { + @SuppressWarnings("unchecked") + K key = (K) stream.readObject(); + @SuppressWarnings("unchecked") + V value = (V) stream.readObject(); + map.get(key).add(value); + } + setMap(map); + } + + @GwtIncompatible // java serialization not supported + private static final long serialVersionUID = 1; +} diff --git a/src/main/java/com/google/common/collect/LinkedHashMultimapGwtSerializationDependencies.java b/src/main/java/com/google/common/collect/LinkedHashMultimapGwtSerializationDependencies.java new file mode 100644 index 0000000..bb4a2e4 --- /dev/null +++ b/src/main/java/com/google/common/collect/LinkedHashMultimapGwtSerializationDependencies.java @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2016 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import com.google.common.annotations.GwtCompatible; +import java.util.Collection; +import java.util.Map; + +/** + * A dummy superclass to support GWT serialization of the element types of a {@link + * LinkedHashMultimap}. The GWT supersource for this class contains a field for each type. + * + *

For details about this hack, see {@code GwtSerializationDependencies}, which takes the same + * approach but with a subclass rather than a superclass. + * + *

TODO(cpovirk): Consider applying this subclass approach to our other types. + */ +@GwtCompatible(emulated = true) +abstract class LinkedHashMultimapGwtSerializationDependencies + extends AbstractSetMultimap { + LinkedHashMultimapGwtSerializationDependencies(Map> map) { + super(map); + } +} diff --git a/src/main/java/com/google/common/collect/LinkedHashMultiset.java b/src/main/java/com/google/common/collect/LinkedHashMultiset.java new file mode 100644 index 0000000..87c2ad2 --- /dev/null +++ b/src/main/java/com/google/common/collect/LinkedHashMultiset.java @@ -0,0 +1,101 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import com.google.common.annotations.GwtCompatible; +import com.google.common.annotations.GwtIncompatible; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.util.LinkedHashMap; + +/** + * A {@code Multiset} implementation with predictable iteration order. Its iterator orders elements + * according to when the first occurrence of the element was added. When the multiset contains + * multiple instances of an element, those instances are consecutive in the iteration order. If all + * occurrences of an element are removed, after which that element is added to the multiset, the + * element will appear at the end of the iteration. + * + *

See the Guava User Guide article on {@code + * Multiset}. + * + * @author Kevin Bourrillion + * @author Jared Levy + * @since 2.0 + */ +@GwtCompatible(serializable = true, emulated = true) +public final class LinkedHashMultiset extends AbstractMapBasedMultiset { + + /** Creates a new, empty {@code LinkedHashMultiset} using the default initial capacity. */ + public static LinkedHashMultiset create() { + return new LinkedHashMultiset(); + } + + /** + * Creates a new, empty {@code LinkedHashMultiset} with the specified expected number of distinct + * elements. + * + * @param distinctElements the expected number of distinct elements + * @throws IllegalArgumentException if {@code distinctElements} is negative + */ + public static LinkedHashMultiset create(int distinctElements) { + return new LinkedHashMultiset(distinctElements); + } + + /** + * Creates a new {@code LinkedHashMultiset} containing the specified elements. + * + *

This implementation is highly efficient when {@code elements} is itself a {@link Multiset}. + * + * @param elements the elements that the multiset should contain + */ + public static LinkedHashMultiset create(Iterable elements) { + LinkedHashMultiset multiset = create(Multisets.inferDistinctElements(elements)); + Iterables.addAll(multiset, elements); + return multiset; + } + + private LinkedHashMultiset() { + super(new LinkedHashMap()); + } + + private LinkedHashMultiset(int distinctElements) { + super(Maps.newLinkedHashMapWithExpectedSize(distinctElements)); + } + + /** + * @serialData the number of distinct elements, the first element, its count, the second element, + * its count, and so on + */ + @GwtIncompatible // java.io.ObjectOutputStream + private void writeObject(ObjectOutputStream stream) throws IOException { + stream.defaultWriteObject(); + Serialization.writeMultiset(this, stream); + } + + @GwtIncompatible // java.io.ObjectInputStream + private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException { + stream.defaultReadObject(); + int distinctElements = Serialization.readCount(stream); + setBackingMap(new LinkedHashMap()); + Serialization.populateMultiset(this, stream, distinctElements); + } + + @GwtIncompatible // not needed in emulated source + private static final long serialVersionUID = 0; +} diff --git a/src/main/java/com/google/common/collect/LinkedListMultimap.java b/src/main/java/com/google/common/collect/LinkedListMultimap.java new file mode 100644 index 0000000..14e1123 --- /dev/null +++ b/src/main/java/com/google/common/collect/LinkedListMultimap.java @@ -0,0 +1,854 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.base.Preconditions.checkPositionIndex; +import static com.google.common.base.Preconditions.checkState; +import static com.google.common.collect.CollectPreconditions.checkRemove; +import static java.util.Collections.unmodifiableList; + +import com.google.common.annotations.GwtCompatible; +import com.google.common.annotations.GwtIncompatible; + + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.Serializable; +import java.util.AbstractSequentialList; +import java.util.Collection; +import java.util.ConcurrentModificationException; +import java.util.Iterator; +import java.util.List; +import java.util.ListIterator; +import java.util.Map; +import java.util.Map.Entry; +import java.util.NoSuchElementException; +import java.util.Set; +import java.util.function.Consumer; + + +/** + * An implementation of {@code ListMultimap} that supports deterministic iteration order for both + * keys and values. The iteration order is preserved across non-distinct key values. For example, + * for the following multimap definition: + * + *

{@code
+ * Multimap multimap = LinkedListMultimap.create();
+ * multimap.put(key1, foo);
+ * multimap.put(key2, bar);
+ * multimap.put(key1, baz);
+ * }
+ * + * ... the iteration order for {@link #keys()} is {@code [key1, key2, key1]}, and similarly for + * {@link #entries()}. Unlike {@link LinkedHashMultimap}, the iteration order is kept consistent + * between keys, entries and values. For example, calling: + * + *
{@code
+ * multimap.remove(key1, foo);
+ * }
+ * + *

changes the entries iteration order to {@code [key2=bar, key1=baz]} and the key iteration + * order to {@code [key2, key1]}. The {@link #entries()} iterator returns mutable map entries, and + * {@link #replaceValues} attempts to preserve iteration order as much as possible. + * + *

The collections returned by {@link #keySet()} and {@link #asMap} iterate through the keys in + * the order they were first added to the multimap. Similarly, {@link #get}, {@link #removeAll}, and + * {@link #replaceValues} return collections that iterate through the values in the order they were + * added. The collections generated by {@link #entries()}, {@link #keys()}, and {@link #values} + * iterate across the key-value mappings in the order they were added to the multimap. + * + *

The {@link #values()} and {@link #entries()} methods both return a {@code List}, instead of + * the {@code Collection} specified by the {@link ListMultimap} interface. + * + *

The methods {@link #get}, {@link #keySet()}, {@link #keys()}, {@link #values}, {@link + * #entries()}, and {@link #asMap} return collections that are views of the multimap. If the + * multimap is modified while an iteration over any of those collections is in progress, except + * through the iterator's methods, the results of the iteration are undefined. + * + *

Keys and values may be null. All optional multimap methods are supported, and all returned + * views are modifiable. + * + *

This class is not threadsafe when any concurrent operations update the multimap. Concurrent + * read operations will work correctly. To allow concurrent update operations, wrap your multimap + * with a call to {@link Multimaps#synchronizedListMultimap}. + * + *

See the Guava User Guide article on {@code + * Multimap}. + * + * @author Mike Bostock + * @since 2.0 + */ +@GwtCompatible(serializable = true, emulated = true) +public class LinkedListMultimap extends AbstractMultimap + implements ListMultimap, Serializable { + /* + * Order is maintained using a linked list containing all key-value pairs. In + * addition, a series of disjoint linked lists of "siblings", each containing + * the values for a specific key, is used to implement {@link + * ValueForKeyIterator} in constant time. + */ + + private static final class Node extends AbstractMapEntry { + final K key; + V value; + Node next; // the next node (with any key) + Node previous; // the previous node (with any key) + Node nextSibling; // the next node with the same key + Node previousSibling; // the previous node with the same key + + Node(K key, V value) { + this.key = key; + this.value = value; + } + + @Override + public K getKey() { + return key; + } + + @Override + public V getValue() { + return value; + } + + @Override + public V setValue(V newValue) { + V result = value; + this.value = newValue; + return result; + } + } + + private static class KeyList { + Node head; + Node tail; + int count; + + KeyList(Node firstNode) { + this.head = firstNode; + this.tail = firstNode; + firstNode.previousSibling = null; + firstNode.nextSibling = null; + this.count = 1; + } + } + + private transient Node head; // the head for all keys + private transient Node tail; // the tail for all keys + private transient Map> keyToKeyList; + private transient int size; + + /* + * Tracks modifications to keyToKeyList so that addition or removal of keys invalidates + * preexisting iterators. This does *not* track simple additions and removals of values + * that are not the first to be added or last to be removed for their key. + */ + private transient int modCount; + + /** Creates a new, empty {@code LinkedListMultimap} with the default initial capacity. */ + public static LinkedListMultimap create() { + return new LinkedListMultimap<>(); + } + + /** + * Constructs an empty {@code LinkedListMultimap} with enough capacity to hold the specified + * number of keys without rehashing. + * + * @param expectedKeys the expected number of distinct keys + * @throws IllegalArgumentException if {@code expectedKeys} is negative + */ + public static LinkedListMultimap create(int expectedKeys) { + return new LinkedListMultimap<>(expectedKeys); + } + + /** + * Constructs a {@code LinkedListMultimap} with the same mappings as the specified {@code + * Multimap}. The new multimap has the same {@link Multimap#entries()} iteration order as the + * input multimap. + * + * @param multimap the multimap whose contents are copied to this multimap + */ + public static LinkedListMultimap create( + Multimap multimap) { + return new LinkedListMultimap<>(multimap); + } + + LinkedListMultimap() { + this(12); + } + + private LinkedListMultimap(int expectedKeys) { + keyToKeyList = Platform.newHashMapWithExpectedSize(expectedKeys); + } + + private LinkedListMultimap(Multimap multimap) { + this(multimap.keySet().size()); + putAll(multimap); + } + + /** + * Adds a new node for the specified key-value pair before the specified {@code nextSibling} + * element, or at the end of the list if {@code nextSibling} is null. Note: if {@code nextSibling} + * is specified, it MUST be for an node for the same {@code key}! + */ + + private Node addNode(K key, V value, Node nextSibling) { + Node node = new Node<>(key, value); + if (head == null) { // empty list + head = tail = node; + keyToKeyList.put(key, new KeyList(node)); + modCount++; + } else if (nextSibling == null) { // non-empty list, add to tail + tail.next = node; + node.previous = tail; + tail = node; + KeyList keyList = keyToKeyList.get(key); + if (keyList == null) { + keyToKeyList.put(key, keyList = new KeyList<>(node)); + modCount++; + } else { + keyList.count++; + Node keyTail = keyList.tail; + keyTail.nextSibling = node; + node.previousSibling = keyTail; + keyList.tail = node; + } + } else { // non-empty list, insert before nextSibling + KeyList keyList = keyToKeyList.get(key); + keyList.count++; + node.previous = nextSibling.previous; + node.previousSibling = nextSibling.previousSibling; + node.next = nextSibling; + node.nextSibling = nextSibling; + if (nextSibling.previousSibling == null) { // nextSibling was key head + keyToKeyList.get(key).head = node; + } else { + nextSibling.previousSibling.nextSibling = node; + } + if (nextSibling.previous == null) { // nextSibling was head + head = node; + } else { + nextSibling.previous.next = node; + } + nextSibling.previous = node; + nextSibling.previousSibling = node; + } + size++; + return node; + } + + /** + * Removes the specified node from the linked list. This method is only intended to be used from + * the {@code Iterator} classes. See also {@link LinkedListMultimap#removeAllNodes(Object)}. + */ + private void removeNode(Node node) { + if (node.previous != null) { + node.previous.next = node.next; + } else { // node was head + head = node.next; + } + if (node.next != null) { + node.next.previous = node.previous; + } else { // node was tail + tail = node.previous; + } + if (node.previousSibling == null && node.nextSibling == null) { + KeyList keyList = keyToKeyList.remove(node.key); + keyList.count = 0; + modCount++; + } else { + KeyList keyList = keyToKeyList.get(node.key); + keyList.count--; + + if (node.previousSibling == null) { + keyList.head = node.nextSibling; + } else { + node.previousSibling.nextSibling = node.nextSibling; + } + + if (node.nextSibling == null) { + keyList.tail = node.previousSibling; + } else { + node.nextSibling.previousSibling = node.previousSibling; + } + } + size--; + } + + /** Removes all nodes for the specified key. */ + private void removeAllNodes(Object key) { + Iterators.clear(new ValueForKeyIterator(key)); + } + + /** Helper method for verifying that an iterator element is present. */ + private static void checkElement(Object node) { + if (node == null) { + throw new NoSuchElementException(); + } + } + + /** An {@code Iterator} over all nodes. */ + private class NodeIterator implements ListIterator> { + int nextIndex; + Node next; + Node current; + Node previous; + int expectedModCount = modCount; + + NodeIterator(int index) { + int size = size(); + checkPositionIndex(index, size); + if (index >= (size / 2)) { + previous = tail; + nextIndex = size; + while (index++ < size) { + previous(); + } + } else { + next = head; + while (index-- > 0) { + next(); + } + } + current = null; + } + + private void checkForConcurrentModification() { + if (modCount != expectedModCount) { + throw new ConcurrentModificationException(); + } + } + + @Override + public boolean hasNext() { + checkForConcurrentModification(); + return next != null; + } + + + @Override + public Node next() { + checkForConcurrentModification(); + checkElement(next); + previous = current = next; + next = next.next; + nextIndex++; + return current; + } + + @Override + public void remove() { + checkForConcurrentModification(); + checkRemove(current != null); + if (current != next) { // after call to next() + previous = current.previous; + nextIndex--; + } else { // after call to previous() + next = current.next; + } + removeNode(current); + current = null; + expectedModCount = modCount; + } + + @Override + public boolean hasPrevious() { + checkForConcurrentModification(); + return previous != null; + } + + + @Override + public Node previous() { + checkForConcurrentModification(); + checkElement(previous); + next = current = previous; + previous = previous.previous; + nextIndex--; + return current; + } + + @Override + public int nextIndex() { + return nextIndex; + } + + @Override + public int previousIndex() { + return nextIndex - 1; + } + + @Override + public void set(Entry e) { + throw new UnsupportedOperationException(); + } + + @Override + public void add(Entry e) { + throw new UnsupportedOperationException(); + } + + void setValue(V value) { + checkState(current != null); + current.value = value; + } + } + + /** An {@code Iterator} over distinct keys in key head order. */ + private class DistinctKeyIterator implements Iterator { + final Set seenKeys = Sets.newHashSetWithExpectedSize(keySet().size()); + Node next = head; + Node current; + int expectedModCount = modCount; + + private void checkForConcurrentModification() { + if (modCount != expectedModCount) { + throw new ConcurrentModificationException(); + } + } + + @Override + public boolean hasNext() { + checkForConcurrentModification(); + return next != null; + } + + @Override + public K next() { + checkForConcurrentModification(); + checkElement(next); + current = next; + seenKeys.add(current.key); + do { // skip ahead to next unseen key + next = next.next; + } while ((next != null) && !seenKeys.add(next.key)); + return current.key; + } + + @Override + public void remove() { + checkForConcurrentModification(); + checkRemove(current != null); + removeAllNodes(current.key); + current = null; + expectedModCount = modCount; + } + } + + /** A {@code ListIterator} over values for a specified key. */ + private class ValueForKeyIterator implements ListIterator { + final Object key; + int nextIndex; + Node next; + Node current; + Node previous; + + /** Constructs a new iterator over all values for the specified key. */ + ValueForKeyIterator(Object key) { + this.key = key; + KeyList keyList = keyToKeyList.get(key); + next = (keyList == null) ? null : keyList.head; + } + + /** + * Constructs a new iterator over all values for the specified key starting at the specified + * index. This constructor is optimized so that it starts at either the head or the tail, + * depending on which is closer to the specified index. This allows adds to the tail to be done + * in constant time. + * + * @throws IndexOutOfBoundsException if index is invalid + */ + public ValueForKeyIterator(Object key, int index) { + KeyList keyList = keyToKeyList.get(key); + int size = (keyList == null) ? 0 : keyList.count; + checkPositionIndex(index, size); + if (index >= (size / 2)) { + previous = (keyList == null) ? null : keyList.tail; + nextIndex = size; + while (index++ < size) { + previous(); + } + } else { + next = (keyList == null) ? null : keyList.head; + while (index-- > 0) { + next(); + } + } + this.key = key; + current = null; + } + + @Override + public boolean hasNext() { + return next != null; + } + + + @Override + public V next() { + checkElement(next); + previous = current = next; + next = next.nextSibling; + nextIndex++; + return current.value; + } + + @Override + public boolean hasPrevious() { + return previous != null; + } + + + @Override + public V previous() { + checkElement(previous); + next = current = previous; + previous = previous.previousSibling; + nextIndex--; + return current.value; + } + + @Override + public int nextIndex() { + return nextIndex; + } + + @Override + public int previousIndex() { + return nextIndex - 1; + } + + @Override + public void remove() { + checkRemove(current != null); + if (current != next) { // after call to next() + previous = current.previousSibling; + nextIndex--; + } else { // after call to previous() + next = current.nextSibling; + } + removeNode(current); + current = null; + } + + @Override + public void set(V value) { + checkState(current != null); + current.value = value; + } + + @Override + @SuppressWarnings("unchecked") + public void add(V value) { + previous = addNode((K) key, value, next); + nextIndex++; + current = null; + } + } + + // Query Operations + + @Override + public int size() { + return size; + } + + @Override + public boolean isEmpty() { + return head == null; + } + + @Override + public boolean containsKey(Object key) { + return keyToKeyList.containsKey(key); + } + + @Override + public boolean containsValue(Object value) { + return values().contains(value); + } + + // Modification Operations + + /** + * Stores a key-value pair in the multimap. + * + * @param key key to store in the multimap + * @param value value to store in the multimap + * @return {@code true} always + */ + + @Override + public boolean put(K key, V value) { + addNode(key, value, null); + return true; + } + + // Bulk Operations + + /** + * {@inheritDoc} + * + *

If any entries for the specified {@code key} already exist in the multimap, their values are + * changed in-place without affecting the iteration order. + * + *

The returned list is immutable and implements {@link java.util.RandomAccess}. + */ + + @Override + public List replaceValues(K key, Iterable values) { + List oldValues = getCopy(key); + ListIterator keyValues = new ValueForKeyIterator(key); + Iterator newValues = values.iterator(); + + // Replace existing values, if any. + while (keyValues.hasNext() && newValues.hasNext()) { + keyValues.next(); + keyValues.set(newValues.next()); + } + + // Remove remaining old values, if any. + while (keyValues.hasNext()) { + keyValues.next(); + keyValues.remove(); + } + + // Add remaining new values, if any. + while (newValues.hasNext()) { + keyValues.add(newValues.next()); + } + + return oldValues; + } + + private List getCopy(Object key) { + return unmodifiableList(Lists.newArrayList(new ValueForKeyIterator(key))); + } + + /** + * {@inheritDoc} + * + *

The returned list is immutable and implements {@link java.util.RandomAccess}. + */ + + @Override + public List removeAll(Object key) { + List oldValues = getCopy(key); + removeAllNodes(key); + return oldValues; + } + + @Override + public void clear() { + head = null; + tail = null; + keyToKeyList.clear(); + size = 0; + modCount++; + } + + // Views + + /** + * {@inheritDoc} + * + *

If the multimap is modified while an iteration over the list is in progress (except through + * the iterator's own {@code add}, {@code set} or {@code remove} operations) the results of the + * iteration are undefined. + * + *

The returned list is not serializable and does not have random access. + */ + @Override + public List get(final K key) { + return new AbstractSequentialList() { + @Override + public int size() { + KeyList keyList = keyToKeyList.get(key); + return (keyList == null) ? 0 : keyList.count; + } + + @Override + public ListIterator listIterator(int index) { + return new ValueForKeyIterator(key, index); + } + }; + } + + @Override + Set createKeySet() { + + class KeySetImpl extends Sets.ImprovedAbstractSet { + @Override + public int size() { + return keyToKeyList.size(); + } + + @Override + public Iterator iterator() { + return new DistinctKeyIterator(); + } + + @Override + public boolean contains(Object key) { // for performance + return containsKey(key); + } + + @Override + public boolean remove(Object o) { // for performance + return !LinkedListMultimap.this.removeAll(o).isEmpty(); + } + } + return new KeySetImpl(); + } + + @Override + Multiset createKeys() { + return new Multimaps.Keys(this); + } + + /** + * {@inheritDoc} + * + *

The iterator generated by the returned collection traverses the values in the order they + * were added to the multimap. Because the values may have duplicates and follow the insertion + * ordering, this method returns a {@link List}, instead of the {@link Collection} specified in + * the {@link ListMultimap} interface. + */ + @Override + public List values() { + return (List) super.values(); + } + + @Override + List createValues() { + + class ValuesImpl extends AbstractSequentialList { + @Override + public int size() { + return size; + } + + @Override + public ListIterator listIterator(int index) { + final NodeIterator nodeItr = new NodeIterator(index); + return new TransformedListIterator, V>(nodeItr) { + @Override + V transform(Entry entry) { + return entry.getValue(); + } + + @Override + public void set(V value) { + nodeItr.setValue(value); + } + }; + } + } + return new ValuesImpl(); + } + + /** + * {@inheritDoc} + * + *

The iterator generated by the returned collection traverses the entries in the order they + * were added to the multimap. Because the entries may have duplicates and follow the insertion + * ordering, this method returns a {@link List}, instead of the {@link Collection} specified in + * the {@link ListMultimap} interface. + * + *

An entry's {@link Entry#getKey} method always returns the same key, regardless of what + * happens subsequently. As long as the corresponding key-value mapping is not removed from the + * multimap, {@link Entry#getValue} returns the value from the multimap, which may change over + * time, and {@link Entry#setValue} modifies that value. Removing the mapping from the multimap + * does not alter the value returned by {@code getValue()}, though a subsequent {@code setValue()} + * call won't update the multimap but will lead to a revised value being returned by {@code + * getValue()}. + */ + @Override + public List> entries() { + return (List>) super.entries(); + } + + @Override + List> createEntries() { + + class EntriesImpl extends AbstractSequentialList> { + @Override + public int size() { + return size; + } + + @Override + public ListIterator> listIterator(int index) { + return new NodeIterator(index); + } + + @Override + public void forEach(Consumer> action) { + checkNotNull(action); + for (Node node = head; node != null; node = node.next) { + action.accept(node); + } + } + } + return new EntriesImpl(); + } + + @Override + Iterator> entryIterator() { + throw new AssertionError("should never be called"); + } + + @Override + Map> createAsMap() { + return new Multimaps.AsMap<>(this); + } + + /** + * @serialData the number of distinct keys, and then for each distinct key: the first key, the + * number of values for that key, and the key's values, followed by successive keys and values + * from the entries() ordering + */ + @GwtIncompatible // java.io.ObjectOutputStream + private void writeObject(ObjectOutputStream stream) throws IOException { + stream.defaultWriteObject(); + stream.writeInt(size()); + for (Entry entry : entries()) { + stream.writeObject(entry.getKey()); + stream.writeObject(entry.getValue()); + } + } + + @GwtIncompatible // java.io.ObjectInputStream + private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException { + stream.defaultReadObject(); + keyToKeyList = Maps.newLinkedHashMap(); + int size = stream.readInt(); + for (int i = 0; i < size; i++) { + @SuppressWarnings("unchecked") // reading data stored by writeObject + K key = (K) stream.readObject(); + @SuppressWarnings("unchecked") // reading data stored by writeObject + V value = (V) stream.readObject(); + put(key, value); + } + } + + @GwtIncompatible // java serialization not supported + private static final long serialVersionUID = 0; +} diff --git a/src/main/java/com/google/common/collect/ListMultimap.java b/src/main/java/com/google/common/collect/ListMultimap.java new file mode 100644 index 0000000..cfe6a30 --- /dev/null +++ b/src/main/java/com/google/common/collect/ListMultimap.java @@ -0,0 +1,97 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import com.google.common.annotations.GwtCompatible; + +import java.util.Collection; +import java.util.List; +import java.util.Map; + + +/** + * A {@code Multimap} that can hold duplicate key-value pairs and that maintains the insertion + * ordering of values for a given key. See the {@link Multimap} documentation for information common + * to all multimaps. + * + *

The {@link #get}, {@link #removeAll}, and {@link #replaceValues} methods each return a {@link + * List} of values. Though the method signature doesn't say so explicitly, the map returned by + * {@link #asMap} has {@code List} values. + * + *

See the Guava User Guide article on {@code + * Multimap}. + * + * @author Jared Levy + * @since 2.0 + */ +@GwtCompatible +public interface ListMultimap extends Multimap { + /** + * {@inheritDoc} + * + *

Because the values for a given key may have duplicates and follow the insertion ordering, + * this method returns a {@link List}, instead of the {@link java.util.Collection} specified in + * the {@link Multimap} interface. + */ + @Override + List get(K key); + + /** + * {@inheritDoc} + * + *

Because the values for a given key may have duplicates and follow the insertion ordering, + * this method returns a {@link List}, instead of the {@link java.util.Collection} specified in + * the {@link Multimap} interface. + */ + + @Override + List removeAll(Object key); + + /** + * {@inheritDoc} + * + *

Because the values for a given key may have duplicates and follow the insertion ordering, + * this method returns a {@link List}, instead of the {@link java.util.Collection} specified in + * the {@link Multimap} interface. + */ + + @Override + List replaceValues(K key, Iterable values); + + /** + * {@inheritDoc} + * + *

Note: The returned map's values are guaranteed to be of type {@link List}. To obtain + * this map with the more specific generic type {@code Map>}, call {@link + * Multimaps#asMap(ListMultimap)} instead. + */ + @Override + Map> asMap(); + + /** + * Compares the specified object to this multimap for equality. + * + *

Two {@code ListMultimap} instances are equal if, for each key, they contain the same values + * in the same order. If the value orderings disagree, the multimaps will not be considered equal. + * + *

An empty {@code ListMultimap} is equal to any other empty {@code Multimap}, including an + * empty {@code SetMultimap}. + */ + @Override + boolean equals(Object obj); +} diff --git a/src/main/java/com/google/common/collect/Lists.java b/src/main/java/com/google/common/collect/Lists.java new file mode 100644 index 0000000..af52f69 --- /dev/null +++ b/src/main/java/com/google/common/collect/Lists.java @@ -0,0 +1,1150 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkElementIndex; +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.base.Preconditions.checkPositionIndex; +import static com.google.common.base.Preconditions.checkPositionIndexes; +import static com.google.common.base.Preconditions.checkState; +import static com.google.common.collect.CollectPreconditions.checkNonnegative; +import static com.google.common.collect.CollectPreconditions.checkRemove; + +import com.google.common.annotations.Beta; +import com.google.common.annotations.GwtCompatible; +import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.Function; +import com.google.common.base.Objects; +import com.google.common.math.IntMath; +import com.google.common.primitives.Ints; +import java.io.Serializable; +import java.math.RoundingMode; +import java.util.AbstractList; +import java.util.AbstractSequentialList; +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.RandomAccess; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.function.Predicate; + + +/** + * Static utility methods pertaining to {@link List} instances. Also see this class's counterparts + * {@link Sets}, {@link Maps} and {@link Queues}. + * + *

See the Guava User Guide article on {@code Lists}. + * + * @author Kevin Bourrillion + * @author Mike Bostock + * @author Louis Wasserman + * @since 2.0 + */ +@GwtCompatible(emulated = true) +public final class Lists { + private Lists() {} + + // ArrayList + + /** + * Creates a mutable, empty {@code ArrayList} instance (for Java 6 and earlier). + * + *

Note: if mutability is not required, use {@link ImmutableList#of()} instead. + * + *

Note for Java 7 and later: this method is now unnecessary and should be treated as + * deprecated. Instead, use the {@code ArrayList} {@linkplain ArrayList#ArrayList() constructor} + * directly, taking advantage of the new "diamond" syntax. + */ + @GwtCompatible(serializable = true) + public static ArrayList newArrayList() { + return new ArrayList<>(); + } + + /** + * Creates a mutable {@code ArrayList} instance containing the given elements. + * + *

Note: essentially the only reason to use this method is when you will need to add or + * remove elements later. Otherwise, for non-null elements use {@link ImmutableList#of()} (for + * varargs) or {@link ImmutableList#copyOf(Object[])} (for an array) instead. If any elements + * might be null, or you need support for {@link List#set(int, Object)}, use {@link + * Arrays#asList}. + * + *

Note that even when you do need the ability to add or remove, this method provides only a + * tiny bit of syntactic sugar for {@code newArrayList(}{@link Arrays#asList asList}{@code + * (...))}, or for creating an empty list then calling {@link Collections#addAll}. This method is + * not actually very useful and will likely be deprecated in the future. + */ + @SafeVarargs + @GwtCompatible(serializable = true) + public static ArrayList newArrayList(E... elements) { + checkNotNull(elements); // for GWT + // Avoid integer overflow when a large array is passed in + int capacity = computeArrayListCapacity(elements.length); + ArrayList list = new ArrayList<>(capacity); + Collections.addAll(list, elements); + return list; + } + + /** + * Creates a mutable {@code ArrayList} instance containing the given elements; a very thin + * shortcut for creating an empty list then calling {@link Iterables#addAll}. + * + *

Note: if mutability is not required and the elements are non-null, use {@link + * ImmutableList#copyOf(Iterable)} instead. (Or, change {@code elements} to be a {@link + * FluentIterable} and call {@code elements.toList()}.) + * + *

Note for Java 7 and later: if {@code elements} is a {@link Collection}, you don't + * need this method. Use the {@code ArrayList} {@linkplain ArrayList#ArrayList(Collection) + * constructor} directly, taking advantage of the new "diamond" + * syntax. + */ + @GwtCompatible(serializable = true) + public static ArrayList newArrayList(Iterable elements) { + checkNotNull(elements); // for GWT + // Let ArrayList's sizing logic work, if possible + return (elements instanceof Collection) + ? new ArrayList<>(Collections2.cast(elements)) + : newArrayList(elements.iterator()); + } + + /** + * Creates a mutable {@code ArrayList} instance containing the given elements; a very thin + * shortcut for creating an empty list and then calling {@link Iterators#addAll}. + * + *

Note: if mutability is not required and the elements are non-null, use {@link + * ImmutableList#copyOf(Iterator)} instead. + */ + @GwtCompatible(serializable = true) + public static ArrayList newArrayList(Iterator elements) { + ArrayList list = newArrayList(); + Iterators.addAll(list, elements); + return list; + } + + @VisibleForTesting + static int computeArrayListCapacity(int arraySize) { + checkNonnegative(arraySize, "arraySize"); + + // TODO(kevinb): Figure out the right behavior, and document it + return Ints.saturatedCast(5L + arraySize + (arraySize / 10)); + } + + /** + * Creates an {@code ArrayList} instance backed by an array with the specified initial size; + * simply delegates to {@link ArrayList#ArrayList(int)}. + * + *

Note for Java 7 and later: this method is now unnecessary and should be treated as + * deprecated. Instead, use {@code new }{@link ArrayList#ArrayList(int) ArrayList}{@code <>(int)} + * directly, taking advantage of the new "diamond" syntax. + * (Unlike here, there is no risk of overload ambiguity, since the {@code ArrayList} constructors + * very wisely did not accept varargs.) + * + * @param initialArraySize the exact size of the initial backing array for the returned array list + * ({@code ArrayList} documentation calls this value the "capacity") + * @return a new, empty {@code ArrayList} which is guaranteed not to resize itself unless its size + * reaches {@code initialArraySize + 1} + * @throws IllegalArgumentException if {@code initialArraySize} is negative + */ + @GwtCompatible(serializable = true) + public static ArrayList newArrayListWithCapacity(int initialArraySize) { + checkNonnegative(initialArraySize, "initialArraySize"); // for GWT. + return new ArrayList<>(initialArraySize); + } + + /** + * Creates an {@code ArrayList} instance to hold {@code estimatedSize} elements, plus an + * unspecified amount of padding; you almost certainly mean to call {@link + * #newArrayListWithCapacity} (see that method for further advice on usage). + * + *

Note: This method will soon be deprecated. Even in the rare case that you do want + * some amount of padding, it's best if you choose your desired amount explicitly. + * + * @param estimatedSize an estimate of the eventual {@link List#size()} of the new list + * @return a new, empty {@code ArrayList}, sized appropriately to hold the estimated number of + * elements + * @throws IllegalArgumentException if {@code estimatedSize} is negative + */ + @GwtCompatible(serializable = true) + public static ArrayList newArrayListWithExpectedSize(int estimatedSize) { + return new ArrayList<>(computeArrayListCapacity(estimatedSize)); + } + + // LinkedList + + /** + * Creates a mutable, empty {@code LinkedList} instance (for Java 6 and earlier). + * + *

Note: if you won't be adding any elements to the list, use {@link ImmutableList#of()} + * instead. + * + *

Performance note: {@link ArrayList} and {@link java.util.ArrayDeque} consistently + * outperform {@code LinkedList} except in certain rare and specific situations. Unless you have + * spent a lot of time benchmarking your specific needs, use one of those instead. + * + *

Note for Java 7 and later: this method is now unnecessary and should be treated as + * deprecated. Instead, use the {@code LinkedList} {@linkplain LinkedList#LinkedList() + * constructor} directly, taking advantage of the new "diamond" + * syntax. + */ + @GwtCompatible(serializable = true) + public static LinkedList newLinkedList() { + return new LinkedList<>(); + } + + /** + * Creates a mutable {@code LinkedList} instance containing the given elements; a very thin + * shortcut for creating an empty list then calling {@link Iterables#addAll}. + * + *

Note: if mutability is not required and the elements are non-null, use {@link + * ImmutableList#copyOf(Iterable)} instead. (Or, change {@code elements} to be a {@link + * FluentIterable} and call {@code elements.toList()}.) + * + *

Performance note: {@link ArrayList} and {@link java.util.ArrayDeque} consistently + * outperform {@code LinkedList} except in certain rare and specific situations. Unless you have + * spent a lot of time benchmarking your specific needs, use one of those instead. + * + *

Note for Java 7 and later: if {@code elements} is a {@link Collection}, you don't + * need this method. Use the {@code LinkedList} {@linkplain LinkedList#LinkedList(Collection) + * constructor} directly, taking advantage of the new "diamond" + * syntax. + */ + @GwtCompatible(serializable = true) + public static LinkedList newLinkedList(Iterable elements) { + LinkedList list = newLinkedList(); + Iterables.addAll(list, elements); + return list; + } + + /** + * Creates an empty {@code CopyOnWriteArrayList} instance. + * + *

Note: if you need an immutable empty {@link List}, use {@link Collections#emptyList} + * instead. + * + * @return a new, empty {@code CopyOnWriteArrayList} + * @since 12.0 + */ + @GwtIncompatible // CopyOnWriteArrayList + public static CopyOnWriteArrayList newCopyOnWriteArrayList() { + return new CopyOnWriteArrayList<>(); + } + + /** + * Creates a {@code CopyOnWriteArrayList} instance containing the given elements. + * + * @param elements the elements that the list should contain, in order + * @return a new {@code CopyOnWriteArrayList} containing those elements + * @since 12.0 + */ + @GwtIncompatible // CopyOnWriteArrayList + public static CopyOnWriteArrayList newCopyOnWriteArrayList( + Iterable elements) { + // We copy elements to an ArrayList first, rather than incurring the + // quadratic cost of adding them to the COWAL directly. + Collection elementsCollection = + (elements instanceof Collection) ? Collections2.cast(elements) : newArrayList(elements); + return new CopyOnWriteArrayList<>(elementsCollection); + } + + /** + * Returns an unmodifiable list containing the specified first element and backed by the specified + * array of additional elements. Changes to the {@code rest} array will be reflected in the + * returned list. Unlike {@link Arrays#asList}, the returned list is unmodifiable. + * + *

This is useful when a varargs method needs to use a signature such as {@code (Foo firstFoo, + * Foo... moreFoos)}, in order to avoid overload ambiguity or to enforce a minimum argument count. + * + *

The returned list is serializable and implements {@link RandomAccess}. + * + * @param first the first element + * @param rest an array of additional elements, possibly empty + * @return an unmodifiable list containing the specified elements + */ + public static List asList(E first, E[] rest) { + return new OnePlusArrayList<>(first, rest); + } + + /** + * Returns an unmodifiable list containing the specified first and second element, and backed by + * the specified array of additional elements. Changes to the {@code rest} array will be reflected + * in the returned list. Unlike {@link Arrays#asList}, the returned list is unmodifiable. + * + *

This is useful when a varargs method needs to use a signature such as {@code (Foo firstFoo, + * Foo secondFoo, Foo... moreFoos)}, in order to avoid overload ambiguity or to enforce a minimum + * argument count. + * + *

The returned list is serializable and implements {@link RandomAccess}. + * + * @param first the first element + * @param second the second element + * @param rest an array of additional elements, possibly empty + * @return an unmodifiable list containing the specified elements + */ + public static List asList(E first, E second, E[] rest) { + return new TwoPlusArrayList<>(first, second, rest); + } + + /** @see Lists#asList(Object, Object[]) */ + private static class OnePlusArrayList extends AbstractList + implements Serializable, RandomAccess { + final E first; + final E[] rest; + + OnePlusArrayList(E first, E[] rest) { + this.first = first; + this.rest = checkNotNull(rest); + } + + @Override + public int size() { + return IntMath.saturatedAdd(rest.length, 1); + } + + @Override + public E get(int index) { + // check explicitly so the IOOBE will have the right message + checkElementIndex(index, size()); + return (index == 0) ? first : rest[index - 1]; + } + + private static final long serialVersionUID = 0; + } + + /** @see Lists#asList(Object, Object, Object[]) */ + private static class TwoPlusArrayList extends AbstractList + implements Serializable, RandomAccess { + final E first; + final E second; + final E[] rest; + + TwoPlusArrayList(E first, E second, E[] rest) { + this.first = first; + this.second = second; + this.rest = checkNotNull(rest); + } + + @Override + public int size() { + return IntMath.saturatedAdd(rest.length, 2); + } + + @Override + public E get(int index) { + switch (index) { + case 0: + return first; + case 1: + return second; + default: + // check explicitly so the IOOBE will have the right message + checkElementIndex(index, size()); + return rest[index - 2]; + } + } + + private static final long serialVersionUID = 0; + } + + /** + * Returns every possible list that can be formed by choosing one element from each of the given + * lists in order; the "n-ary Cartesian + * product" of the lists. For example: + * + *

{@code
+   * Lists.cartesianProduct(ImmutableList.of(
+   *     ImmutableList.of(1, 2),
+   *     ImmutableList.of("A", "B", "C")))
+   * }
+ * + *

returns a list containing six lists in the following order: + * + *

    + *
  • {@code ImmutableList.of(1, "A")} + *
  • {@code ImmutableList.of(1, "B")} + *
  • {@code ImmutableList.of(1, "C")} + *
  • {@code ImmutableList.of(2, "A")} + *
  • {@code ImmutableList.of(2, "B")} + *
  • {@code ImmutableList.of(2, "C")} + *
+ * + *

The result is guaranteed to be in the "traditional", lexicographical order for Cartesian + * products that you would get from nesting for loops: + * + *

{@code
+   * for (B b0 : lists.get(0)) {
+   *   for (B b1 : lists.get(1)) {
+   *     ...
+   *     ImmutableList tuple = ImmutableList.of(b0, b1, ...);
+   *     // operate on tuple
+   *   }
+   * }
+   * }
+ * + *

Note that if any input list is empty, the Cartesian product will also be empty. If no lists + * at all are provided (an empty list), the resulting Cartesian product has one element, an empty + * list (counter-intuitive, but mathematically consistent). + * + *

Performance notes: while the cartesian product of lists of size {@code m, n, p} is a + * list of size {@code m x n x p}, its actual memory consumption is much smaller. When the + * cartesian product is constructed, the input lists are merely copied. Only as the resulting list + * is iterated are the individual lists created, and these are not retained after iteration. + * + * @param lists the lists to choose elements from, in the order that the elements chosen from + * those lists should appear in the resulting lists + * @param any common base class shared by all axes (often just {@link Object}) + * @return the Cartesian product, as an immutable list containing immutable lists + * @throws IllegalArgumentException if the size of the cartesian product would be greater than + * {@link Integer#MAX_VALUE} + * @throws NullPointerException if {@code lists}, any one of the {@code lists}, or any element of + * a provided list is null + * @since 19.0 + */ + public static List> cartesianProduct(List> lists) { + return CartesianList.create(lists); + } + + /** + * Returns every possible list that can be formed by choosing one element from each of the given + * lists in order; the "n-ary Cartesian + * product" of the lists. For example: + * + *

{@code
+   * Lists.cartesianProduct(ImmutableList.of(
+   *     ImmutableList.of(1, 2),
+   *     ImmutableList.of("A", "B", "C")))
+   * }
+ * + *

returns a list containing six lists in the following order: + * + *

    + *
  • {@code ImmutableList.of(1, "A")} + *
  • {@code ImmutableList.of(1, "B")} + *
  • {@code ImmutableList.of(1, "C")} + *
  • {@code ImmutableList.of(2, "A")} + *
  • {@code ImmutableList.of(2, "B")} + *
  • {@code ImmutableList.of(2, "C")} + *
+ * + *

The result is guaranteed to be in the "traditional", lexicographical order for Cartesian + * products that you would get from nesting for loops: + * + *

{@code
+   * for (B b0 : lists.get(0)) {
+   *   for (B b1 : lists.get(1)) {
+   *     ...
+   *     ImmutableList tuple = ImmutableList.of(b0, b1, ...);
+   *     // operate on tuple
+   *   }
+   * }
+   * }
+ * + *

Note that if any input list is empty, the Cartesian product will also be empty. If no lists + * at all are provided (an empty list), the resulting Cartesian product has one element, an empty + * list (counter-intuitive, but mathematically consistent). + * + *

Performance notes: while the cartesian product of lists of size {@code m, n, p} is a + * list of size {@code m x n x p}, its actual memory consumption is much smaller. When the + * cartesian product is constructed, the input lists are merely copied. Only as the resulting list + * is iterated are the individual lists created, and these are not retained after iteration. + * + * @param lists the lists to choose elements from, in the order that the elements chosen from + * those lists should appear in the resulting lists + * @param any common base class shared by all axes (often just {@link Object}) + * @return the Cartesian product, as an immutable list containing immutable lists + * @throws IllegalArgumentException if the size of the cartesian product would be greater than + * {@link Integer#MAX_VALUE} + * @throws NullPointerException if {@code lists}, any one of the {@code lists}, or any element of + * a provided list is null + * @since 19.0 + */ + @SafeVarargs + public static List> cartesianProduct(List... lists) { + return cartesianProduct(Arrays.asList(lists)); + } + + /** + * Returns a list that applies {@code function} to each element of {@code fromList}. The returned + * list is a transformed view of {@code fromList}; changes to {@code fromList} will be reflected + * in the returned list and vice versa. + * + *

Since functions are not reversible, the transform is one-way and new items cannot be stored + * in the returned list. The {@code add}, {@code addAll} and {@code set} methods are unsupported + * in the returned list. + * + *

The function is applied lazily, invoked when needed. This is necessary for the returned list + * to be a view, but it means that the function will be applied many times for bulk operations + * like {@link List#contains} and {@link List#hashCode}. For this to perform well, {@code + * function} should be fast. To avoid lazy evaluation when the returned list doesn't need to be a + * view, copy the returned list into a new list of your choosing. + * + *

If {@code fromList} implements {@link RandomAccess}, so will the returned list. The returned + * list is threadsafe if the supplied list and function are. + * + *

If only a {@code Collection} or {@code Iterable} input is available, use {@link + * Collections2#transform} or {@link Iterables#transform}. + * + *

Note: serializing the returned list is implemented by serializing {@code fromList}, + * its contents, and {@code function} -- not by serializing the transformed values. This + * can lead to surprising behavior, so serializing the returned list is not recommended. + * Instead, copy the list using {@link ImmutableList#copyOf(Collection)} (for example), then + * serialize the copy. Other methods similar to this do not implement serialization at all for + * this reason. + * + *

Java 8 users: many use cases for this method are better addressed by {@link + * java.util.stream.Stream#map}. This method is not being deprecated, but we gently encourage you + * to migrate to streams. + */ + public static List transform( + List fromList, Function function) { + return (fromList instanceof RandomAccess) + ? new TransformingRandomAccessList<>(fromList, function) + : new TransformingSequentialList<>(fromList, function); + } + + /** + * Implementation of a sequential transforming list. + * + * @see Lists#transform + */ + private static class TransformingSequentialList extends AbstractSequentialList + implements Serializable { + final List fromList; + final Function function; + + TransformingSequentialList(List fromList, Function function) { + this.fromList = checkNotNull(fromList); + this.function = checkNotNull(function); + } + + /** + * The default implementation inherited is based on iteration and removal of each element which + * can be overkill. That's why we forward this call directly to the backing list. + */ + @Override + public void clear() { + fromList.clear(); + } + + @Override + public int size() { + return fromList.size(); + } + + @Override + public ListIterator listIterator(final int index) { + return new TransformedListIterator(fromList.listIterator(index)) { + @Override + T transform(F from) { + return function.apply(from); + } + }; + } + + @Override + public boolean removeIf(Predicate filter) { + checkNotNull(filter); + return fromList.removeIf(element -> filter.test(function.apply(element))); + } + + private static final long serialVersionUID = 0; + } + + /** + * Implementation of a transforming random access list. We try to make as many of these methods + * pass-through to the source list as possible so that the performance characteristics of the + * source list and transformed list are similar. + * + * @see Lists#transform + */ + private static class TransformingRandomAccessList extends AbstractList + implements RandomAccess, Serializable { + final List fromList; + final Function function; + + TransformingRandomAccessList(List fromList, Function function) { + this.fromList = checkNotNull(fromList); + this.function = checkNotNull(function); + } + + @Override + public void clear() { + fromList.clear(); + } + + @Override + public T get(int index) { + return function.apply(fromList.get(index)); + } + + @Override + public Iterator iterator() { + return listIterator(); + } + + @Override + public ListIterator listIterator(int index) { + return new TransformedListIterator(fromList.listIterator(index)) { + @Override + T transform(F from) { + return function.apply(from); + } + }; + } + + @Override + public boolean isEmpty() { + return fromList.isEmpty(); + } + + @Override + public boolean removeIf(Predicate filter) { + checkNotNull(filter); + return fromList.removeIf(element -> filter.test(function.apply(element))); + } + + @Override + public T remove(int index) { + return function.apply(fromList.remove(index)); + } + + @Override + public int size() { + return fromList.size(); + } + + private static final long serialVersionUID = 0; + } + + /** + * Returns consecutive {@linkplain List#subList(int, int) sublists} of a list, each of the same + * size (the final list may be smaller). For example, partitioning a list containing {@code [a, b, + * c, d, e]} with a partition size of 3 yields {@code [[a, b, c], [d, e]]} -- an outer list + * containing two inner lists of three and two elements, all in the original order. + * + *

The outer list is unmodifiable, but reflects the latest state of the source list. The inner + * lists are sublist views of the original list, produced on demand using {@link List#subList(int, + * int)}, and are subject to all the usual caveats about modification as explained in that API. + * + * @param list the list to return consecutive sublists of + * @param size the desired size of each sublist (the last may be smaller) + * @return a list of consecutive sublists + * @throws IllegalArgumentException if {@code partitionSize} is nonpositive + */ + public static List> partition(List list, int size) { + checkNotNull(list); + checkArgument(size > 0); + return (list instanceof RandomAccess) + ? new RandomAccessPartition<>(list, size) + : new Partition<>(list, size); + } + + private static class Partition extends AbstractList> { + final List list; + final int size; + + Partition(List list, int size) { + this.list = list; + this.size = size; + } + + @Override + public List get(int index) { + checkElementIndex(index, size()); + int start = index * size; + int end = Math.min(start + size, list.size()); + return list.subList(start, end); + } + + @Override + public int size() { + return IntMath.divide(list.size(), size, RoundingMode.CEILING); + } + + @Override + public boolean isEmpty() { + return list.isEmpty(); + } + } + + private static class RandomAccessPartition extends Partition implements RandomAccess { + RandomAccessPartition(List list, int size) { + super(list, size); + } + } + + /** + * Returns a view of the specified string as an immutable list of {@code Character} values. + * + * @since 7.0 + */ + public static ImmutableList charactersOf(String string) { + return new StringAsImmutableList(checkNotNull(string)); + } + + /** + * Returns a view of the specified {@code CharSequence} as a {@code List}, viewing + * {@code sequence} as a sequence of Unicode code units. The view does not support any + * modification operations, but reflects any changes to the underlying character sequence. + * + * @param sequence the character sequence to view as a {@code List} of characters + * @return an {@code List} view of the character sequence + * @since 7.0 + */ + @Beta + public static List charactersOf(CharSequence sequence) { + return new CharSequenceAsList(checkNotNull(sequence)); + } + + @SuppressWarnings("serial") // serialized using ImmutableList serialization + private static final class StringAsImmutableList extends ImmutableList { + + private final String string; + + StringAsImmutableList(String string) { + this.string = string; + } + + @Override + public int indexOf(Object object) { + return (object instanceof Character) ? string.indexOf((Character) object) : -1; + } + + @Override + public int lastIndexOf(Object object) { + return (object instanceof Character) ? string.lastIndexOf((Character) object) : -1; + } + + @Override + public ImmutableList subList(int fromIndex, int toIndex) { + checkPositionIndexes(fromIndex, toIndex, size()); // for GWT + return charactersOf(string.substring(fromIndex, toIndex)); + } + + @Override + boolean isPartialView() { + return false; + } + + @Override + public Character get(int index) { + checkElementIndex(index, size()); // for GWT + return string.charAt(index); + } + + @Override + public int size() { + return string.length(); + } + } + + private static final class CharSequenceAsList extends AbstractList { + private final CharSequence sequence; + + CharSequenceAsList(CharSequence sequence) { + this.sequence = sequence; + } + + @Override + public Character get(int index) { + checkElementIndex(index, size()); // for GWT + return sequence.charAt(index); + } + + @Override + public int size() { + return sequence.length(); + } + } + + /** + * Returns a reversed view of the specified list. For example, {@code + * Lists.reverse(Arrays.asList(1, 2, 3))} returns a list containing {@code 3, 2, 1}. The returned + * list is backed by this list, so changes in the returned list are reflected in this list, and + * vice-versa. The returned list supports all of the optional list operations supported by this + * list. + * + *

The returned list is random-access if the specified list is random access. + * + * @since 7.0 + */ + public static List reverse(List list) { + if (list instanceof ImmutableList) { + return ((ImmutableList) list).reverse(); + } else if (list instanceof ReverseList) { + return ((ReverseList) list).getForwardList(); + } else if (list instanceof RandomAccess) { + return new RandomAccessReverseList<>(list); + } else { + return new ReverseList<>(list); + } + } + + private static class ReverseList extends AbstractList { + private final List forwardList; + + ReverseList(List forwardList) { + this.forwardList = checkNotNull(forwardList); + } + + List getForwardList() { + return forwardList; + } + + private int reverseIndex(int index) { + int size = size(); + checkElementIndex(index, size); + return (size - 1) - index; + } + + private int reversePosition(int index) { + int size = size(); + checkPositionIndex(index, size); + return size - index; + } + + @Override + public void add(int index, T element) { + forwardList.add(reversePosition(index), element); + } + + @Override + public void clear() { + forwardList.clear(); + } + + @Override + public T remove(int index) { + return forwardList.remove(reverseIndex(index)); + } + + @Override + protected void removeRange(int fromIndex, int toIndex) { + subList(fromIndex, toIndex).clear(); + } + + @Override + public T set(int index, T element) { + return forwardList.set(reverseIndex(index), element); + } + + @Override + public T get(int index) { + return forwardList.get(reverseIndex(index)); + } + + @Override + public int size() { + return forwardList.size(); + } + + @Override + public List subList(int fromIndex, int toIndex) { + checkPositionIndexes(fromIndex, toIndex, size()); + return reverse(forwardList.subList(reversePosition(toIndex), reversePosition(fromIndex))); + } + + @Override + public Iterator iterator() { + return listIterator(); + } + + @Override + public ListIterator listIterator(int index) { + int start = reversePosition(index); + final ListIterator forwardIterator = forwardList.listIterator(start); + return new ListIterator() { + + boolean canRemoveOrSet; + + @Override + public void add(T e) { + forwardIterator.add(e); + forwardIterator.previous(); + canRemoveOrSet = false; + } + + @Override + public boolean hasNext() { + return forwardIterator.hasPrevious(); + } + + @Override + public boolean hasPrevious() { + return forwardIterator.hasNext(); + } + + @Override + public T next() { + if (!hasNext()) { + throw new NoSuchElementException(); + } + canRemoveOrSet = true; + return forwardIterator.previous(); + } + + @Override + public int nextIndex() { + return reversePosition(forwardIterator.nextIndex()); + } + + @Override + public T previous() { + if (!hasPrevious()) { + throw new NoSuchElementException(); + } + canRemoveOrSet = true; + return forwardIterator.next(); + } + + @Override + public int previousIndex() { + return nextIndex() - 1; + } + + @Override + public void remove() { + checkRemove(canRemoveOrSet); + forwardIterator.remove(); + canRemoveOrSet = false; + } + + @Override + public void set(T e) { + checkState(canRemoveOrSet); + forwardIterator.set(e); + } + }; + } + } + + private static class RandomAccessReverseList extends ReverseList implements RandomAccess { + RandomAccessReverseList(List forwardList) { + super(forwardList); + } + } + + /** An implementation of {@link List#hashCode()}. */ + static int hashCodeImpl(List list) { + // TODO(lowasser): worth optimizing for RandomAccess? + int hashCode = 1; + for (Object o : list) { + hashCode = 31 * hashCode + (o == null ? 0 : o.hashCode()); + + hashCode = ~~hashCode; + // needed to deal with GWT integer overflow + } + return hashCode; + } + + /** An implementation of {@link List#equals(Object)}. */ + static boolean equalsImpl(List thisList, Object other) { + if (other == checkNotNull(thisList)) { + return true; + } + if (!(other instanceof List)) { + return false; + } + List otherList = (List) other; + int size = thisList.size(); + if (size != otherList.size()) { + return false; + } + if (thisList instanceof RandomAccess && otherList instanceof RandomAccess) { + // avoid allocation and use the faster loop + for (int i = 0; i < size; i++) { + if (!Objects.equal(thisList.get(i), otherList.get(i))) { + return false; + } + } + return true; + } else { + return Iterators.elementsEqual(thisList.iterator(), otherList.iterator()); + } + } + + /** An implementation of {@link List#addAll(int, Collection)}. */ + static boolean addAllImpl(List list, int index, Iterable elements) { + boolean changed = false; + ListIterator listIterator = list.listIterator(index); + for (E e : elements) { + listIterator.add(e); + changed = true; + } + return changed; + } + + /** An implementation of {@link List#indexOf(Object)}. */ + static int indexOfImpl(List list, Object element) { + if (list instanceof RandomAccess) { + return indexOfRandomAccess(list, element); + } else { + ListIterator listIterator = list.listIterator(); + while (listIterator.hasNext()) { + if (Objects.equal(element, listIterator.next())) { + return listIterator.previousIndex(); + } + } + return -1; + } + } + + private static int indexOfRandomAccess(List list, Object element) { + int size = list.size(); + if (element == null) { + for (int i = 0; i < size; i++) { + if (list.get(i) == null) { + return i; + } + } + } else { + for (int i = 0; i < size; i++) { + if (element.equals(list.get(i))) { + return i; + } + } + } + return -1; + } + + /** An implementation of {@link List#lastIndexOf(Object)}. */ + static int lastIndexOfImpl(List list, Object element) { + if (list instanceof RandomAccess) { + return lastIndexOfRandomAccess(list, element); + } else { + ListIterator listIterator = list.listIterator(list.size()); + while (listIterator.hasPrevious()) { + if (Objects.equal(element, listIterator.previous())) { + return listIterator.nextIndex(); + } + } + return -1; + } + } + + private static int lastIndexOfRandomAccess(List list, Object element) { + if (element == null) { + for (int i = list.size() - 1; i >= 0; i--) { + if (list.get(i) == null) { + return i; + } + } + } else { + for (int i = list.size() - 1; i >= 0; i--) { + if (element.equals(list.get(i))) { + return i; + } + } + } + return -1; + } + + /** Returns an implementation of {@link List#listIterator(int)}. */ + static ListIterator listIteratorImpl(List list, int index) { + return new AbstractListWrapper<>(list).listIterator(index); + } + + /** An implementation of {@link List#subList(int, int)}. */ + static List subListImpl(final List list, int fromIndex, int toIndex) { + List wrapper; + if (list instanceof RandomAccess) { + wrapper = + new RandomAccessListWrapper(list) { + @Override + public ListIterator listIterator(int index) { + return backingList.listIterator(index); + } + + private static final long serialVersionUID = 0; + }; + } else { + wrapper = + new AbstractListWrapper(list) { + @Override + public ListIterator listIterator(int index) { + return backingList.listIterator(index); + } + + private static final long serialVersionUID = 0; + }; + } + return wrapper.subList(fromIndex, toIndex); + } + + private static class AbstractListWrapper extends AbstractList { + final List backingList; + + AbstractListWrapper(List backingList) { + this.backingList = checkNotNull(backingList); + } + + @Override + public void add(int index, E element) { + backingList.add(index, element); + } + + @Override + public boolean addAll(int index, Collection c) { + return backingList.addAll(index, c); + } + + @Override + public E get(int index) { + return backingList.get(index); + } + + @Override + public E remove(int index) { + return backingList.remove(index); + } + + @Override + public E set(int index, E element) { + return backingList.set(index, element); + } + + @Override + public boolean contains(Object o) { + return backingList.contains(o); + } + + @Override + public int size() { + return backingList.size(); + } + } + + private static class RandomAccessListWrapper extends AbstractListWrapper + implements RandomAccess { + RandomAccessListWrapper(List backingList) { + super(backingList); + } + } + + /** Used to avoid http://bugs.sun.com/view_bug.do?bug_id=6558557 */ + static List cast(Iterable iterable) { + return (List) iterable; + } +} diff --git a/src/main/java/com/google/common/collect/MapDifference.java b/src/main/java/com/google/common/collect/MapDifference.java new file mode 100644 index 0000000..ecd6ca1 --- /dev/null +++ b/src/main/java/com/google/common/collect/MapDifference.java @@ -0,0 +1,107 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import com.google.common.annotations.GwtCompatible; +import java.util.Map; + + +/** + * An object representing the differences between two maps. + * + * @author Kevin Bourrillion + * @since 2.0 + */ +@GwtCompatible +public interface MapDifference { + /** + * Returns {@code true} if there are no differences between the two maps; that is, if the maps are + * equal. + */ + boolean areEqual(); + + /** + * Returns an unmodifiable map containing the entries from the left map whose keys are not present + * in the right map. + */ + Map entriesOnlyOnLeft(); + + /** + * Returns an unmodifiable map containing the entries from the right map whose keys are not + * present in the left map. + */ + Map entriesOnlyOnRight(); + + /** + * Returns an unmodifiable map containing the entries that appear in both maps; that is, the + * intersection of the two maps. + */ + Map entriesInCommon(); + + /** + * Returns an unmodifiable map describing keys that appear in both maps, but with different + * values. + */ + Map> entriesDiffering(); + + /** + * Compares the specified object with this instance for equality. Returns {@code true} if the + * given object is also a {@code MapDifference} and the values returned by the {@link + * #entriesOnlyOnLeft()}, {@link #entriesOnlyOnRight()}, {@link #entriesInCommon()} and {@link + * #entriesDiffering()} of the two instances are equal. + */ + @Override + boolean equals(Object object); + + /** + * Returns the hash code for this instance. This is defined as the hash code of + * + *

{@code
+   * Arrays.asList(entriesOnlyOnLeft(), entriesOnlyOnRight(),
+   *     entriesInCommon(), entriesDiffering())
+   * }
+ */ + @Override + int hashCode(); + + /** + * A difference between the mappings from two maps with the same key. The {@link #leftValue} and + * {@link #rightValue} are not equal, and one but not both of them may be null. + * + * @since 2.0 + */ + interface ValueDifference { + /** Returns the value from the left map (possibly null). */ + V leftValue(); + + /** Returns the value from the right map (possibly null). */ + V rightValue(); + + /** + * Two instances are considered equal if their {@link #leftValue()} values are equal and their + * {@link #rightValue()} values are also equal. + */ + @Override + boolean equals(Object other); + + /** + * The hash code equals the value {@code Arrays.asList(leftValue(), rightValue()).hashCode()}. + */ + @Override + int hashCode(); + } +} diff --git a/src/main/java/com/google/common/collect/MapMaker.java b/src/main/java/com/google/common/collect/MapMaker.java new file mode 100644 index 0000000..76dfeb9 --- /dev/null +++ b/src/main/java/com/google/common/collect/MapMaker.java @@ -0,0 +1,310 @@ +/* + * Copyright (C) 2009 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.collect; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.base.Preconditions.checkState; + +import com.google.common.annotations.GwtCompatible; +import com.google.common.annotations.GwtIncompatible; +import com.google.common.base.Ascii; +import com.google.common.base.Equivalence; +import com.google.common.base.MoreObjects; +import com.google.common.collect.MapMakerInternalMap.Strength; + +import java.lang.ref.WeakReference; +import java.util.ConcurrentModificationException; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + + +/** + * A builder of {@link ConcurrentMap} instances that can have keys or values automatically wrapped + * in {@linkplain WeakReference weak} references. + * + *

Usage example: + * + *

{@code
+ * ConcurrentMap timers = new MapMaker()
+ *     .concurrencyLevel(4)
+ *     .weakKeys()
+ *     .makeMap();
+ * }
+ * + *

These features are all optional; {@code new MapMaker().makeMap()} returns a valid concurrent + * map that behaves similarly to a {@link ConcurrentHashMap}. + * + *

The returned map is implemented as a hash table with similar performance characteristics to + * {@link ConcurrentHashMap}. It supports all optional operations of the {@code ConcurrentMap} + * interface. It does not permit null keys or values. + * + *

Note: by default, the returned map uses equality comparisons (the {@link Object#equals + * equals} method) to determine equality for keys or values. However, if {@link #weakKeys} was + * specified, the map uses identity ({@code ==}) comparisons instead for keys. Likewise, if {@link + * #weakValues} was specified, the map uses identity comparisons for values. + * + *

The view collections of the returned map have weakly consistent iterators. This means + * that they are safe for concurrent use, but if other threads modify the map after the iterator is + * created, it is undefined which of these changes, if any, are reflected in that iterator. These + * iterators never throw {@link ConcurrentModificationException}. + * + *

If {@link #weakKeys} or {@link #weakValues} are requested, it is possible for a key or value + * present in the map to be reclaimed by the garbage collector. Entries with reclaimed keys or + * values may be removed from the map on each map modification or on occasional map accesses; such + * entries may be counted by {@link Map#size}, but will never be visible to read or write + * operations. A partially-reclaimed entry is never exposed to the user. Any {@link Map.Entry} + * instance retrieved from the map's {@linkplain Map#entrySet entry set} is a snapshot of that + * entry's state at the time of retrieval; such entries do, however, support {@link + * Map.Entry#setValue}, which simply calls {@link Map#put} on the entry's key. + * + *

The maps produced by {@code MapMaker} are serializable, and the deserialized maps retain all + * the configuration properties of the original map. During deserialization, if the original map had + * used weak references, the entries are reconstructed as they were, but it's not unlikely they'll + * be quickly garbage-collected before they are ever accessed. + * + *

{@code new MapMaker().weakKeys().makeMap()} is a recommended replacement for {@link + * java.util.WeakHashMap}, but note that it compares keys using object identity whereas {@code + * WeakHashMap} uses {@link Object#equals}. + * + * @author Bob Lee + * @author Charles Fry + * @author Kevin Bourrillion + * @since 2.0 + */ +@GwtCompatible(emulated = true) +public final class MapMaker { + private static final int DEFAULT_INITIAL_CAPACITY = 16; + private static final int DEFAULT_CONCURRENCY_LEVEL = 4; + + static final int UNSET_INT = -1; + + // TODO(kevinb): dispense with this after benchmarking + boolean useCustomMap; + + int initialCapacity = UNSET_INT; + int concurrencyLevel = UNSET_INT; + + Strength keyStrength; + Strength valueStrength; + + Equivalence keyEquivalence; + + /** + * Constructs a new {@code MapMaker} instance with default settings, including strong keys, strong + * values, and no automatic eviction of any kind. + */ + public MapMaker() {} + + /** + * Sets a custom {@code Equivalence} strategy for comparing keys. + * + *

By default, the map uses {@link Equivalence#identity} to determine key equality when {@link + * #weakKeys} is specified, and {@link Equivalence#equals()} otherwise. The only place this is + * used is in {@link Interners.WeakInterner}. + */ + + @GwtIncompatible // To be supported + MapMaker keyEquivalence(Equivalence equivalence) { + checkState(keyEquivalence == null, "key equivalence was already set to %s", keyEquivalence); + keyEquivalence = checkNotNull(equivalence); + this.useCustomMap = true; + return this; + } + + Equivalence getKeyEquivalence() { + return MoreObjects.firstNonNull(keyEquivalence, getKeyStrength().defaultEquivalence()); + } + + /** + * Sets the minimum total size for the internal hash tables. For example, if the initial capacity + * is {@code 60}, and the concurrency level is {@code 8}, then eight segments are created, each + * having a hash table of size eight. Providing a large enough estimate at construction time + * avoids the need for expensive resizing operations later, but setting this value unnecessarily + * high wastes memory. + * + * @throws IllegalArgumentException if {@code initialCapacity} is negative + * @throws IllegalStateException if an initial capacity was already set + */ + + public MapMaker initialCapacity(int initialCapacity) { + checkState( + this.initialCapacity == UNSET_INT, + "initial capacity was already set to %s", + this.initialCapacity); + checkArgument(initialCapacity >= 0); + this.initialCapacity = initialCapacity; + return this; + } + + int getInitialCapacity() { + return (initialCapacity == UNSET_INT) ? DEFAULT_INITIAL_CAPACITY : initialCapacity; + } + + /** + * Guides the allowed concurrency among update operations. Used as a hint for internal sizing. The + * table is internally partitioned to try to permit the indicated number of concurrent updates + * without contention. Because assignment of entries to these partitions is not necessarily + * uniform, the actual concurrency observed may vary. Ideally, you should choose a value to + * accommodate as many threads as will ever concurrently modify the table. Using a significantly + * higher value than you need can waste space and time, and a significantly lower value can lead + * to thread contention. But overestimates and underestimates within an order of magnitude do not + * usually have much noticeable impact. A value of one permits only one thread to modify the map + * at a time, but since read operations can proceed concurrently, this still yields higher + * concurrency than full synchronization. Defaults to 4. + * + *

Note: Prior to Guava release 9.0, the default was 16. It is possible the default will + * change again in the future. If you care about this value, you should always choose it + * explicitly. + * + * @throws IllegalArgumentException if {@code concurrencyLevel} is nonpositive + * @throws IllegalStateException if a concurrency level was already set + */ + + public MapMaker concurrencyLevel(int concurrencyLevel) { + checkState( + this.concurrencyLevel == UNSET_INT, + "concurrency level was already set to %s", + this.concurrencyLevel); + checkArgument(concurrencyLevel > 0); + this.concurrencyLevel = concurrencyLevel; + return this; + } + + int getConcurrencyLevel() { + return (concurrencyLevel == UNSET_INT) ? DEFAULT_CONCURRENCY_LEVEL : concurrencyLevel; + } + + /** + * Specifies that each key (not value) stored in the map should be wrapped in a {@link + * WeakReference} (by default, strong references are used). + * + *

Warning: when this method is used, the resulting map will use identity ({@code ==}) + * comparison to determine equality of keys, which is a technical violation of the {@link Map} + * specification, and may not be what you expect. + * + * @throws IllegalStateException if the key strength was already set + * @see WeakReference + */ + + @GwtIncompatible // java.lang.ref.WeakReference + public MapMaker weakKeys() { + return setKeyStrength(Strength.WEAK); + } + + MapMaker setKeyStrength(Strength strength) { + checkState(keyStrength == null, "Key strength was already set to %s", keyStrength); + keyStrength = checkNotNull(strength); + if (strength != Strength.STRONG) { + // STRONG could be used during deserialization. + useCustomMap = true; + } + return this; + } + + Strength getKeyStrength() { + return MoreObjects.firstNonNull(keyStrength, Strength.STRONG); + } + + /** + * Specifies that each value (not key) stored in the map should be wrapped in a {@link + * WeakReference} (by default, strong references are used). + * + *

Weak values will be garbage collected once they are weakly reachable. This makes them a poor + * candidate for caching. + * + *

Warning: when this method is used, the resulting map will use identity ({@code ==}) + * comparison to determine equality of values. This technically violates the specifications of the + * methods {@link Map#containsValue containsValue}, {@link ConcurrentMap#remove(Object, Object) + * remove(Object, Object)} and {@link ConcurrentMap#replace(Object, Object, Object) replace(K, V, + * V)}, and may not be what you expect. + * + * @throws IllegalStateException if the value strength was already set + * @see WeakReference + */ + + @GwtIncompatible // java.lang.ref.WeakReference + public MapMaker weakValues() { + return setValueStrength(Strength.WEAK); + } + + /** + * A dummy singleton value type used by {@link Interners}. + * + *

{@link MapMakerInternalMap} can optimize for memory usage in this case; see {@link + * MapMakerInternalMap#createWithDummyValues}. + */ + enum Dummy { + VALUE + } + + MapMaker setValueStrength(Strength strength) { + checkState(valueStrength == null, "Value strength was already set to %s", valueStrength); + valueStrength = checkNotNull(strength); + if (strength != Strength.STRONG) { + // STRONG could be used during deserialization. + useCustomMap = true; + } + return this; + } + + Strength getValueStrength() { + return MoreObjects.firstNonNull(valueStrength, Strength.STRONG); + } + + /** + * Builds a thread-safe map. This method does not alter the state of this {@code MapMaker} + * instance, so it can be invoked again to create multiple independent maps. + * + *

The bulk operations {@code putAll}, {@code equals}, and {@code clear} are not guaranteed to + * be performed atomically on the returned map. Additionally, {@code size} and {@code + * containsValue} are implemented as bulk read operations, and thus may fail to observe concurrent + * writes. + * + * @return a serializable concurrent map having the requested features + */ + public ConcurrentMap makeMap() { + if (!useCustomMap) { + return new ConcurrentHashMap<>(getInitialCapacity(), 0.75f, getConcurrencyLevel()); + } + return MapMakerInternalMap.create(this); + } + + /** + * Returns a string representation for this MapMaker instance. The exact form of the returned + * string is not specified. + */ + @Override + public String toString() { + MoreObjects.ToStringHelper s = MoreObjects.toStringHelper(this); + if (initialCapacity != UNSET_INT) { + s.add("initialCapacity", initialCapacity); + } + if (concurrencyLevel != UNSET_INT) { + s.add("concurrencyLevel", concurrencyLevel); + } + if (keyStrength != null) { + s.add("keyStrength", Ascii.toLowerCase(keyStrength.toString())); + } + if (valueStrength != null) { + s.add("valueStrength", Ascii.toLowerCase(valueStrength.toString())); + } + if (keyEquivalence != null) { + s.addValue("keyEquivalence"); + } + return s.toString(); + } +} diff --git a/src/main/java/com/google/common/collect/MapMakerInternalMap.java b/src/main/java/com/google/common/collect/MapMakerInternalMap.java new file mode 100644 index 0000000..cf618cb --- /dev/null +++ b/src/main/java/com/google/common/collect/MapMakerInternalMap.java @@ -0,0 +1,2943 @@ +/* + * Copyright (C) 2009 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.collect; + +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.collect.CollectPreconditions.checkRemove; + +import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.Equivalence; +import com.google.common.collect.MapMaker.Dummy; +import com.google.common.primitives.Ints; + + + + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.Serializable; +import java.lang.ref.Reference; +import java.lang.ref.ReferenceQueue; +import java.lang.ref.WeakReference; +import java.util.AbstractCollection; +import java.util.AbstractMap; +import java.util.AbstractSet; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; +import java.util.Map; +import java.util.NoSuchElementException; +import java.util.Set; +import java.util.concurrent.CancellationException; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicReferenceArray; +import java.util.concurrent.locks.ReentrantLock; + + + +/** + * The concurrent hash map implementation built by {@link MapMaker}. + * + *

This implementation is heavily derived from revision 1.96 of ConcurrentHashMap.java. + * + * @param the type of the keys in the map + * @param the type of the values in the map + * @param the type of the {@link InternalEntry} entry implementation used internally + * @param the type of the {@link Segment} entry implementation used internally + * @author Bob Lee + * @author Charles Fry + * @author Doug Lea ({@code ConcurrentHashMap}) + */ +// TODO(kak/cpovirk): Consider removing from this class. +@GwtIncompatible +@SuppressWarnings("GuardedBy") // TODO(b/35466881): Fix or suppress. +class MapMakerInternalMap< + K, + V, + E extends MapMakerInternalMap.InternalEntry, + S extends MapMakerInternalMap.Segment> + extends AbstractMap implements ConcurrentMap, Serializable { + + /* + * The basic strategy is to subdivide the table among Segments, each of which itself is a + * concurrently readable hash table. The map supports non-blocking reads and concurrent writes + * across different segments. + * + * The page replacement algorithm's data structures are kept casually consistent with the map. The + * ordering of writes to a segment is sequentially consistent. An update to the map and recording + * of reads may not be immediately reflected on the algorithm's data structures. These structures + * are guarded by a lock and operations are applied in batches to avoid lock contention. The + * penalty of applying the batches is spread across threads so that the amortized cost is slightly + * higher than performing just the operation without enforcing the capacity constraint. + * + * This implementation uses a per-segment queue to record a memento of the additions, removals, + * and accesses that were performed on the map. The queue is drained on writes and when it exceeds + * its capacity threshold. + * + * The Least Recently Used page replacement algorithm was chosen due to its simplicity, high hit + * rate, and ability to be implemented with O(1) time complexity. The initial LRU implementation + * operates per-segment rather than globally for increased implementation simplicity. We expect + * the cache hit rate to be similar to that of a global LRU algorithm. + */ + + // Constants + + /** + * The maximum capacity, used if a higher value is implicitly specified by either of the + * constructors with arguments. MUST be a power of two no greater than {@code 1<<30} to ensure + * that entries are indexable using ints. + */ + static final int MAXIMUM_CAPACITY = Ints.MAX_POWER_OF_TWO; + + /** The maximum number of segments to allow; used to bound constructor arguments. */ + static final int MAX_SEGMENTS = 1 << 16; // slightly conservative + + /** Number of (unsynchronized) retries in the containsValue method. */ + static final int CONTAINS_VALUE_RETRIES = 3; + + /** + * Number of cache access operations that can be buffered per segment before the cache's recency + * ordering information is updated. This is used to avoid lock contention by recording a memento + * of reads and delaying a lock acquisition until the threshold is crossed or a mutation occurs. + * + *

This must be a (2^n)-1 as it is used as a mask. + */ + static final int DRAIN_THRESHOLD = 0x3F; + + /** + * Maximum number of entries to be drained in a single cleanup run. This applies independently to + * the cleanup queue and both reference queues. + */ + // TODO(fry): empirically optimize this + static final int DRAIN_MAX = 16; + + static final long CLEANUP_EXECUTOR_DELAY_SECS = 60; + + // Fields + + /** + * Mask value for indexing into segments. The upper bits of a key's hash code are used to choose + * the segment. + */ + final transient int segmentMask; + + /** + * Shift value for indexing within segments. Helps prevent entries that end up in the same segment + * from also ending up in the same bucket. + */ + final transient int segmentShift; + + /** The segments, each of which is a specialized hash table. */ + final transient Segment[] segments; + + /** The concurrency level. */ + final int concurrencyLevel; + + /** Strategy for comparing keys. */ + final Equivalence keyEquivalence; + + /** Strategy for handling entries and segments in a type-safe and efficient manner. */ + final transient InternalEntryHelper entryHelper; + + /** + * Creates a new, empty map with the specified strategy, initial capacity and concurrency level. + */ + private MapMakerInternalMap(MapMaker builder, InternalEntryHelper entryHelper) { + concurrencyLevel = Math.min(builder.getConcurrencyLevel(), MAX_SEGMENTS); + + keyEquivalence = builder.getKeyEquivalence(); + this.entryHelper = entryHelper; + + int initialCapacity = Math.min(builder.getInitialCapacity(), MAXIMUM_CAPACITY); + + // Find power-of-two sizes best matching arguments. Constraints: + // (segmentCount > concurrencyLevel) + int segmentShift = 0; + int segmentCount = 1; + while (segmentCount < concurrencyLevel) { + ++segmentShift; + segmentCount <<= 1; + } + this.segmentShift = 32 - segmentShift; + segmentMask = segmentCount - 1; + + this.segments = newSegmentArray(segmentCount); + + int segmentCapacity = initialCapacity / segmentCount; + if (segmentCapacity * segmentCount < initialCapacity) { + ++segmentCapacity; + } + + int segmentSize = 1; + while (segmentSize < segmentCapacity) { + segmentSize <<= 1; + } + + for (int i = 0; i < this.segments.length; ++i) { + this.segments[i] = createSegment(segmentSize, MapMaker.UNSET_INT); + } + } + + /** Returns a fresh {@link MapMakerInternalMap} as specified by the given {@code builder}. */ + static MapMakerInternalMap, ?> create( + MapMaker builder) { + if (builder.getKeyStrength() == Strength.STRONG + && builder.getValueStrength() == Strength.STRONG) { + return new MapMakerInternalMap<>(builder, StrongKeyStrongValueEntry.Helper.instance()); + } + if (builder.getKeyStrength() == Strength.STRONG + && builder.getValueStrength() == Strength.WEAK) { + return new MapMakerInternalMap<>(builder, StrongKeyWeakValueEntry.Helper.instance()); + } + if (builder.getKeyStrength() == Strength.WEAK + && builder.getValueStrength() == Strength.STRONG) { + return new MapMakerInternalMap<>(builder, WeakKeyStrongValueEntry.Helper.instance()); + } + if (builder.getKeyStrength() == Strength.WEAK && builder.getValueStrength() == Strength.WEAK) { + return new MapMakerInternalMap<>(builder, WeakKeyWeakValueEntry.Helper.instance()); + } + throw new AssertionError(); + } + + /** + * Returns a fresh {@link MapMakerInternalMap} with {@link MapMaker.Dummy} values but otherwise as + * specified by the given {@code builder}. The returned {@link MapMakerInternalMap} will be + * optimized to saved memory. Since {@link MapMaker.Dummy} is a singleton, we don't need to store + * any values at all. Because of this optimization, {@code build.getValueStrength()} must be + * {@link Strength#STRONG}. + * + *

This method is intended to only be used by the internal implementation of {@link Interners}, + * since a map of dummy values is the exact use case there. + */ + static + MapMakerInternalMap, ?> createWithDummyValues( + MapMaker builder) { + if (builder.getKeyStrength() == Strength.STRONG + && builder.getValueStrength() == Strength.STRONG) { + return new MapMakerInternalMap<>(builder, StrongKeyDummyValueEntry.Helper.instance()); + } + if (builder.getKeyStrength() == Strength.WEAK + && builder.getValueStrength() == Strength.STRONG) { + return new MapMakerInternalMap<>(builder, WeakKeyDummyValueEntry.Helper.instance()); + } + if (builder.getValueStrength() == Strength.WEAK) { + throw new IllegalArgumentException("Map cannot have both weak and dummy values"); + } + throw new AssertionError(); + } + + enum Strength { + STRONG { + @Override + Equivalence defaultEquivalence() { + return Equivalence.equals(); + } + }, + + WEAK { + @Override + Equivalence defaultEquivalence() { + return Equivalence.identity(); + } + }; + + /** + * Returns the default equivalence strategy used to compare and hash keys or values referenced + * at this strength. This strategy will be used unless the user explicitly specifies an + * alternate strategy. + */ + abstract Equivalence defaultEquivalence(); + } + + /** + * A helper object for operating on {@link InternalEntry} instances in a type-safe and efficient + * manner. + * + *

For each of the four combinations of strong/weak key and strong/weak value, there are + * corresponding {@link InternalEntry}, {@link Segment}, and {@link InternalEntryHelper} + * implementations. + * + * @param the type of the key in each entry + * @param the type of the value in each entry + * @param the type of the {@link InternalEntry} entry implementation + * @param the type of the {@link Segment} entry implementation + */ + interface InternalEntryHelper< + K, V, E extends InternalEntry, S extends Segment> { + /** The strength of the key type in each entry. */ + Strength keyStrength(); + + /** The strength of the value type in each entry. */ + Strength valueStrength(); + + /** Returns a freshly created segment, typed at the {@code S} type. */ + S newSegment(MapMakerInternalMap map, int initialCapacity, int maxSegmentSize); + + /** + * Returns a freshly created entry, typed at the {@code E} type, for the given {@code segment}. + */ + E newEntry(S segment, K key, int hash, E next); + + /** + * Returns a freshly created entry, typed at the {@code E} type, for the given {@code segment}, + * that is a copy of the given {@code entry}. + */ + E copy(S segment, E entry, E newNext); + + /** + * Sets the value of the given {@code entry} in the given {@code segment} to be the given {@code + * value} + */ + void setValue(S segment, E entry, V value); + } + + /** + * An entry in a hash table of a {@link Segment}. + * + *

Entries in the map can be in the following states: + * + *

Valid: - Live: valid key/value are set + * + *

Invalid: - Collected: key/value was partially collected, but not yet cleaned up + */ + interface InternalEntry> { + /** Gets the next entry in the chain. */ + E getNext(); + + /** Gets the entry's hash. */ + int getHash(); + + /** Gets the key for this entry. */ + K getKey(); + + /** Gets the value for the entry. */ + V getValue(); + } + + /* + * Note: the following classes have a lot of duplicate code. It sucks, but it saves a lot of + * memory. If only Java had mixins! + */ + + /** Base class for {@link InternalEntry} implementations for strong keys. */ + abstract static class AbstractStrongKeyEntry> + implements InternalEntry { + final K key; + final int hash; + final E next; + + AbstractStrongKeyEntry(K key, int hash, E next) { + this.key = key; + this.hash = hash; + this.next = next; + } + + @Override + public K getKey() { + return this.key; + } + + @Override + public int getHash() { + return hash; + } + + @Override + public E getNext() { + return next; + } + } + + /** Marker interface for {@link InternalEntry} implementations for strong values. */ + interface StrongValueEntry> + extends InternalEntry {} + + /** Marker interface for {@link InternalEntry} implementations for weak values. */ + interface WeakValueEntry> extends InternalEntry { + /** Gets the weak value reference held by entry. */ + WeakValueReference getValueReference(); + + /** + * Clears the weak value reference held by the entry. Should be used when the entry's value is + * overwritten. + */ + void clearValue(); + } + + @SuppressWarnings("unchecked") // impl never uses a parameter or returns any non-null value + static > + WeakValueReference unsetWeakValueReference() { + return (WeakValueReference) UNSET_WEAK_VALUE_REFERENCE; + } + + /** Concrete implementation of {@link InternalEntry} for strong keys and strong values. */ + static final class StrongKeyStrongValueEntry + extends AbstractStrongKeyEntry> + implements StrongValueEntry> { + private volatile V value = null; + + StrongKeyStrongValueEntry(K key, int hash, StrongKeyStrongValueEntry next) { + super(key, hash, next); + } + + @Override + public V getValue() { + return value; + } + + void setValue(V value) { + this.value = value; + } + + StrongKeyStrongValueEntry copy(StrongKeyStrongValueEntry newNext) { + StrongKeyStrongValueEntry newEntry = + new StrongKeyStrongValueEntry<>(this.key, this.hash, newNext); + newEntry.value = this.value; + return newEntry; + } + + /** Concrete implementation of {@link InternalEntryHelper} for strong keys and strong values. */ + static final class Helper + implements InternalEntryHelper< + K, V, StrongKeyStrongValueEntry, StrongKeyStrongValueSegment> { + private static final Helper INSTANCE = new Helper<>(); + + @SuppressWarnings("unchecked") + static Helper instance() { + return (Helper) INSTANCE; + } + + @Override + public Strength keyStrength() { + return Strength.STRONG; + } + + @Override + public Strength valueStrength() { + return Strength.STRONG; + } + + @Override + public StrongKeyStrongValueSegment newSegment( + MapMakerInternalMap< + K, V, StrongKeyStrongValueEntry, StrongKeyStrongValueSegment> + map, + int initialCapacity, + int maxSegmentSize) { + return new StrongKeyStrongValueSegment<>(map, initialCapacity, maxSegmentSize); + } + + @Override + public StrongKeyStrongValueEntry copy( + StrongKeyStrongValueSegment segment, + StrongKeyStrongValueEntry entry, + StrongKeyStrongValueEntry newNext) { + return entry.copy(newNext); + } + + @Override + public void setValue( + StrongKeyStrongValueSegment segment, + StrongKeyStrongValueEntry entry, + V value) { + entry.setValue(value); + } + + @Override + public StrongKeyStrongValueEntry newEntry( + StrongKeyStrongValueSegment segment, + K key, + int hash, + StrongKeyStrongValueEntry next) { + return new StrongKeyStrongValueEntry<>(key, hash, next); + } + } + } + + /** Concrete implementation of {@link InternalEntry} for strong keys and weak values. */ + static final class StrongKeyWeakValueEntry + extends AbstractStrongKeyEntry> + implements WeakValueEntry> { + private volatile WeakValueReference> valueReference = + unsetWeakValueReference(); + + StrongKeyWeakValueEntry(K key, int hash, StrongKeyWeakValueEntry next) { + super(key, hash, next); + } + + @Override + public V getValue() { + return valueReference.get(); + } + + @Override + public void clearValue() { + valueReference.clear(); + } + + void setValue(V value, ReferenceQueue queueForValues) { + WeakValueReference> previous = this.valueReference; + this.valueReference = new WeakValueReferenceImpl<>(queueForValues, value, this); + previous.clear(); + } + + StrongKeyWeakValueEntry copy( + ReferenceQueue queueForValues, StrongKeyWeakValueEntry newNext) { + StrongKeyWeakValueEntry newEntry = new StrongKeyWeakValueEntry<>(key, hash, newNext); + newEntry.valueReference = valueReference.copyFor(queueForValues, newEntry); + return newEntry; + } + + @Override + public WeakValueReference> getValueReference() { + return valueReference; + } + + /** Concrete implementation of {@link InternalEntryHelper} for strong keys and weak values. */ + static final class Helper + implements InternalEntryHelper< + K, V, StrongKeyWeakValueEntry, StrongKeyWeakValueSegment> { + private static final Helper INSTANCE = new Helper<>(); + + @SuppressWarnings("unchecked") + static Helper instance() { + return (Helper) INSTANCE; + } + + @Override + public Strength keyStrength() { + return Strength.STRONG; + } + + @Override + public Strength valueStrength() { + return Strength.WEAK; + } + + @Override + public StrongKeyWeakValueSegment newSegment( + MapMakerInternalMap, StrongKeyWeakValueSegment> + map, + int initialCapacity, + int maxSegmentSize) { + return new StrongKeyWeakValueSegment<>(map, initialCapacity, maxSegmentSize); + } + + @Override + public StrongKeyWeakValueEntry copy( + StrongKeyWeakValueSegment segment, + StrongKeyWeakValueEntry entry, + StrongKeyWeakValueEntry newNext) { + if (Segment.isCollected(entry)) { + return null; + } + return entry.copy(segment.queueForValues, newNext); + } + + @Override + public void setValue( + StrongKeyWeakValueSegment segment, StrongKeyWeakValueEntry entry, V value) { + entry.setValue(value, segment.queueForValues); + } + + @Override + public StrongKeyWeakValueEntry newEntry( + StrongKeyWeakValueSegment segment, + K key, + int hash, + StrongKeyWeakValueEntry next) { + return new StrongKeyWeakValueEntry<>(key, hash, next); + } + } + } + + /** Concrete implementation of {@link InternalEntry} for strong keys and {@link Dummy} values. */ + static final class StrongKeyDummyValueEntry + extends AbstractStrongKeyEntry> + implements StrongValueEntry> { + StrongKeyDummyValueEntry(K key, int hash, StrongKeyDummyValueEntry next) { + super(key, hash, next); + } + + @Override + public Dummy getValue() { + return Dummy.VALUE; + } + + void setValue(Dummy value) {} + + StrongKeyDummyValueEntry copy(StrongKeyDummyValueEntry newNext) { + return new StrongKeyDummyValueEntry(this.key, this.hash, newNext); + } + + /** + * Concrete implementation of {@link InternalEntryHelper} for strong keys and {@link Dummy} + * values. + */ + static final class Helper + implements InternalEntryHelper< + K, Dummy, StrongKeyDummyValueEntry, StrongKeyDummyValueSegment> { + private static final Helper INSTANCE = new Helper<>(); + + @SuppressWarnings("unchecked") + static Helper instance() { + return (Helper) INSTANCE; + } + + @Override + public Strength keyStrength() { + return Strength.STRONG; + } + + @Override + public Strength valueStrength() { + return Strength.STRONG; + } + + @Override + public StrongKeyDummyValueSegment newSegment( + MapMakerInternalMap, StrongKeyDummyValueSegment> + map, + int initialCapacity, + int maxSegmentSize) { + return new StrongKeyDummyValueSegment(map, initialCapacity, maxSegmentSize); + } + + @Override + public StrongKeyDummyValueEntry copy( + StrongKeyDummyValueSegment segment, + StrongKeyDummyValueEntry entry, + StrongKeyDummyValueEntry newNext) { + return entry.copy(newNext); + } + + @Override + public void setValue( + StrongKeyDummyValueSegment segment, StrongKeyDummyValueEntry entry, Dummy value) {} + + @Override + public StrongKeyDummyValueEntry newEntry( + StrongKeyDummyValueSegment segment, + K key, + int hash, + StrongKeyDummyValueEntry next) { + return new StrongKeyDummyValueEntry(key, hash, next); + } + } + } + + /** Base class for {@link InternalEntry} implementations for weak keys. */ + abstract static class AbstractWeakKeyEntry> + extends WeakReference implements InternalEntry { + final int hash; + final E next; + + AbstractWeakKeyEntry(ReferenceQueue queue, K key, int hash, E next) { + super(key, queue); + this.hash = hash; + this.next = next; + } + + @Override + public K getKey() { + return get(); + } + + @Override + public int getHash() { + return hash; + } + + @Override + public E getNext() { + return next; + } + } + + /** Concrete implementation of {@link InternalEntry} for weak keys and {@link Dummy} values. */ + static final class WeakKeyDummyValueEntry + extends AbstractWeakKeyEntry> + implements StrongValueEntry> { + WeakKeyDummyValueEntry( + ReferenceQueue queue, K key, int hash, WeakKeyDummyValueEntry next) { + super(queue, key, hash, next); + } + + @Override + public Dummy getValue() { + return Dummy.VALUE; + } + + void setValue(Dummy value) {} + + WeakKeyDummyValueEntry copy( + ReferenceQueue queueForKeys, WeakKeyDummyValueEntry newNext) { + return new WeakKeyDummyValueEntry(queueForKeys, getKey(), this.hash, newNext); + } + + /** + * Concrete implementation of {@link InternalEntryHelper} for weak keys and {@link Dummy} + * values. + */ + static final class Helper + implements InternalEntryHelper< + K, Dummy, WeakKeyDummyValueEntry, WeakKeyDummyValueSegment> { + private static final Helper INSTANCE = new Helper<>(); + + @SuppressWarnings("unchecked") + static Helper instance() { + return (Helper) INSTANCE; + } + + @Override + public Strength keyStrength() { + return Strength.WEAK; + } + + @Override + public Strength valueStrength() { + return Strength.STRONG; + } + + @Override + public WeakKeyDummyValueSegment newSegment( + MapMakerInternalMap, WeakKeyDummyValueSegment> map, + int initialCapacity, + int maxSegmentSize) { + return new WeakKeyDummyValueSegment(map, initialCapacity, maxSegmentSize); + } + + @Override + public WeakKeyDummyValueEntry copy( + WeakKeyDummyValueSegment segment, + WeakKeyDummyValueEntry entry, + WeakKeyDummyValueEntry newNext) { + if (entry.getKey() == null) { + // key collected + return null; + } + return entry.copy(segment.queueForKeys, newNext); + } + + @Override + public void setValue( + WeakKeyDummyValueSegment segment, WeakKeyDummyValueEntry entry, Dummy value) {} + + @Override + public WeakKeyDummyValueEntry newEntry( + WeakKeyDummyValueSegment segment, + K key, + int hash, + WeakKeyDummyValueEntry next) { + return new WeakKeyDummyValueEntry(segment.queueForKeys, key, hash, next); + } + } + } + + /** Concrete implementation of {@link InternalEntry} for weak keys and strong values. */ + static final class WeakKeyStrongValueEntry + extends AbstractWeakKeyEntry> + implements StrongValueEntry> { + private volatile V value = null; + + WeakKeyStrongValueEntry( + ReferenceQueue queue, K key, int hash, WeakKeyStrongValueEntry next) { + super(queue, key, hash, next); + } + + @Override + public V getValue() { + return value; + } + + void setValue(V value) { + this.value = value; + } + + WeakKeyStrongValueEntry copy( + ReferenceQueue queueForKeys, WeakKeyStrongValueEntry newNext) { + WeakKeyStrongValueEntry newEntry = + new WeakKeyStrongValueEntry<>(queueForKeys, getKey(), this.hash, newNext); + newEntry.setValue(value); + return newEntry; + } + + /** Concrete implementation of {@link InternalEntryHelper} for weak keys and strong values. */ + static final class Helper + implements InternalEntryHelper< + K, V, WeakKeyStrongValueEntry, WeakKeyStrongValueSegment> { + private static final Helper INSTANCE = new Helper<>(); + + @SuppressWarnings("unchecked") + static Helper instance() { + return (Helper) INSTANCE; + } + + @Override + public Strength keyStrength() { + return Strength.WEAK; + } + + @Override + public Strength valueStrength() { + return Strength.STRONG; + } + + @Override + public WeakKeyStrongValueSegment newSegment( + MapMakerInternalMap, WeakKeyStrongValueSegment> + map, + int initialCapacity, + int maxSegmentSize) { + return new WeakKeyStrongValueSegment<>(map, initialCapacity, maxSegmentSize); + } + + @Override + public WeakKeyStrongValueEntry copy( + WeakKeyStrongValueSegment segment, + WeakKeyStrongValueEntry entry, + WeakKeyStrongValueEntry newNext) { + if (entry.getKey() == null) { + // key collected + return null; + } + return entry.copy(segment.queueForKeys, newNext); + } + + @Override + public void setValue( + WeakKeyStrongValueSegment segment, WeakKeyStrongValueEntry entry, V value) { + entry.setValue(value); + } + + @Override + public WeakKeyStrongValueEntry newEntry( + WeakKeyStrongValueSegment segment, + K key, + int hash, + WeakKeyStrongValueEntry next) { + return new WeakKeyStrongValueEntry<>(segment.queueForKeys, key, hash, next); + } + } + } + + /** Concrete implementation of {@link InternalEntry} for weak keys and weak values. */ + static final class WeakKeyWeakValueEntry + extends AbstractWeakKeyEntry> + implements WeakValueEntry> { + private volatile WeakValueReference> valueReference = + unsetWeakValueReference(); + + WeakKeyWeakValueEntry( + ReferenceQueue queue, K key, int hash, WeakKeyWeakValueEntry next) { + super(queue, key, hash, next); + } + + @Override + public V getValue() { + return valueReference.get(); + } + + WeakKeyWeakValueEntry copy( + ReferenceQueue queueForKeys, + ReferenceQueue queueForValues, + WeakKeyWeakValueEntry newNext) { + WeakKeyWeakValueEntry newEntry = + new WeakKeyWeakValueEntry<>(queueForKeys, getKey(), this.hash, newNext); + newEntry.valueReference = valueReference.copyFor(queueForValues, newEntry); + return newEntry; + } + + @Override + public void clearValue() { + valueReference.clear(); + } + + void setValue(V value, ReferenceQueue queueForValues) { + WeakValueReference> previous = this.valueReference; + this.valueReference = new WeakValueReferenceImpl<>(queueForValues, value, this); + previous.clear(); + } + + @Override + public WeakValueReference> getValueReference() { + return valueReference; + } + + /** Concrete implementation of {@link InternalEntryHelper} for weak keys and weak values. */ + static final class Helper + implements InternalEntryHelper< + K, V, WeakKeyWeakValueEntry, WeakKeyWeakValueSegment> { + private static final Helper INSTANCE = new Helper<>(); + + @SuppressWarnings("unchecked") + static Helper instance() { + return (Helper) INSTANCE; + } + + @Override + public Strength keyStrength() { + return Strength.WEAK; + } + + @Override + public Strength valueStrength() { + return Strength.WEAK; + } + + @Override + public WeakKeyWeakValueSegment newSegment( + MapMakerInternalMap, WeakKeyWeakValueSegment> map, + int initialCapacity, + int maxSegmentSize) { + return new WeakKeyWeakValueSegment<>(map, initialCapacity, maxSegmentSize); + } + + @Override + public WeakKeyWeakValueEntry copy( + WeakKeyWeakValueSegment segment, + WeakKeyWeakValueEntry entry, + WeakKeyWeakValueEntry newNext) { + if (entry.getKey() == null) { + // key collected + return null; + } + if (Segment.isCollected(entry)) { + return null; + } + return entry.copy(segment.queueForKeys, segment.queueForValues, newNext); + } + + @Override + public void setValue( + WeakKeyWeakValueSegment segment, WeakKeyWeakValueEntry entry, V value) { + entry.setValue(value, segment.queueForValues); + } + + @Override + public WeakKeyWeakValueEntry newEntry( + WeakKeyWeakValueSegment segment, + K key, + int hash, + WeakKeyWeakValueEntry next) { + return new WeakKeyWeakValueEntry<>(segment.queueForKeys, key, hash, next); + } + } + } + + /** A weakly referenced value that also has a reference to its containing entry. */ + interface WeakValueReference> { + /** + * Returns the current value being referenced, or {@code null} if there is none (e.g. because + * either it got collected, or {@link #clear} was called, or it wasn't set in the first place). + */ + + V get(); + + /** Returns the entry which contains this {@link WeakValueReference}. */ + E getEntry(); + + /** Unsets the referenced value. Subsequent calls to {@link #get} will return {@code null}. */ + void clear(); + + /** + * Returns a freshly created {@link WeakValueReference} for the given {@code entry} (and on the + * given {@code queue} with the same value as this {@link WeakValueReference}. + */ + WeakValueReference copyFor(ReferenceQueue queue, E entry); + } + + /** + * A dummy implementation of {@link InternalEntry}, solely for use in the type signature of {@link + * #UNSET_WEAK_VALUE_REFERENCE} below. + */ + static final class DummyInternalEntry + implements InternalEntry { + private DummyInternalEntry() { + throw new AssertionError(); + } + + @Override + public DummyInternalEntry getNext() { + throw new AssertionError(); + } + + @Override + public int getHash() { + throw new AssertionError(); + } + + @Override + public Object getKey() { + throw new AssertionError(); + } + + @Override + public Object getValue() { + throw new AssertionError(); + } + } + + /** + * A singleton {@link WeakValueReference} used to denote an unset value in a entry with weak + * values. + */ + static final WeakValueReference UNSET_WEAK_VALUE_REFERENCE = + new WeakValueReference() { + @Override + public DummyInternalEntry getEntry() { + return null; + } + + @Override + public void clear() {} + + @Override + public Object get() { + return null; + } + + @Override + public WeakValueReference copyFor( + ReferenceQueue queue, DummyInternalEntry entry) { + return this; + } + }; + + /** Concrete implementation of {@link WeakValueReference}. */ + static final class WeakValueReferenceImpl> + extends WeakReference implements WeakValueReference { + final E entry; + + WeakValueReferenceImpl(ReferenceQueue queue, V referent, E entry) { + super(referent, queue); + this.entry = entry; + } + + @Override + public E getEntry() { + return entry; + } + + @Override + public WeakValueReference copyFor(ReferenceQueue queue, E entry) { + return new WeakValueReferenceImpl<>(queue, get(), entry); + } + } + + /** + * Applies a supplemental hash function to a given hash code, which defends against poor quality + * hash functions. This is critical when the concurrent hash map uses power-of-two length hash + * tables, that otherwise encounter collisions for hash codes that do not differ in lower or upper + * bits. + * + * @param h hash code + */ + static int rehash(int h) { + // Spread bits to regularize both segment and index locations, + // using variant of single-word Wang/Jenkins hash. + // TODO(kevinb): use Hashing/move this to Hashing? + h += (h << 15) ^ 0xffffcd7d; + h ^= (h >>> 10); + h += (h << 3); + h ^= (h >>> 6); + h += (h << 2) + (h << 14); + return h ^ (h >>> 16); + } + + /** + * This method is a convenience for testing. Code should call {@link Segment#copyEntry} directly. + */ + // Guarded By Segment.this + @VisibleForTesting + E copyEntry(E original, E newNext) { + int hash = original.getHash(); + return segmentFor(hash).copyEntry(original, newNext); + } + + int hash(Object key) { + int h = keyEquivalence.hash(key); + return rehash(h); + } + + void reclaimValue(WeakValueReference valueReference) { + E entry = valueReference.getEntry(); + int hash = entry.getHash(); + segmentFor(hash).reclaimValue(entry.getKey(), hash, valueReference); + } + + void reclaimKey(E entry) { + int hash = entry.getHash(); + segmentFor(hash).reclaimKey(entry, hash); + } + + /** + * This method is a convenience for testing. Code should call {@link Segment#getLiveValue} + * instead. + */ + @VisibleForTesting + boolean isLiveForTesting(InternalEntry entry) { + return segmentFor(entry.getHash()).getLiveValueForTesting(entry) != null; + } + + /** + * Returns the segment that should be used for a key with the given hash. + * + * @param hash the hash code for the key + * @return the segment + */ + Segment segmentFor(int hash) { + // TODO(fry): Lazily create segments? + return segments[(hash >>> segmentShift) & segmentMask]; + } + + Segment createSegment(int initialCapacity, int maxSegmentSize) { + return entryHelper.newSegment(this, initialCapacity, maxSegmentSize); + } + + /** + * Gets the value from an entry. Returns {@code null} if the entry is invalid, partially-collected + * or computing. + */ + V getLiveValue(E entry) { + if (entry.getKey() == null) { + return null; + } + return entry.getValue(); + } + + @SuppressWarnings("unchecked") + final Segment[] newSegmentArray(int ssize) { + return new Segment[ssize]; + } + + // Inner Classes + + /** + * Segments are specialized versions of hash tables. This subclass inherits from ReentrantLock + * opportunistically, just to simplify some locking and avoid separate construction. + */ + @SuppressWarnings("serial") // This class is never serialized. + abstract static class Segment< + K, V, E extends InternalEntry, S extends Segment> + extends ReentrantLock { + + /* + * Segments maintain a table of entry lists that are ALWAYS kept in a consistent state, so can + * be read without locking. Next fields of nodes are immutable (final). All list additions are + * performed at the front of each bin. This makes it easy to check changes, and also fast to + * traverse. When nodes would otherwise be changed, new nodes are created to replace them. This + * works well for hash tables since the bin lists tend to be short. (The average length is less + * than two.) + * + * Read operations can thus proceed without locking, but rely on selected uses of volatiles to + * ensure that completed write operations performed by other threads are noticed. For most + * purposes, the "count" field, tracking the number of elements, serves as that volatile + * variable ensuring visibility. This is convenient because this field needs to be read in many + * read operations anyway: + * + * - All (unsynchronized) read operations must first read the "count" field, and should not + * look at table entries if it is 0. + * + * - All (synchronized) write operations should write to the "count" field after structurally + * changing any bin. The operations must not take any action that could even momentarily + * cause a concurrent read operation to see inconsistent data. This is made easier by the + * nature of the read operations in Map. For example, no operation can reveal that the table + * has grown but the threshold has not yet been updated, so there are no atomicity requirements + * for this with respect to reads. + * + * As a guide, all critical volatile reads and writes to the count field are marked in code + * comments. + */ + + final MapMakerInternalMap map; + + /** + * The number of live elements in this segment's region. This does not include unset elements + * which are awaiting cleanup. + */ + volatile int count; + + /** + * Number of updates that alter the size of the table. This is used during bulk-read methods to + * make sure they see a consistent snapshot: If modCounts change during a traversal of segments + * computing size or checking containsValue, then we might have an inconsistent view of state so + * (usually) must retry. + */ + int modCount; + + /** + * The table is expanded when its size exceeds this threshold. (The value of this field is + * always {@code (int) (capacity * 0.75)}.) + */ + int threshold; + + /** The per-segment table. */ + volatile AtomicReferenceArray table; + + /** The maximum size of this map. MapMaker.UNSET_INT if there is no maximum. */ + final int maxSegmentSize; + + /** + * A counter of the number of reads since the last write, used to drain queues on a small + * fraction of read operations. + */ + final AtomicInteger readCount = new AtomicInteger(); + + Segment(MapMakerInternalMap map, int initialCapacity, int maxSegmentSize) { + this.map = map; + this.maxSegmentSize = maxSegmentSize; + initTable(newEntryArray(initialCapacity)); + } + + /** + * Returns {@code this} up-casted to the specific {@link Segment} implementation type {@code S}. + * + *

This method exists so that the {@link Segment} code can be generic in terms of {@code S}, + * the type of the concrete implementation. + */ + abstract S self(); + + /** Drains the reference queues used by this segment, if any. */ + + void maybeDrainReferenceQueues() {} + + /** Clears the reference queues used by this segment, if any. */ + void maybeClearReferenceQueues() {} + + /** Sets the value of the given {@code entry}. */ + void setValue(E entry, V value) { + this.map.entryHelper.setValue(self(), entry, value); + } + + /** Returns a copy of the given {@code entry}. */ + E copyEntry(E original, E newNext) { + return this.map.entryHelper.copy(self(), original, newNext); + } + + AtomicReferenceArray newEntryArray(int size) { + return new AtomicReferenceArray(size); + } + + void initTable(AtomicReferenceArray newTable) { + this.threshold = newTable.length() * 3 / 4; // 0.75 + if (this.threshold == maxSegmentSize) { + // prevent spurious expansion before eviction + this.threshold++; + } + this.table = newTable; + } + + // Convenience methods for testing + + /** + * Unsafe cast of the given entry to {@code E}, the type of the specific {@link InternalEntry} + * implementation type. + * + *

This method is provided as a convenience for tests. Otherwise they'd need to be + * knowledgable about all the implementation details of our type system trickery. + */ + abstract E castForTesting(InternalEntry entry); + + /** Unsafely extracts the key reference queue used by this segment. */ + ReferenceQueue getKeyReferenceQueueForTesting() { + throw new AssertionError(); + } + + /** Unsafely extracts the value reference queue used by this segment. */ + ReferenceQueue getValueReferenceQueueForTesting() { + throw new AssertionError(); + } + + /** Unsafely extracts the weak value reference inside of the given {@code entry}. */ + WeakValueReference getWeakValueReferenceForTesting(InternalEntry entry) { + throw new AssertionError(); + } + + /** + * Unsafely creates of a fresh {@link WeakValueReference}, referencing the given {@code value}, + * for the given {@code entry} + */ + WeakValueReference newWeakValueReferenceForTesting( + InternalEntry entry, V value) { + throw new AssertionError(); + } + + /** + * Unsafely sets the weak value reference inside the given {@code entry} to be the given {@code + * valueReference} + */ + void setWeakValueReferenceForTesting( + InternalEntry entry, + WeakValueReference> valueReference) { + throw new AssertionError(); + } + + /** + * Unsafely sets the given index of this segment's internal hash table to be the given entry. + */ + void setTableEntryForTesting(int i, InternalEntry entry) { + table.set(i, castForTesting(entry)); + } + + /** Unsafely returns a copy of the given entry. */ + E copyForTesting(InternalEntry entry, InternalEntry newNext) { + return this.map.entryHelper.copy(self(), castForTesting(entry), castForTesting(newNext)); + } + + /** Unsafely sets the value of the given entry. */ + void setValueForTesting(InternalEntry entry, V value) { + this.map.entryHelper.setValue(self(), castForTesting(entry), value); + } + + /** Unsafely returns a fresh entry. */ + E newEntryForTesting(K key, int hash, InternalEntry next) { + return this.map.entryHelper.newEntry(self(), key, hash, castForTesting(next)); + } + + /** Unsafely removes the given entry from this segment's hash table. */ + + boolean removeTableEntryForTesting(InternalEntry entry) { + return removeEntryForTesting(castForTesting(entry)); + } + + /** Unsafely removes the given entry from the given chain in this segment's hash table. */ + E removeFromChainForTesting(InternalEntry first, InternalEntry entry) { + return removeFromChain(castForTesting(first), castForTesting(entry)); + } + + /** + * Unsafely returns the value of the given entry if it's still live, or {@code null} otherwise. + */ + + V getLiveValueForTesting(InternalEntry entry) { + return getLiveValue(castForTesting(entry)); + } + + // reference queues, for garbage collection cleanup + + /** Cleanup collected entries when the lock is available. */ + void tryDrainReferenceQueues() { + if (tryLock()) { + try { + maybeDrainReferenceQueues(); + } finally { + unlock(); + } + } + } + + + void drainKeyReferenceQueue(ReferenceQueue keyReferenceQueue) { + Reference ref; + int i = 0; + while ((ref = keyReferenceQueue.poll()) != null) { + @SuppressWarnings("unchecked") + E entry = (E) ref; + map.reclaimKey(entry); + if (++i == DRAIN_MAX) { + break; + } + } + } + + + void drainValueReferenceQueue(ReferenceQueue valueReferenceQueue) { + Reference ref; + int i = 0; + while ((ref = valueReferenceQueue.poll()) != null) { + @SuppressWarnings("unchecked") + WeakValueReference valueReference = (WeakValueReference) ref; + map.reclaimValue(valueReference); + if (++i == DRAIN_MAX) { + break; + } + } + } + + void clearReferenceQueue(ReferenceQueue referenceQueue) { + while (referenceQueue.poll() != null) {} + } + + /** Returns first entry of bin for given hash. */ + E getFirst(int hash) { + // read this volatile field only once + AtomicReferenceArray table = this.table; + return table.get(hash & (table.length() - 1)); + } + + // Specialized implementations of map methods + + E getEntry(Object key, int hash) { + if (count != 0) { // read-volatile + for (E e = getFirst(hash); e != null; e = e.getNext()) { + if (e.getHash() != hash) { + continue; + } + + K entryKey = e.getKey(); + if (entryKey == null) { + tryDrainReferenceQueues(); + continue; + } + + if (map.keyEquivalence.equivalent(key, entryKey)) { + return e; + } + } + } + + return null; + } + + E getLiveEntry(Object key, int hash) { + return getEntry(key, hash); + } + + V get(Object key, int hash) { + try { + E e = getLiveEntry(key, hash); + if (e == null) { + return null; + } + + V value = e.getValue(); + if (value == null) { + tryDrainReferenceQueues(); + } + return value; + } finally { + postReadCleanup(); + } + } + + boolean containsKey(Object key, int hash) { + try { + if (count != 0) { // read-volatile + E e = getLiveEntry(key, hash); + return e != null && e.getValue() != null; + } + + return false; + } finally { + postReadCleanup(); + } + } + + /** + * This method is a convenience for testing. Code should call {@link + * MapMakerInternalMap#containsValue} directly. + */ + @VisibleForTesting + boolean containsValue(Object value) { + try { + if (count != 0) { // read-volatile + AtomicReferenceArray table = this.table; + int length = table.length(); + for (int i = 0; i < length; ++i) { + for (E e = table.get(i); e != null; e = e.getNext()) { + V entryValue = getLiveValue(e); + if (entryValue == null) { + continue; + } + if (map.valueEquivalence().equivalent(value, entryValue)) { + return true; + } + } + } + } + + return false; + } finally { + postReadCleanup(); + } + } + + V put(K key, int hash, V value, boolean onlyIfAbsent) { + lock(); + try { + preWriteCleanup(); + + int newCount = this.count + 1; + if (newCount > this.threshold) { // ensure capacity + expand(); + newCount = this.count + 1; + } + + AtomicReferenceArray table = this.table; + int index = hash & (table.length() - 1); + E first = table.get(index); + + // Look for an existing entry. + for (E e = first; e != null; e = e.getNext()) { + K entryKey = e.getKey(); + if (e.getHash() == hash + && entryKey != null + && map.keyEquivalence.equivalent(key, entryKey)) { + // We found an existing entry. + + V entryValue = e.getValue(); + + if (entryValue == null) { + ++modCount; + setValue(e, value); + newCount = this.count; // count remains unchanged + this.count = newCount; // write-volatile + return null; + } else if (onlyIfAbsent) { + // Mimic + // "if (!map.containsKey(key)) ... + // else return map.get(key); + return entryValue; + } else { + // clobber existing entry, count remains unchanged + ++modCount; + setValue(e, value); + return entryValue; + } + } + } + + // Create a new entry. + ++modCount; + E newEntry = map.entryHelper.newEntry(self(), key, hash, first); + setValue(newEntry, value); + table.set(index, newEntry); + this.count = newCount; // write-volatile + return null; + } finally { + unlock(); + } + } + + /** Expands the table if possible. */ + + void expand() { + AtomicReferenceArray oldTable = table; + int oldCapacity = oldTable.length(); + if (oldCapacity >= MAXIMUM_CAPACITY) { + return; + } + + /* + * Reclassify nodes in each list to new Map. Because we are using power-of-two expansion, the + * elements from each bin must either stay at same index, or move with a power of two offset. + * We eliminate unnecessary node creation by catching cases where old nodes can be reused + * because their next fields won't change. Statistically, at the default threshold, only + * about one-sixth of them need cloning when a table doubles. The nodes they replace will be + * garbage collectable as soon as they are no longer referenced by any reader thread that may + * be in the midst of traversing table right now. + */ + + int newCount = count; + AtomicReferenceArray newTable = newEntryArray(oldCapacity << 1); + threshold = newTable.length() * 3 / 4; + int newMask = newTable.length() - 1; + for (int oldIndex = 0; oldIndex < oldCapacity; ++oldIndex) { + // We need to guarantee that any existing reads of old Map can + // proceed. So we cannot yet null out each bin. + E head = oldTable.get(oldIndex); + + if (head != null) { + E next = head.getNext(); + int headIndex = head.getHash() & newMask; + + // Single node on list + if (next == null) { + newTable.set(headIndex, head); + } else { + // Reuse the consecutive sequence of nodes with the same target + // index from the end of the list. tail points to the first + // entry in the reusable list. + E tail = head; + int tailIndex = headIndex; + for (E e = next; e != null; e = e.getNext()) { + int newIndex = e.getHash() & newMask; + if (newIndex != tailIndex) { + // The index changed. We'll need to copy the previous entry. + tailIndex = newIndex; + tail = e; + } + } + newTable.set(tailIndex, tail); + + // Clone nodes leading up to the tail. + for (E e = head; e != tail; e = e.getNext()) { + int newIndex = e.getHash() & newMask; + E newNext = newTable.get(newIndex); + E newFirst = copyEntry(e, newNext); + if (newFirst != null) { + newTable.set(newIndex, newFirst); + } else { + newCount--; + } + } + } + } + } + table = newTable; + this.count = newCount; + } + + boolean replace(K key, int hash, V oldValue, V newValue) { + lock(); + try { + preWriteCleanup(); + + AtomicReferenceArray table = this.table; + int index = hash & (table.length() - 1); + E first = table.get(index); + + for (E e = first; e != null; e = e.getNext()) { + K entryKey = e.getKey(); + if (e.getHash() == hash + && entryKey != null + && map.keyEquivalence.equivalent(key, entryKey)) { + // If the value disappeared, this entry is partially collected, + // and we should pretend like it doesn't exist. + V entryValue = e.getValue(); + if (entryValue == null) { + if (isCollected(e)) { + int newCount = this.count - 1; + ++modCount; + E newFirst = removeFromChain(first, e); + newCount = this.count - 1; + table.set(index, newFirst); + this.count = newCount; // write-volatile + } + return false; + } + + if (map.valueEquivalence().equivalent(oldValue, entryValue)) { + ++modCount; + setValue(e, newValue); + return true; + } else { + // Mimic + // "if (map.containsKey(key) && map.get(key).equals(oldValue))..." + return false; + } + } + } + + return false; + } finally { + unlock(); + } + } + + V replace(K key, int hash, V newValue) { + lock(); + try { + preWriteCleanup(); + + AtomicReferenceArray table = this.table; + int index = hash & (table.length() - 1); + E first = table.get(index); + + for (E e = first; e != null; e = e.getNext()) { + K entryKey = e.getKey(); + if (e.getHash() == hash + && entryKey != null + && map.keyEquivalence.equivalent(key, entryKey)) { + // If the value disappeared, this entry is partially collected, + // and we should pretend like it doesn't exist. + V entryValue = e.getValue(); + if (entryValue == null) { + if (isCollected(e)) { + int newCount = this.count - 1; + ++modCount; + E newFirst = removeFromChain(first, e); + newCount = this.count - 1; + table.set(index, newFirst); + this.count = newCount; // write-volatile + } + return null; + } + + ++modCount; + setValue(e, newValue); + return entryValue; + } + } + + return null; + } finally { + unlock(); + } + } + + + V remove(Object key, int hash) { + lock(); + try { + preWriteCleanup(); + + int newCount = this.count - 1; + AtomicReferenceArray table = this.table; + int index = hash & (table.length() - 1); + E first = table.get(index); + + for (E e = first; e != null; e = e.getNext()) { + K entryKey = e.getKey(); + if (e.getHash() == hash + && entryKey != null + && map.keyEquivalence.equivalent(key, entryKey)) { + V entryValue = e.getValue(); + + if (entryValue != null) { + // TODO(kak): Remove this branch + } else if (isCollected(e)) { + // TODO(kak): Remove this branch + } else { + return null; + } + + ++modCount; + E newFirst = removeFromChain(first, e); + newCount = this.count - 1; + table.set(index, newFirst); + this.count = newCount; // write-volatile + return entryValue; + } + } + + return null; + } finally { + unlock(); + } + } + + boolean remove(Object key, int hash, Object value) { + lock(); + try { + preWriteCleanup(); + + int newCount = this.count - 1; + AtomicReferenceArray table = this.table; + int index = hash & (table.length() - 1); + E first = table.get(index); + + for (E e = first; e != null; e = e.getNext()) { + K entryKey = e.getKey(); + if (e.getHash() == hash + && entryKey != null + && map.keyEquivalence.equivalent(key, entryKey)) { + V entryValue = e.getValue(); + + boolean explicitRemoval = false; + if (map.valueEquivalence().equivalent(value, entryValue)) { + explicitRemoval = true; + } else if (isCollected(e)) { + // TODO(kak): Remove this branch + } else { + return false; + } + + ++modCount; + E newFirst = removeFromChain(first, e); + newCount = this.count - 1; + table.set(index, newFirst); + this.count = newCount; // write-volatile + return explicitRemoval; + } + } + + return false; + } finally { + unlock(); + } + } + + void clear() { + if (count != 0) { + lock(); + try { + AtomicReferenceArray table = this.table; + for (int i = 0; i < table.length(); ++i) { + table.set(i, null); + } + maybeClearReferenceQueues(); + readCount.set(0); + + ++modCount; + count = 0; // write-volatile + } finally { + unlock(); + } + } + } + + /** + * Removes an entry from within a table. All entries following the removed node can stay, but + * all preceding ones need to be cloned. + * + *

This method does not decrement count for the removed entry, but does decrement count for + * all partially collected entries which are skipped. As such callers which are modifying count + * must re-read it after calling removeFromChain. + * + * @param first the first entry of the table + * @param entry the entry being removed from the table + * @return the new first entry for the table + */ + + E removeFromChain(E first, E entry) { + int newCount = count; + E newFirst = entry.getNext(); + for (E e = first; e != entry; e = e.getNext()) { + E next = copyEntry(e, newFirst); + if (next != null) { + newFirst = next; + } else { + newCount--; + } + } + this.count = newCount; + return newFirst; + } + + /** Removes an entry whose key has been garbage collected. */ + + boolean reclaimKey(E entry, int hash) { + lock(); + try { + int newCount = count - 1; + AtomicReferenceArray table = this.table; + int index = hash & (table.length() - 1); + E first = table.get(index); + + for (E e = first; e != null; e = e.getNext()) { + if (e == entry) { + ++modCount; + E newFirst = removeFromChain(first, e); + newCount = this.count - 1; + table.set(index, newFirst); + this.count = newCount; // write-volatile + return true; + } + } + + return false; + } finally { + unlock(); + } + } + + /** Removes an entry whose value has been garbage collected. */ + + boolean reclaimValue(K key, int hash, WeakValueReference valueReference) { + lock(); + try { + int newCount = this.count - 1; + AtomicReferenceArray table = this.table; + int index = hash & (table.length() - 1); + E first = table.get(index); + + for (E e = first; e != null; e = e.getNext()) { + K entryKey = e.getKey(); + if (e.getHash() == hash + && entryKey != null + && map.keyEquivalence.equivalent(key, entryKey)) { + WeakValueReference v = ((WeakValueEntry) e).getValueReference(); + if (v == valueReference) { + ++modCount; + E newFirst = removeFromChain(first, e); + newCount = this.count - 1; + table.set(index, newFirst); + this.count = newCount; // write-volatile + return true; + } + return false; + } + } + + return false; + } finally { + unlock(); + } + } + + /** Clears a value that has not yet been set, and thus does not require count to be modified. */ + + boolean clearValueForTesting( + K key, + int hash, + WeakValueReference> valueReference) { + lock(); + try { + AtomicReferenceArray table = this.table; + int index = hash & (table.length() - 1); + E first = table.get(index); + + for (E e = first; e != null; e = e.getNext()) { + K entryKey = e.getKey(); + if (e.getHash() == hash + && entryKey != null + && map.keyEquivalence.equivalent(key, entryKey)) { + WeakValueReference v = ((WeakValueEntry) e).getValueReference(); + if (v == valueReference) { + E newFirst = removeFromChain(first, e); + table.set(index, newFirst); + return true; + } + return false; + } + } + + return false; + } finally { + unlock(); + } + } + + + boolean removeEntryForTesting(E entry) { + int hash = entry.getHash(); + int newCount = this.count - 1; + AtomicReferenceArray table = this.table; + int index = hash & (table.length() - 1); + E first = table.get(index); + + for (E e = first; e != null; e = e.getNext()) { + if (e == entry) { + ++modCount; + E newFirst = removeFromChain(first, e); + newCount = this.count - 1; + table.set(index, newFirst); + this.count = newCount; // write-volatile + return true; + } + } + + return false; + } + + /** + * Returns {@code true} if the value has been partially collected, meaning that the value is + * null. + */ + static > boolean isCollected(E entry) { + return entry.getValue() == null; + } + + /** + * Gets the value from an entry. Returns {@code null} if the entry is invalid or + * partially-collected. + */ + + V getLiveValue(E entry) { + if (entry.getKey() == null) { + tryDrainReferenceQueues(); + return null; + } + V value = entry.getValue(); + if (value == null) { + tryDrainReferenceQueues(); + return null; + } + + return value; + } + + /** + * Performs routine cleanup following a read. Normally cleanup happens during writes, or from + * the cleanupExecutor. If cleanup is not observed after a sufficient number of reads, try + * cleaning up from the read thread. + */ + void postReadCleanup() { + if ((readCount.incrementAndGet() & DRAIN_THRESHOLD) == 0) { + runCleanup(); + } + } + + /** + * Performs routine cleanup prior to executing a write. This should be called every time a write + * thread acquires the segment lock, immediately after acquiring the lock. + */ + + void preWriteCleanup() { + runLockedCleanup(); + } + + void runCleanup() { + runLockedCleanup(); + } + + void runLockedCleanup() { + if (tryLock()) { + try { + maybeDrainReferenceQueues(); + readCount.set(0); + } finally { + unlock(); + } + } + } + } + + /** Concrete implementation of {@link Segment} for strong keys and strong values. */ + static final class StrongKeyStrongValueSegment + extends Segment, StrongKeyStrongValueSegment> { + StrongKeyStrongValueSegment( + MapMakerInternalMap< + K, V, StrongKeyStrongValueEntry, StrongKeyStrongValueSegment> + map, + int initialCapacity, + int maxSegmentSize) { + super(map, initialCapacity, maxSegmentSize); + } + + @Override + StrongKeyStrongValueSegment self() { + return this; + } + + @SuppressWarnings("unchecked") + @Override + public StrongKeyStrongValueEntry castForTesting(InternalEntry entry) { + return (StrongKeyStrongValueEntry) entry; + } + } + + /** Concrete implementation of {@link Segment} for strong keys and weak values. */ + static final class StrongKeyWeakValueSegment + extends Segment, StrongKeyWeakValueSegment> { + private final ReferenceQueue queueForValues = new ReferenceQueue(); + + StrongKeyWeakValueSegment( + MapMakerInternalMap, StrongKeyWeakValueSegment> + map, + int initialCapacity, + int maxSegmentSize) { + super(map, initialCapacity, maxSegmentSize); + } + + @Override + StrongKeyWeakValueSegment self() { + return this; + } + + @Override + ReferenceQueue getValueReferenceQueueForTesting() { + return queueForValues; + } + + @SuppressWarnings("unchecked") + @Override + public StrongKeyWeakValueEntry castForTesting(InternalEntry entry) { + return (StrongKeyWeakValueEntry) entry; + } + + @Override + public WeakValueReference> getWeakValueReferenceForTesting( + InternalEntry e) { + return castForTesting(e).getValueReference(); + } + + @Override + public WeakValueReference> newWeakValueReferenceForTesting( + InternalEntry e, V value) { + return new WeakValueReferenceImpl<>(queueForValues, value, castForTesting(e)); + } + + @Override + public void setWeakValueReferenceForTesting( + InternalEntry e, + WeakValueReference> valueReference) { + StrongKeyWeakValueEntry entry = castForTesting(e); + @SuppressWarnings("unchecked") + WeakValueReference> newValueReference = + (WeakValueReference>) valueReference; + WeakValueReference> previous = entry.valueReference; + entry.valueReference = newValueReference; + previous.clear(); + } + + @Override + void maybeDrainReferenceQueues() { + drainValueReferenceQueue(queueForValues); + } + + @Override + void maybeClearReferenceQueues() { + clearReferenceQueue(queueForValues); + } + } + + /** Concrete implementation of {@link Segment} for strong keys and {@link Dummy} values. */ + static final class StrongKeyDummyValueSegment + extends Segment, StrongKeyDummyValueSegment> { + StrongKeyDummyValueSegment( + MapMakerInternalMap, StrongKeyDummyValueSegment> + map, + int initialCapacity, + int maxSegmentSize) { + super(map, initialCapacity, maxSegmentSize); + } + + @Override + StrongKeyDummyValueSegment self() { + return this; + } + + @SuppressWarnings("unchecked") + @Override + public StrongKeyDummyValueEntry castForTesting(InternalEntry entry) { + return (StrongKeyDummyValueEntry) entry; + } + } + + /** Concrete implementation of {@link Segment} for weak keys and strong values. */ + static final class WeakKeyStrongValueSegment + extends Segment, WeakKeyStrongValueSegment> { + private final ReferenceQueue queueForKeys = new ReferenceQueue(); + + WeakKeyStrongValueSegment( + MapMakerInternalMap, WeakKeyStrongValueSegment> + map, + int initialCapacity, + int maxSegmentSize) { + super(map, initialCapacity, maxSegmentSize); + } + + @Override + WeakKeyStrongValueSegment self() { + return this; + } + + @Override + ReferenceQueue getKeyReferenceQueueForTesting() { + return queueForKeys; + } + + @SuppressWarnings("unchecked") + @Override + public WeakKeyStrongValueEntry castForTesting(InternalEntry entry) { + return (WeakKeyStrongValueEntry) entry; + } + + @Override + void maybeDrainReferenceQueues() { + drainKeyReferenceQueue(queueForKeys); + } + + @Override + void maybeClearReferenceQueues() { + clearReferenceQueue(queueForKeys); + } + } + + /** Concrete implementation of {@link Segment} for weak keys and weak values. */ + static final class WeakKeyWeakValueSegment + extends Segment, WeakKeyWeakValueSegment> { + private final ReferenceQueue queueForKeys = new ReferenceQueue(); + private final ReferenceQueue queueForValues = new ReferenceQueue(); + + WeakKeyWeakValueSegment( + MapMakerInternalMap, WeakKeyWeakValueSegment> map, + int initialCapacity, + int maxSegmentSize) { + super(map, initialCapacity, maxSegmentSize); + } + + @Override + WeakKeyWeakValueSegment self() { + return this; + } + + @Override + ReferenceQueue getKeyReferenceQueueForTesting() { + return queueForKeys; + } + + @Override + ReferenceQueue getValueReferenceQueueForTesting() { + return queueForValues; + } + + @SuppressWarnings("unchecked") + @Override + public WeakKeyWeakValueEntry castForTesting(InternalEntry entry) { + return (WeakKeyWeakValueEntry) entry; + } + + @Override + public WeakValueReference> getWeakValueReferenceForTesting( + InternalEntry e) { + return castForTesting(e).getValueReference(); + } + + @Override + public WeakValueReference> newWeakValueReferenceForTesting( + InternalEntry e, V value) { + return new WeakValueReferenceImpl<>(queueForValues, value, castForTesting(e)); + } + + @Override + public void setWeakValueReferenceForTesting( + InternalEntry e, + WeakValueReference> valueReference) { + WeakKeyWeakValueEntry entry = castForTesting(e); + @SuppressWarnings("unchecked") + WeakValueReference> newValueReference = + (WeakValueReference>) valueReference; + WeakValueReference> previous = entry.valueReference; + entry.valueReference = newValueReference; + previous.clear(); + } + + @Override + void maybeDrainReferenceQueues() { + drainKeyReferenceQueue(queueForKeys); + drainValueReferenceQueue(queueForValues); + } + + @Override + void maybeClearReferenceQueues() { + clearReferenceQueue(queueForKeys); + } + } + + /** Concrete implementation of {@link Segment} for weak keys and {@link Dummy} values. */ + static final class WeakKeyDummyValueSegment + extends Segment, WeakKeyDummyValueSegment> { + private final ReferenceQueue queueForKeys = new ReferenceQueue(); + + WeakKeyDummyValueSegment( + MapMakerInternalMap, WeakKeyDummyValueSegment> map, + int initialCapacity, + int maxSegmentSize) { + super(map, initialCapacity, maxSegmentSize); + } + + @Override + WeakKeyDummyValueSegment self() { + return this; + } + + @Override + ReferenceQueue getKeyReferenceQueueForTesting() { + return queueForKeys; + } + + @SuppressWarnings("unchecked") + @Override + public WeakKeyDummyValueEntry castForTesting(InternalEntry entry) { + return (WeakKeyDummyValueEntry) entry; + } + + @Override + void maybeDrainReferenceQueues() { + drainKeyReferenceQueue(queueForKeys); + } + + @Override + void maybeClearReferenceQueues() { + clearReferenceQueue(queueForKeys); + } + } + + static final class CleanupMapTask implements Runnable { + final WeakReference> mapReference; + + public CleanupMapTask(MapMakerInternalMap map) { + this.mapReference = new WeakReference>(map); + } + + @Override + public void run() { + MapMakerInternalMap map = mapReference.get(); + if (map == null) { + throw new CancellationException(); + } + + for (Segment segment : map.segments) { + segment.runCleanup(); + } + } + } + + @VisibleForTesting + Strength keyStrength() { + return entryHelper.keyStrength(); + } + + @VisibleForTesting + Strength valueStrength() { + return entryHelper.valueStrength(); + } + + @VisibleForTesting + Equivalence valueEquivalence() { + return entryHelper.valueStrength().defaultEquivalence(); + } + + // ConcurrentMap methods + + @Override + public boolean isEmpty() { + /* + * Sum per-segment modCounts to avoid mis-reporting when elements are concurrently added and + * removed in one segment while checking another, in which case the table was never actually + * empty at any point. (The sum ensures accuracy up through at least 1<<31 per-segment + * modifications before recheck.) Method containsValue() uses similar constructions for + * stability checks. + */ + long sum = 0L; + Segment[] segments = this.segments; + for (int i = 0; i < segments.length; ++i) { + if (segments[i].count != 0) { + return false; + } + sum += segments[i].modCount; + } + + if (sum != 0L) { // recheck unless no modifications + for (int i = 0; i < segments.length; ++i) { + if (segments[i].count != 0) { + return false; + } + sum -= segments[i].modCount; + } + return sum == 0L; + } + return true; + } + + @Override + public int size() { + Segment[] segments = this.segments; + long sum = 0; + for (int i = 0; i < segments.length; ++i) { + sum += segments[i].count; + } + return Ints.saturatedCast(sum); + } + + @Override + public V get(Object key) { + if (key == null) { + return null; + } + int hash = hash(key); + return segmentFor(hash).get(key, hash); + } + + /** + * Returns the internal entry for the specified key. The entry may be computing or partially + * collected. Does not impact recency ordering. + */ + E getEntry(Object key) { + if (key == null) { + return null; + } + int hash = hash(key); + return segmentFor(hash).getEntry(key, hash); + } + + @Override + public boolean containsKey(Object key) { + if (key == null) { + return false; + } + int hash = hash(key); + return segmentFor(hash).containsKey(key, hash); + } + + @Override + public boolean containsValue(Object value) { + if (value == null) { + return false; + } + + // This implementation is patterned after ConcurrentHashMap, but without the locking. The only + // way for it to return a false negative would be for the target value to jump around in the map + // such that none of the subsequent iterations observed it, despite the fact that at every point + // in time it was present somewhere int the map. This becomes increasingly unlikely as + // CONTAINS_VALUE_RETRIES increases, though without locking it is theoretically possible. + final Segment[] segments = this.segments; + long last = -1L; + for (int i = 0; i < CONTAINS_VALUE_RETRIES; i++) { + long sum = 0L; + for (Segment segment : segments) { + // ensure visibility of most recent completed write + int unused = segment.count; // read-volatile + + AtomicReferenceArray table = segment.table; + for (int j = 0; j < table.length(); j++) { + for (E e = table.get(j); e != null; e = e.getNext()) { + V v = segment.getLiveValue(e); + if (v != null && valueEquivalence().equivalent(value, v)) { + return true; + } + } + } + sum += segment.modCount; + } + if (sum == last) { + break; + } + last = sum; + } + return false; + } + + + @Override + public V put(K key, V value) { + checkNotNull(key); + checkNotNull(value); + int hash = hash(key); + return segmentFor(hash).put(key, hash, value, false); + } + + + @Override + public V putIfAbsent(K key, V value) { + checkNotNull(key); + checkNotNull(value); + int hash = hash(key); + return segmentFor(hash).put(key, hash, value, true); + } + + @Override + public void putAll(Map m) { + for (Entry e : m.entrySet()) { + put(e.getKey(), e.getValue()); + } + } + + + @Override + public V remove(Object key) { + if (key == null) { + return null; + } + int hash = hash(key); + return segmentFor(hash).remove(key, hash); + } + + + @Override + public boolean remove(Object key, Object value) { + if (key == null || value == null) { + return false; + } + int hash = hash(key); + return segmentFor(hash).remove(key, hash, value); + } + + + @Override + public boolean replace(K key, V oldValue, V newValue) { + checkNotNull(key); + checkNotNull(newValue); + if (oldValue == null) { + return false; + } + int hash = hash(key); + return segmentFor(hash).replace(key, hash, oldValue, newValue); + } + + + @Override + public V replace(K key, V value) { + checkNotNull(key); + checkNotNull(value); + int hash = hash(key); + return segmentFor(hash).replace(key, hash, value); + } + + @Override + public void clear() { + for (Segment segment : segments) { + segment.clear(); + } + } + + transient Set keySet; + + @Override + public Set keySet() { + Set ks = keySet; + return (ks != null) ? ks : (keySet = new KeySet()); + } + + transient Collection values; + + @Override + public Collection values() { + Collection vs = values; + return (vs != null) ? vs : (values = new Values()); + } + + transient Set> entrySet; + + @Override + public Set> entrySet() { + Set> es = entrySet; + return (es != null) ? es : (entrySet = new EntrySet()); + } + + // Iterator Support + + abstract class HashIterator implements Iterator { + + int nextSegmentIndex; + int nextTableIndex; + Segment currentSegment; + AtomicReferenceArray currentTable; + E nextEntry; + WriteThroughEntry nextExternal; + WriteThroughEntry lastReturned; + + HashIterator() { + nextSegmentIndex = segments.length - 1; + nextTableIndex = -1; + advance(); + } + + @Override + public abstract T next(); + + final void advance() { + nextExternal = null; + + if (nextInChain()) { + return; + } + + if (nextInTable()) { + return; + } + + while (nextSegmentIndex >= 0) { + currentSegment = segments[nextSegmentIndex--]; + if (currentSegment.count != 0) { + currentTable = currentSegment.table; + nextTableIndex = currentTable.length() - 1; + if (nextInTable()) { + return; + } + } + } + } + + /** Finds the next entry in the current chain. Returns {@code true} if an entry was found. */ + boolean nextInChain() { + if (nextEntry != null) { + for (nextEntry = nextEntry.getNext(); nextEntry != null; nextEntry = nextEntry.getNext()) { + if (advanceTo(nextEntry)) { + return true; + } + } + } + return false; + } + + /** Finds the next entry in the current table. Returns {@code true} if an entry was found. */ + boolean nextInTable() { + while (nextTableIndex >= 0) { + if ((nextEntry = currentTable.get(nextTableIndex--)) != null) { + if (advanceTo(nextEntry) || nextInChain()) { + return true; + } + } + } + return false; + } + + /** + * Advances to the given entry. Returns {@code true} if the entry was valid, {@code false} if it + * should be skipped. + */ + boolean advanceTo(E entry) { + try { + K key = entry.getKey(); + V value = getLiveValue(entry); + if (value != null) { + nextExternal = new WriteThroughEntry(key, value); + return true; + } else { + // Skip stale entry. + return false; + } + } finally { + currentSegment.postReadCleanup(); + } + } + + @Override + public boolean hasNext() { + return nextExternal != null; + } + + WriteThroughEntry nextEntry() { + if (nextExternal == null) { + throw new NoSuchElementException(); + } + lastReturned = nextExternal; + advance(); + return lastReturned; + } + + @Override + public void remove() { + checkRemove(lastReturned != null); + MapMakerInternalMap.this.remove(lastReturned.getKey()); + lastReturned = null; + } + } + + final class KeyIterator extends HashIterator { + + @Override + public K next() { + return nextEntry().getKey(); + } + } + + final class ValueIterator extends HashIterator { + + @Override + public V next() { + return nextEntry().getValue(); + } + } + + /** + * Custom Entry class used by EntryIterator.next(), that relays setValue changes to the underlying + * map. + */ + final class WriteThroughEntry extends AbstractMapEntry { + final K key; // non-null + V value; // non-null + + WriteThroughEntry(K key, V value) { + this.key = key; + this.value = value; + } + + @Override + public K getKey() { + return key; + } + + @Override + public V getValue() { + return value; + } + + @Override + public boolean equals(Object object) { + // Cannot use key and value equivalence + if (object instanceof Entry) { + Entry that = (Entry) object; + return key.equals(that.getKey()) && value.equals(that.getValue()); + } + return false; + } + + @Override + public int hashCode() { + // Cannot use key and value equivalence + return key.hashCode() ^ value.hashCode(); + } + + @Override + public V setValue(V newValue) { + V oldValue = put(key, newValue); + value = newValue; // only if put succeeds + return oldValue; + } + } + + final class EntryIterator extends HashIterator> { + + @Override + public Entry next() { + return nextEntry(); + } + } + + + final class KeySet extends SafeToArraySet { + + @Override + public Iterator iterator() { + return new KeyIterator(); + } + + @Override + public int size() { + return MapMakerInternalMap.this.size(); + } + + @Override + public boolean isEmpty() { + return MapMakerInternalMap.this.isEmpty(); + } + + @Override + public boolean contains(Object o) { + return MapMakerInternalMap.this.containsKey(o); + } + + @Override + public boolean remove(Object o) { + return MapMakerInternalMap.this.remove(o) != null; + } + + @Override + public void clear() { + MapMakerInternalMap.this.clear(); + } + } + + + final class Values extends AbstractCollection { + + @Override + public Iterator iterator() { + return new ValueIterator(); + } + + @Override + public int size() { + return MapMakerInternalMap.this.size(); + } + + @Override + public boolean isEmpty() { + return MapMakerInternalMap.this.isEmpty(); + } + + @Override + public boolean contains(Object o) { + return MapMakerInternalMap.this.containsValue(o); + } + + @Override + public void clear() { + MapMakerInternalMap.this.clear(); + } + + // super.toArray() may misbehave if size() is inaccurate, at least on old versions of Android. + // https://code.google.com/p/android/issues/detail?id=36519 / http://r.android.com/47508 + + @Override + public Object[] toArray() { + return toArrayList(this).toArray(); + } + + @Override + public T[] toArray(T[] a) { + return toArrayList(this).toArray(a); + } + } + + + final class EntrySet extends SafeToArraySet> { + + @Override + public Iterator> iterator() { + return new EntryIterator(); + } + + @Override + public boolean contains(Object o) { + if (!(o instanceof Entry)) { + return false; + } + Entry e = (Entry) o; + Object key = e.getKey(); + if (key == null) { + return false; + } + V v = MapMakerInternalMap.this.get(key); + + return v != null && valueEquivalence().equivalent(e.getValue(), v); + } + + @Override + public boolean remove(Object o) { + if (!(o instanceof Entry)) { + return false; + } + Entry e = (Entry) o; + Object key = e.getKey(); + return key != null && MapMakerInternalMap.this.remove(key, e.getValue()); + } + + @Override + public int size() { + return MapMakerInternalMap.this.size(); + } + + @Override + public boolean isEmpty() { + return MapMakerInternalMap.this.isEmpty(); + } + + @Override + public void clear() { + MapMakerInternalMap.this.clear(); + } + } + + private abstract static class SafeToArraySet extends AbstractSet { + // super.toArray() may misbehave if size() is inaccurate, at least on old versions of Android. + // https://code.google.com/p/android/issues/detail?id=36519 / http://r.android.com/47508 + + @Override + public Object[] toArray() { + return toArrayList(this).toArray(); + } + + @Override + public T[] toArray(T[] a) { + return toArrayList(this).toArray(a); + } + } + + private static ArrayList toArrayList(Collection c) { + // Avoid calling ArrayList(Collection), which may call back into toArray. + ArrayList result = new ArrayList<>(c.size()); + Iterators.addAll(result, c.iterator()); + return result; + } + + // Serialization Support + + private static final long serialVersionUID = 5; + + Object writeReplace() { + return new SerializationProxy<>( + entryHelper.keyStrength(), + entryHelper.valueStrength(), + keyEquivalence, + entryHelper.valueStrength().defaultEquivalence(), + concurrencyLevel, + this); + } + + /** + * The actual object that gets serialized. Unfortunately, readResolve() doesn't get called when a + * circular dependency is present, so the proxy must be able to behave as the map itself. + */ + abstract static class AbstractSerializationProxy extends ForwardingConcurrentMap + implements Serializable { + private static final long serialVersionUID = 3; + + final Strength keyStrength; + final Strength valueStrength; + final Equivalence keyEquivalence; + final Equivalence valueEquivalence; + final int concurrencyLevel; + + transient ConcurrentMap delegate; + + AbstractSerializationProxy( + Strength keyStrength, + Strength valueStrength, + Equivalence keyEquivalence, + Equivalence valueEquivalence, + int concurrencyLevel, + ConcurrentMap delegate) { + this.keyStrength = keyStrength; + this.valueStrength = valueStrength; + this.keyEquivalence = keyEquivalence; + this.valueEquivalence = valueEquivalence; + this.concurrencyLevel = concurrencyLevel; + this.delegate = delegate; + } + + @Override + protected ConcurrentMap delegate() { + return delegate; + } + + void writeMapTo(ObjectOutputStream out) throws IOException { + out.writeInt(delegate.size()); + for (Entry entry : delegate.entrySet()) { + out.writeObject(entry.getKey()); + out.writeObject(entry.getValue()); + } + out.writeObject(null); // terminate entries + } + + @SuppressWarnings("deprecation") // serialization of deprecated feature + MapMaker readMapMaker(ObjectInputStream in) throws IOException { + int size = in.readInt(); + return new MapMaker() + .initialCapacity(size) + .setKeyStrength(keyStrength) + .setValueStrength(valueStrength) + .keyEquivalence(keyEquivalence) + .concurrencyLevel(concurrencyLevel); + } + + @SuppressWarnings("unchecked") + void readEntries(ObjectInputStream in) throws IOException, ClassNotFoundException { + while (true) { + K key = (K) in.readObject(); + if (key == null) { + break; // terminator + } + V value = (V) in.readObject(); + delegate.put(key, value); + } + } + } + + /** + * The actual object that gets serialized. Unfortunately, readResolve() doesn't get called when a + * circular dependency is present, so the proxy must be able to behave as the map itself. + */ + private static final class SerializationProxy extends AbstractSerializationProxy { + private static final long serialVersionUID = 3; + + SerializationProxy( + Strength keyStrength, + Strength valueStrength, + Equivalence keyEquivalence, + Equivalence valueEquivalence, + int concurrencyLevel, + ConcurrentMap delegate) { + super( + keyStrength, valueStrength, keyEquivalence, valueEquivalence, concurrencyLevel, delegate); + } + + private void writeObject(ObjectOutputStream out) throws IOException { + out.defaultWriteObject(); + writeMapTo(out); + } + + private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { + in.defaultReadObject(); + MapMaker mapMaker = readMapMaker(in); + delegate = mapMaker.makeMap(); + readEntries(in); + } + + private Object readResolve() { + return delegate; + } + } +} diff --git a/src/main/java/com/google/common/collect/Maps.java b/src/main/java/com/google/common/collect/Maps.java new file mode 100644 index 0000000..25f8633 --- /dev/null +++ b/src/main/java/com/google/common/collect/Maps.java @@ -0,0 +1,4269 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.base.Predicates.compose; +import static com.google.common.collect.CollectPreconditions.checkEntryNotNull; +import static com.google.common.collect.CollectPreconditions.checkNonnegative; + +import com.google.common.annotations.Beta; +import com.google.common.annotations.GwtCompatible; +import com.google.common.annotations.GwtIncompatible; +import com.google.common.base.Converter; +import com.google.common.base.Equivalence; +import com.google.common.base.Function; +import com.google.common.base.Objects; +import com.google.common.base.Preconditions; +import com.google.common.base.Predicate; +import com.google.common.base.Predicates; +import com.google.common.collect.MapDifference.ValueDifference; +import com.google.common.primitives.Ints; + + + + +import java.io.Serializable; +import java.util.AbstractCollection; +import java.util.AbstractMap; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.EnumMap; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.IdentityHashMap; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Map.Entry; +import java.util.NavigableMap; +import java.util.NavigableSet; +import java.util.Properties; +import java.util.Set; +import java.util.SortedMap; +import java.util.SortedSet; +import java.util.Spliterator; +import java.util.Spliterators; +import java.util.TreeMap; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.function.BiConsumer; +import java.util.function.BiFunction; +import java.util.function.BinaryOperator; +import java.util.function.Consumer; +import java.util.stream.Collector; + + + +/** + * Static utility methods pertaining to {@link Map} instances (including instances of {@link + * SortedMap}, {@link BiMap}, etc.). Also see this class's counterparts {@link Lists}, {@link Sets} + * and {@link Queues}. + * + *

See the Guava User Guide article on {@code Maps}. + * + * @author Kevin Bourrillion + * @author Mike Bostock + * @author Isaac Shum + * @author Louis Wasserman + * @since 2.0 + */ +@GwtCompatible(emulated = true) +public final class Maps { + private Maps() {} + + private enum EntryFunction implements Function, Object> { + KEY { + @Override + public Object apply(Entry entry) { + return entry.getKey(); + } + }, + VALUE { + @Override + public Object apply(Entry entry) { + return entry.getValue(); + } + }; + } + + @SuppressWarnings("unchecked") + static Function, K> keyFunction() { + return (Function) EntryFunction.KEY; + } + + @SuppressWarnings("unchecked") + static Function, V> valueFunction() { + return (Function) EntryFunction.VALUE; + } + + static Iterator keyIterator(Iterator> entryIterator) { + return new TransformedIterator, K>(entryIterator) { + @Override + K transform(Entry entry) { + return entry.getKey(); + } + }; + } + + static Iterator valueIterator(Iterator> entryIterator) { + return new TransformedIterator, V>(entryIterator) { + @Override + V transform(Entry entry) { + return entry.getValue(); + } + }; + } + + /** + * Returns an immutable map instance containing the given entries. Internally, the returned map + * will be backed by an {@link EnumMap}. + * + *

The iteration order of the returned map follows the enum's iteration order, not the order in + * which the elements appear in the given map. + * + * @param map the map to make an immutable copy of + * @return an immutable map containing those entries + * @since 14.0 + */ + @GwtCompatible(serializable = true) + public static , V> ImmutableMap immutableEnumMap( + Map map) { + if (map instanceof ImmutableEnumMap) { + @SuppressWarnings("unchecked") // safe covariant cast + ImmutableEnumMap result = (ImmutableEnumMap) map; + return result; + } + Iterator> entryItr = map.entrySet().iterator(); + if (!entryItr.hasNext()) { + return ImmutableMap.of(); + } + Entry entry1 = entryItr.next(); + K key1 = entry1.getKey(); + V value1 = entry1.getValue(); + checkEntryNotNull(key1, value1); + Class clazz = key1.getDeclaringClass(); + EnumMap enumMap = new EnumMap<>(clazz); + enumMap.put(key1, value1); + while (entryItr.hasNext()) { + Entry entry = entryItr.next(); + K key = entry.getKey(); + V value = entry.getValue(); + checkEntryNotNull(key, value); + enumMap.put(key, value); + } + return ImmutableEnumMap.asImmutable(enumMap); + } + + private static class Accumulator, V> { + private final BinaryOperator mergeFunction; + private EnumMap map = null; + + Accumulator(BinaryOperator mergeFunction) { + this.mergeFunction = mergeFunction; + } + + void put(K key, V value) { + if (map == null) { + map = new EnumMap<>(key.getDeclaringClass()); + } + map.merge(key, value, mergeFunction); + } + + Accumulator combine(Accumulator other) { + if (this.map == null) { + return other; + } else if (other.map == null) { + return this; + } else { + other.map.forEach(this::put); + return this; + } + } + + ImmutableMap toImmutableMap() { + return (map == null) ? ImmutableMap.of() : ImmutableEnumMap.asImmutable(map); + } + } + + /** + * Returns a {@link Collector} that accumulates elements into an {@code ImmutableMap} whose keys + * and values are the result of applying the provided mapping functions to the input elements. The + * resulting implementation is specialized for enum key types. The returned map and its views will + * iterate over keys in their enum definition order, not encounter order. + * + *

If the mapped keys contain duplicates, an {@code IllegalArgumentException} is thrown when + * the collection operation is performed. (This differs from the {@code Collector} returned by + * {@link java.util.stream.Collectors#toMap(java.util.function.Function, + * java.util.function.Function) Collectors.toMap(Function, Function)}, which throws an {@code + * IllegalStateException}.) + * + * @since 21.0 + */ + public static , V> Collector> toImmutableEnumMap( + java.util.function.Function keyFunction, + java.util.function.Function valueFunction) { + checkNotNull(keyFunction); + checkNotNull(valueFunction); + return Collector.of( + () -> + new Accumulator( + (v1, v2) -> { + throw new IllegalArgumentException("Multiple values for key: " + v1 + ", " + v2); + }), + (accum, t) -> { + K key = checkNotNull(keyFunction.apply(t), "Null key for input %s", t); + V newValue = checkNotNull(valueFunction.apply(t), "Null value for input %s", t); + accum.put(key, newValue); + }, + Accumulator::combine, + Accumulator::toImmutableMap, + Collector.Characteristics.UNORDERED); + } + + /** + * Returns a {@link Collector} that accumulates elements into an {@code ImmutableMap} whose keys + * and values are the result of applying the provided mapping functions to the input elements. The + * resulting implementation is specialized for enum key types. The returned map and its views will + * iterate over keys in their enum definition order, not encounter order. + * + *

If the mapped keys contain duplicates, the values are merged using the specified merging + * function. + * + * @since 21.0 + */ + public static , V> Collector> toImmutableEnumMap( + java.util.function.Function keyFunction, + java.util.function.Function valueFunction, + BinaryOperator mergeFunction) { + checkNotNull(keyFunction); + checkNotNull(valueFunction); + checkNotNull(mergeFunction); + // not UNORDERED because we don't know if mergeFunction is commutative + return Collector.of( + () -> new Accumulator(mergeFunction), + (accum, t) -> { + K key = checkNotNull(keyFunction.apply(t), "Null key for input %s", t); + V newValue = checkNotNull(valueFunction.apply(t), "Null value for input %s", t); + accum.put(key, newValue); + }, + Accumulator::combine, + Accumulator::toImmutableMap); + } + + /** + * Creates a mutable, empty {@code HashMap} instance. + * + *

Note: if mutability is not required, use {@link ImmutableMap#of()} instead. + * + *

Note: if {@code K} is an {@code enum} type, use {@link #newEnumMap} instead. + * + *

Note for Java 7 and later: this method is now unnecessary and should be treated as + * deprecated. Instead, use the {@code HashMap} constructor directly, taking advantage of the new + * "diamond" syntax. + * + * @return a new, empty {@code HashMap} + */ + public static HashMap newHashMap() { + return new HashMap<>(); + } + + /** + * Creates a mutable {@code HashMap} instance with the same mappings as the specified map. + * + *

Note: if mutability is not required, use {@link ImmutableMap#copyOf(Map)} instead. + * + *

Note: if {@code K} is an {@link Enum} type, use {@link #newEnumMap} instead. + * + *

Note for Java 7 and later: this method is now unnecessary and should be treated as + * deprecated. Instead, use the {@code HashMap} constructor directly, taking advantage of the new + * "diamond" syntax. + * + * @param map the mappings to be placed in the new map + * @return a new {@code HashMap} initialized with the mappings from {@code map} + */ + public static HashMap newHashMap(Map map) { + return new HashMap<>(map); + } + + /** + * Creates a {@code HashMap} instance, with a high enough "initial capacity" that it should + * hold {@code expectedSize} elements without growth. This behavior cannot be broadly guaranteed, + * but it is observed to be true for OpenJDK 1.7. It also can't be guaranteed that the method + * isn't inadvertently oversizing the returned map. + * + * @param expectedSize the number of entries you expect to add to the returned map + * @return a new, empty {@code HashMap} with enough capacity to hold {@code expectedSize} entries + * without resizing + * @throws IllegalArgumentException if {@code expectedSize} is negative + */ + public static HashMap newHashMapWithExpectedSize(int expectedSize) { + return new HashMap<>(capacity(expectedSize)); + } + + /** + * Returns a capacity that is sufficient to keep the map from being resized as long as it grows no + * larger than expectedSize and the load factor is ≥ its default (0.75). + */ + static int capacity(int expectedSize) { + if (expectedSize < 3) { + checkNonnegative(expectedSize, "expectedSize"); + return expectedSize + 1; + } + if (expectedSize < Ints.MAX_POWER_OF_TWO) { + // This is the calculation used in JDK8 to resize when a putAll + // happens; it seems to be the most conservative calculation we + // can make. 0.75 is the default load factor. + return (int) ((float) expectedSize / 0.75F + 1.0F); + } + return Integer.MAX_VALUE; // any large value + } + + /** + * Creates a mutable, empty, insertion-ordered {@code LinkedHashMap} instance. + * + *

Note: if mutability is not required, use {@link ImmutableMap#of()} instead. + * + *

Note for Java 7 and later: this method is now unnecessary and should be treated as + * deprecated. Instead, use the {@code LinkedHashMap} constructor directly, taking advantage of + * the new "diamond" syntax. + * + * @return a new, empty {@code LinkedHashMap} + */ + public static LinkedHashMap newLinkedHashMap() { + return new LinkedHashMap<>(); + } + + /** + * Creates a mutable, insertion-ordered {@code LinkedHashMap} instance with the same + * mappings as the specified map. + * + *

Note: if mutability is not required, use {@link ImmutableMap#copyOf(Map)} instead. + * + *

Note for Java 7 and later: this method is now unnecessary and should be treated as + * deprecated. Instead, use the {@code LinkedHashMap} constructor directly, taking advantage of + * the new "diamond" syntax. + * + * @param map the mappings to be placed in the new map + * @return a new, {@code LinkedHashMap} initialized with the mappings from {@code map} + */ + public static LinkedHashMap newLinkedHashMap(Map map) { + return new LinkedHashMap<>(map); + } + + /** + * Creates a {@code LinkedHashMap} instance, with a high enough "initial capacity" that it + * should hold {@code expectedSize} elements without growth. This behavior cannot be + * broadly guaranteed, but it is observed to be true for OpenJDK 1.7. It also can't be guaranteed + * that the method isn't inadvertently oversizing the returned map. + * + * @param expectedSize the number of entries you expect to add to the returned map + * @return a new, empty {@code LinkedHashMap} with enough capacity to hold {@code expectedSize} + * entries without resizing + * @throws IllegalArgumentException if {@code expectedSize} is negative + * @since 19.0 + */ + public static LinkedHashMap newLinkedHashMapWithExpectedSize(int expectedSize) { + return new LinkedHashMap<>(capacity(expectedSize)); + } + + /** + * Creates a new empty {@link ConcurrentHashMap} instance. + * + * @since 3.0 + */ + public static ConcurrentMap newConcurrentMap() { + return new ConcurrentHashMap<>(); + } + + /** + * Creates a mutable, empty {@code TreeMap} instance using the natural ordering of its + * elements. + * + *

Note: if mutability is not required, use {@link ImmutableSortedMap#of()} instead. + * + *

Note for Java 7 and later: this method is now unnecessary and should be treated as + * deprecated. Instead, use the {@code TreeMap} constructor directly, taking advantage of the new + * "diamond" syntax. + * + * @return a new, empty {@code TreeMap} + */ + public static TreeMap newTreeMap() { + return new TreeMap<>(); + } + + /** + * Creates a mutable {@code TreeMap} instance with the same mappings as the specified map + * and using the same ordering as the specified map. + * + *

Note: if mutability is not required, use {@link + * ImmutableSortedMap#copyOfSorted(SortedMap)} instead. + * + *

Note for Java 7 and later: this method is now unnecessary and should be treated as + * deprecated. Instead, use the {@code TreeMap} constructor directly, taking advantage of the new + * "diamond" syntax. + * + * @param map the sorted map whose mappings are to be placed in the new map and whose comparator + * is to be used to sort the new map + * @return a new {@code TreeMap} initialized with the mappings from {@code map} and using the + * comparator of {@code map} + */ + public static TreeMap newTreeMap(SortedMap map) { + return new TreeMap<>(map); + } + + /** + * Creates a mutable, empty {@code TreeMap} instance using the given comparator. + * + *

Note: if mutability is not required, use {@code + * ImmutableSortedMap.orderedBy(comparator).build()} instead. + * + *

Note for Java 7 and later: this method is now unnecessary and should be treated as + * deprecated. Instead, use the {@code TreeMap} constructor directly, taking advantage of the new + * "diamond" syntax. + * + * @param comparator the comparator to sort the keys with + * @return a new, empty {@code TreeMap} + */ + public static TreeMap newTreeMap(Comparator comparator) { + // Ideally, the extra type parameter "C" shouldn't be necessary. It is a + // work-around of a compiler type inference quirk that prevents the + // following code from being compiled: + // Comparator> comparator = null; + // Map, String> map = newTreeMap(comparator); + return new TreeMap<>(comparator); + } + + /** + * Creates an {@code EnumMap} instance. + * + * @param type the key type for this map + * @return a new, empty {@code EnumMap} + */ + public static , V> EnumMap newEnumMap(Class type) { + return new EnumMap<>(checkNotNull(type)); + } + + /** + * Creates an {@code EnumMap} with the same mappings as the specified map. + * + *

Note for Java 7 and later: this method is now unnecessary and should be treated as + * deprecated. Instead, use the {@code EnumMap} constructor directly, taking advantage of the new + * "diamond" syntax. + * + * @param map the map from which to initialize this {@code EnumMap} + * @return a new {@code EnumMap} initialized with the mappings from {@code map} + * @throws IllegalArgumentException if {@code m} is not an {@code EnumMap} instance and contains + * no mappings + */ + public static , V> EnumMap newEnumMap(Map map) { + return new EnumMap<>(map); + } + + /** + * Creates an {@code IdentityHashMap} instance. + * + *

Note for Java 7 and later: this method is now unnecessary and should be treated as + * deprecated. Instead, use the {@code IdentityHashMap} constructor directly, taking advantage of + * the new "diamond" syntax. + * + * @return a new, empty {@code IdentityHashMap} + */ + public static IdentityHashMap newIdentityHashMap() { + return new IdentityHashMap<>(); + } + + /** + * Computes the difference between two maps. This difference is an immutable snapshot of the state + * of the maps at the time this method is called. It will never change, even if the maps change at + * a later time. + * + *

Since this method uses {@code HashMap} instances internally, the keys of the supplied maps + * must be well-behaved with respect to {@link Object#equals} and {@link Object#hashCode}. + * + *

Note:If you only need to know whether two maps have the same mappings, call {@code + * left.equals(right)} instead of this method. + * + * @param left the map to treat as the "left" map for purposes of comparison + * @param right the map to treat as the "right" map for purposes of comparison + * @return the difference between the two maps + */ + @SuppressWarnings("unchecked") + public static MapDifference difference( + Map left, Map right) { + if (left instanceof SortedMap) { + SortedMap sortedLeft = (SortedMap) left; + return difference(sortedLeft, right); + } + return difference(left, right, Equivalence.equals()); + } + + /** + * Computes the difference between two maps. This difference is an immutable snapshot of the state + * of the maps at the time this method is called. It will never change, even if the maps change at + * a later time. + * + *

Since this method uses {@code HashMap} instances internally, the keys of the supplied maps + * must be well-behaved with respect to {@link Object#equals} and {@link Object#hashCode}. + * + * @param left the map to treat as the "left" map for purposes of comparison + * @param right the map to treat as the "right" map for purposes of comparison + * @param valueEquivalence the equivalence relationship to use to compare values + * @return the difference between the two maps + * @since 10.0 + */ + public static MapDifference difference( + Map left, + Map right, + Equivalence valueEquivalence) { + Preconditions.checkNotNull(valueEquivalence); + + Map onlyOnLeft = newLinkedHashMap(); + Map onlyOnRight = new LinkedHashMap<>(right); // will whittle it down + Map onBoth = newLinkedHashMap(); + Map> differences = newLinkedHashMap(); + doDifference(left, right, valueEquivalence, onlyOnLeft, onlyOnRight, onBoth, differences); + return new MapDifferenceImpl<>(onlyOnLeft, onlyOnRight, onBoth, differences); + } + + /** + * Computes the difference between two sorted maps, using the comparator of the left map, or + * {@code Ordering.natural()} if the left map uses the natural ordering of its elements. This + * difference is an immutable snapshot of the state of the maps at the time this method is called. + * It will never change, even if the maps change at a later time. + * + *

Since this method uses {@code TreeMap} instances internally, the keys of the right map must + * all compare as distinct according to the comparator of the left map. + * + *

Note:If you only need to know whether two sorted maps have the same mappings, call + * {@code left.equals(right)} instead of this method. + * + * @param left the map to treat as the "left" map for purposes of comparison + * @param right the map to treat as the "right" map for purposes of comparison + * @return the difference between the two maps + * @since 11.0 + */ + public static SortedMapDifference difference( + SortedMap left, Map right) { + checkNotNull(left); + checkNotNull(right); + Comparator comparator = orNaturalOrder(left.comparator()); + SortedMap onlyOnLeft = Maps.newTreeMap(comparator); + SortedMap onlyOnRight = Maps.newTreeMap(comparator); + onlyOnRight.putAll(right); // will whittle it down + SortedMap onBoth = Maps.newTreeMap(comparator); + SortedMap> differences = Maps.newTreeMap(comparator); + doDifference(left, right, Equivalence.equals(), onlyOnLeft, onlyOnRight, onBoth, differences); + return new SortedMapDifferenceImpl<>(onlyOnLeft, onlyOnRight, onBoth, differences); + } + + private static void doDifference( + Map left, + Map right, + Equivalence valueEquivalence, + Map onlyOnLeft, + Map onlyOnRight, + Map onBoth, + Map> differences) { + for (Entry entry : left.entrySet()) { + K leftKey = entry.getKey(); + V leftValue = entry.getValue(); + if (right.containsKey(leftKey)) { + V rightValue = onlyOnRight.remove(leftKey); + if (valueEquivalence.equivalent(leftValue, rightValue)) { + onBoth.put(leftKey, leftValue); + } else { + differences.put(leftKey, ValueDifferenceImpl.create(leftValue, rightValue)); + } + } else { + onlyOnLeft.put(leftKey, leftValue); + } + } + } + + private static Map unmodifiableMap(Map map) { + if (map instanceof SortedMap) { + return Collections.unmodifiableSortedMap((SortedMap) map); + } else { + return Collections.unmodifiableMap(map); + } + } + + static class MapDifferenceImpl implements MapDifference { + final Map onlyOnLeft; + final Map onlyOnRight; + final Map onBoth; + final Map> differences; + + MapDifferenceImpl( + Map onlyOnLeft, + Map onlyOnRight, + Map onBoth, + Map> differences) { + this.onlyOnLeft = unmodifiableMap(onlyOnLeft); + this.onlyOnRight = unmodifiableMap(onlyOnRight); + this.onBoth = unmodifiableMap(onBoth); + this.differences = unmodifiableMap(differences); + } + + @Override + public boolean areEqual() { + return onlyOnLeft.isEmpty() && onlyOnRight.isEmpty() && differences.isEmpty(); + } + + @Override + public Map entriesOnlyOnLeft() { + return onlyOnLeft; + } + + @Override + public Map entriesOnlyOnRight() { + return onlyOnRight; + } + + @Override + public Map entriesInCommon() { + return onBoth; + } + + @Override + public Map> entriesDiffering() { + return differences; + } + + @Override + public boolean equals(Object object) { + if (object == this) { + return true; + } + if (object instanceof MapDifference) { + MapDifference other = (MapDifference) object; + return entriesOnlyOnLeft().equals(other.entriesOnlyOnLeft()) + && entriesOnlyOnRight().equals(other.entriesOnlyOnRight()) + && entriesInCommon().equals(other.entriesInCommon()) + && entriesDiffering().equals(other.entriesDiffering()); + } + return false; + } + + @Override + public int hashCode() { + return Objects.hashCode( + entriesOnlyOnLeft(), entriesOnlyOnRight(), entriesInCommon(), entriesDiffering()); + } + + @Override + public String toString() { + if (areEqual()) { + return "equal"; + } + + StringBuilder result = new StringBuilder("not equal"); + if (!onlyOnLeft.isEmpty()) { + result.append(": only on left=").append(onlyOnLeft); + } + if (!onlyOnRight.isEmpty()) { + result.append(": only on right=").append(onlyOnRight); + } + if (!differences.isEmpty()) { + result.append(": value differences=").append(differences); + } + return result.toString(); + } + } + + static class ValueDifferenceImpl implements MapDifference.ValueDifference { + private final V left; + private final V right; + + static ValueDifference create(V left, V right) { + return new ValueDifferenceImpl(left, right); + } + + private ValueDifferenceImpl(V left, V right) { + this.left = left; + this.right = right; + } + + @Override + public V leftValue() { + return left; + } + + @Override + public V rightValue() { + return right; + } + + @Override + public boolean equals(Object object) { + if (object instanceof MapDifference.ValueDifference) { + MapDifference.ValueDifference that = (MapDifference.ValueDifference) object; + return Objects.equal(this.left, that.leftValue()) + && Objects.equal(this.right, that.rightValue()); + } + return false; + } + + @Override + public int hashCode() { + return Objects.hashCode(left, right); + } + + @Override + public String toString() { + return "(" + left + ", " + right + ")"; + } + } + + static class SortedMapDifferenceImpl extends MapDifferenceImpl + implements SortedMapDifference { + SortedMapDifferenceImpl( + SortedMap onlyOnLeft, + SortedMap onlyOnRight, + SortedMap onBoth, + SortedMap> differences) { + super(onlyOnLeft, onlyOnRight, onBoth, differences); + } + + @Override + public SortedMap> entriesDiffering() { + return (SortedMap>) super.entriesDiffering(); + } + + @Override + public SortedMap entriesInCommon() { + return (SortedMap) super.entriesInCommon(); + } + + @Override + public SortedMap entriesOnlyOnLeft() { + return (SortedMap) super.entriesOnlyOnLeft(); + } + + @Override + public SortedMap entriesOnlyOnRight() { + return (SortedMap) super.entriesOnlyOnRight(); + } + } + + /** + * Returns the specified comparator if not null; otherwise returns {@code Ordering.natural()}. + * This method is an abomination of generics; the only purpose of this method is to contain the + * ugly type-casting in one place. + */ + @SuppressWarnings("unchecked") + static Comparator orNaturalOrder(Comparator comparator) { + if (comparator != null) { // can't use ? : because of javac bug 5080917 + return comparator; + } + return (Comparator) Ordering.natural(); + } + + /** + * Returns a live {@link Map} view whose keys are the contents of {@code set} and whose values are + * computed on demand using {@code function}. To get an immutable copy instead, use {@link + * #toMap(Iterable, Function)}. + * + *

Specifically, for each {@code k} in the backing set, the returned map has an entry mapping + * {@code k} to {@code function.apply(k)}. The {@code keySet}, {@code values}, and {@code + * entrySet} views of the returned map iterate in the same order as the backing set. + * + *

Modifications to the backing set are read through to the returned map. The returned map + * supports removal operations if the backing set does. Removal operations write through to the + * backing set. The returned map does not support put operations. + * + *

Warning: If the function rejects {@code null}, caution is required to make sure the + * set does not contain {@code null}, because the view cannot stop {@code null} from being added + * to the set. + * + *

Warning: This method assumes that for any instance {@code k} of key type {@code K}, + * {@code k.equals(k2)} implies that {@code k2} is also of type {@code K}. Using a key type for + * which this may not hold, such as {@code ArrayList}, may risk a {@code ClassCastException} when + * calling methods on the resulting map view. + * + * @since 14.0 + */ + public static Map asMap(Set set, Function function) { + return new AsMapView<>(set, function); + } + + /** + * Returns a view of the sorted set as a map, mapping keys from the set according to the specified + * function. + * + *

Specifically, for each {@code k} in the backing set, the returned map has an entry mapping + * {@code k} to {@code function.apply(k)}. The {@code keySet}, {@code values}, and {@code + * entrySet} views of the returned map iterate in the same order as the backing set. + * + *

Modifications to the backing set are read through to the returned map. The returned map + * supports removal operations if the backing set does. Removal operations write through to the + * backing set. The returned map does not support put operations. + * + *

Warning: If the function rejects {@code null}, caution is required to make sure the + * set does not contain {@code null}, because the view cannot stop {@code null} from being added + * to the set. + * + *

Warning: This method assumes that for any instance {@code k} of key type {@code K}, + * {@code k.equals(k2)} implies that {@code k2} is also of type {@code K}. Using a key type for + * which this may not hold, such as {@code ArrayList}, may risk a {@code ClassCastException} when + * calling methods on the resulting map view. + * + * @since 14.0 + */ + public static SortedMap asMap(SortedSet set, Function function) { + return new SortedAsMapView<>(set, function); + } + + /** + * Returns a view of the navigable set as a map, mapping keys from the set according to the + * specified function. + * + *

Specifically, for each {@code k} in the backing set, the returned map has an entry mapping + * {@code k} to {@code function.apply(k)}. The {@code keySet}, {@code values}, and {@code + * entrySet} views of the returned map iterate in the same order as the backing set. + * + *

Modifications to the backing set are read through to the returned map. The returned map + * supports removal operations if the backing set does. Removal operations write through to the + * backing set. The returned map does not support put operations. + * + *

Warning: If the function rejects {@code null}, caution is required to make sure the + * set does not contain {@code null}, because the view cannot stop {@code null} from being added + * to the set. + * + *

Warning: This method assumes that for any instance {@code k} of key type {@code K}, + * {@code k.equals(k2)} implies that {@code k2} is also of type {@code K}. Using a key type for + * which this may not hold, such as {@code ArrayList}, may risk a {@code ClassCastException} when + * calling methods on the resulting map view. + * + * @since 14.0 + */ + @GwtIncompatible // NavigableMap + public static NavigableMap asMap( + NavigableSet set, Function function) { + return new NavigableAsMapView<>(set, function); + } + + private static class AsMapView extends ViewCachingAbstractMap { + + private final Set set; + final Function function; + + Set backingSet() { + return set; + } + + AsMapView(Set set, Function function) { + this.set = checkNotNull(set); + this.function = checkNotNull(function); + } + + @Override + public Set createKeySet() { + return removeOnlySet(backingSet()); + } + + @Override + Collection createValues() { + return Collections2.transform(set, function); + } + + @Override + public int size() { + return backingSet().size(); + } + + @Override + public boolean containsKey(Object key) { + return backingSet().contains(key); + } + + @Override + public V get(Object key) { + return getOrDefault(key, null); + } + + @Override + public V getOrDefault(Object key, V defaultValue) { + if (Collections2.safeContains(backingSet(), key)) { + @SuppressWarnings("unchecked") // unsafe, but Javadoc warns about it + K k = (K) key; + return function.apply(k); + } else { + return defaultValue; + } + } + + @Override + public V remove(Object key) { + if (backingSet().remove(key)) { + @SuppressWarnings("unchecked") // unsafe, but Javadoc warns about it + K k = (K) key; + return function.apply(k); + } else { + return null; + } + } + + @Override + public void clear() { + backingSet().clear(); + } + + @Override + protected Set> createEntrySet() { + + class EntrySetImpl extends EntrySet { + @Override + Map map() { + return AsMapView.this; + } + + @Override + public Iterator> iterator() { + return asMapEntryIterator(backingSet(), function); + } + } + return new EntrySetImpl(); + } + + @Override + public void forEach(BiConsumer action) { + checkNotNull(action); + // avoids allocation of entries + backingSet().forEach(k -> action.accept(k, function.apply(k))); + } + } + + static Iterator> asMapEntryIterator( + Set set, final Function function) { + return new TransformedIterator>(set.iterator()) { + @Override + Entry transform(final K key) { + return immutableEntry(key, function.apply(key)); + } + }; + } + + private static class SortedAsMapView extends AsMapView implements SortedMap { + + SortedAsMapView(SortedSet set, Function function) { + super(set, function); + } + + @Override + SortedSet backingSet() { + return (SortedSet) super.backingSet(); + } + + @Override + public Comparator comparator() { + return backingSet().comparator(); + } + + @Override + public Set keySet() { + return removeOnlySortedSet(backingSet()); + } + + @Override + public SortedMap subMap(K fromKey, K toKey) { + return asMap(backingSet().subSet(fromKey, toKey), function); + } + + @Override + public SortedMap headMap(K toKey) { + return asMap(backingSet().headSet(toKey), function); + } + + @Override + public SortedMap tailMap(K fromKey) { + return asMap(backingSet().tailSet(fromKey), function); + } + + @Override + public K firstKey() { + return backingSet().first(); + } + + @Override + public K lastKey() { + return backingSet().last(); + } + } + + @GwtIncompatible // NavigableMap + private static final class NavigableAsMapView extends AbstractNavigableMap { + /* + * Using AbstractNavigableMap is simpler than extending SortedAsMapView and rewriting all the + * NavigableMap methods. + */ + + private final NavigableSet set; + private final Function function; + + NavigableAsMapView(NavigableSet ks, Function vFunction) { + this.set = checkNotNull(ks); + this.function = checkNotNull(vFunction); + } + + @Override + public NavigableMap subMap( + K fromKey, boolean fromInclusive, K toKey, boolean toInclusive) { + return asMap(set.subSet(fromKey, fromInclusive, toKey, toInclusive), function); + } + + @Override + public NavigableMap headMap(K toKey, boolean inclusive) { + return asMap(set.headSet(toKey, inclusive), function); + } + + @Override + public NavigableMap tailMap(K fromKey, boolean inclusive) { + return asMap(set.tailSet(fromKey, inclusive), function); + } + + @Override + public Comparator comparator() { + return set.comparator(); + } + + @Override + public V get(Object key) { + return getOrDefault(key, null); + } + + @Override + public V getOrDefault(Object key, V defaultValue) { + if (Collections2.safeContains(set, key)) { + @SuppressWarnings("unchecked") // unsafe, but Javadoc warns about it + K k = (K) key; + return function.apply(k); + } else { + return defaultValue; + } + } + + @Override + public void clear() { + set.clear(); + } + + @Override + Iterator> entryIterator() { + return asMapEntryIterator(set, function); + } + + @Override + Spliterator> entrySpliterator() { + return CollectSpliterators.map(set.spliterator(), e -> immutableEntry(e, function.apply(e))); + } + + @Override + public void forEach(BiConsumer action) { + set.forEach(k -> action.accept(k, function.apply(k))); + } + + @Override + Iterator> descendingEntryIterator() { + return descendingMap().entrySet().iterator(); + } + + @Override + public NavigableSet navigableKeySet() { + return removeOnlyNavigableSet(set); + } + + @Override + public int size() { + return set.size(); + } + + @Override + public NavigableMap descendingMap() { + return asMap(set.descendingSet(), function); + } + } + + private static Set removeOnlySet(final Set set) { + return new ForwardingSet() { + @Override + protected Set delegate() { + return set; + } + + @Override + public boolean add(E element) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean addAll(Collection es) { + throw new UnsupportedOperationException(); + } + }; + } + + private static SortedSet removeOnlySortedSet(final SortedSet set) { + return new ForwardingSortedSet() { + @Override + protected SortedSet delegate() { + return set; + } + + @Override + public boolean add(E element) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean addAll(Collection es) { + throw new UnsupportedOperationException(); + } + + @Override + public SortedSet headSet(E toElement) { + return removeOnlySortedSet(super.headSet(toElement)); + } + + @Override + public SortedSet subSet(E fromElement, E toElement) { + return removeOnlySortedSet(super.subSet(fromElement, toElement)); + } + + @Override + public SortedSet tailSet(E fromElement) { + return removeOnlySortedSet(super.tailSet(fromElement)); + } + }; + } + + @GwtIncompatible // NavigableSet + private static NavigableSet removeOnlyNavigableSet(final NavigableSet set) { + return new ForwardingNavigableSet() { + @Override + protected NavigableSet delegate() { + return set; + } + + @Override + public boolean add(E element) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean addAll(Collection es) { + throw new UnsupportedOperationException(); + } + + @Override + public SortedSet headSet(E toElement) { + return removeOnlySortedSet(super.headSet(toElement)); + } + + @Override + public NavigableSet headSet(E toElement, boolean inclusive) { + return removeOnlyNavigableSet(super.headSet(toElement, inclusive)); + } + + @Override + public SortedSet subSet(E fromElement, E toElement) { + return removeOnlySortedSet(super.subSet(fromElement, toElement)); + } + + @Override + public NavigableSet subSet( + E fromElement, boolean fromInclusive, E toElement, boolean toInclusive) { + return removeOnlyNavigableSet( + super.subSet(fromElement, fromInclusive, toElement, toInclusive)); + } + + @Override + public SortedSet tailSet(E fromElement) { + return removeOnlySortedSet(super.tailSet(fromElement)); + } + + @Override + public NavigableSet tailSet(E fromElement, boolean inclusive) { + return removeOnlyNavigableSet(super.tailSet(fromElement, inclusive)); + } + + @Override + public NavigableSet descendingSet() { + return removeOnlyNavigableSet(super.descendingSet()); + } + }; + } + + /** + * Returns an immutable map whose keys are the distinct elements of {@code keys} and whose value + * for each key was computed by {@code valueFunction}. The map's iteration order is the order of + * the first appearance of each key in {@code keys}. + * + *

When there are multiple instances of a key in {@code keys}, it is unspecified whether {@code + * valueFunction} will be applied to more than one instance of that key and, if it is, which + * result will be mapped to that key in the returned map. + * + *

If {@code keys} is a {@link Set}, a live view can be obtained instead of a copy using {@link + * Maps#asMap(Set, Function)}. + * + * @throws NullPointerException if any element of {@code keys} is {@code null}, or if {@code + * valueFunction} produces {@code null} for any key + * @since 14.0 + */ + public static ImmutableMap toMap( + Iterable keys, Function valueFunction) { + return toMap(keys.iterator(), valueFunction); + } + + /** + * Returns an immutable map whose keys are the distinct elements of {@code keys} and whose value + * for each key was computed by {@code valueFunction}. The map's iteration order is the order of + * the first appearance of each key in {@code keys}. + * + *

When there are multiple instances of a key in {@code keys}, it is unspecified whether {@code + * valueFunction} will be applied to more than one instance of that key and, if it is, which + * result will be mapped to that key in the returned map. + * + * @throws NullPointerException if any element of {@code keys} is {@code null}, or if {@code + * valueFunction} produces {@code null} for any key + * @since 14.0 + */ + public static ImmutableMap toMap( + Iterator keys, Function valueFunction) { + checkNotNull(valueFunction); + // Using LHM instead of a builder so as not to fail on duplicate keys + Map builder = newLinkedHashMap(); + while (keys.hasNext()) { + K key = keys.next(); + builder.put(key, valueFunction.apply(key)); + } + return ImmutableMap.copyOf(builder); + } + + /** + * Returns a map with the given {@code values}, indexed by keys derived from those values. In + * other words, each input value produces an entry in the map whose key is the result of applying + * {@code keyFunction} to that value. These entries appear in the same order as the input values. + * Example usage: + * + *

{@code
+   * Color red = new Color("red", 255, 0, 0);
+   * ...
+   * ImmutableSet allColors = ImmutableSet.of(red, green, blue);
+   *
+   * Map colorForName =
+   *     uniqueIndex(allColors, toStringFunction());
+   * assertThat(colorForName).containsEntry("red", red);
+   * }
+ * + *

If your index may associate multiple values with each key, use {@link + * Multimaps#index(Iterable, Function) Multimaps.index}. + * + * @param values the values to use when constructing the {@code Map} + * @param keyFunction the function used to produce the key for each value + * @return a map mapping the result of evaluating the function {@code keyFunction} on each value + * in the input collection to that value + * @throws IllegalArgumentException if {@code keyFunction} produces the same key for more than one + * value in the input collection + * @throws NullPointerException if any element of {@code values} is {@code null}, or if {@code + * keyFunction} produces {@code null} for any value + */ + + public static ImmutableMap uniqueIndex( + Iterable values, Function keyFunction) { + // TODO(lowasser): consider presizing the builder if values is a Collection + return uniqueIndex(values.iterator(), keyFunction); + } + + /** + * Returns a map with the given {@code values}, indexed by keys derived from those values. In + * other words, each input value produces an entry in the map whose key is the result of applying + * {@code keyFunction} to that value. These entries appear in the same order as the input values. + * Example usage: + * + *

{@code
+   * Color red = new Color("red", 255, 0, 0);
+   * ...
+   * Iterator allColors = ImmutableSet.of(red, green, blue).iterator();
+   *
+   * Map colorForName =
+   *     uniqueIndex(allColors, toStringFunction());
+   * assertThat(colorForName).containsEntry("red", red);
+   * }
+ * + *

If your index may associate multiple values with each key, use {@link + * Multimaps#index(Iterator, Function) Multimaps.index}. + * + * @param values the values to use when constructing the {@code Map} + * @param keyFunction the function used to produce the key for each value + * @return a map mapping the result of evaluating the function {@code keyFunction} on each value + * in the input collection to that value + * @throws IllegalArgumentException if {@code keyFunction} produces the same key for more than one + * value in the input collection + * @throws NullPointerException if any element of {@code values} is {@code null}, or if {@code + * keyFunction} produces {@code null} for any value + * @since 10.0 + */ + + public static ImmutableMap uniqueIndex( + Iterator values, Function keyFunction) { + checkNotNull(keyFunction); + ImmutableMap.Builder builder = ImmutableMap.builder(); + while (values.hasNext()) { + V value = values.next(); + builder.put(keyFunction.apply(value), value); + } + try { + return builder.build(); + } catch (IllegalArgumentException duplicateKeys) { + throw new IllegalArgumentException( + duplicateKeys.getMessage() + + ". To index multiple values under a key, use Multimaps.index."); + } + } + + /** + * Creates an {@code ImmutableMap} from a {@code Properties} instance. Properties + * normally derive from {@code Map}, but they typically contain strings, which is + * awkward. This method lets you get a plain-old-{@code Map} out of a {@code Properties}. + * + * @param properties a {@code Properties} object to be converted + * @return an immutable map containing all the entries in {@code properties} + * @throws ClassCastException if any key in {@code Properties} is not a {@code String} + * @throws NullPointerException if any key or value in {@code Properties} is null + */ + @GwtIncompatible // java.util.Properties + public static ImmutableMap fromProperties(Properties properties) { + ImmutableMap.Builder builder = ImmutableMap.builder(); + + for (Enumeration e = properties.propertyNames(); e.hasMoreElements(); ) { + String key = (String) e.nextElement(); + builder.put(key, properties.getProperty(key)); + } + + return builder.build(); + } + + /** + * Returns an immutable map entry with the specified key and value. The {@link Entry#setValue} + * operation throws an {@link UnsupportedOperationException}. + * + *

The returned entry is serializable. + * + *

Java 9 users: consider using {@code java.util.Map.entry(key, value)} if the key and + * value are non-null and the entry does not need to be serializable. + * + * @param key the key to be associated with the returned entry + * @param value the value to be associated with the returned entry + */ + @GwtCompatible(serializable = true) + public static Entry immutableEntry(K key, V value) { + return new ImmutableEntry<>(key, value); + } + + /** + * Returns an unmodifiable view of the specified set of entries. The {@link Entry#setValue} + * operation throws an {@link UnsupportedOperationException}, as do any operations that would + * modify the returned set. + * + * @param entrySet the entries for which to return an unmodifiable view + * @return an unmodifiable view of the entries + */ + static Set> unmodifiableEntrySet(Set> entrySet) { + return new UnmodifiableEntrySet<>(Collections.unmodifiableSet(entrySet)); + } + + /** + * Returns an unmodifiable view of the specified map entry. The {@link Entry#setValue} operation + * throws an {@link UnsupportedOperationException}. This also has the side-effect of redefining + * {@code equals} to comply with the Entry contract, to avoid a possible nefarious implementation + * of equals. + * + * @param entry the entry for which to return an unmodifiable view + * @return an unmodifiable view of the entry + */ + static Entry unmodifiableEntry(final Entry entry) { + checkNotNull(entry); + return new AbstractMapEntry() { + @Override + public K getKey() { + return entry.getKey(); + } + + @Override + public V getValue() { + return entry.getValue(); + } + }; + } + + static UnmodifiableIterator> unmodifiableEntryIterator( + final Iterator> entryIterator) { + return new UnmodifiableIterator>() { + @Override + public boolean hasNext() { + return entryIterator.hasNext(); + } + + @Override + public Entry next() { + return unmodifiableEntry(entryIterator.next()); + } + }; + } + + /** @see Multimaps#unmodifiableEntries */ + static class UnmodifiableEntries extends ForwardingCollection> { + private final Collection> entries; + + UnmodifiableEntries(Collection> entries) { + this.entries = entries; + } + + @Override + protected Collection> delegate() { + return entries; + } + + @Override + public Iterator> iterator() { + return unmodifiableEntryIterator(entries.iterator()); + } + + // See java.util.Collections.UnmodifiableEntrySet for details on attacks. + + @Override + public Object[] toArray() { + return standardToArray(); + } + + @Override + public T[] toArray(T[] array) { + return standardToArray(array); + } + } + + /** @see Maps#unmodifiableEntrySet(Set) */ + static class UnmodifiableEntrySet extends UnmodifiableEntries + implements Set> { + UnmodifiableEntrySet(Set> entries) { + super(entries); + } + + // See java.util.Collections.UnmodifiableEntrySet for details on attacks. + + @Override + public boolean equals(Object object) { + return Sets.equalsImpl(this, object); + } + + @Override + public int hashCode() { + return Sets.hashCodeImpl(this); + } + } + + /** + * Returns a {@link Converter} that converts values using {@link BiMap#get bimap.get()}, and whose + * inverse view converts values using {@link BiMap#inverse bimap.inverse()}{@code .get()}. + * + *

To use a plain {@link Map} as a {@link Function}, see {@link + * com.google.common.base.Functions#forMap(Map)} or {@link + * com.google.common.base.Functions#forMap(Map, Object)}. + * + * @since 16.0 + */ + public static Converter asConverter(final BiMap bimap) { + return new BiMapConverter<>(bimap); + } + + private static final class BiMapConverter extends Converter implements Serializable { + private final BiMap bimap; + + BiMapConverter(BiMap bimap) { + this.bimap = checkNotNull(bimap); + } + + @Override + protected B doForward(A a) { + return convert(bimap, a); + } + + @Override + protected A doBackward(B b) { + return convert(bimap.inverse(), b); + } + + private static Y convert(BiMap bimap, X input) { + Y output = bimap.get(input); + checkArgument(output != null, "No non-null mapping present for input: %s", input); + return output; + } + + @Override + public boolean equals(Object object) { + if (object instanceof BiMapConverter) { + BiMapConverter that = (BiMapConverter) object; + return this.bimap.equals(that.bimap); + } + return false; + } + + @Override + public int hashCode() { + return bimap.hashCode(); + } + + // There's really no good way to implement toString() without printing the entire BiMap, right? + @Override + public String toString() { + return "Maps.asConverter(" + bimap + ")"; + } + + private static final long serialVersionUID = 0L; + } + + /** + * Returns a synchronized (thread-safe) bimap backed by the specified bimap. In order to guarantee + * serial access, it is critical that all access to the backing bimap is accomplished + * through the returned bimap. + * + *

It is imperative that the user manually synchronize on the returned map when accessing any + * of its collection views: + * + *

{@code
+   * BiMap map = Maps.synchronizedBiMap(
+   *     HashBiMap.create());
+   * ...
+   * Set set = map.keySet();  // Needn't be in synchronized block
+   * ...
+   * synchronized (map) {  // Synchronizing on map, not set!
+   *   Iterator it = set.iterator(); // Must be in synchronized block
+   *   while (it.hasNext()) {
+   *     foo(it.next());
+   *   }
+   * }
+   * }
+ * + *

Failure to follow this advice may result in non-deterministic behavior. + * + *

The returned bimap will be serializable if the specified bimap is serializable. + * + * @param bimap the bimap to be wrapped in a synchronized view + * @return a synchronized view of the specified bimap + */ + public static BiMap synchronizedBiMap(BiMap bimap) { + return Synchronized.biMap(bimap, null); + } + + /** + * Returns an unmodifiable view of the specified bimap. This method allows modules to provide + * users with "read-only" access to internal bimaps. Query operations on the returned bimap "read + * through" to the specified bimap, and attempts to modify the returned map, whether direct or via + * its collection views, result in an {@code UnsupportedOperationException}. + * + *

The returned bimap will be serializable if the specified bimap is serializable. + * + * @param bimap the bimap for which an unmodifiable view is to be returned + * @return an unmodifiable view of the specified bimap + */ + public static BiMap unmodifiableBiMap(BiMap bimap) { + return new UnmodifiableBiMap<>(bimap, null); + } + + /** @see Maps#unmodifiableBiMap(BiMap) */ + private static class UnmodifiableBiMap extends ForwardingMap + implements BiMap, Serializable { + final Map unmodifiableMap; + final BiMap delegate; + BiMap inverse; + transient Set values; + + UnmodifiableBiMap(BiMap delegate, BiMap inverse) { + unmodifiableMap = Collections.unmodifiableMap(delegate); + this.delegate = delegate; + this.inverse = inverse; + } + + @Override + protected Map delegate() { + return unmodifiableMap; + } + + @Override + public V forcePut(K key, V value) { + throw new UnsupportedOperationException(); + } + + @Override + public BiMap inverse() { + BiMap result = inverse; + return (result == null) + ? inverse = new UnmodifiableBiMap<>(delegate.inverse(), this) + : result; + } + + @Override + public Set values() { + Set result = values; + return (result == null) ? values = Collections.unmodifiableSet(delegate.values()) : result; + } + + private static final long serialVersionUID = 0; + } + + /** + * Returns a view of a map where each value is transformed by a function. All other properties of + * the map, such as iteration order, are left intact. For example, the code: + * + *

{@code
+   * Map map = ImmutableMap.of("a", 4, "b", 9);
+   * Function sqrt =
+   *     new Function() {
+   *       public Double apply(Integer in) {
+   *         return Math.sqrt((int) in);
+   *       }
+   *     };
+   * Map transformed = Maps.transformValues(map, sqrt);
+   * System.out.println(transformed);
+   * }
+ * + * ... prints {@code {a=2.0, b=3.0}}. + * + *

Changes in the underlying map are reflected in this view. Conversely, this view supports + * removal operations, and these are reflected in the underlying map. + * + *

It's acceptable for the underlying map to contain null keys, and even null values provided + * that the function is capable of accepting null input. The transformed map might contain null + * values, if the function sometimes gives a null result. + * + *

The returned map is not thread-safe or serializable, even if the underlying map is. + * + *

The function is applied lazily, invoked when needed. This is necessary for the returned map + * to be a view, but it means that the function will be applied many times for bulk operations + * like {@link Map#containsValue} and {@code Map.toString()}. For this to perform well, {@code + * function} should be fast. To avoid lazy evaluation when the returned map doesn't need to be a + * view, copy the returned map into a new map of your choosing. + */ + public static Map transformValues( + Map fromMap, Function function) { + return transformEntries(fromMap, asEntryTransformer(function)); + } + + /** + * Returns a view of a sorted map where each value is transformed by a function. All other + * properties of the map, such as iteration order, are left intact. For example, the code: + * + *

{@code
+   * SortedMap map = ImmutableSortedMap.of("a", 4, "b", 9);
+   * Function sqrt =
+   *     new Function() {
+   *       public Double apply(Integer in) {
+   *         return Math.sqrt((int) in);
+   *       }
+   *     };
+   * SortedMap transformed =
+   *      Maps.transformValues(map, sqrt);
+   * System.out.println(transformed);
+   * }
+ * + * ... prints {@code {a=2.0, b=3.0}}. + * + *

Changes in the underlying map are reflected in this view. Conversely, this view supports + * removal operations, and these are reflected in the underlying map. + * + *

It's acceptable for the underlying map to contain null keys, and even null values provided + * that the function is capable of accepting null input. The transformed map might contain null + * values, if the function sometimes gives a null result. + * + *

The returned map is not thread-safe or serializable, even if the underlying map is. + * + *

The function is applied lazily, invoked when needed. This is necessary for the returned map + * to be a view, but it means that the function will be applied many times for bulk operations + * like {@link Map#containsValue} and {@code Map.toString()}. For this to perform well, {@code + * function} should be fast. To avoid lazy evaluation when the returned map doesn't need to be a + * view, copy the returned map into a new map of your choosing. + * + * @since 11.0 + */ + public static SortedMap transformValues( + SortedMap fromMap, Function function) { + return transformEntries(fromMap, asEntryTransformer(function)); + } + + /** + * Returns a view of a navigable map where each value is transformed by a function. All other + * properties of the map, such as iteration order, are left intact. For example, the code: + * + *

{@code
+   * NavigableMap map = Maps.newTreeMap();
+   * map.put("a", 4);
+   * map.put("b", 9);
+   * Function sqrt =
+   *     new Function() {
+   *       public Double apply(Integer in) {
+   *         return Math.sqrt((int) in);
+   *       }
+   *     };
+   * NavigableMap transformed =
+   *      Maps.transformNavigableValues(map, sqrt);
+   * System.out.println(transformed);
+   * }
+ * + * ... prints {@code {a=2.0, b=3.0}}. + * + *

Changes in the underlying map are reflected in this view. Conversely, this view supports + * removal operations, and these are reflected in the underlying map. + * + *

It's acceptable for the underlying map to contain null keys, and even null values provided + * that the function is capable of accepting null input. The transformed map might contain null + * values, if the function sometimes gives a null result. + * + *

The returned map is not thread-safe or serializable, even if the underlying map is. + * + *

The function is applied lazily, invoked when needed. This is necessary for the returned map + * to be a view, but it means that the function will be applied many times for bulk operations + * like {@link Map#containsValue} and {@code Map.toString()}. For this to perform well, {@code + * function} should be fast. To avoid lazy evaluation when the returned map doesn't need to be a + * view, copy the returned map into a new map of your choosing. + * + * @since 13.0 + */ + @GwtIncompatible // NavigableMap + public static NavigableMap transformValues( + NavigableMap fromMap, Function function) { + return transformEntries(fromMap, asEntryTransformer(function)); + } + + /** + * Returns a view of a map whose values are derived from the original map's entries. In contrast + * to {@link #transformValues}, this method's entry-transformation logic may depend on the key as + * well as the value. + * + *

All other properties of the transformed map, such as iteration order, are left intact. For + * example, the code: + * + *

{@code
+   * Map options =
+   *     ImmutableMap.of("verbose", true, "sort", false);
+   * EntryTransformer flagPrefixer =
+   *     new EntryTransformer() {
+   *       public String transformEntry(String key, Boolean value) {
+   *         return value ? key : "no" + key;
+   *       }
+   *     };
+   * Map transformed =
+   *     Maps.transformEntries(options, flagPrefixer);
+   * System.out.println(transformed);
+   * }
+ * + * ... prints {@code {verbose=verbose, sort=nosort}}. + * + *

Changes in the underlying map are reflected in this view. Conversely, this view supports + * removal operations, and these are reflected in the underlying map. + * + *

It's acceptable for the underlying map to contain null keys and null values provided that + * the transformer is capable of accepting null inputs. The transformed map might contain null + * values if the transformer sometimes gives a null result. + * + *

The returned map is not thread-safe or serializable, even if the underlying map is. + * + *

The transformer is applied lazily, invoked when needed. This is necessary for the returned + * map to be a view, but it means that the transformer will be applied many times for bulk + * operations like {@link Map#containsValue} and {@link Object#toString}. For this to perform + * well, {@code transformer} should be fast. To avoid lazy evaluation when the returned map + * doesn't need to be a view, copy the returned map into a new map of your choosing. + * + *

Warning: This method assumes that for any instance {@code k} of {@code + * EntryTransformer} key type {@code K}, {@code k.equals(k2)} implies that {@code k2} is also of + * type {@code K}. Using an {@code EntryTransformer} key type for which this may not hold, such as + * {@code ArrayList}, may risk a {@code ClassCastException} when calling methods on the + * transformed map. + * + * @since 7.0 + */ + public static Map transformEntries( + Map fromMap, EntryTransformer transformer) { + return new TransformedEntriesMap<>(fromMap, transformer); + } + + /** + * Returns a view of a sorted map whose values are derived from the original sorted map's entries. + * In contrast to {@link #transformValues}, this method's entry-transformation logic may depend on + * the key as well as the value. + * + *

All other properties of the transformed map, such as iteration order, are left intact. For + * example, the code: + * + *

{@code
+   * Map options =
+   *     ImmutableSortedMap.of("verbose", true, "sort", false);
+   * EntryTransformer flagPrefixer =
+   *     new EntryTransformer() {
+   *       public String transformEntry(String key, Boolean value) {
+   *         return value ? key : "yes" + key;
+   *       }
+   *     };
+   * SortedMap transformed =
+   *     Maps.transformEntries(options, flagPrefixer);
+   * System.out.println(transformed);
+   * }
+ * + * ... prints {@code {sort=yessort, verbose=verbose}}. + * + *

Changes in the underlying map are reflected in this view. Conversely, this view supports + * removal operations, and these are reflected in the underlying map. + * + *

It's acceptable for the underlying map to contain null keys and null values provided that + * the transformer is capable of accepting null inputs. The transformed map might contain null + * values if the transformer sometimes gives a null result. + * + *

The returned map is not thread-safe or serializable, even if the underlying map is. + * + *

The transformer is applied lazily, invoked when needed. This is necessary for the returned + * map to be a view, but it means that the transformer will be applied many times for bulk + * operations like {@link Map#containsValue} and {@link Object#toString}. For this to perform + * well, {@code transformer} should be fast. To avoid lazy evaluation when the returned map + * doesn't need to be a view, copy the returned map into a new map of your choosing. + * + *

Warning: This method assumes that for any instance {@code k} of {@code + * EntryTransformer} key type {@code K}, {@code k.equals(k2)} implies that {@code k2} is also of + * type {@code K}. Using an {@code EntryTransformer} key type for which this may not hold, such as + * {@code ArrayList}, may risk a {@code ClassCastException} when calling methods on the + * transformed map. + * + * @since 11.0 + */ + public static SortedMap transformEntries( + SortedMap fromMap, EntryTransformer transformer) { + return new TransformedEntriesSortedMap<>(fromMap, transformer); + } + + /** + * Returns a view of a navigable map whose values are derived from the original navigable map's + * entries. In contrast to {@link #transformValues}, this method's entry-transformation logic may + * depend on the key as well as the value. + * + *

All other properties of the transformed map, such as iteration order, are left intact. For + * example, the code: + * + *

{@code
+   * NavigableMap options = Maps.newTreeMap();
+   * options.put("verbose", false);
+   * options.put("sort", true);
+   * EntryTransformer flagPrefixer =
+   *     new EntryTransformer() {
+   *       public String transformEntry(String key, Boolean value) {
+   *         return value ? key : ("yes" + key);
+   *       }
+   *     };
+   * NavigableMap transformed =
+   *     LabsMaps.transformNavigableEntries(options, flagPrefixer);
+   * System.out.println(transformed);
+   * }
+ * + * ... prints {@code {sort=yessort, verbose=verbose}}. + * + *

Changes in the underlying map are reflected in this view. Conversely, this view supports + * removal operations, and these are reflected in the underlying map. + * + *

It's acceptable for the underlying map to contain null keys and null values provided that + * the transformer is capable of accepting null inputs. The transformed map might contain null + * values if the transformer sometimes gives a null result. + * + *

The returned map is not thread-safe or serializable, even if the underlying map is. + * + *

The transformer is applied lazily, invoked when needed. This is necessary for the returned + * map to be a view, but it means that the transformer will be applied many times for bulk + * operations like {@link Map#containsValue} and {@link Object#toString}. For this to perform + * well, {@code transformer} should be fast. To avoid lazy evaluation when the returned map + * doesn't need to be a view, copy the returned map into a new map of your choosing. + * + *

Warning: This method assumes that for any instance {@code k} of {@code + * EntryTransformer} key type {@code K}, {@code k.equals(k2)} implies that {@code k2} is also of + * type {@code K}. Using an {@code EntryTransformer} key type for which this may not hold, such as + * {@code ArrayList}, may risk a {@code ClassCastException} when calling methods on the + * transformed map. + * + * @since 13.0 + */ + @GwtIncompatible // NavigableMap + public static NavigableMap transformEntries( + final NavigableMap fromMap, EntryTransformer transformer) { + return new TransformedEntriesNavigableMap<>(fromMap, transformer); + } + + /** + * A transformation of the value of a key-value pair, using both key and value as inputs. To apply + * the transformation to a map, use {@link Maps#transformEntries(Map, EntryTransformer)}. + * + * @param the key type of the input and output entries + * @param the value type of the input entry + * @param the value type of the output entry + * @since 7.0 + */ + @FunctionalInterface + public interface EntryTransformer { + /** + * Determines an output value based on a key-value pair. This method is generally + * expected, but not absolutely required, to have the following properties: + * + *

    + *
  • Its execution does not cause any observable side effects. + *
  • The computation is consistent with equals; that is, {@link Objects#equal + * Objects.equal}{@code (k1, k2) &&} {@link Objects#equal}{@code (v1, v2)} implies that + * {@code Objects.equal(transformer.transform(k1, v1), transformer.transform(k2, v2))}. + *
+ * + * @throws NullPointerException if the key or value is null and this transformer does not accept + * null arguments + */ + V2 transformEntry(K key, V1 value); + } + + /** Views a function as an entry transformer that ignores the entry key. */ + static EntryTransformer asEntryTransformer( + final Function function) { + checkNotNull(function); + return new EntryTransformer() { + @Override + public V2 transformEntry(K key, V1 value) { + return function.apply(value); + } + }; + } + + static Function asValueToValueFunction( + final EntryTransformer transformer, final K key) { + checkNotNull(transformer); + return new Function() { + @Override + public V2 apply(V1 v1) { + return transformer.transformEntry(key, v1); + } + }; + } + + /** Views an entry transformer as a function from {@code Entry} to values. */ + static Function, V2> asEntryToValueFunction( + final EntryTransformer transformer) { + checkNotNull(transformer); + return new Function, V2>() { + @Override + public V2 apply(Entry entry) { + return transformer.transformEntry(entry.getKey(), entry.getValue()); + } + }; + } + + /** Returns a view of an entry transformed by the specified transformer. */ + static Entry transformEntry( + final EntryTransformer transformer, final Entry entry) { + checkNotNull(transformer); + checkNotNull(entry); + return new AbstractMapEntry() { + @Override + public K getKey() { + return entry.getKey(); + } + + @Override + public V2 getValue() { + return transformer.transformEntry(entry.getKey(), entry.getValue()); + } + }; + } + + /** Views an entry transformer as a function from entries to entries. */ + static Function, Entry> asEntryToEntryFunction( + final EntryTransformer transformer) { + checkNotNull(transformer); + return new Function, Entry>() { + @Override + public Entry apply(final Entry entry) { + return transformEntry(transformer, entry); + } + }; + } + + static class TransformedEntriesMap extends IteratorBasedAbstractMap { + final Map fromMap; + final EntryTransformer transformer; + + TransformedEntriesMap( + Map fromMap, EntryTransformer transformer) { + this.fromMap = checkNotNull(fromMap); + this.transformer = checkNotNull(transformer); + } + + @Override + public int size() { + return fromMap.size(); + } + + @Override + public boolean containsKey(Object key) { + return fromMap.containsKey(key); + } + + @Override + public V2 get(Object key) { + return getOrDefault(key, null); + } + + // safe as long as the user followed the Warning in the javadoc + @SuppressWarnings("unchecked") + @Override + public V2 getOrDefault(Object key, V2 defaultValue) { + V1 value = fromMap.get(key); + return (value != null || fromMap.containsKey(key)) + ? transformer.transformEntry((K) key, value) + : defaultValue; + } + + // safe as long as the user followed the Warning in the javadoc + @SuppressWarnings("unchecked") + @Override + public V2 remove(Object key) { + return fromMap.containsKey(key) + ? transformer.transformEntry((K) key, fromMap.remove(key)) + : null; + } + + @Override + public void clear() { + fromMap.clear(); + } + + @Override + public Set keySet() { + return fromMap.keySet(); + } + + @Override + Iterator> entryIterator() { + return Iterators.transform( + fromMap.entrySet().iterator(), Maps.asEntryToEntryFunction(transformer)); + } + + @Override + Spliterator> entrySpliterator() { + return CollectSpliterators.map( + fromMap.entrySet().spliterator(), Maps.asEntryToEntryFunction(transformer)); + } + + @Override + public void forEach(BiConsumer action) { + checkNotNull(action); + // avoids creating new Entry objects + fromMap.forEach((k, v1) -> action.accept(k, transformer.transformEntry(k, v1))); + } + + @Override + public Collection values() { + return new Values<>(this); + } + } + + static class TransformedEntriesSortedMap extends TransformedEntriesMap + implements SortedMap { + + protected SortedMap fromMap() { + return (SortedMap) fromMap; + } + + TransformedEntriesSortedMap( + SortedMap fromMap, EntryTransformer transformer) { + super(fromMap, transformer); + } + + @Override + public Comparator comparator() { + return fromMap().comparator(); + } + + @Override + public K firstKey() { + return fromMap().firstKey(); + } + + @Override + public SortedMap headMap(K toKey) { + return transformEntries(fromMap().headMap(toKey), transformer); + } + + @Override + public K lastKey() { + return fromMap().lastKey(); + } + + @Override + public SortedMap subMap(K fromKey, K toKey) { + return transformEntries(fromMap().subMap(fromKey, toKey), transformer); + } + + @Override + public SortedMap tailMap(K fromKey) { + return transformEntries(fromMap().tailMap(fromKey), transformer); + } + } + + @GwtIncompatible // NavigableMap + private static class TransformedEntriesNavigableMap + extends TransformedEntriesSortedMap implements NavigableMap { + + TransformedEntriesNavigableMap( + NavigableMap fromMap, EntryTransformer transformer) { + super(fromMap, transformer); + } + + @Override + public Entry ceilingEntry(K key) { + return transformEntry(fromMap().ceilingEntry(key)); + } + + @Override + public K ceilingKey(K key) { + return fromMap().ceilingKey(key); + } + + @Override + public NavigableSet descendingKeySet() { + return fromMap().descendingKeySet(); + } + + @Override + public NavigableMap descendingMap() { + return transformEntries(fromMap().descendingMap(), transformer); + } + + @Override + public Entry firstEntry() { + return transformEntry(fromMap().firstEntry()); + } + + @Override + public Entry floorEntry(K key) { + return transformEntry(fromMap().floorEntry(key)); + } + + @Override + public K floorKey(K key) { + return fromMap().floorKey(key); + } + + @Override + public NavigableMap headMap(K toKey) { + return headMap(toKey, false); + } + + @Override + public NavigableMap headMap(K toKey, boolean inclusive) { + return transformEntries(fromMap().headMap(toKey, inclusive), transformer); + } + + @Override + public Entry higherEntry(K key) { + return transformEntry(fromMap().higherEntry(key)); + } + + @Override + public K higherKey(K key) { + return fromMap().higherKey(key); + } + + @Override + public Entry lastEntry() { + return transformEntry(fromMap().lastEntry()); + } + + @Override + public Entry lowerEntry(K key) { + return transformEntry(fromMap().lowerEntry(key)); + } + + @Override + public K lowerKey(K key) { + return fromMap().lowerKey(key); + } + + @Override + public NavigableSet navigableKeySet() { + return fromMap().navigableKeySet(); + } + + @Override + public Entry pollFirstEntry() { + return transformEntry(fromMap().pollFirstEntry()); + } + + @Override + public Entry pollLastEntry() { + return transformEntry(fromMap().pollLastEntry()); + } + + @Override + public NavigableMap subMap( + K fromKey, boolean fromInclusive, K toKey, boolean toInclusive) { + return transformEntries( + fromMap().subMap(fromKey, fromInclusive, toKey, toInclusive), transformer); + } + + @Override + public NavigableMap subMap(K fromKey, K toKey) { + return subMap(fromKey, true, toKey, false); + } + + @Override + public NavigableMap tailMap(K fromKey) { + return tailMap(fromKey, true); + } + + @Override + public NavigableMap tailMap(K fromKey, boolean inclusive) { + return transformEntries(fromMap().tailMap(fromKey, inclusive), transformer); + } + + private Entry transformEntry(Entry entry) { + return (entry == null) ? null : Maps.transformEntry(transformer, entry); + } + + @Override + protected NavigableMap fromMap() { + return (NavigableMap) super.fromMap(); + } + } + + static Predicate> keyPredicateOnEntries(Predicate keyPredicate) { + return compose(keyPredicate, Maps.keyFunction()); + } + + static Predicate> valuePredicateOnEntries(Predicate valuePredicate) { + return compose(valuePredicate, Maps.valueFunction()); + } + + /** + * Returns a map containing the mappings in {@code unfiltered} whose keys satisfy a predicate. The + * returned map is a live view of {@code unfiltered}; changes to one affect the other. + * + *

The resulting map's {@code keySet()}, {@code entrySet()}, and {@code values()} views have + * iterators that don't support {@code remove()}, but all other methods are supported by the map + * and its views. When given a key that doesn't satisfy the predicate, the map's {@code put()} and + * {@code putAll()} methods throw an {@link IllegalArgumentException}. + * + *

When methods such as {@code removeAll()} and {@code clear()} are called on the filtered map + * or its views, only mappings whose keys satisfy the filter will be removed from the underlying + * map. + * + *

The returned map isn't threadsafe or serializable, even if {@code unfiltered} is. + * + *

Many of the filtered map's methods, such as {@code size()}, iterate across every key/value + * mapping in the underlying map and determine which satisfy the filter. When a live view is + * not needed, it may be faster to copy the filtered map and use the copy. + * + *

Warning: {@code keyPredicate} must be consistent with equals, as documented at + * {@link Predicate#apply}. Do not provide a predicate such as {@code + * Predicates.instanceOf(ArrayList.class)}, which is inconsistent with equals. + */ + public static Map filterKeys( + Map unfiltered, final Predicate keyPredicate) { + checkNotNull(keyPredicate); + Predicate> entryPredicate = keyPredicateOnEntries(keyPredicate); + return (unfiltered instanceof AbstractFilteredMap) + ? filterFiltered((AbstractFilteredMap) unfiltered, entryPredicate) + : new FilteredKeyMap(checkNotNull(unfiltered), keyPredicate, entryPredicate); + } + + /** + * Returns a sorted map containing the mappings in {@code unfiltered} whose keys satisfy a + * predicate. The returned map is a live view of {@code unfiltered}; changes to one affect the + * other. + * + *

The resulting map's {@code keySet()}, {@code entrySet()}, and {@code values()} views have + * iterators that don't support {@code remove()}, but all other methods are supported by the map + * and its views. When given a key that doesn't satisfy the predicate, the map's {@code put()} and + * {@code putAll()} methods throw an {@link IllegalArgumentException}. + * + *

When methods such as {@code removeAll()} and {@code clear()} are called on the filtered map + * or its views, only mappings whose keys satisfy the filter will be removed from the underlying + * map. + * + *

The returned map isn't threadsafe or serializable, even if {@code unfiltered} is. + * + *

Many of the filtered map's methods, such as {@code size()}, iterate across every key/value + * mapping in the underlying map and determine which satisfy the filter. When a live view is + * not needed, it may be faster to copy the filtered map and use the copy. + * + *

Warning: {@code keyPredicate} must be consistent with equals, as documented at + * {@link Predicate#apply}. Do not provide a predicate such as {@code + * Predicates.instanceOf(ArrayList.class)}, which is inconsistent with equals. + * + * @since 11.0 + */ + public static SortedMap filterKeys( + SortedMap unfiltered, final Predicate keyPredicate) { + // TODO(lowasser): Return a subclass of Maps.FilteredKeyMap for slightly better + // performance. + return filterEntries(unfiltered, Maps.keyPredicateOnEntries(keyPredicate)); + } + + /** + * Returns a navigable map containing the mappings in {@code unfiltered} whose keys satisfy a + * predicate. The returned map is a live view of {@code unfiltered}; changes to one affect the + * other. + * + *

The resulting map's {@code keySet()}, {@code entrySet()}, and {@code values()} views have + * iterators that don't support {@code remove()}, but all other methods are supported by the map + * and its views. When given a key that doesn't satisfy the predicate, the map's {@code put()} and + * {@code putAll()} methods throw an {@link IllegalArgumentException}. + * + *

When methods such as {@code removeAll()} and {@code clear()} are called on the filtered map + * or its views, only mappings whose keys satisfy the filter will be removed from the underlying + * map. + * + *

The returned map isn't threadsafe or serializable, even if {@code unfiltered} is. + * + *

Many of the filtered map's methods, such as {@code size()}, iterate across every key/value + * mapping in the underlying map and determine which satisfy the filter. When a live view is + * not needed, it may be faster to copy the filtered map and use the copy. + * + *

Warning: {@code keyPredicate} must be consistent with equals, as documented at + * {@link Predicate#apply}. Do not provide a predicate such as {@code + * Predicates.instanceOf(ArrayList.class)}, which is inconsistent with equals. + * + * @since 14.0 + */ + @GwtIncompatible // NavigableMap + public static NavigableMap filterKeys( + NavigableMap unfiltered, final Predicate keyPredicate) { + // TODO(lowasser): Return a subclass of Maps.FilteredKeyMap for slightly better + // performance. + return filterEntries(unfiltered, Maps.keyPredicateOnEntries(keyPredicate)); + } + + /** + * Returns a bimap containing the mappings in {@code unfiltered} whose keys satisfy a predicate. + * The returned bimap is a live view of {@code unfiltered}; changes to one affect the other. + * + *

The resulting bimap's {@code keySet()}, {@code entrySet()}, and {@code values()} views have + * iterators that don't support {@code remove()}, but all other methods are supported by the bimap + * and its views. When given a key that doesn't satisfy the predicate, the bimap's {@code put()}, + * {@code forcePut()} and {@code putAll()} methods throw an {@link IllegalArgumentException}. + * + *

When methods such as {@code removeAll()} and {@code clear()} are called on the filtered + * bimap or its views, only mappings that satisfy the filter will be removed from the underlying + * bimap. + * + *

The returned bimap isn't threadsafe or serializable, even if {@code unfiltered} is. + * + *

Many of the filtered bimap's methods, such as {@code size()}, iterate across every key in + * the underlying bimap and determine which satisfy the filter. When a live view is not + * needed, it may be faster to copy the filtered bimap and use the copy. + * + *

Warning: {@code entryPredicate} must be consistent with equals , as documented + * at {@link Predicate#apply}. + * + * @since 14.0 + */ + public static BiMap filterKeys( + BiMap unfiltered, final Predicate keyPredicate) { + checkNotNull(keyPredicate); + return filterEntries(unfiltered, Maps.keyPredicateOnEntries(keyPredicate)); + } + + /** + * Returns a map containing the mappings in {@code unfiltered} whose values satisfy a predicate. + * The returned map is a live view of {@code unfiltered}; changes to one affect the other. + * + *

The resulting map's {@code keySet()}, {@code entrySet()}, and {@code values()} views have + * iterators that don't support {@code remove()}, but all other methods are supported by the map + * and its views. When given a value that doesn't satisfy the predicate, the map's {@code put()}, + * {@code putAll()}, and {@link Entry#setValue} methods throw an {@link IllegalArgumentException}. + * + *

When methods such as {@code removeAll()} and {@code clear()} are called on the filtered map + * or its views, only mappings whose values satisfy the filter will be removed from the underlying + * map. + * + *

The returned map isn't threadsafe or serializable, even if {@code unfiltered} is. + * + *

Many of the filtered map's methods, such as {@code size()}, iterate across every key/value + * mapping in the underlying map and determine which satisfy the filter. When a live view is + * not needed, it may be faster to copy the filtered map and use the copy. + * + *

Warning: {@code valuePredicate} must be consistent with equals, as documented + * at {@link Predicate#apply}. Do not provide a predicate such as {@code + * Predicates.instanceOf(ArrayList.class)}, which is inconsistent with equals. + */ + public static Map filterValues( + Map unfiltered, final Predicate valuePredicate) { + return filterEntries(unfiltered, Maps.valuePredicateOnEntries(valuePredicate)); + } + + /** + * Returns a sorted map containing the mappings in {@code unfiltered} whose values satisfy a + * predicate. The returned map is a live view of {@code unfiltered}; changes to one affect the + * other. + * + *

The resulting map's {@code keySet()}, {@code entrySet()}, and {@code values()} views have + * iterators that don't support {@code remove()}, but all other methods are supported by the map + * and its views. When given a value that doesn't satisfy the predicate, the map's {@code put()}, + * {@code putAll()}, and {@link Entry#setValue} methods throw an {@link IllegalArgumentException}. + * + *

When methods such as {@code removeAll()} and {@code clear()} are called on the filtered map + * or its views, only mappings whose values satisfy the filter will be removed from the underlying + * map. + * + *

The returned map isn't threadsafe or serializable, even if {@code unfiltered} is. + * + *

Many of the filtered map's methods, such as {@code size()}, iterate across every key/value + * mapping in the underlying map and determine which satisfy the filter. When a live view is + * not needed, it may be faster to copy the filtered map and use the copy. + * + *

Warning: {@code valuePredicate} must be consistent with equals, as documented + * at {@link Predicate#apply}. Do not provide a predicate such as {@code + * Predicates.instanceOf(ArrayList.class)}, which is inconsistent with equals. + * + * @since 11.0 + */ + public static SortedMap filterValues( + SortedMap unfiltered, final Predicate valuePredicate) { + return filterEntries(unfiltered, Maps.valuePredicateOnEntries(valuePredicate)); + } + + /** + * Returns a navigable map containing the mappings in {@code unfiltered} whose values satisfy a + * predicate. The returned map is a live view of {@code unfiltered}; changes to one affect the + * other. + * + *

The resulting map's {@code keySet()}, {@code entrySet()}, and {@code values()} views have + * iterators that don't support {@code remove()}, but all other methods are supported by the map + * and its views. When given a value that doesn't satisfy the predicate, the map's {@code put()}, + * {@code putAll()}, and {@link Entry#setValue} methods throw an {@link IllegalArgumentException}. + * + *

When methods such as {@code removeAll()} and {@code clear()} are called on the filtered map + * or its views, only mappings whose values satisfy the filter will be removed from the underlying + * map. + * + *

The returned map isn't threadsafe or serializable, even if {@code unfiltered} is. + * + *

Many of the filtered map's methods, such as {@code size()}, iterate across every key/value + * mapping in the underlying map and determine which satisfy the filter. When a live view is + * not needed, it may be faster to copy the filtered map and use the copy. + * + *

Warning: {@code valuePredicate} must be consistent with equals, as documented + * at {@link Predicate#apply}. Do not provide a predicate such as {@code + * Predicates.instanceOf(ArrayList.class)}, which is inconsistent with equals. + * + * @since 14.0 + */ + @GwtIncompatible // NavigableMap + public static NavigableMap filterValues( + NavigableMap unfiltered, final Predicate valuePredicate) { + return filterEntries(unfiltered, Maps.valuePredicateOnEntries(valuePredicate)); + } + + /** + * Returns a bimap containing the mappings in {@code unfiltered} whose values satisfy a predicate. + * The returned bimap is a live view of {@code unfiltered}; changes to one affect the other. + * + *

The resulting bimap's {@code keySet()}, {@code entrySet()}, and {@code values()} views have + * iterators that don't support {@code remove()}, but all other methods are supported by the bimap + * and its views. When given a value that doesn't satisfy the predicate, the bimap's {@code + * put()}, {@code forcePut()} and {@code putAll()} methods throw an {@link + * IllegalArgumentException}. Similarly, the map's entries have a {@link Entry#setValue} method + * that throws an {@link IllegalArgumentException} when the provided value doesn't satisfy the + * predicate. + * + *

When methods such as {@code removeAll()} and {@code clear()} are called on the filtered + * bimap or its views, only mappings that satisfy the filter will be removed from the underlying + * bimap. + * + *

The returned bimap isn't threadsafe or serializable, even if {@code unfiltered} is. + * + *

Many of the filtered bimap's methods, such as {@code size()}, iterate across every value in + * the underlying bimap and determine which satisfy the filter. When a live view is not + * needed, it may be faster to copy the filtered bimap and use the copy. + * + *

Warning: {@code entryPredicate} must be consistent with equals , as documented + * at {@link Predicate#apply}. + * + * @since 14.0 + */ + public static BiMap filterValues( + BiMap unfiltered, final Predicate valuePredicate) { + return filterEntries(unfiltered, Maps.valuePredicateOnEntries(valuePredicate)); + } + + /** + * Returns a map containing the mappings in {@code unfiltered} that satisfy a predicate. The + * returned map is a live view of {@code unfiltered}; changes to one affect the other. + * + *

The resulting map's {@code keySet()}, {@code entrySet()}, and {@code values()} views have + * iterators that don't support {@code remove()}, but all other methods are supported by the map + * and its views. When given a key/value pair that doesn't satisfy the predicate, the map's {@code + * put()} and {@code putAll()} methods throw an {@link IllegalArgumentException}. Similarly, the + * map's entries have a {@link Entry#setValue} method that throws an {@link + * IllegalArgumentException} when the existing key and the provided value don't satisfy the + * predicate. + * + *

When methods such as {@code removeAll()} and {@code clear()} are called on the filtered map + * or its views, only mappings that satisfy the filter will be removed from the underlying map. + * + *

The returned map isn't threadsafe or serializable, even if {@code unfiltered} is. + * + *

Many of the filtered map's methods, such as {@code size()}, iterate across every key/value + * mapping in the underlying map and determine which satisfy the filter. When a live view is + * not needed, it may be faster to copy the filtered map and use the copy. + * + *

Warning: {@code entryPredicate} must be consistent with equals, as documented + * at {@link Predicate#apply}. + */ + public static Map filterEntries( + Map unfiltered, Predicate> entryPredicate) { + checkNotNull(entryPredicate); + return (unfiltered instanceof AbstractFilteredMap) + ? filterFiltered((AbstractFilteredMap) unfiltered, entryPredicate) + : new FilteredEntryMap(checkNotNull(unfiltered), entryPredicate); + } + + /** + * Returns a sorted map containing the mappings in {@code unfiltered} that satisfy a predicate. + * The returned map is a live view of {@code unfiltered}; changes to one affect the other. + * + *

The resulting map's {@code keySet()}, {@code entrySet()}, and {@code values()} views have + * iterators that don't support {@code remove()}, but all other methods are supported by the map + * and its views. When given a key/value pair that doesn't satisfy the predicate, the map's {@code + * put()} and {@code putAll()} methods throw an {@link IllegalArgumentException}. Similarly, the + * map's entries have a {@link Entry#setValue} method that throws an {@link + * IllegalArgumentException} when the existing key and the provided value don't satisfy the + * predicate. + * + *

When methods such as {@code removeAll()} and {@code clear()} are called on the filtered map + * or its views, only mappings that satisfy the filter will be removed from the underlying map. + * + *

The returned map isn't threadsafe or serializable, even if {@code unfiltered} is. + * + *

Many of the filtered map's methods, such as {@code size()}, iterate across every key/value + * mapping in the underlying map and determine which satisfy the filter. When a live view is + * not needed, it may be faster to copy the filtered map and use the copy. + * + *

Warning: {@code entryPredicate} must be consistent with equals, as documented + * at {@link Predicate#apply}. + * + * @since 11.0 + */ + public static SortedMap filterEntries( + SortedMap unfiltered, Predicate> entryPredicate) { + checkNotNull(entryPredicate); + return (unfiltered instanceof FilteredEntrySortedMap) + ? filterFiltered((FilteredEntrySortedMap) unfiltered, entryPredicate) + : new FilteredEntrySortedMap(checkNotNull(unfiltered), entryPredicate); + } + + /** + * Returns a sorted map containing the mappings in {@code unfiltered} that satisfy a predicate. + * The returned map is a live view of {@code unfiltered}; changes to one affect the other. + * + *

The resulting map's {@code keySet()}, {@code entrySet()}, and {@code values()} views have + * iterators that don't support {@code remove()}, but all other methods are supported by the map + * and its views. When given a key/value pair that doesn't satisfy the predicate, the map's {@code + * put()} and {@code putAll()} methods throw an {@link IllegalArgumentException}. Similarly, the + * map's entries have a {@link Entry#setValue} method that throws an {@link + * IllegalArgumentException} when the existing key and the provided value don't satisfy the + * predicate. + * + *

When methods such as {@code removeAll()} and {@code clear()} are called on the filtered map + * or its views, only mappings that satisfy the filter will be removed from the underlying map. + * + *

The returned map isn't threadsafe or serializable, even if {@code unfiltered} is. + * + *

Many of the filtered map's methods, such as {@code size()}, iterate across every key/value + * mapping in the underlying map and determine which satisfy the filter. When a live view is + * not needed, it may be faster to copy the filtered map and use the copy. + * + *

Warning: {@code entryPredicate} must be consistent with equals, as documented + * at {@link Predicate#apply}. + * + * @since 14.0 + */ + @GwtIncompatible // NavigableMap + public static NavigableMap filterEntries( + NavigableMap unfiltered, Predicate> entryPredicate) { + checkNotNull(entryPredicate); + return (unfiltered instanceof FilteredEntryNavigableMap) + ? filterFiltered((FilteredEntryNavigableMap) unfiltered, entryPredicate) + : new FilteredEntryNavigableMap(checkNotNull(unfiltered), entryPredicate); + } + + /** + * Returns a bimap containing the mappings in {@code unfiltered} that satisfy a predicate. The + * returned bimap is a live view of {@code unfiltered}; changes to one affect the other. + * + *

The resulting bimap's {@code keySet()}, {@code entrySet()}, and {@code values()} views have + * iterators that don't support {@code remove()}, but all other methods are supported by the bimap + * and its views. When given a key/value pair that doesn't satisfy the predicate, the bimap's + * {@code put()}, {@code forcePut()} and {@code putAll()} methods throw an {@link + * IllegalArgumentException}. Similarly, the map's entries have an {@link Entry#setValue} method + * that throws an {@link IllegalArgumentException} when the existing key and the provided value + * don't satisfy the predicate. + * + *

When methods such as {@code removeAll()} and {@code clear()} are called on the filtered + * bimap or its views, only mappings that satisfy the filter will be removed from the underlying + * bimap. + * + *

The returned bimap isn't threadsafe or serializable, even if {@code unfiltered} is. + * + *

Many of the filtered bimap's methods, such as {@code size()}, iterate across every key/value + * mapping in the underlying bimap and determine which satisfy the filter. When a live view is + * not needed, it may be faster to copy the filtered bimap and use the copy. + * + *

Warning: {@code entryPredicate} must be consistent with equals , as documented + * at {@link Predicate#apply}. + * + * @since 14.0 + */ + public static BiMap filterEntries( + BiMap unfiltered, Predicate> entryPredicate) { + checkNotNull(unfiltered); + checkNotNull(entryPredicate); + return (unfiltered instanceof FilteredEntryBiMap) + ? filterFiltered((FilteredEntryBiMap) unfiltered, entryPredicate) + : new FilteredEntryBiMap(unfiltered, entryPredicate); + } + + /** + * Support {@code clear()}, {@code removeAll()}, and {@code retainAll()} when filtering a filtered + * map. + */ + private static Map filterFiltered( + AbstractFilteredMap map, Predicate> entryPredicate) { + return new FilteredEntryMap<>( + map.unfiltered, Predicates.>and(map.predicate, entryPredicate)); + } + + /** + * Support {@code clear()}, {@code removeAll()}, and {@code retainAll()} when filtering a filtered + * sorted map. + */ + private static SortedMap filterFiltered( + FilteredEntrySortedMap map, Predicate> entryPredicate) { + Predicate> predicate = Predicates.>and(map.predicate, entryPredicate); + return new FilteredEntrySortedMap<>(map.sortedMap(), predicate); + } + + /** + * Support {@code clear()}, {@code removeAll()}, and {@code retainAll()} when filtering a filtered + * navigable map. + */ + @GwtIncompatible // NavigableMap + private static NavigableMap filterFiltered( + FilteredEntryNavigableMap map, Predicate> entryPredicate) { + Predicate> predicate = + Predicates.>and(map.entryPredicate, entryPredicate); + return new FilteredEntryNavigableMap<>(map.unfiltered, predicate); + } + + /** + * Support {@code clear()}, {@code removeAll()}, and {@code retainAll()} when filtering a filtered + * map. + */ + private static BiMap filterFiltered( + FilteredEntryBiMap map, Predicate> entryPredicate) { + Predicate> predicate = Predicates.>and(map.predicate, entryPredicate); + return new FilteredEntryBiMap<>(map.unfiltered(), predicate); + } + + private abstract static class AbstractFilteredMap extends ViewCachingAbstractMap { + final Map unfiltered; + final Predicate> predicate; + + AbstractFilteredMap(Map unfiltered, Predicate> predicate) { + this.unfiltered = unfiltered; + this.predicate = predicate; + } + + boolean apply(Object key, V value) { + // This method is called only when the key is in the map, implying that + // key is a K. + @SuppressWarnings("unchecked") + K k = (K) key; + return predicate.apply(Maps.immutableEntry(k, value)); + } + + @Override + public V put(K key, V value) { + checkArgument(apply(key, value)); + return unfiltered.put(key, value); + } + + @Override + public void putAll(Map map) { + for (Entry entry : map.entrySet()) { + checkArgument(apply(entry.getKey(), entry.getValue())); + } + unfiltered.putAll(map); + } + + @Override + public boolean containsKey(Object key) { + return unfiltered.containsKey(key) && apply(key, unfiltered.get(key)); + } + + @Override + public V get(Object key) { + V value = unfiltered.get(key); + return ((value != null) && apply(key, value)) ? value : null; + } + + @Override + public boolean isEmpty() { + return entrySet().isEmpty(); + } + + @Override + public V remove(Object key) { + return containsKey(key) ? unfiltered.remove(key) : null; + } + + @Override + Collection createValues() { + return new FilteredMapValues<>(this, unfiltered, predicate); + } + } + + private static final class FilteredMapValues extends Maps.Values { + final Map unfiltered; + final Predicate> predicate; + + FilteredMapValues( + Map filteredMap, Map unfiltered, Predicate> predicate) { + super(filteredMap); + this.unfiltered = unfiltered; + this.predicate = predicate; + } + + @Override + public boolean remove(Object o) { + Iterator> entryItr = unfiltered.entrySet().iterator(); + while (entryItr.hasNext()) { + Entry entry = entryItr.next(); + if (predicate.apply(entry) && Objects.equal(entry.getValue(), o)) { + entryItr.remove(); + return true; + } + } + return false; + } + + @Override + public boolean removeAll(Collection collection) { + Iterator> entryItr = unfiltered.entrySet().iterator(); + boolean result = false; + while (entryItr.hasNext()) { + Entry entry = entryItr.next(); + if (predicate.apply(entry) && collection.contains(entry.getValue())) { + entryItr.remove(); + result = true; + } + } + return result; + } + + @Override + public boolean retainAll(Collection collection) { + Iterator> entryItr = unfiltered.entrySet().iterator(); + boolean result = false; + while (entryItr.hasNext()) { + Entry entry = entryItr.next(); + if (predicate.apply(entry) && !collection.contains(entry.getValue())) { + entryItr.remove(); + result = true; + } + } + return result; + } + + @Override + public Object[] toArray() { + // creating an ArrayList so filtering happens once + return Lists.newArrayList(iterator()).toArray(); + } + + @Override + public T[] toArray(T[] array) { + return Lists.newArrayList(iterator()).toArray(array); + } + } + + private static class FilteredKeyMap extends AbstractFilteredMap { + final Predicate keyPredicate; + + FilteredKeyMap( + Map unfiltered, + Predicate keyPredicate, + Predicate> entryPredicate) { + super(unfiltered, entryPredicate); + this.keyPredicate = keyPredicate; + } + + @Override + protected Set> createEntrySet() { + return Sets.filter(unfiltered.entrySet(), predicate); + } + + @Override + Set createKeySet() { + return Sets.filter(unfiltered.keySet(), keyPredicate); + } + + // The cast is called only when the key is in the unfiltered map, implying + // that key is a K. + @Override + @SuppressWarnings("unchecked") + public boolean containsKey(Object key) { + return unfiltered.containsKey(key) && keyPredicate.apply((K) key); + } + } + + static class FilteredEntryMap extends AbstractFilteredMap { + /** + * Entries in this set satisfy the predicate, but they don't validate the input to {@code + * Entry.setValue()}. + */ + final Set> filteredEntrySet; + + FilteredEntryMap(Map unfiltered, Predicate> entryPredicate) { + super(unfiltered, entryPredicate); + filteredEntrySet = Sets.filter(unfiltered.entrySet(), predicate); + } + + @Override + protected Set> createEntrySet() { + return new EntrySet(); + } + + + private class EntrySet extends ForwardingSet> { + @Override + protected Set> delegate() { + return filteredEntrySet; + } + + @Override + public Iterator> iterator() { + return new TransformedIterator, Entry>(filteredEntrySet.iterator()) { + @Override + Entry transform(final Entry entry) { + return new ForwardingMapEntry() { + @Override + protected Entry delegate() { + return entry; + } + + @Override + public V setValue(V newValue) { + checkArgument(apply(getKey(), newValue)); + return super.setValue(newValue); + } + }; + } + }; + } + } + + @Override + Set createKeySet() { + return new KeySet(); + } + + static boolean removeAllKeys( + Map map, Predicate> entryPredicate, Collection keyCollection) { + Iterator> entryItr = map.entrySet().iterator(); + boolean result = false; + while (entryItr.hasNext()) { + Entry entry = entryItr.next(); + if (entryPredicate.apply(entry) && keyCollection.contains(entry.getKey())) { + entryItr.remove(); + result = true; + } + } + return result; + } + + static boolean retainAllKeys( + Map map, Predicate> entryPredicate, Collection keyCollection) { + Iterator> entryItr = map.entrySet().iterator(); + boolean result = false; + while (entryItr.hasNext()) { + Entry entry = entryItr.next(); + if (entryPredicate.apply(entry) && !keyCollection.contains(entry.getKey())) { + entryItr.remove(); + result = true; + } + } + return result; + } + + + class KeySet extends Maps.KeySet { + KeySet() { + super(FilteredEntryMap.this); + } + + @Override + public boolean remove(Object o) { + if (containsKey(o)) { + unfiltered.remove(o); + return true; + } + return false; + } + + @Override + public boolean removeAll(Collection collection) { + return removeAllKeys(unfiltered, predicate, collection); + } + + @Override + public boolean retainAll(Collection collection) { + return retainAllKeys(unfiltered, predicate, collection); + } + + @Override + public Object[] toArray() { + // creating an ArrayList so filtering happens once + return Lists.newArrayList(iterator()).toArray(); + } + + @Override + public T[] toArray(T[] array) { + return Lists.newArrayList(iterator()).toArray(array); + } + } + } + + private static class FilteredEntrySortedMap extends FilteredEntryMap + implements SortedMap { + + FilteredEntrySortedMap( + SortedMap unfiltered, Predicate> entryPredicate) { + super(unfiltered, entryPredicate); + } + + SortedMap sortedMap() { + return (SortedMap) unfiltered; + } + + @Override + public SortedSet keySet() { + return (SortedSet) super.keySet(); + } + + @Override + SortedSet createKeySet() { + return new SortedKeySet(); + } + + + class SortedKeySet extends KeySet implements SortedSet { + @Override + public Comparator comparator() { + return sortedMap().comparator(); + } + + @Override + public SortedSet subSet(K fromElement, K toElement) { + return (SortedSet) subMap(fromElement, toElement).keySet(); + } + + @Override + public SortedSet headSet(K toElement) { + return (SortedSet) headMap(toElement).keySet(); + } + + @Override + public SortedSet tailSet(K fromElement) { + return (SortedSet) tailMap(fromElement).keySet(); + } + + @Override + public K first() { + return firstKey(); + } + + @Override + public K last() { + return lastKey(); + } + } + + @Override + public Comparator comparator() { + return sortedMap().comparator(); + } + + @Override + public K firstKey() { + // correctly throws NoSuchElementException when filtered map is empty. + return keySet().iterator().next(); + } + + @Override + public K lastKey() { + SortedMap headMap = sortedMap(); + while (true) { + // correctly throws NoSuchElementException when filtered map is empty. + K key = headMap.lastKey(); + if (apply(key, unfiltered.get(key))) { + return key; + } + headMap = sortedMap().headMap(key); + } + } + + @Override + public SortedMap headMap(K toKey) { + return new FilteredEntrySortedMap<>(sortedMap().headMap(toKey), predicate); + } + + @Override + public SortedMap subMap(K fromKey, K toKey) { + return new FilteredEntrySortedMap<>(sortedMap().subMap(fromKey, toKey), predicate); + } + + @Override + public SortedMap tailMap(K fromKey) { + return new FilteredEntrySortedMap<>(sortedMap().tailMap(fromKey), predicate); + } + } + + @GwtIncompatible // NavigableMap + private static class FilteredEntryNavigableMap extends AbstractNavigableMap { + /* + * It's less code to extend AbstractNavigableMap and forward the filtering logic to + * FilteredEntryMap than to extend FilteredEntrySortedMap and reimplement all the NavigableMap + * methods. + */ + + private final NavigableMap unfiltered; + private final Predicate> entryPredicate; + private final Map filteredDelegate; + + FilteredEntryNavigableMap( + NavigableMap unfiltered, Predicate> entryPredicate) { + this.unfiltered = checkNotNull(unfiltered); + this.entryPredicate = entryPredicate; + this.filteredDelegate = new FilteredEntryMap<>(unfiltered, entryPredicate); + } + + @Override + public Comparator comparator() { + return unfiltered.comparator(); + } + + @Override + public NavigableSet navigableKeySet() { + return new Maps.NavigableKeySet(this) { + @Override + public boolean removeAll(Collection collection) { + return FilteredEntryMap.removeAllKeys(unfiltered, entryPredicate, collection); + } + + @Override + public boolean retainAll(Collection collection) { + return FilteredEntryMap.retainAllKeys(unfiltered, entryPredicate, collection); + } + }; + } + + @Override + public Collection values() { + return new FilteredMapValues<>(this, unfiltered, entryPredicate); + } + + @Override + Iterator> entryIterator() { + return Iterators.filter(unfiltered.entrySet().iterator(), entryPredicate); + } + + @Override + Iterator> descendingEntryIterator() { + return Iterators.filter(unfiltered.descendingMap().entrySet().iterator(), entryPredicate); + } + + @Override + public int size() { + return filteredDelegate.size(); + } + + @Override + public boolean isEmpty() { + return !Iterables.any(unfiltered.entrySet(), entryPredicate); + } + + @Override + public V get(Object key) { + return filteredDelegate.get(key); + } + + @Override + public boolean containsKey(Object key) { + return filteredDelegate.containsKey(key); + } + + @Override + public V put(K key, V value) { + return filteredDelegate.put(key, value); + } + + @Override + public V remove(Object key) { + return filteredDelegate.remove(key); + } + + @Override + public void putAll(Map m) { + filteredDelegate.putAll(m); + } + + @Override + public void clear() { + filteredDelegate.clear(); + } + + @Override + public Set> entrySet() { + return filteredDelegate.entrySet(); + } + + @Override + public Entry pollFirstEntry() { + return Iterables.removeFirstMatching(unfiltered.entrySet(), entryPredicate); + } + + @Override + public Entry pollLastEntry() { + return Iterables.removeFirstMatching(unfiltered.descendingMap().entrySet(), entryPredicate); + } + + @Override + public NavigableMap descendingMap() { + return filterEntries(unfiltered.descendingMap(), entryPredicate); + } + + @Override + public NavigableMap subMap( + K fromKey, boolean fromInclusive, K toKey, boolean toInclusive) { + return filterEntries( + unfiltered.subMap(fromKey, fromInclusive, toKey, toInclusive), entryPredicate); + } + + @Override + public NavigableMap headMap(K toKey, boolean inclusive) { + return filterEntries(unfiltered.headMap(toKey, inclusive), entryPredicate); + } + + @Override + public NavigableMap tailMap(K fromKey, boolean inclusive) { + return filterEntries(unfiltered.tailMap(fromKey, inclusive), entryPredicate); + } + } + + static final class FilteredEntryBiMap extends FilteredEntryMap + implements BiMap { + private final BiMap inverse; + + private static Predicate> inversePredicate( + final Predicate> forwardPredicate) { + return new Predicate>() { + @Override + public boolean apply(Entry input) { + return forwardPredicate.apply(Maps.immutableEntry(input.getValue(), input.getKey())); + } + }; + } + + FilteredEntryBiMap(BiMap delegate, Predicate> predicate) { + super(delegate, predicate); + this.inverse = + new FilteredEntryBiMap<>(delegate.inverse(), inversePredicate(predicate), this); + } + + private FilteredEntryBiMap( + BiMap delegate, Predicate> predicate, BiMap inverse) { + super(delegate, predicate); + this.inverse = inverse; + } + + BiMap unfiltered() { + return (BiMap) unfiltered; + } + + @Override + public V forcePut(K key, V value) { + checkArgument(apply(key, value)); + return unfiltered().forcePut(key, value); + } + + @Override + public void replaceAll(BiFunction function) { + unfiltered() + .replaceAll( + (key, value) -> + predicate.apply(Maps.immutableEntry(key, value)) + ? function.apply(key, value) + : value); + } + + @Override + public BiMap inverse() { + return inverse; + } + + @Override + public Set values() { + return inverse.keySet(); + } + } + + /** + * Returns an unmodifiable view of the specified navigable map. Query operations on the returned + * map read through to the specified map, and attempts to modify the returned map, whether direct + * or via its views, result in an {@code UnsupportedOperationException}. + * + *

The returned navigable map will be serializable if the specified navigable map is + * serializable. + * + *

This method's signature will not permit you to convert a {@code NavigableMap} to a {@code NavigableMap}. If it permitted this, the returned map's {@code + * comparator()} method might return a {@code Comparator}, which works only on a + * particular subtype of {@code K}, but promise that it's a {@code Comparator}, which + * must work on any type of {@code K}. + * + * @param map the navigable map for which an unmodifiable view is to be returned + * @return an unmodifiable view of the specified navigable map + * @since 12.0 + */ + @GwtIncompatible // NavigableMap + public static NavigableMap unmodifiableNavigableMap( + NavigableMap map) { + checkNotNull(map); + if (map instanceof UnmodifiableNavigableMap) { + @SuppressWarnings("unchecked") // covariant + NavigableMap result = (NavigableMap) map; + return result; + } else { + return new UnmodifiableNavigableMap<>(map); + } + } + + private static Entry unmodifiableOrNull( + Entry entry) { + return (entry == null) ? null : Maps.unmodifiableEntry(entry); + } + + @GwtIncompatible // NavigableMap + static class UnmodifiableNavigableMap extends ForwardingSortedMap + implements NavigableMap, Serializable { + private final NavigableMap delegate; + + UnmodifiableNavigableMap(NavigableMap delegate) { + this.delegate = delegate; + } + + UnmodifiableNavigableMap( + NavigableMap delegate, UnmodifiableNavigableMap descendingMap) { + this.delegate = delegate; + this.descendingMap = descendingMap; + } + + @Override + protected SortedMap delegate() { + return Collections.unmodifiableSortedMap(delegate); + } + + @Override + public Entry lowerEntry(K key) { + return unmodifiableOrNull(delegate.lowerEntry(key)); + } + + @Override + public K lowerKey(K key) { + return delegate.lowerKey(key); + } + + @Override + public Entry floorEntry(K key) { + return unmodifiableOrNull(delegate.floorEntry(key)); + } + + @Override + public K floorKey(K key) { + return delegate.floorKey(key); + } + + @Override + public Entry ceilingEntry(K key) { + return unmodifiableOrNull(delegate.ceilingEntry(key)); + } + + @Override + public K ceilingKey(K key) { + return delegate.ceilingKey(key); + } + + @Override + public Entry higherEntry(K key) { + return unmodifiableOrNull(delegate.higherEntry(key)); + } + + @Override + public K higherKey(K key) { + return delegate.higherKey(key); + } + + @Override + public Entry firstEntry() { + return unmodifiableOrNull(delegate.firstEntry()); + } + + @Override + public Entry lastEntry() { + return unmodifiableOrNull(delegate.lastEntry()); + } + + @Override + public final Entry pollFirstEntry() { + throw new UnsupportedOperationException(); + } + + @Override + public final Entry pollLastEntry() { + throw new UnsupportedOperationException(); + } + + private transient UnmodifiableNavigableMap descendingMap; + + @Override + public NavigableMap descendingMap() { + UnmodifiableNavigableMap result = descendingMap; + return (result == null) + ? descendingMap = new UnmodifiableNavigableMap<>(delegate.descendingMap(), this) + : result; + } + + @Override + public Set keySet() { + return navigableKeySet(); + } + + @Override + public NavigableSet navigableKeySet() { + return Sets.unmodifiableNavigableSet(delegate.navigableKeySet()); + } + + @Override + public NavigableSet descendingKeySet() { + return Sets.unmodifiableNavigableSet(delegate.descendingKeySet()); + } + + @Override + public SortedMap subMap(K fromKey, K toKey) { + return subMap(fromKey, true, toKey, false); + } + + @Override + public NavigableMap subMap( + K fromKey, boolean fromInclusive, K toKey, boolean toInclusive) { + return Maps.unmodifiableNavigableMap( + delegate.subMap(fromKey, fromInclusive, toKey, toInclusive)); + } + + @Override + public SortedMap headMap(K toKey) { + return headMap(toKey, false); + } + + @Override + public NavigableMap headMap(K toKey, boolean inclusive) { + return Maps.unmodifiableNavigableMap(delegate.headMap(toKey, inclusive)); + } + + @Override + public SortedMap tailMap(K fromKey) { + return tailMap(fromKey, true); + } + + @Override + public NavigableMap tailMap(K fromKey, boolean inclusive) { + return Maps.unmodifiableNavigableMap(delegate.tailMap(fromKey, inclusive)); + } + } + + /** + * Returns a synchronized (thread-safe) navigable map backed by the specified navigable map. In + * order to guarantee serial access, it is critical that all access to the backing + * navigable map is accomplished through the returned navigable map (or its views). + * + *

It is imperative that the user manually synchronize on the returned navigable map when + * iterating over any of its collection views, or the collections views of any of its {@code + * descendingMap}, {@code subMap}, {@code headMap} or {@code tailMap} views. + * + *

{@code
+   * NavigableMap map = synchronizedNavigableMap(new TreeMap());
+   *
+   * // Needn't be in synchronized block
+   * NavigableSet set = map.navigableKeySet();
+   *
+   * synchronized (map) { // Synchronizing on map, not set!
+   *   Iterator it = set.iterator(); // Must be in synchronized block
+   *   while (it.hasNext()) {
+   *     foo(it.next());
+   *   }
+   * }
+   * }
+ * + *

or: + * + *

{@code
+   * NavigableMap map = synchronizedNavigableMap(new TreeMap());
+   * NavigableMap map2 = map.subMap(foo, false, bar, true);
+   *
+   * // Needn't be in synchronized block
+   * NavigableSet set2 = map2.descendingKeySet();
+   *
+   * synchronized (map) { // Synchronizing on map, not map2 or set2!
+   *   Iterator it = set2.iterator(); // Must be in synchronized block
+   *   while (it.hasNext()) {
+   *     foo(it.next());
+   *   }
+   * }
+   * }
+ * + *

Failure to follow this advice may result in non-deterministic behavior. + * + *

The returned navigable map will be serializable if the specified navigable map is + * serializable. + * + * @param navigableMap the navigable map to be "wrapped" in a synchronized navigable map. + * @return a synchronized view of the specified navigable map. + * @since 13.0 + */ + @GwtIncompatible // NavigableMap + public static NavigableMap synchronizedNavigableMap( + NavigableMap navigableMap) { + return Synchronized.navigableMap(navigableMap); + } + + /** + * {@code AbstractMap} extension that makes it easy to cache customized keySet, values, and + * entrySet views. + */ + @GwtCompatible + abstract static class ViewCachingAbstractMap extends AbstractMap { + /** + * Creates the entry set to be returned by {@link #entrySet()}. This method is invoked at most + * once on a given map, at the time when {@code entrySet} is first called. + */ + abstract Set> createEntrySet(); + + private transient Set> entrySet; + + @Override + public Set> entrySet() { + Set> result = entrySet; + return (result == null) ? entrySet = createEntrySet() : result; + } + + private transient Set keySet; + + @Override + public Set keySet() { + Set result = keySet; + return (result == null) ? keySet = createKeySet() : result; + } + + Set createKeySet() { + return new KeySet<>(this); + } + + private transient Collection values; + + @Override + public Collection values() { + Collection result = values; + return (result == null) ? values = createValues() : result; + } + + Collection createValues() { + return new Values<>(this); + } + } + + abstract static class IteratorBasedAbstractMap extends AbstractMap { + @Override + public abstract int size(); + + abstract Iterator> entryIterator(); + + Spliterator> entrySpliterator() { + return Spliterators.spliterator( + entryIterator(), size(), Spliterator.SIZED | Spliterator.DISTINCT); + } + + @Override + public Set> entrySet() { + return new EntrySet() { + @Override + Map map() { + return IteratorBasedAbstractMap.this; + } + + @Override + public Iterator> iterator() { + return entryIterator(); + } + + @Override + public Spliterator> spliterator() { + return entrySpliterator(); + } + + @Override + public void forEach(Consumer> action) { + forEachEntry(action); + } + }; + } + + void forEachEntry(Consumer> action) { + entryIterator().forEachRemaining(action); + } + + @Override + public void clear() { + Iterators.clear(entryIterator()); + } + } + + /** + * Delegates to {@link Map#get}. Returns {@code null} on {@code ClassCastException} and {@code + * NullPointerException}. + */ + static V safeGet(Map map, Object key) { + checkNotNull(map); + try { + return map.get(key); + } catch (ClassCastException | NullPointerException e) { + return null; + } + } + + /** + * Delegates to {@link Map#containsKey}. Returns {@code false} on {@code ClassCastException} and + * {@code NullPointerException}. + */ + static boolean safeContainsKey(Map map, Object key) { + checkNotNull(map); + try { + return map.containsKey(key); + } catch (ClassCastException | NullPointerException e) { + return false; + } + } + + /** + * Delegates to {@link Map#remove}. Returns {@code null} on {@code ClassCastException} and {@code + * NullPointerException}. + */ + static V safeRemove(Map map, Object key) { + checkNotNull(map); + try { + return map.remove(key); + } catch (ClassCastException | NullPointerException e) { + return null; + } + } + + /** An admittedly inefficient implementation of {@link Map#containsKey}. */ + static boolean containsKeyImpl(Map map, Object key) { + return Iterators.contains(keyIterator(map.entrySet().iterator()), key); + } + + /** An implementation of {@link Map#containsValue}. */ + static boolean containsValueImpl(Map map, Object value) { + return Iterators.contains(valueIterator(map.entrySet().iterator()), value); + } + + /** + * Implements {@code Collection.contains} safely for forwarding collections of map entries. If + * {@code o} is an instance of {@code Entry}, it is wrapped using {@link #unmodifiableEntry} to + * protect against a possible nefarious equals method. + * + *

Note that {@code c} is the backing (delegate) collection, rather than the forwarding + * collection. + * + * @param c the delegate (unwrapped) collection of map entries + * @param o the object that might be contained in {@code c} + * @return {@code true} if {@code c} contains {@code o} + */ + static boolean containsEntryImpl(Collection> c, Object o) { + if (!(o instanceof Entry)) { + return false; + } + return c.contains(unmodifiableEntry((Entry) o)); + } + + /** + * Implements {@code Collection.remove} safely for forwarding collections of map entries. If + * {@code o} is an instance of {@code Entry}, it is wrapped using {@link #unmodifiableEntry} to + * protect against a possible nefarious equals method. + * + *

Note that {@code c} is backing (delegate) collection, rather than the forwarding collection. + * + * @param c the delegate (unwrapped) collection of map entries + * @param o the object to remove from {@code c} + * @return {@code true} if {@code c} was changed + */ + static boolean removeEntryImpl(Collection> c, Object o) { + if (!(o instanceof Entry)) { + return false; + } + return c.remove(unmodifiableEntry((Entry) o)); + } + + /** An implementation of {@link Map#equals}. */ + static boolean equalsImpl(Map map, Object object) { + if (map == object) { + return true; + } else if (object instanceof Map) { + Map o = (Map) object; + return map.entrySet().equals(o.entrySet()); + } + return false; + } + + /** An implementation of {@link Map#toString}. */ + static String toStringImpl(Map map) { + StringBuilder sb = Collections2.newStringBuilderForCollection(map.size()).append('{'); + boolean first = true; + for (Entry entry : map.entrySet()) { + if (!first) { + sb.append(", "); + } + first = false; + sb.append(entry.getKey()).append('=').append(entry.getValue()); + } + return sb.append('}').toString(); + } + + /** An implementation of {@link Map#putAll}. */ + static void putAllImpl(Map self, Map map) { + for (Entry entry : map.entrySet()) { + self.put(entry.getKey(), entry.getValue()); + } + } + + static class KeySet extends Sets.ImprovedAbstractSet { + final Map map; + + KeySet(Map map) { + this.map = checkNotNull(map); + } + + Map map() { + return map; + } + + @Override + public Iterator iterator() { + return keyIterator(map().entrySet().iterator()); + } + + @Override + public void forEach(Consumer action) { + checkNotNull(action); + // avoids entry allocation for those maps that allocate entries on iteration + map.forEach((k, v) -> action.accept(k)); + } + + @Override + public int size() { + return map().size(); + } + + @Override + public boolean isEmpty() { + return map().isEmpty(); + } + + @Override + public boolean contains(Object o) { + return map().containsKey(o); + } + + @Override + public boolean remove(Object o) { + if (contains(o)) { + map().remove(o); + return true; + } + return false; + } + + @Override + public void clear() { + map().clear(); + } + } + + static K keyOrNull(Entry entry) { + return (entry == null) ? null : entry.getKey(); + } + + static V valueOrNull(Entry entry) { + return (entry == null) ? null : entry.getValue(); + } + + static class SortedKeySet extends KeySet implements SortedSet { + SortedKeySet(SortedMap map) { + super(map); + } + + @Override + SortedMap map() { + return (SortedMap) super.map(); + } + + @Override + public Comparator comparator() { + return map().comparator(); + } + + @Override + public SortedSet subSet(K fromElement, K toElement) { + return new SortedKeySet<>(map().subMap(fromElement, toElement)); + } + + @Override + public SortedSet headSet(K toElement) { + return new SortedKeySet<>(map().headMap(toElement)); + } + + @Override + public SortedSet tailSet(K fromElement) { + return new SortedKeySet<>(map().tailMap(fromElement)); + } + + @Override + public K first() { + return map().firstKey(); + } + + @Override + public K last() { + return map().lastKey(); + } + } + + @GwtIncompatible // NavigableMap + static class NavigableKeySet extends SortedKeySet implements NavigableSet { + NavigableKeySet(NavigableMap map) { + super(map); + } + + @Override + NavigableMap map() { + return (NavigableMap) map; + } + + @Override + public K lower(K e) { + return map().lowerKey(e); + } + + @Override + public K floor(K e) { + return map().floorKey(e); + } + + @Override + public K ceiling(K e) { + return map().ceilingKey(e); + } + + @Override + public K higher(K e) { + return map().higherKey(e); + } + + @Override + public K pollFirst() { + return keyOrNull(map().pollFirstEntry()); + } + + @Override + public K pollLast() { + return keyOrNull(map().pollLastEntry()); + } + + @Override + public NavigableSet descendingSet() { + return map().descendingKeySet(); + } + + @Override + public Iterator descendingIterator() { + return descendingSet().iterator(); + } + + @Override + public NavigableSet subSet( + K fromElement, boolean fromInclusive, K toElement, boolean toInclusive) { + return map().subMap(fromElement, fromInclusive, toElement, toInclusive).navigableKeySet(); + } + + @Override + public SortedSet subSet(K fromElement, K toElement) { + return subSet(fromElement, true, toElement, false); + } + + @Override + public NavigableSet headSet(K toElement, boolean inclusive) { + return map().headMap(toElement, inclusive).navigableKeySet(); + } + + @Override + public SortedSet headSet(K toElement) { + return headSet(toElement, false); + } + + @Override + public NavigableSet tailSet(K fromElement, boolean inclusive) { + return map().tailMap(fromElement, inclusive).navigableKeySet(); + } + + @Override + public SortedSet tailSet(K fromElement) { + return tailSet(fromElement, true); + } + } + + static class Values extends AbstractCollection { + final Map map; + + Values(Map map) { + this.map = checkNotNull(map); + } + + final Map map() { + return map; + } + + @Override + public Iterator iterator() { + return valueIterator(map().entrySet().iterator()); + } + + @Override + public void forEach(Consumer action) { + checkNotNull(action); + // avoids allocation of entries for those maps that generate fresh entries on iteration + map.forEach((k, v) -> action.accept(v)); + } + + @Override + public boolean remove(Object o) { + try { + return super.remove(o); + } catch (UnsupportedOperationException e) { + for (Entry entry : map().entrySet()) { + if (Objects.equal(o, entry.getValue())) { + map().remove(entry.getKey()); + return true; + } + } + return false; + } + } + + @Override + public boolean removeAll(Collection c) { + try { + return super.removeAll(checkNotNull(c)); + } catch (UnsupportedOperationException e) { + Set toRemove = Sets.newHashSet(); + for (Entry entry : map().entrySet()) { + if (c.contains(entry.getValue())) { + toRemove.add(entry.getKey()); + } + } + return map().keySet().removeAll(toRemove); + } + } + + @Override + public boolean retainAll(Collection c) { + try { + return super.retainAll(checkNotNull(c)); + } catch (UnsupportedOperationException e) { + Set toRetain = Sets.newHashSet(); + for (Entry entry : map().entrySet()) { + if (c.contains(entry.getValue())) { + toRetain.add(entry.getKey()); + } + } + return map().keySet().retainAll(toRetain); + } + } + + @Override + public int size() { + return map().size(); + } + + @Override + public boolean isEmpty() { + return map().isEmpty(); + } + + @Override + public boolean contains(Object o) { + return map().containsValue(o); + } + + @Override + public void clear() { + map().clear(); + } + } + + abstract static class EntrySet extends Sets.ImprovedAbstractSet> { + abstract Map map(); + + @Override + public int size() { + return map().size(); + } + + @Override + public void clear() { + map().clear(); + } + + @Override + public boolean contains(Object o) { + if (o instanceof Entry) { + Entry entry = (Entry) o; + Object key = entry.getKey(); + V value = Maps.safeGet(map(), key); + return Objects.equal(value, entry.getValue()) && (value != null || map().containsKey(key)); + } + return false; + } + + @Override + public boolean isEmpty() { + return map().isEmpty(); + } + + @Override + public boolean remove(Object o) { + if (contains(o)) { + Entry entry = (Entry) o; + return map().keySet().remove(entry.getKey()); + } + return false; + } + + @Override + public boolean removeAll(Collection c) { + try { + return super.removeAll(checkNotNull(c)); + } catch (UnsupportedOperationException e) { + // if the iterators don't support remove + return Sets.removeAllImpl(this, c.iterator()); + } + } + + @Override + public boolean retainAll(Collection c) { + try { + return super.retainAll(checkNotNull(c)); + } catch (UnsupportedOperationException e) { + // if the iterators don't support remove + Set keys = Sets.newHashSetWithExpectedSize(c.size()); + for (Object o : c) { + if (contains(o)) { + Entry entry = (Entry) o; + keys.add(entry.getKey()); + } + } + return map().keySet().retainAll(keys); + } + } + } + + @GwtIncompatible // NavigableMap + abstract static class DescendingMap extends ForwardingMap + implements NavigableMap { + + abstract NavigableMap forward(); + + @Override + protected final Map delegate() { + return forward(); + } + + private transient Comparator comparator; + + @SuppressWarnings("unchecked") + @Override + public Comparator comparator() { + Comparator result = comparator; + if (result == null) { + Comparator forwardCmp = forward().comparator(); + if (forwardCmp == null) { + forwardCmp = (Comparator) Ordering.natural(); + } + result = comparator = reverse(forwardCmp); + } + return result; + } + + // If we inline this, we get a javac error. + private static Ordering reverse(Comparator forward) { + return Ordering.from(forward).reverse(); + } + + @Override + public K firstKey() { + return forward().lastKey(); + } + + @Override + public K lastKey() { + return forward().firstKey(); + } + + @Override + public Entry lowerEntry(K key) { + return forward().higherEntry(key); + } + + @Override + public K lowerKey(K key) { + return forward().higherKey(key); + } + + @Override + public Entry floorEntry(K key) { + return forward().ceilingEntry(key); + } + + @Override + public K floorKey(K key) { + return forward().ceilingKey(key); + } + + @Override + public Entry ceilingEntry(K key) { + return forward().floorEntry(key); + } + + @Override + public K ceilingKey(K key) { + return forward().floorKey(key); + } + + @Override + public Entry higherEntry(K key) { + return forward().lowerEntry(key); + } + + @Override + public K higherKey(K key) { + return forward().lowerKey(key); + } + + @Override + public Entry firstEntry() { + return forward().lastEntry(); + } + + @Override + public Entry lastEntry() { + return forward().firstEntry(); + } + + @Override + public Entry pollFirstEntry() { + return forward().pollLastEntry(); + } + + @Override + public Entry pollLastEntry() { + return forward().pollFirstEntry(); + } + + @Override + public NavigableMap descendingMap() { + return forward(); + } + + private transient Set> entrySet; + + @Override + public Set> entrySet() { + Set> result = entrySet; + return (result == null) ? entrySet = createEntrySet() : result; + } + + abstract Iterator> entryIterator(); + + Set> createEntrySet() { + + class EntrySetImpl extends EntrySet { + @Override + Map map() { + return DescendingMap.this; + } + + @Override + public Iterator> iterator() { + return entryIterator(); + } + } + return new EntrySetImpl(); + } + + @Override + public Set keySet() { + return navigableKeySet(); + } + + private transient NavigableSet navigableKeySet; + + @Override + public NavigableSet navigableKeySet() { + NavigableSet result = navigableKeySet; + return (result == null) ? navigableKeySet = new NavigableKeySet<>(this) : result; + } + + @Override + public NavigableSet descendingKeySet() { + return forward().navigableKeySet(); + } + + @Override + public NavigableMap subMap( + K fromKey, boolean fromInclusive, K toKey, boolean toInclusive) { + return forward().subMap(toKey, toInclusive, fromKey, fromInclusive).descendingMap(); + } + + @Override + public SortedMap subMap(K fromKey, K toKey) { + return subMap(fromKey, true, toKey, false); + } + + @Override + public NavigableMap headMap(K toKey, boolean inclusive) { + return forward().tailMap(toKey, inclusive).descendingMap(); + } + + @Override + public SortedMap headMap(K toKey) { + return headMap(toKey, false); + } + + @Override + public NavigableMap tailMap(K fromKey, boolean inclusive) { + return forward().headMap(fromKey, inclusive).descendingMap(); + } + + @Override + public SortedMap tailMap(K fromKey) { + return tailMap(fromKey, true); + } + + @Override + public Collection values() { + return new Values<>(this); + } + + @Override + public String toString() { + return standardToString(); + } + } + + /** Returns a map from the ith element of list to i. */ + static ImmutableMap indexMap(Collection list) { + ImmutableMap.Builder builder = new ImmutableMap.Builder<>(list.size()); + int i = 0; + for (E e : list) { + builder.put(e, i++); + } + return builder.build(); + } + + /** + * Returns a view of the portion of {@code map} whose keys are contained by {@code range}. + * + *

This method delegates to the appropriate methods of {@link NavigableMap} (namely {@link + * NavigableMap#subMap(Object, boolean, Object, boolean) subMap()}, {@link + * NavigableMap#tailMap(Object, boolean) tailMap()}, and {@link NavigableMap#headMap(Object, + * boolean) headMap()}) to actually construct the view. Consult these methods for a full + * description of the returned view's behavior. + * + *

Warning: {@code Range}s always represent a range of values using the values' natural + * ordering. {@code NavigableMap} on the other hand can specify a custom ordering via a {@link + * Comparator}, which can violate the natural ordering. Using this method (or in general using + * {@code Range}) with unnaturally-ordered maps can lead to unexpected and undefined behavior. + * + * @since 20.0 + */ + @Beta + @GwtIncompatible // NavigableMap + public static , V> NavigableMap subMap( + NavigableMap map, Range range) { + if (map.comparator() != null + && map.comparator() != Ordering.natural() + && range.hasLowerBound() + && range.hasUpperBound()) { + checkArgument( + map.comparator().compare(range.lowerEndpoint(), range.upperEndpoint()) <= 0, + "map is using a custom comparator which is inconsistent with the natural ordering."); + } + if (range.hasLowerBound() && range.hasUpperBound()) { + return map.subMap( + range.lowerEndpoint(), + range.lowerBoundType() == BoundType.CLOSED, + range.upperEndpoint(), + range.upperBoundType() == BoundType.CLOSED); + } else if (range.hasLowerBound()) { + return map.tailMap(range.lowerEndpoint(), range.lowerBoundType() == BoundType.CLOSED); + } else if (range.hasUpperBound()) { + return map.headMap(range.upperEndpoint(), range.upperBoundType() == BoundType.CLOSED); + } + return checkNotNull(map); + } +} diff --git a/src/main/java/com/google/common/collect/MinMaxPriorityQueue.java b/src/main/java/com/google/common/collect/MinMaxPriorityQueue.java new file mode 100644 index 0000000..bf3e126 --- /dev/null +++ b/src/main/java/com/google/common/collect/MinMaxPriorityQueue.java @@ -0,0 +1,956 @@ +/* + * Copyright (C) 2010 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.base.Preconditions.checkPositionIndex; +import static com.google.common.base.Preconditions.checkState; +import static com.google.common.collect.CollectPreconditions.checkRemove; + +import com.google.common.annotations.Beta; +import com.google.common.annotations.GwtCompatible; +import com.google.common.annotations.VisibleForTesting; +import com.google.common.math.IntMath; + + + +import java.util.AbstractQueue; +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.ConcurrentModificationException; +import java.util.Iterator; +import java.util.List; +import java.util.NoSuchElementException; +import java.util.PriorityQueue; +import java.util.Queue; + + + +/** + * A double-ended priority queue, which provides constant-time access to both its least element and + * its greatest element, as determined by the queue's specified comparator. If no comparator is + * given at creation time, the natural order of elements is used. If no maximum size is given at + * creation time, the queue is unbounded. + * + *

Usage example: + * + *

{@code
+ * MinMaxPriorityQueue users = MinMaxPriorityQueue.orderedBy(userComparator)
+ *     .maximumSize(1000)
+ *     .create();
+ * }
+ * + *

As a {@link Queue} it functions exactly as a {@link PriorityQueue}: its head element -- the + * implicit target of the methods {@link #peek()}, {@link #poll()} and {@link #remove()} -- is + * defined as the least element in the queue according to the queue's comparator. But unlike + * a regular priority queue, the methods {@link #peekLast}, {@link #pollLast} and {@link + * #removeLast} are also provided, to act on the greatest element in the queue instead. + * + *

A min-max priority queue can be configured with a maximum size. If so, each time the size of + * the queue exceeds that value, the queue automatically removes its greatest element according to + * its comparator (which might be the element that was just added). This is different from + * conventional bounded queues, which either block or reject new elements when full. + * + *

This implementation is based on the min-max heap developed by Atkinson, et al. + * Unlike many other double-ended priority queues, it stores elements in a single array, as compact + * as the traditional heap data structure used in {@link PriorityQueue}. + * + *

This class is not thread-safe, and does not accept null elements. + * + *

Performance notes: + * + *

    + *
  • If you only access one end of the queue, and do use a maximum size, this class will perform + * significantly worse than a {@code PriorityQueue} with manual eviction above the maximum + * size. In many cases {@link Ordering#leastOf} may work for your use case with significantly + * improved (and asymptotically superior) performance. + *
  • The retrieval operations {@link #peek}, {@link #peekFirst}, {@link #peekLast}, {@link + * #element}, and {@link #size} are constant-time. + *
  • The enqueuing and dequeuing operations ({@link #offer}, {@link #add}, and all the forms of + * {@link #poll} and {@link #remove()}) run in {@code O(log n) time}. + *
  • The {@link #remove(Object)} and {@link #contains} operations require linear ({@code O(n)}) + * time. + *
  • If you only access one end of the queue, and don't use a maximum size, this class is + * functionally equivalent to {@link PriorityQueue}, but significantly slower. + *
+ * + * @author Sverre Sundsdal + * @author Torbjorn Gannholm + * @since 8.0 + */ +@Beta +@GwtCompatible +public final class MinMaxPriorityQueue extends AbstractQueue { + + /** + * Creates a new min-max priority queue with default settings: natural order, no maximum size, no + * initial contents, and an initial expected size of 11. + */ + public static > MinMaxPriorityQueue create() { + return new Builder(Ordering.natural()).create(); + } + + /** + * Creates a new min-max priority queue using natural order, no maximum size, and initially + * containing the given elements. + */ + public static > MinMaxPriorityQueue create( + Iterable initialContents) { + return new Builder(Ordering.natural()).create(initialContents); + } + + /** + * Creates and returns a new builder, configured to build {@code MinMaxPriorityQueue} instances + * that use {@code comparator} to determine the least and greatest elements. + */ + public static Builder orderedBy(Comparator comparator) { + return new Builder(comparator); + } + + /** + * Creates and returns a new builder, configured to build {@code MinMaxPriorityQueue} instances + * sized appropriately to hold {@code expectedSize} elements. + */ + public static Builder expectedSize(int expectedSize) { + return new Builder(Ordering.natural()).expectedSize(expectedSize); + } + + /** + * Creates and returns a new builder, configured to build {@code MinMaxPriorityQueue} instances + * that are limited to {@code maximumSize} elements. Each time a queue grows beyond this bound, it + * immediately removes its greatest element (according to its comparator), which might be the + * element that was just added. + */ + public static Builder maximumSize(int maximumSize) { + return new Builder(Ordering.natural()).maximumSize(maximumSize); + } + + /** + * The builder class used in creation of min-max priority queues. Instead of constructing one + * directly, use {@link MinMaxPriorityQueue#orderedBy(Comparator)}, {@link + * MinMaxPriorityQueue#expectedSize(int)} or {@link MinMaxPriorityQueue#maximumSize(int)}. + * + * @param the upper bound on the eventual type that can be produced by this builder (for + * example, a {@code Builder} can produce a {@code Queue} or {@code + * Queue} but not a {@code Queue}). + * @since 8.0 + */ + @Beta + public static final class Builder { + /* + * TODO(kevinb): when the dust settles, see if we still need this or can + * just default to DEFAULT_CAPACITY. + */ + private static final int UNSET_EXPECTED_SIZE = -1; + + private final Comparator comparator; + private int expectedSize = UNSET_EXPECTED_SIZE; + private int maximumSize = Integer.MAX_VALUE; + + private Builder(Comparator comparator) { + this.comparator = checkNotNull(comparator); + } + + /** + * Configures this builder to build min-max priority queues with an initial expected size of + * {@code expectedSize}. + */ + + public Builder expectedSize(int expectedSize) { + checkArgument(expectedSize >= 0); + this.expectedSize = expectedSize; + return this; + } + + /** + * Configures this builder to build {@code MinMaxPriorityQueue} instances that are limited to + * {@code maximumSize} elements. Each time a queue grows beyond this bound, it immediately + * removes its greatest element (according to its comparator), which might be the element that + * was just added. + */ + + public Builder maximumSize(int maximumSize) { + checkArgument(maximumSize > 0); + this.maximumSize = maximumSize; + return this; + } + + /** + * Builds a new min-max priority queue using the previously specified options, and having no + * initial contents. + */ + public MinMaxPriorityQueue create() { + return create(Collections.emptySet()); + } + + /** + * Builds a new min-max priority queue using the previously specified options, and having the + * given initial elements. + */ + public MinMaxPriorityQueue create(Iterable initialContents) { + MinMaxPriorityQueue queue = + new MinMaxPriorityQueue( + this, initialQueueSize(expectedSize, maximumSize, initialContents)); + for (T element : initialContents) { + queue.offer(element); + } + return queue; + } + + @SuppressWarnings("unchecked") // safe "contravariant cast" + private Ordering ordering() { + return Ordering.from((Comparator) comparator); + } + } + + private final Heap minHeap; + private final Heap maxHeap; + @VisibleForTesting final int maximumSize; + private Object[] queue; + private int size; + private int modCount; + + private MinMaxPriorityQueue(Builder builder, int queueSize) { + Ordering ordering = builder.ordering(); + this.minHeap = new Heap(ordering); + this.maxHeap = new Heap(ordering.reverse()); + minHeap.otherHeap = maxHeap; + maxHeap.otherHeap = minHeap; + + this.maximumSize = builder.maximumSize; + // TODO(kevinb): pad? + this.queue = new Object[queueSize]; + } + + @Override + public int size() { + return size; + } + + /** + * Adds the given element to this queue. If this queue has a maximum size, after adding {@code + * element} the queue will automatically evict its greatest element (according to its comparator), + * which may be {@code element} itself. + * + * @return {@code true} always + */ + + @Override + public boolean add(E element) { + offer(element); + return true; + } + + + @Override + public boolean addAll(Collection newElements) { + boolean modified = false; + for (E element : newElements) { + offer(element); + modified = true; + } + return modified; + } + + /** + * Adds the given element to this queue. If this queue has a maximum size, after adding {@code + * element} the queue will automatically evict its greatest element (according to its comparator), + * which may be {@code element} itself. + */ + + @Override + public boolean offer(E element) { + checkNotNull(element); + modCount++; + int insertIndex = size++; + + growIfNeeded(); + + // Adds the element to the end of the heap and bubbles it up to the correct + // position. + heapForIndex(insertIndex).bubbleUp(insertIndex, element); + return size <= maximumSize || pollLast() != element; + } + + + @Override + public E poll() { + return isEmpty() ? null : removeAndGet(0); + } + + @SuppressWarnings("unchecked") // we must carefully only allow Es to get in + E elementData(int index) { + return (E) queue[index]; + } + + @Override + public E peek() { + return isEmpty() ? null : elementData(0); + } + + /** Returns the index of the max element. */ + private int getMaxElementIndex() { + switch (size) { + case 1: + return 0; // The lone element in the queue is the maximum. + case 2: + return 1; // The lone element in the maxHeap is the maximum. + default: + // The max element must sit on the first level of the maxHeap. It is + // actually the *lesser* of the two from the maxHeap's perspective. + return (maxHeap.compareElements(1, 2) <= 0) ? 1 : 2; + } + } + + /** + * Removes and returns the least element of this queue, or returns {@code null} if the queue is + * empty. + */ + + public E pollFirst() { + return poll(); + } + + /** + * Removes and returns the least element of this queue. + * + * @throws NoSuchElementException if the queue is empty + */ + + public E removeFirst() { + return remove(); + } + + /** + * Retrieves, but does not remove, the least element of this queue, or returns {@code null} if the + * queue is empty. + */ + public E peekFirst() { + return peek(); + } + + /** + * Removes and returns the greatest element of this queue, or returns {@code null} if the queue is + * empty. + */ + + public E pollLast() { + return isEmpty() ? null : removeAndGet(getMaxElementIndex()); + } + + /** + * Removes and returns the greatest element of this queue. + * + * @throws NoSuchElementException if the queue is empty + */ + + public E removeLast() { + if (isEmpty()) { + throw new NoSuchElementException(); + } + return removeAndGet(getMaxElementIndex()); + } + + /** + * Retrieves, but does not remove, the greatest element of this queue, or returns {@code null} if + * the queue is empty. + */ + public E peekLast() { + return isEmpty() ? null : elementData(getMaxElementIndex()); + } + + /** + * Removes the element at position {@code index}. + * + *

Normally this method leaves the elements at up to {@code index - 1}, inclusive, untouched. + * Under these circumstances, it returns {@code null}. + * + *

Occasionally, in order to maintain the heap invariant, it must swap a later element of the + * list with one before {@code index}. Under these circumstances it returns a pair of elements as + * a {@link MoveDesc}. The first one is the element that was previously at the end of the heap and + * is now at some position before {@code index}. The second element is the one that was swapped + * down to replace the element at {@code index}. This fact is used by iterator.remove so as to + * visit elements during a traversal once and only once. + */ + @VisibleForTesting + + MoveDesc removeAt(int index) { + checkPositionIndex(index, size); + modCount++; + size--; + if (size == index) { + queue[size] = null; + return null; + } + E actualLastElement = elementData(size); + int lastElementAt = heapForIndex(size).swapWithConceptuallyLastElement(actualLastElement); + if (lastElementAt == index) { + // 'actualLastElement' is now at 'lastElementAt', and the element that was at 'lastElementAt' + // is now at the end of queue. If that's the element we wanted to remove in the first place, + // don't try to (incorrectly) trickle it. Instead, just delete it and we're done. + queue[size] = null; + return null; + } + E toTrickle = elementData(size); + queue[size] = null; + MoveDesc changes = fillHole(index, toTrickle); + if (lastElementAt < index) { + // Last element is moved to before index, swapped with trickled element. + if (changes == null) { + // The trickled element is still after index. + return new MoveDesc(actualLastElement, toTrickle); + } else { + // The trickled element is back before index, but the replaced element + // has now been moved after index. + return new MoveDesc(actualLastElement, changes.replaced); + } + } + // Trickled element was after index to begin with, no adjustment needed. + return changes; + } + + private MoveDesc fillHole(int index, E toTrickle) { + Heap heap = heapForIndex(index); + // We consider elementData(index) a "hole", and we want to fill it + // with the last element of the heap, toTrickle. + // Since the last element of the heap is from the bottom level, we + // optimistically fill index position with elements from lower levels, + // moving the hole down. In most cases this reduces the number of + // comparisons with toTrickle, but in some cases we will need to bubble it + // all the way up again. + int vacated = heap.fillHoleAt(index); + // Try to see if toTrickle can be bubbled up min levels. + int bubbledTo = heap.bubbleUpAlternatingLevels(vacated, toTrickle); + if (bubbledTo == vacated) { + // Could not bubble toTrickle up min levels, try moving + // it from min level to max level (or max to min level) and bubble up + // there. + return heap.tryCrossOverAndBubbleUp(index, vacated, toTrickle); + } else { + return (bubbledTo < index) ? new MoveDesc(toTrickle, elementData(index)) : null; + } + } + + // Returned from removeAt() to iterator.remove() + static class MoveDesc { + final E toTrickle; + final E replaced; + + MoveDesc(E toTrickle, E replaced) { + this.toTrickle = toTrickle; + this.replaced = replaced; + } + } + + /** Removes and returns the value at {@code index}. */ + private E removeAndGet(int index) { + E value = elementData(index); + removeAt(index); + return value; + } + + private Heap heapForIndex(int i) { + return isEvenLevel(i) ? minHeap : maxHeap; + } + + private static final int EVEN_POWERS_OF_TWO = 0x55555555; + private static final int ODD_POWERS_OF_TWO = 0xaaaaaaaa; + + @VisibleForTesting + static boolean isEvenLevel(int index) { + int oneBased = ~~(index + 1); // for GWT + checkState(oneBased > 0, "negative index"); + return (oneBased & EVEN_POWERS_OF_TWO) > (oneBased & ODD_POWERS_OF_TWO); + } + + /** + * Returns {@code true} if the MinMax heap structure holds. This is only used in testing. + * + *

TODO(kevinb): move to the test class? + */ + @VisibleForTesting + boolean isIntact() { + for (int i = 1; i < size; i++) { + if (!heapForIndex(i).verifyIndex(i)) { + return false; + } + } + return true; + } + + /** + * Each instance of MinMaxPriortyQueue encapsulates two instances of Heap: a min-heap and a + * max-heap. Conceptually, these might each have their own array for storage, but for efficiency's + * sake they are stored interleaved on alternate heap levels in the same array (MMPQ.queue). + */ + + private class Heap { + final Ordering ordering; + Heap otherHeap; + + Heap(Ordering ordering) { + this.ordering = ordering; + } + + int compareElements(int a, int b) { + return ordering.compare(elementData(a), elementData(b)); + } + + /** + * Tries to move {@code toTrickle} from a min to a max level and bubble up there. If it moved + * before {@code removeIndex} this method returns a pair as described in {@link #removeAt}. + */ + MoveDesc tryCrossOverAndBubbleUp(int removeIndex, int vacated, E toTrickle) { + int crossOver = crossOver(vacated, toTrickle); + if (crossOver == vacated) { + return null; + } + // Successfully crossed over from min to max. + // Bubble up max levels. + E parent; + // If toTrickle is moved up to a parent of removeIndex, the parent is + // placed in removeIndex position. We must return that to the iterator so + // that it knows to skip it. + if (crossOver < removeIndex) { + // We crossed over to the parent level in crossOver, so the parent + // has already been moved. + parent = elementData(removeIndex); + } else { + parent = elementData(getParentIndex(removeIndex)); + } + // bubble it up the opposite heap + if (otherHeap.bubbleUpAlternatingLevels(crossOver, toTrickle) < removeIndex) { + return new MoveDesc(toTrickle, parent); + } else { + return null; + } + } + + /** Bubbles a value from {@code index} up the appropriate heap if required. */ + void bubbleUp(int index, E x) { + int crossOver = crossOverUp(index, x); + + Heap heap; + if (crossOver == index) { + heap = this; + } else { + index = crossOver; + heap = otherHeap; + } + heap.bubbleUpAlternatingLevels(index, x); + } + + /** + * Bubbles a value from {@code index} up the levels of this heap, and returns the index the + * element ended up at. + */ + + int bubbleUpAlternatingLevels(int index, E x) { + while (index > 2) { + int grandParentIndex = getGrandparentIndex(index); + E e = elementData(grandParentIndex); + if (ordering.compare(e, x) <= 0) { + break; + } + queue[index] = e; + index = grandParentIndex; + } + queue[index] = x; + return index; + } + + /** + * Returns the index of minimum value between {@code index} and {@code index + len}, or {@code + * -1} if {@code index} is greater than {@code size}. + */ + int findMin(int index, int len) { + if (index >= size) { + return -1; + } + checkState(index > 0); + int limit = Math.min(index, size - len) + len; + int minIndex = index; + for (int i = index + 1; i < limit; i++) { + if (compareElements(i, minIndex) < 0) { + minIndex = i; + } + } + return minIndex; + } + + /** Returns the minimum child or {@code -1} if no child exists. */ + int findMinChild(int index) { + return findMin(getLeftChildIndex(index), 2); + } + + /** Returns the minimum grand child or -1 if no grand child exists. */ + int findMinGrandChild(int index) { + int leftChildIndex = getLeftChildIndex(index); + if (leftChildIndex < 0) { + return -1; + } + return findMin(getLeftChildIndex(leftChildIndex), 4); + } + + /** + * Moves an element one level up from a min level to a max level (or vice versa). Returns the + * new position of the element. + */ + int crossOverUp(int index, E x) { + if (index == 0) { + queue[0] = x; + return 0; + } + int parentIndex = getParentIndex(index); + E parentElement = elementData(parentIndex); + if (parentIndex != 0) { + // This is a guard for the case of the childless uncle. + // Since the end of the array is actually the middle of the heap, + // a smaller childless uncle can become a child of x when we + // bubble up alternate levels, violating the invariant. + int grandparentIndex = getParentIndex(parentIndex); + int uncleIndex = getRightChildIndex(grandparentIndex); + if (uncleIndex != parentIndex && getLeftChildIndex(uncleIndex) >= size) { + E uncleElement = elementData(uncleIndex); + if (ordering.compare(uncleElement, parentElement) < 0) { + parentIndex = uncleIndex; + parentElement = uncleElement; + } + } + } + if (ordering.compare(parentElement, x) < 0) { + queue[index] = parentElement; + queue[parentIndex] = x; + return parentIndex; + } + queue[index] = x; + return index; + } + + /** + * Swap {@code actualLastElement} with the conceptually correct last element of the heap. + * Returns the index that {@code actualLastElement} now resides in. + * + *

Since the last element of the array is actually in the middle of the sorted structure, a + * childless uncle node could be smaller, which would corrupt the invariant if this element + * becomes the new parent of the uncle. In that case, we first switch the last element with its + * uncle, before returning. + */ + int swapWithConceptuallyLastElement(E actualLastElement) { + int parentIndex = getParentIndex(size); + if (parentIndex != 0) { + int grandparentIndex = getParentIndex(parentIndex); + int uncleIndex = getRightChildIndex(grandparentIndex); + if (uncleIndex != parentIndex && getLeftChildIndex(uncleIndex) >= size) { + E uncleElement = elementData(uncleIndex); + if (ordering.compare(uncleElement, actualLastElement) < 0) { + queue[uncleIndex] = actualLastElement; + queue[size] = uncleElement; + return uncleIndex; + } + } + } + return size; + } + + /** + * Crosses an element over to the opposite heap by moving it one level down (or up if there are + * no elements below it). + * + *

Returns the new position of the element. + */ + int crossOver(int index, E x) { + int minChildIndex = findMinChild(index); + // TODO(kevinb): split the && into two if's and move crossOverUp so it's + // only called when there's no child. + if ((minChildIndex > 0) && (ordering.compare(elementData(minChildIndex), x) < 0)) { + queue[index] = elementData(minChildIndex); + queue[minChildIndex] = x; + return minChildIndex; + } + return crossOverUp(index, x); + } + + /** + * Fills the hole at {@code index} by moving in the least of its grandchildren to this position, + * then recursively filling the new hole created. + * + * @return the position of the new hole (where the lowest grandchild moved from, that had no + * grandchild to replace it) + */ + int fillHoleAt(int index) { + int minGrandchildIndex; + while ((minGrandchildIndex = findMinGrandChild(index)) > 0) { + queue[index] = elementData(minGrandchildIndex); + index = minGrandchildIndex; + } + return index; + } + + private boolean verifyIndex(int i) { + if ((getLeftChildIndex(i) < size) && (compareElements(i, getLeftChildIndex(i)) > 0)) { + return false; + } + if ((getRightChildIndex(i) < size) && (compareElements(i, getRightChildIndex(i)) > 0)) { + return false; + } + if ((i > 0) && (compareElements(i, getParentIndex(i)) > 0)) { + return false; + } + if ((i > 2) && (compareElements(getGrandparentIndex(i), i) > 0)) { + return false; + } + return true; + } + + // These would be static if inner classes could have static members. + + private int getLeftChildIndex(int i) { + return i * 2 + 1; + } + + private int getRightChildIndex(int i) { + return i * 2 + 2; + } + + private int getParentIndex(int i) { + return (i - 1) / 2; + } + + private int getGrandparentIndex(int i) { + return getParentIndex(getParentIndex(i)); // (i - 3) / 4 + } + } + + /** + * Iterates the elements of the queue in no particular order. + * + *

If the underlying queue is modified during iteration an exception will be thrown. + */ + private class QueueIterator implements Iterator { + private int cursor = -1; + private int nextCursor = -1; + private int expectedModCount = modCount; + // The same element is not allowed in both forgetMeNot and skipMe, but duplicates are allowed in + // either of them, up to the same multiplicity as the queue. + private Queue forgetMeNot; + private List skipMe; + private E lastFromForgetMeNot; + private boolean canRemove; + + @Override + public boolean hasNext() { + checkModCount(); + nextNotInSkipMe(cursor + 1); + return (nextCursor < size()) || ((forgetMeNot != null) && !forgetMeNot.isEmpty()); + } + + @Override + public E next() { + checkModCount(); + nextNotInSkipMe(cursor + 1); + if (nextCursor < size()) { + cursor = nextCursor; + canRemove = true; + return elementData(cursor); + } else if (forgetMeNot != null) { + cursor = size(); + lastFromForgetMeNot = forgetMeNot.poll(); + if (lastFromForgetMeNot != null) { + canRemove = true; + return lastFromForgetMeNot; + } + } + throw new NoSuchElementException("iterator moved past last element in queue."); + } + + @Override + public void remove() { + checkRemove(canRemove); + checkModCount(); + canRemove = false; + expectedModCount++; + if (cursor < size()) { + MoveDesc moved = removeAt(cursor); + if (moved != null) { + if (forgetMeNot == null) { + forgetMeNot = new ArrayDeque(); + skipMe = new ArrayList(3); + } + if (!foundAndRemovedExactReference(skipMe, moved.toTrickle)) { + forgetMeNot.add(moved.toTrickle); + } + if (!foundAndRemovedExactReference(forgetMeNot, moved.replaced)) { + skipMe.add(moved.replaced); + } + } + cursor--; + nextCursor--; + } else { // we must have set lastFromForgetMeNot in next() + checkState(removeExact(lastFromForgetMeNot)); + lastFromForgetMeNot = null; + } + } + + /** Returns true if an exact reference (==) was found and removed from the supplied iterable. */ + private boolean foundAndRemovedExactReference(Iterable elements, E target) { + for (Iterator it = elements.iterator(); it.hasNext(); ) { + E element = it.next(); + if (element == target) { + it.remove(); + return true; + } + } + return false; + } + + /** Removes only this exact instance, not others that are equals() */ + private boolean removeExact(Object target) { + for (int i = 0; i < size; i++) { + if (queue[i] == target) { + removeAt(i); + return true; + } + } + return false; + } + + private void checkModCount() { + if (modCount != expectedModCount) { + throw new ConcurrentModificationException(); + } + } + + /** + * Advances nextCursor to the index of the first element after {@code c} that is not in {@code + * skipMe} and returns {@code size()} if there is no such element. + */ + private void nextNotInSkipMe(int c) { + if (nextCursor < c) { + if (skipMe != null) { + while (c < size() && foundAndRemovedExactReference(skipMe, elementData(c))) { + c++; + } + } + nextCursor = c; + } + } + } + + /** + * Returns an iterator over the elements contained in this collection, in no particular + * order. + * + *

The iterator is fail-fast: If the MinMaxPriorityQueue is modified at any time after + * the iterator is created, in any way except through the iterator's own remove method, the + * iterator will generally throw a {@link ConcurrentModificationException}. Thus, in the face of + * concurrent modification, the iterator fails quickly and cleanly, rather than risking arbitrary, + * non-deterministic behavior at an undetermined time in the future. + * + *

Note that the fail-fast behavior of an iterator cannot be guaranteed as it is, generally + * speaking, impossible to make any hard guarantees in the presence of unsynchronized concurrent + * modification. Fail-fast iterators throw {@code ConcurrentModificationException} on a + * best-effort basis. Therefore, it would be wrong to write a program that depended on this + * exception for its correctness: the fail-fast behavior of iterators should be used only to + * detect bugs. + * + * @return an iterator over the elements contained in this collection + */ + @Override + public Iterator iterator() { + return new QueueIterator(); + } + + @Override + public void clear() { + for (int i = 0; i < size; i++) { + queue[i] = null; + } + size = 0; + } + + @Override + public Object[] toArray() { + Object[] copyTo = new Object[size]; + System.arraycopy(queue, 0, copyTo, 0, size); + return copyTo; + } + + /** + * Returns the comparator used to order the elements in this queue. Obeys the general contract of + * {@link PriorityQueue#comparator}, but returns {@link Ordering#natural} instead of {@code null} + * to indicate natural ordering. + */ + public Comparator comparator() { + return minHeap.ordering; + } + + @VisibleForTesting + int capacity() { + return queue.length; + } + + // Size/capacity-related methods + + private static final int DEFAULT_CAPACITY = 11; + + @VisibleForTesting + static int initialQueueSize( + int configuredExpectedSize, int maximumSize, Iterable initialContents) { + // Start with what they said, if they said it, otherwise DEFAULT_CAPACITY + int result = + (configuredExpectedSize == Builder.UNSET_EXPECTED_SIZE) + ? DEFAULT_CAPACITY + : configuredExpectedSize; + + // Enlarge to contain initial contents + if (initialContents instanceof Collection) { + int initialSize = ((Collection) initialContents).size(); + result = Math.max(result, initialSize); + } + + // Now cap it at maxSize + 1 + return capAtMaximumSize(result, maximumSize); + } + + private void growIfNeeded() { + if (size > queue.length) { + int newCapacity = calculateNewCapacity(); + Object[] newQueue = new Object[newCapacity]; + System.arraycopy(queue, 0, newQueue, 0, queue.length); + queue = newQueue; + } + } + + /** Returns ~2x the old capacity if small; ~1.5x otherwise. */ + private int calculateNewCapacity() { + int oldCapacity = queue.length; + int newCapacity = + (oldCapacity < 64) ? (oldCapacity + 1) * 2 : IntMath.checkedMultiply(oldCapacity / 2, 3); + return capAtMaximumSize(newCapacity, maximumSize); + } + + /** There's no reason for the queueSize to ever be more than maxSize + 1 */ + private static int capAtMaximumSize(int queueSize, int maximumSize) { + return Math.min(queueSize - 1, maximumSize) + 1; // don't overflow + } +} diff --git a/src/main/java/com/google/common/collect/MoreCollectors.java b/src/main/java/com/google/common/collect/MoreCollectors.java new file mode 100644 index 0000000..714ba0e --- /dev/null +++ b/src/main/java/com/google/common/collect/MoreCollectors.java @@ -0,0 +1,168 @@ +/* + * Copyright (C) 2016 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.annotations.GwtCompatible; +import java.util.ArrayList; +import java.util.List; +import java.util.NoSuchElementException; +import java.util.Optional; +import java.util.stream.Collector; + + +/** + * Collectors not present in {@code java.util.stream.Collectors} that are not otherwise associated + * with a {@code com.google.common} type. + * + * @author Louis Wasserman + * @since 21.0 + */ +@GwtCompatible +public final class MoreCollectors { + + /* + * TODO(lowasser): figure out if we can convert this to a concurrent AtomicReference-based + * collector without breaking j2cl? + */ + private static final Collector> TO_OPTIONAL = + Collector.of( + ToOptionalState::new, + ToOptionalState::add, + ToOptionalState::combine, + ToOptionalState::getOptional, + Collector.Characteristics.UNORDERED); + + /** + * A collector that converts a stream of zero or one elements to an {@code Optional}. The returned + * collector throws an {@code IllegalArgumentException} if the stream consists of two or more + * elements, and a {@code NullPointerException} if the stream consists of exactly one element, + * which is null. + */ + @SuppressWarnings("unchecked") + public static Collector> toOptional() { + return (Collector) TO_OPTIONAL; + } + + private static final Object NULL_PLACEHOLDER = new Object(); + + private static final Collector ONLY_ELEMENT = + Collector.of( + ToOptionalState::new, + (state, o) -> state.add((o == null) ? NULL_PLACEHOLDER : o), + ToOptionalState::combine, + state -> { + Object result = state.getElement(); + return (result == NULL_PLACEHOLDER) ? null : result; + }, + Collector.Characteristics.UNORDERED); + + /** + * A collector that takes a stream containing exactly one element and returns that element. The + * returned collector throws an {@code IllegalArgumentException} if the stream consists of two or + * more elements, and a {@code NoSuchElementException} if the stream is empty. + */ + @SuppressWarnings("unchecked") + public static Collector onlyElement() { + return (Collector) ONLY_ELEMENT; + } + + /** + * This atrocity is here to let us report several of the elements in the stream if there were more + * than one, not just two. + */ + private static final class ToOptionalState { + static final int MAX_EXTRAS = 4; + + Object element; + List extras; + + ToOptionalState() { + element = null; + extras = null; + } + + IllegalArgumentException multiples(boolean overflow) { + StringBuilder sb = + new StringBuilder().append("expected one element but was: <").append(element); + for (Object o : extras) { + sb.append(", ").append(o); + } + if (overflow) { + sb.append(", ..."); + } + sb.append('>'); + throw new IllegalArgumentException(sb.toString()); + } + + void add(Object o) { + checkNotNull(o); + if (element == null) { + this.element = o; + } else if (extras == null) { + extras = new ArrayList<>(MAX_EXTRAS); + extras.add(o); + } else if (extras.size() < MAX_EXTRAS) { + extras.add(o); + } else { + throw multiples(true); + } + } + + ToOptionalState combine(ToOptionalState other) { + if (element == null) { + return other; + } else if (other.element == null) { + return this; + } else { + if (extras == null) { + extras = new ArrayList<>(); + } + extras.add(other.element); + if (other.extras != null) { + this.extras.addAll(other.extras); + } + if (extras.size() > MAX_EXTRAS) { + extras.subList(MAX_EXTRAS, extras.size()).clear(); + throw multiples(true); + } + return this; + } + } + + Optional getOptional() { + if (extras == null) { + return Optional.ofNullable(element); + } else { + throw multiples(false); + } + } + + Object getElement() { + if (element == null) { + throw new NoSuchElementException(); + } else if (extras == null) { + return element; + } else { + throw multiples(false); + } + } + } + + private MoreCollectors() {} +} diff --git a/src/main/java/com/google/common/collect/Multimap.java b/src/main/java/com/google/common/collect/Multimap.java new file mode 100644 index 0000000..818cc8a --- /dev/null +++ b/src/main/java/com/google/common/collect/Multimap.java @@ -0,0 +1,384 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.annotations.GwtCompatible; + + +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; +import java.util.function.BiConsumer; + + +/** + * A collection that maps keys to values, similar to {@link Map}, but in which each key may be + * associated with multiple values. You can visualize the contents of a multimap either as a + * map from keys to nonempty collections of values: + * + *
    + *
  • a → 1, 2 + *
  • b → 3 + *
+ * + * ... or as a single "flattened" collection of key-value pairs: + * + *
    + *
  • a → 1 + *
  • a → 2 + *
  • b → 3 + *
+ * + *

Important: although the first interpretation resembles how most multimaps are + * implemented, the design of the {@code Multimap} API is based on the second form. + * So, using the multimap shown above as an example, the {@link #size} is {@code 3}, not {@code 2}, + * and the {@link #values} collection is {@code [1, 2, 3]}, not {@code [[1, 2], [3]]}. For those + * times when the first style is more useful, use the multimap's {@link #asMap} view (or create a + * {@code Map>} in the first place). + * + *

Example

+ * + *

The following code: + * + *

{@code
+ * ListMultimap multimap = ArrayListMultimap.create();
+ * for (President pres : US_PRESIDENTS_IN_ORDER) {
+ *   multimap.put(pres.firstName(), pres.lastName());
+ * }
+ * for (String firstName : multimap.keySet()) {
+ *   List lastNames = multimap.get(firstName);
+ *   out.println(firstName + ": " + lastNames);
+ * }
+ * }
+ * + * ... produces output such as: + * + *
{@code
+ * Zachary: [Taylor]
+ * John: [Adams, Adams, Tyler, Kennedy]  // Remember, Quincy!
+ * George: [Washington, Bush, Bush]
+ * Grover: [Cleveland, Cleveland]        // Two, non-consecutive terms, rep'ing NJ!
+ * ...
+ * }
+ * + *

Views

+ * + *

Much of the power of the multimap API comes from the view collections it provides. + * These always reflect the latest state of the multimap itself. When they support modification, the + * changes are write-through (they automatically update the backing multimap). These view + * collections are: + * + *

    + *
  • {@link #asMap}, mentioned above + *
  • {@link #keys}, {@link #keySet}, {@link #values}, {@link #entries}, which are similar to the + * corresponding view collections of {@link Map} + *
  • and, notably, even the collection returned by {@link #get get(key)} is an active view of + * the values corresponding to {@code key} + *
+ * + *

The collections returned by the {@link #replaceValues replaceValues} and {@link #removeAll + * removeAll} methods, which contain values that have just been removed from the multimap, are + * naturally not views. + * + *

Subinterfaces

+ * + *

Instead of using the {@code Multimap} interface directly, prefer the subinterfaces {@link + * ListMultimap} and {@link SetMultimap}. These take their names from the fact that the collections + * they return from {@code get} behave like (and, of course, implement) {@link List} and {@link + * Set}, respectively. + * + *

For example, the "presidents" code snippet above used a {@code ListMultimap}; if it had used a + * {@code SetMultimap} instead, two presidents would have vanished, and last names might or might + * not appear in chronological order. + * + *

Warning: instances of type {@code Multimap} may not implement {@link Object#equals} in + * the way you expect. Multimaps containing the same key-value pairs, even in the same order, may or + * may not be equal and may or may not have the same {@code hashCode}. The recommended subinterfaces + * provide much stronger guarantees. + * + *

Comparison to a map of collections

+ * + *

Multimaps are commonly used in places where a {@code Map>} would otherwise + * have appeared. The differences include: + * + *

    + *
  • There is no need to populate an empty collection before adding an entry with {@link #put + * put}. + *
  • {@code get} never returns {@code null}, only an empty collection. + *
  • A key is contained in the multimap if and only if it maps to at least one value. Any + * operation that causes a key to have zero associated values has the effect of + * removing that key from the multimap. + *
  • The total entry count is available as {@link #size}. + *
  • Many complex operations become easier; for example, {@code + * Collections.min(multimap.values())} finds the smallest value across all keys. + *
+ * + *

Implementations

+ * + *

As always, prefer the immutable implementations, {@link ImmutableListMultimap} and {@link + * ImmutableSetMultimap}. General-purpose mutable implementations are listed above under "All Known + * Implementing Classes". You can also create a custom multimap, backed by any {@code Map} + * and {@link Collection} types, using the {@link Multimaps#newMultimap Multimaps.newMultimap} + * family of methods. Finally, another popular way to obtain a multimap is using {@link + * Multimaps#index Multimaps.index}. See the {@link Multimaps} class for these and other static + * utilities related to multimaps. + * + *

Other Notes

+ * + *

As with {@code Map}, the behavior of a {@code Multimap} is not specified if key objects + * already present in the multimap change in a manner that affects {@code equals} comparisons. Use + * caution if mutable objects are used as keys in a {@code Multimap}. + * + *

All methods that modify the multimap are optional. The view collections returned by the + * multimap may or may not be modifiable. Any modification method that is not supported will throw + * {@link UnsupportedOperationException}. + * + *

See the Guava User Guide article on {@code + * Multimap}. + * + * @author Jared Levy + * @since 2.0 + */ +@GwtCompatible +public interface Multimap { + // Query Operations + + /** + * Returns the number of key-value pairs in this multimap. + * + *

Note: this method does not return the number of distinct keys in the multimap, + * which is given by {@code keySet().size()} or {@code asMap().size()}. See the opening section of + * the {@link Multimap} class documentation for clarification. + */ + int size(); + + /** + * Returns {@code true} if this multimap contains no key-value pairs. Equivalent to {@code size() + * == 0}, but can in some cases be more efficient. + */ + boolean isEmpty(); + + /** + * Returns {@code true} if this multimap contains at least one key-value pair with the key {@code + * key}. + */ + boolean containsKey(Object key); + + /** + * Returns {@code true} if this multimap contains at least one key-value pair with the value + * {@code value}. + */ + boolean containsValue(Object value); + + /** + * Returns {@code true} if this multimap contains at least one key-value pair with the key {@code + * key} and the value {@code value}. + */ + boolean containsEntry(Object key, Object value); + + // Modification Operations + + /** + * Stores a key-value pair in this multimap. + * + *

Some multimap implementations allow duplicate key-value pairs, in which case {@code put} + * always adds a new key-value pair and increases the multimap size by 1. Other implementations + * prohibit duplicates, and storing a key-value pair that's already in the multimap has no effect. + * + * @return {@code true} if the method increased the size of the multimap, or {@code false} if the + * multimap already contained the key-value pair and doesn't allow duplicates + */ + + boolean put(K key, V value); + + /** + * Removes a single key-value pair with the key {@code key} and the value {@code value} from this + * multimap, if such exists. If multiple key-value pairs in the multimap fit this description, + * which one is removed is unspecified. + * + * @return {@code true} if the multimap changed + */ + + boolean remove(Object key, Object value); + + // Bulk Operations + + /** + * Stores a key-value pair in this multimap for each of {@code values}, all using the same key, + * {@code key}. Equivalent to (but expected to be more efficient than): + * + *

{@code
+   * for (V value : values) {
+   *   put(key, value);
+   * }
+   * }
+ * + *

In particular, this is a no-op if {@code values} is empty. + * + * @return {@code true} if the multimap changed + */ + + boolean putAll(K key, Iterable values); + + /** + * Stores all key-value pairs of {@code multimap} in this multimap, in the order returned by + * {@code multimap.entries()}. + * + * @return {@code true} if the multimap changed + */ + + boolean putAll(Multimap multimap); + + /** + * Stores a collection of values with the same key, replacing any existing values for that key. + * + *

If {@code values} is empty, this is equivalent to {@link #removeAll(Object) removeAll(key)}. + * + * @return the collection of replaced values, or an empty collection if no values were previously + * associated with the key. The collection may be modifiable, but updating it will have + * no effect on the multimap. + */ + + Collection replaceValues(K key, Iterable values); + + /** + * Removes all values associated with the key {@code key}. + * + *

Once this method returns, {@code key} will not be mapped to any values, so it will not + * appear in {@link #keySet()}, {@link #asMap()}, or any other views. + * + * @return the values that were removed (possibly empty). The returned collection may be + * modifiable, but updating it will have no effect on the multimap. + */ + + Collection removeAll(Object key); + + /** Removes all key-value pairs from the multimap, leaving it {@linkplain #isEmpty empty}. */ + void clear(); + + // Views + + /** + * Returns a view collection of the values associated with {@code key} in this multimap, if any. + * Note that when {@code containsKey(key)} is false, this returns an empty collection, not {@code + * null}. + * + *

Changes to the returned collection will update the underlying multimap, and vice versa. + */ + Collection get(K key); + + /** + * Returns a view collection of all distinct keys contained in this multimap. Note that the + * key set contains a key if and only if this multimap maps that key to at least one value. + * + *

Changes to the returned set will update the underlying multimap, and vice versa. However, + * adding to the returned set is not possible. + */ + Set keySet(); + + /** + * Returns a view collection containing the key from each key-value pair in this multimap, + * without collapsing duplicates. This collection has the same size as this multimap, and + * {@code keys().count(k) == get(k).size()} for all {@code k}. + * + *

Changes to the returned multiset will update the underlying multimap, and vice versa. + * However, adding to the returned collection is not possible. + */ + Multiset keys(); + + /** + * Returns a view collection containing the value from each key-value pair contained in + * this multimap, without collapsing duplicates (so {@code values().size() == size()}). + * + *

Changes to the returned collection will update the underlying multimap, and vice versa. + * However, adding to the returned collection is not possible. + */ + Collection values(); + + /** + * Returns a view collection of all key-value pairs contained in this multimap, as {@link Entry} + * instances. + * + *

Changes to the returned collection or the entries it contains will update the underlying + * multimap, and vice versa. However, adding to the returned collection is not possible. + */ + Collection> entries(); + + /** + * Performs the given action for all key-value pairs contained in this multimap. If an ordering is + * specified by the {@code Multimap} implementation, actions will be performed in the order of + * iteration of {@link #entries()}. Exceptions thrown by the action are relayed to the caller. + * + *

To loop over all keys and their associated value collections, write {@code + * Multimaps.asMap(multimap).forEach((key, valueCollection) -> action())}. + * + * @since 21.0 + */ + default void forEach(BiConsumer action) { + checkNotNull(action); + entries().forEach(entry -> action.accept(entry.getKey(), entry.getValue())); + } + + /** + * Returns a view of this multimap as a {@code Map} from each distinct key to the nonempty + * collection of that key's associated values. Note that {@code this.asMap().get(k)} is equivalent + * to {@code this.get(k)} only when {@code k} is a key contained in the multimap; otherwise it + * returns {@code null} as opposed to an empty collection. + * + *

Changes to the returned map or the collections that serve as its values will update the + * underlying multimap, and vice versa. The map does not support {@code put} or {@code putAll}, + * nor do its entries support {@link Entry#setValue setValue}. + */ + Map> asMap(); + + // Comparison and hashing + + /** + * Compares the specified object with this multimap for equality. Two multimaps are equal when + * their map views, as returned by {@link #asMap}, are also equal. + * + *

In general, two multimaps with identical key-value mappings may or may not be equal, + * depending on the implementation. For example, two {@link SetMultimap} instances with the same + * key-value mappings are equal, but equality of two {@link ListMultimap} instances depends on the + * ordering of the values for each key. + * + *

A non-empty {@link SetMultimap} cannot be equal to a non-empty {@link ListMultimap}, since + * their {@link #asMap} views contain unequal collections as values. However, any two empty + * multimaps are equal, because they both have empty {@link #asMap} views. + */ + @Override + boolean equals(Object obj); + + /** + * Returns the hash code for this multimap. + * + *

The hash code of a multimap is defined as the hash code of the map view, as returned by + * {@link Multimap#asMap}. + * + *

In general, two multimaps with identical key-value mappings may or may not have the same + * hash codes, depending on the implementation. For example, two {@link SetMultimap} instances + * with the same key-value mappings will have the same {@code hashCode}, but the {@code hashCode} + * of {@link ListMultimap} instances depends on the ordering of the values for each key. + */ + @Override + int hashCode(); +} diff --git a/src/main/java/com/google/common/collect/MultimapBuilder.java b/src/main/java/com/google/common/collect/MultimapBuilder.java new file mode 100644 index 0000000..161c29d --- /dev/null +++ b/src/main/java/com/google/common/collect/MultimapBuilder.java @@ -0,0 +1,467 @@ +/* + * Copyright (C) 2013 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.collect.CollectPreconditions.checkNonnegative; + +import com.google.common.annotations.GwtCompatible; +import com.google.common.base.Supplier; +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Comparator; +import java.util.EnumMap; +import java.util.EnumSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.SortedSet; +import java.util.TreeMap; +import java.util.TreeSet; + +/** + * A builder for a multimap implementation that allows customization of the backing map and value + * collection implementations used in a particular multimap. + * + *

This can be used to easily configure multimap data structure implementations not provided + * explicitly in {@code com.google.common.collect}, for example: + * + *

{@code
+ * ListMultimap treeListMultimap =
+ *     MultimapBuilder.treeKeys().arrayListValues().build();
+ * SetMultimap hashEnumMultimap =
+ *     MultimapBuilder.hashKeys().enumSetValues(MyEnum.class).build();
+ * }
+ * + *

{@code MultimapBuilder} instances are immutable. Invoking a configuration method has no effect + * on the receiving instance; you must store and use the new builder instance it returns instead. + * + *

The generated multimaps are serializable if the key and value types are serializable, unless + * stated otherwise in one of the configuration methods. + * + * @author Louis Wasserman + * @param An upper bound on the key type of the generated multimap. + * @param An upper bound on the value type of the generated multimap. + * @since 16.0 + */ +@GwtCompatible +public abstract class MultimapBuilder { + /* + * Leaving K and V as upper bounds rather than the actual key and value types allows type + * parameters to be left implicit more often. CacheBuilder uses the same technique. + */ + + private MultimapBuilder() {} + + private static final int DEFAULT_EXPECTED_KEYS = 8; + + /** Uses a hash table to map keys to value collections. */ + public static MultimapBuilderWithKeys hashKeys() { + return hashKeys(DEFAULT_EXPECTED_KEYS); + } + + /** + * Uses a hash table to map keys to value collections, initialized to expect the specified number + * of keys. + * + * @throws IllegalArgumentException if {@code expectedKeys < 0} + */ + public static MultimapBuilderWithKeys hashKeys(final int expectedKeys) { + checkNonnegative(expectedKeys, "expectedKeys"); + return new MultimapBuilderWithKeys() { + @Override + Map> createMap() { + return Platform.newHashMapWithExpectedSize(expectedKeys); + } + }; + } + + /** + * Uses a hash table to map keys to value collections. + * + *

The collections returned by {@link Multimap#keySet()}, {@link Multimap#keys()}, and {@link + * Multimap#asMap()} will iterate through the keys in the order that they were first added to the + * multimap, save that if all values associated with a key are removed and then the key is added + * back into the multimap, that key will come last in the key iteration order. + */ + public static MultimapBuilderWithKeys linkedHashKeys() { + return linkedHashKeys(DEFAULT_EXPECTED_KEYS); + } + + /** + * Uses an hash table to map keys to value collections, initialized to expect the specified number + * of keys. + * + *

The collections returned by {@link Multimap#keySet()}, {@link Multimap#keys()}, and {@link + * Multimap#asMap()} will iterate through the keys in the order that they were first added to the + * multimap, save that if all values associated with a key are removed and then the key is added + * back into the multimap, that key will come last in the key iteration order. + */ + public static MultimapBuilderWithKeys linkedHashKeys(final int expectedKeys) { + checkNonnegative(expectedKeys, "expectedKeys"); + return new MultimapBuilderWithKeys() { + @Override + Map> createMap() { + return Platform.newLinkedHashMapWithExpectedSize(expectedKeys); + } + }; + } + + /** + * Uses a naturally-ordered {@link TreeMap} to map keys to value collections. + * + *

The collections returned by {@link Multimap#keySet()}, {@link Multimap#keys()}, and {@link + * Multimap#asMap()} will iterate through the keys in sorted order. + * + *

For all multimaps generated by the resulting builder, the {@link Multimap#keySet()} can be + * safely cast to a {@link java.util.SortedSet}, and the {@link Multimap#asMap()} can safely be + * cast to a {@link java.util.SortedMap}. + */ + @SuppressWarnings("rawtypes") + public static MultimapBuilderWithKeys treeKeys() { + return treeKeys(Ordering.natural()); + } + + /** + * Uses a {@link TreeMap} sorted by the specified comparator to map keys to value collections. + * + *

The collections returned by {@link Multimap#keySet()}, {@link Multimap#keys()}, and {@link + * Multimap#asMap()} will iterate through the keys in sorted order. + * + *

For all multimaps generated by the resulting builder, the {@link Multimap#keySet()} can be + * safely cast to a {@link java.util.SortedSet}, and the {@link Multimap#asMap()} can safely be + * cast to a {@link java.util.SortedMap}. + * + *

Multimaps generated by the resulting builder will not be serializable if {@code comparator} + * is not serializable. + */ + public static MultimapBuilderWithKeys treeKeys(final Comparator comparator) { + checkNotNull(comparator); + return new MultimapBuilderWithKeys() { + @Override + Map> createMap() { + return new TreeMap<>(comparator); + } + }; + } + + /** + * Uses an {@link EnumMap} to map keys to value collections. + * + * @since 16.0 + */ + public static > MultimapBuilderWithKeys enumKeys( + final Class keyClass) { + checkNotNull(keyClass); + return new MultimapBuilderWithKeys() { + @SuppressWarnings("unchecked") + @Override + Map> createMap() { + // K must actually be K0, since enums are effectively final + // (their subclasses are inaccessible) + return (Map>) new EnumMap>(keyClass); + } + }; + } + + private static final class ArrayListSupplier implements Supplier>, Serializable { + private final int expectedValuesPerKey; + + ArrayListSupplier(int expectedValuesPerKey) { + this.expectedValuesPerKey = checkNonnegative(expectedValuesPerKey, "expectedValuesPerKey"); + } + + @Override + public List get() { + return new ArrayList(expectedValuesPerKey); + } + } + + private enum LinkedListSupplier implements Supplier> { + INSTANCE; + + public static Supplier> instance() { + // Each call generates a fresh LinkedList, which can serve as a List for any V. + @SuppressWarnings({"rawtypes", "unchecked"}) + Supplier> result = (Supplier) INSTANCE; + return result; + } + + @Override + public List get() { + return new LinkedList<>(); + } + } + + private static final class HashSetSupplier implements Supplier>, Serializable { + private final int expectedValuesPerKey; + + HashSetSupplier(int expectedValuesPerKey) { + this.expectedValuesPerKey = checkNonnegative(expectedValuesPerKey, "expectedValuesPerKey"); + } + + @Override + public Set get() { + return Platform.newHashSetWithExpectedSize(expectedValuesPerKey); + } + } + + private static final class LinkedHashSetSupplier implements Supplier>, Serializable { + private final int expectedValuesPerKey; + + LinkedHashSetSupplier(int expectedValuesPerKey) { + this.expectedValuesPerKey = checkNonnegative(expectedValuesPerKey, "expectedValuesPerKey"); + } + + @Override + public Set get() { + return Platform.newLinkedHashSetWithExpectedSize(expectedValuesPerKey); + } + } + + private static final class TreeSetSupplier implements Supplier>, Serializable { + private final Comparator comparator; + + TreeSetSupplier(Comparator comparator) { + this.comparator = checkNotNull(comparator); + } + + @Override + public SortedSet get() { + return new TreeSet(comparator); + } + } + + private static final class EnumSetSupplier> + implements Supplier>, Serializable { + private final Class clazz; + + EnumSetSupplier(Class clazz) { + this.clazz = checkNotNull(clazz); + } + + @Override + public Set get() { + return EnumSet.noneOf(clazz); + } + } + + /** + * An intermediate stage in a {@link MultimapBuilder} in which the key-value collection map + * implementation has been specified, but the value collection implementation has not. + * + * @param The upper bound on the key type of the generated multimap. + * @since 16.0 + */ + public abstract static class MultimapBuilderWithKeys { + + private static final int DEFAULT_EXPECTED_VALUES_PER_KEY = 2; + + MultimapBuilderWithKeys() {} + + abstract Map> createMap(); + + /** Uses an {@link ArrayList} to store value collections. */ + public ListMultimapBuilder arrayListValues() { + return arrayListValues(DEFAULT_EXPECTED_VALUES_PER_KEY); + } + + /** + * Uses an {@link ArrayList} to store value collections, initialized to expect the specified + * number of values per key. + * + * @throws IllegalArgumentException if {@code expectedValuesPerKey < 0} + */ + public ListMultimapBuilder arrayListValues(final int expectedValuesPerKey) { + checkNonnegative(expectedValuesPerKey, "expectedValuesPerKey"); + return new ListMultimapBuilder() { + @Override + public ListMultimap build() { + return Multimaps.newListMultimap( + MultimapBuilderWithKeys.this.createMap(), + new ArrayListSupplier(expectedValuesPerKey)); + } + }; + } + + /** Uses a {@link LinkedList} to store value collections. */ + public ListMultimapBuilder linkedListValues() { + return new ListMultimapBuilder() { + @Override + public ListMultimap build() { + return Multimaps.newListMultimap( + MultimapBuilderWithKeys.this.createMap(), LinkedListSupplier.instance()); + } + }; + } + + /** Uses a hash-based {@code Set} to store value collections. */ + public SetMultimapBuilder hashSetValues() { + return hashSetValues(DEFAULT_EXPECTED_VALUES_PER_KEY); + } + + /** + * Uses a hash-based {@code Set} to store value collections, initialized to expect the specified + * number of values per key. + * + * @throws IllegalArgumentException if {@code expectedValuesPerKey < 0} + */ + public SetMultimapBuilder hashSetValues(final int expectedValuesPerKey) { + checkNonnegative(expectedValuesPerKey, "expectedValuesPerKey"); + return new SetMultimapBuilder() { + @Override + public SetMultimap build() { + return Multimaps.newSetMultimap( + MultimapBuilderWithKeys.this.createMap(), + new HashSetSupplier(expectedValuesPerKey)); + } + }; + } + + /** Uses an insertion-ordered hash-based {@code Set} to store value collections. */ + public SetMultimapBuilder linkedHashSetValues() { + return linkedHashSetValues(DEFAULT_EXPECTED_VALUES_PER_KEY); + } + + /** + * Uses an insertion-ordered hash-based {@code Set} to store value collections, initialized to + * expect the specified number of values per key. + * + * @throws IllegalArgumentException if {@code expectedValuesPerKey < 0} + */ + public SetMultimapBuilder linkedHashSetValues(final int expectedValuesPerKey) { + checkNonnegative(expectedValuesPerKey, "expectedValuesPerKey"); + return new SetMultimapBuilder() { + @Override + public SetMultimap build() { + return Multimaps.newSetMultimap( + MultimapBuilderWithKeys.this.createMap(), + new LinkedHashSetSupplier(expectedValuesPerKey)); + } + }; + } + + /** Uses a naturally-ordered {@link TreeSet} to store value collections. */ + @SuppressWarnings("rawtypes") + public SortedSetMultimapBuilder treeSetValues() { + return treeSetValues(Ordering.natural()); + } + + /** + * Uses a {@link TreeSet} ordered by the specified comparator to store value collections. + * + *

Multimaps generated by the resulting builder will not be serializable if {@code + * comparator} is not serializable. + */ + public SortedSetMultimapBuilder treeSetValues(final Comparator comparator) { + checkNotNull(comparator, "comparator"); + return new SortedSetMultimapBuilder() { + @Override + public SortedSetMultimap build() { + return Multimaps.newSortedSetMultimap( + MultimapBuilderWithKeys.this.createMap(), new TreeSetSupplier(comparator)); + } + }; + } + + /** Uses an {@link EnumSet} to store value collections. */ + public > SetMultimapBuilder enumSetValues( + final Class valueClass) { + checkNotNull(valueClass, "valueClass"); + return new SetMultimapBuilder() { + @Override + public SetMultimap build() { + // V must actually be V0, since enums are effectively final + // (their subclasses are inaccessible) + @SuppressWarnings({"unchecked", "rawtypes"}) + Supplier> factory = (Supplier) new EnumSetSupplier(valueClass); + return Multimaps.newSetMultimap(MultimapBuilderWithKeys.this.createMap(), factory); + } + }; + } + } + + /** Returns a new, empty {@code Multimap} with the specified implementation. */ + public abstract Multimap build(); + + /** + * Returns a {@code Multimap} with the specified implementation, initialized with the entries of + * {@code multimap}. + */ + public Multimap build( + Multimap multimap) { + Multimap result = build(); + result.putAll(multimap); + return result; + } + + /** + * A specialization of {@link MultimapBuilder} that generates {@link ListMultimap} instances. + * + * @since 16.0 + */ + public abstract static class ListMultimapBuilder extends MultimapBuilder { + ListMultimapBuilder() {} + + @Override + public abstract ListMultimap build(); + + @Override + public ListMultimap build( + Multimap multimap) { + return (ListMultimap) super.build(multimap); + } + } + + /** + * A specialization of {@link MultimapBuilder} that generates {@link SetMultimap} instances. + * + * @since 16.0 + */ + public abstract static class SetMultimapBuilder extends MultimapBuilder { + SetMultimapBuilder() {} + + @Override + public abstract SetMultimap build(); + + @Override + public SetMultimap build( + Multimap multimap) { + return (SetMultimap) super.build(multimap); + } + } + + /** + * A specialization of {@link MultimapBuilder} that generates {@link SortedSetMultimap} instances. + * + * @since 16.0 + */ + public abstract static class SortedSetMultimapBuilder extends SetMultimapBuilder { + SortedSetMultimapBuilder() {} + + @Override + public abstract SortedSetMultimap build(); + + @Override + public SortedSetMultimap build( + Multimap multimap) { + return (SortedSetMultimap) super.build(multimap); + } + } +} diff --git a/src/main/java/com/google/common/collect/Multimaps.java b/src/main/java/com/google/common/collect/Multimaps.java new file mode 100644 index 0000000..adceb8e --- /dev/null +++ b/src/main/java/com/google/common/collect/Multimaps.java @@ -0,0 +1,2197 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.collect.CollectPreconditions.checkNonnegative; +import static com.google.common.collect.CollectPreconditions.checkRemove; + +import com.google.common.annotations.Beta; +import com.google.common.annotations.GwtCompatible; +import com.google.common.annotations.GwtIncompatible; +import com.google.common.base.Function; +import com.google.common.base.Predicate; +import com.google.common.base.Predicates; +import com.google.common.base.Supplier; +import com.google.common.collect.Maps.EntryTransformer; + + + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.Serializable; +import java.util.AbstractCollection; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.NavigableSet; +import java.util.NoSuchElementException; +import java.util.Set; +import java.util.SortedSet; +import java.util.Spliterator; +import java.util.function.Consumer; +import java.util.stream.Collector; +import java.util.stream.Stream; + + + +/** + * Provides static methods acting on or generating a {@code Multimap}. + * + *

See the Guava User Guide article on {@code + * Multimaps}. + * + * @author Jared Levy + * @author Robert Konigsberg + * @author Mike Bostock + * @author Louis Wasserman + * @since 2.0 + */ +@GwtCompatible(emulated = true) +public final class Multimaps { + private Multimaps() {} + + /** + * Returns a {@code Collector} accumulating entries into a {@code Multimap} generated from the + * specified supplier. The keys and values of the entries are the result of applying the provided + * mapping functions to the input elements, accumulated in the encounter order of the stream. + * + *

Example: + * + *

{@code
+   * static final ListMultimap FIRST_LETTER_MULTIMAP =
+   *     Stream.of("banana", "apple", "carrot", "asparagus", "cherry")
+   *         .collect(
+   *             toMultimap(
+   *                  str -> str.charAt(0),
+   *                  str -> str.substring(1),
+   *                  MultimapBuilder.treeKeys().arrayListValues()::build));
+   *
+   * // is equivalent to
+   *
+   * static final ListMultimap FIRST_LETTER_MULTIMAP;
+   *
+   * static {
+   *     FIRST_LETTER_MULTIMAP = MultimapBuilder.treeKeys().arrayListValues().build();
+   *     FIRST_LETTER_MULTIMAP.put('b', "anana");
+   *     FIRST_LETTER_MULTIMAP.put('a', "pple");
+   *     FIRST_LETTER_MULTIMAP.put('a', "sparagus");
+   *     FIRST_LETTER_MULTIMAP.put('c', "arrot");
+   *     FIRST_LETTER_MULTIMAP.put('c', "herry");
+   * }
+   * }
+ * + * @since 21.0 + */ + @Beta + public static > Collector toMultimap( + java.util.function.Function keyFunction, + java.util.function.Function valueFunction, + java.util.function.Supplier multimapSupplier) { + checkNotNull(keyFunction); + checkNotNull(valueFunction); + checkNotNull(multimapSupplier); + return Collector.of( + multimapSupplier, + (multimap, input) -> multimap.put(keyFunction.apply(input), valueFunction.apply(input)), + (multimap1, multimap2) -> { + multimap1.putAll(multimap2); + return multimap1; + }); + } + + /** + * Returns a {@code Collector} accumulating entries into a {@code Multimap} generated from the + * specified supplier. Each input element is mapped to a key and a stream of values, each of which + * are put into the resulting {@code Multimap}, in the encounter order of the stream and the + * encounter order of the streams of values. + * + *

Example: + * + *

{@code
+   * static final ListMultimap FIRST_LETTER_MULTIMAP =
+   *     Stream.of("banana", "apple", "carrot", "asparagus", "cherry")
+   *         .collect(
+   *             flatteningToMultimap(
+   *                  str -> str.charAt(0),
+   *                  str -> str.substring(1).chars().mapToObj(c -> (char) c),
+   *                  MultimapBuilder.linkedHashKeys().arrayListValues()::build));
+   *
+   * // is equivalent to
+   *
+   * static final ListMultimap FIRST_LETTER_MULTIMAP;
+   *
+   * static {
+   *     FIRST_LETTER_MULTIMAP = MultimapBuilder.linkedHashKeys().arrayListValues().build();
+   *     FIRST_LETTER_MULTIMAP.putAll('b', Arrays.asList('a', 'n', 'a', 'n', 'a'));
+   *     FIRST_LETTER_MULTIMAP.putAll('a', Arrays.asList('p', 'p', 'l', 'e'));
+   *     FIRST_LETTER_MULTIMAP.putAll('c', Arrays.asList('a', 'r', 'r', 'o', 't'));
+   *     FIRST_LETTER_MULTIMAP.putAll('a', Arrays.asList('s', 'p', 'a', 'r', 'a', 'g', 'u', 's'));
+   *     FIRST_LETTER_MULTIMAP.putAll('c', Arrays.asList('h', 'e', 'r', 'r', 'y'));
+   * }
+   * }
+ * + * @since 21.0 + */ + @Beta + public static > Collector flatteningToMultimap( + java.util.function.Function keyFunction, + java.util.function.Function> valueFunction, + java.util.function.Supplier multimapSupplier) { + checkNotNull(keyFunction); + checkNotNull(valueFunction); + checkNotNull(multimapSupplier); + return Collector.of( + multimapSupplier, + (multimap, input) -> { + K key = keyFunction.apply(input); + Collection valuesForKey = multimap.get(key); + valueFunction.apply(input).forEachOrdered(valuesForKey::add); + }, + (multimap1, multimap2) -> { + multimap1.putAll(multimap2); + return multimap1; + }); + } + + /** + * Creates a new {@code Multimap} backed by {@code map}, whose internal value collections are + * generated by {@code factory}. + * + *

Warning: do not use this method when the collections returned by {@code factory} + * implement either {@link List} or {@code Set}! Use the more specific method {@link + * #newListMultimap}, {@link #newSetMultimap} or {@link #newSortedSetMultimap} instead, to avoid + * very surprising behavior from {@link Multimap#equals}. + * + *

The {@code factory}-generated and {@code map} classes determine the multimap iteration + * order. They also specify the behavior of the {@code equals}, {@code hashCode}, and {@code + * toString} methods for the multimap and its returned views. However, the multimap's {@code get} + * method returns instances of a different class than {@code factory.get()} does. + * + *

The multimap is serializable if {@code map}, {@code factory}, the collections generated by + * {@code factory}, and the multimap contents are all serializable. + * + *

The multimap is not threadsafe when any concurrent operations update the multimap, even if + * {@code map} and the instances generated by {@code factory} are. Concurrent read operations will + * work correctly. To allow concurrent update operations, wrap the multimap with a call to {@link + * #synchronizedMultimap}. + * + *

Call this method only when the simpler methods {@link ArrayListMultimap#create()}, {@link + * HashMultimap#create()}, {@link LinkedHashMultimap#create()}, {@link + * LinkedListMultimap#create()}, {@link TreeMultimap#create()}, and {@link + * TreeMultimap#create(Comparator, Comparator)} won't suffice. + * + *

Note: the multimap assumes complete ownership over of {@code map} and the collections + * returned by {@code factory}. Those objects should not be manually updated and they should not + * use soft, weak, or phantom references. + * + * @param map place to store the mapping from each key to its corresponding values + * @param factory supplier of new, empty collections that will each hold all values for a given + * key + * @throws IllegalArgumentException if {@code map} is not empty + */ + public static Multimap newMultimap( + Map> map, final Supplier> factory) { + return new CustomMultimap<>(map, factory); + } + + private static class CustomMultimap extends AbstractMapBasedMultimap { + transient Supplier> factory; + + CustomMultimap(Map> map, Supplier> factory) { + super(map); + this.factory = checkNotNull(factory); + } + + @Override + Set createKeySet() { + return createMaybeNavigableKeySet(); + } + + @Override + Map> createAsMap() { + return createMaybeNavigableAsMap(); + } + + @Override + protected Collection createCollection() { + return factory.get(); + } + + @Override + Collection unmodifiableCollectionSubclass(Collection collection) { + if (collection instanceof NavigableSet) { + return Sets.unmodifiableNavigableSet((NavigableSet) collection); + } else if (collection instanceof SortedSet) { + return Collections.unmodifiableSortedSet((SortedSet) collection); + } else if (collection instanceof Set) { + return Collections.unmodifiableSet((Set) collection); + } else if (collection instanceof List) { + return Collections.unmodifiableList((List) collection); + } else { + return Collections.unmodifiableCollection(collection); + } + } + + @Override + Collection wrapCollection(K key, Collection collection) { + if (collection instanceof List) { + return wrapList(key, (List) collection, null); + } else if (collection instanceof NavigableSet) { + return new WrappedNavigableSet(key, (NavigableSet) collection, null); + } else if (collection instanceof SortedSet) { + return new WrappedSortedSet(key, (SortedSet) collection, null); + } else if (collection instanceof Set) { + return new WrappedSet(key, (Set) collection); + } else { + return new WrappedCollection(key, collection, null); + } + } + + // can't use Serialization writeMultimap and populateMultimap methods since + // there's no way to generate the empty backing map. + + /** @serialData the factory and the backing map */ + @GwtIncompatible // java.io.ObjectOutputStream + private void writeObject(ObjectOutputStream stream) throws IOException { + stream.defaultWriteObject(); + stream.writeObject(factory); + stream.writeObject(backingMap()); + } + + @GwtIncompatible // java.io.ObjectInputStream + @SuppressWarnings("unchecked") // reading data stored by writeObject + private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException { + stream.defaultReadObject(); + factory = (Supplier>) stream.readObject(); + Map> map = (Map>) stream.readObject(); + setMap(map); + } + + @GwtIncompatible // java serialization not supported + private static final long serialVersionUID = 0; + } + + /** + * Creates a new {@code ListMultimap} that uses the provided map and factory. It can generate a + * multimap based on arbitrary {@link Map} and {@link List} classes. + * + *

The {@code factory}-generated and {@code map} classes determine the multimap iteration + * order. They also specify the behavior of the {@code equals}, {@code hashCode}, and {@code + * toString} methods for the multimap and its returned views. The multimap's {@code get}, {@code + * removeAll}, and {@code replaceValues} methods return {@code RandomAccess} lists if the factory + * does. However, the multimap's {@code get} method returns instances of a different class than + * does {@code factory.get()}. + * + *

The multimap is serializable if {@code map}, {@code factory}, the lists generated by {@code + * factory}, and the multimap contents are all serializable. + * + *

The multimap is not threadsafe when any concurrent operations update the multimap, even if + * {@code map} and the instances generated by {@code factory} are. Concurrent read operations will + * work correctly. To allow concurrent update operations, wrap the multimap with a call to {@link + * #synchronizedListMultimap}. + * + *

Call this method only when the simpler methods {@link ArrayListMultimap#create()} and {@link + * LinkedListMultimap#create()} won't suffice. + * + *

Note: the multimap assumes complete ownership over of {@code map} and the lists returned by + * {@code factory}. Those objects should not be manually updated, they should be empty when + * provided, and they should not use soft, weak, or phantom references. + * + * @param map place to store the mapping from each key to its corresponding values + * @param factory supplier of new, empty lists that will each hold all values for a given key + * @throws IllegalArgumentException if {@code map} is not empty + */ + public static ListMultimap newListMultimap( + Map> map, final Supplier> factory) { + return new CustomListMultimap<>(map, factory); + } + + private static class CustomListMultimap extends AbstractListMultimap { + transient Supplier> factory; + + CustomListMultimap(Map> map, Supplier> factory) { + super(map); + this.factory = checkNotNull(factory); + } + + @Override + Set createKeySet() { + return createMaybeNavigableKeySet(); + } + + @Override + Map> createAsMap() { + return createMaybeNavigableAsMap(); + } + + @Override + protected List createCollection() { + return factory.get(); + } + + /** @serialData the factory and the backing map */ + @GwtIncompatible // java.io.ObjectOutputStream + private void writeObject(ObjectOutputStream stream) throws IOException { + stream.defaultWriteObject(); + stream.writeObject(factory); + stream.writeObject(backingMap()); + } + + @GwtIncompatible // java.io.ObjectInputStream + @SuppressWarnings("unchecked") // reading data stored by writeObject + private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException { + stream.defaultReadObject(); + factory = (Supplier>) stream.readObject(); + Map> map = (Map>) stream.readObject(); + setMap(map); + } + + @GwtIncompatible // java serialization not supported + private static final long serialVersionUID = 0; + } + + /** + * Creates a new {@code SetMultimap} that uses the provided map and factory. It can generate a + * multimap based on arbitrary {@link Map} and {@link Set} classes. + * + *

The {@code factory}-generated and {@code map} classes determine the multimap iteration + * order. They also specify the behavior of the {@code equals}, {@code hashCode}, and {@code + * toString} methods for the multimap and its returned views. However, the multimap's {@code get} + * method returns instances of a different class than {@code factory.get()} does. + * + *

The multimap is serializable if {@code map}, {@code factory}, the sets generated by {@code + * factory}, and the multimap contents are all serializable. + * + *

The multimap is not threadsafe when any concurrent operations update the multimap, even if + * {@code map} and the instances generated by {@code factory} are. Concurrent read operations will + * work correctly. To allow concurrent update operations, wrap the multimap with a call to {@link + * #synchronizedSetMultimap}. + * + *

Call this method only when the simpler methods {@link HashMultimap#create()}, {@link + * LinkedHashMultimap#create()}, {@link TreeMultimap#create()}, and {@link + * TreeMultimap#create(Comparator, Comparator)} won't suffice. + * + *

Note: the multimap assumes complete ownership over of {@code map} and the sets returned by + * {@code factory}. Those objects should not be manually updated and they should not use soft, + * weak, or phantom references. + * + * @param map place to store the mapping from each key to its corresponding values + * @param factory supplier of new, empty sets that will each hold all values for a given key + * @throws IllegalArgumentException if {@code map} is not empty + */ + public static SetMultimap newSetMultimap( + Map> map, final Supplier> factory) { + return new CustomSetMultimap<>(map, factory); + } + + private static class CustomSetMultimap extends AbstractSetMultimap { + transient Supplier> factory; + + CustomSetMultimap(Map> map, Supplier> factory) { + super(map); + this.factory = checkNotNull(factory); + } + + @Override + Set createKeySet() { + return createMaybeNavigableKeySet(); + } + + @Override + Map> createAsMap() { + return createMaybeNavigableAsMap(); + } + + @Override + protected Set createCollection() { + return factory.get(); + } + + @Override + Collection unmodifiableCollectionSubclass(Collection collection) { + if (collection instanceof NavigableSet) { + return Sets.unmodifiableNavigableSet((NavigableSet) collection); + } else if (collection instanceof SortedSet) { + return Collections.unmodifiableSortedSet((SortedSet) collection); + } else { + return Collections.unmodifiableSet((Set) collection); + } + } + + @Override + Collection wrapCollection(K key, Collection collection) { + if (collection instanceof NavigableSet) { + return new WrappedNavigableSet(key, (NavigableSet) collection, null); + } else if (collection instanceof SortedSet) { + return new WrappedSortedSet(key, (SortedSet) collection, null); + } else { + return new WrappedSet(key, (Set) collection); + } + } + + /** @serialData the factory and the backing map */ + @GwtIncompatible // java.io.ObjectOutputStream + private void writeObject(ObjectOutputStream stream) throws IOException { + stream.defaultWriteObject(); + stream.writeObject(factory); + stream.writeObject(backingMap()); + } + + @GwtIncompatible // java.io.ObjectInputStream + @SuppressWarnings("unchecked") // reading data stored by writeObject + private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException { + stream.defaultReadObject(); + factory = (Supplier>) stream.readObject(); + Map> map = (Map>) stream.readObject(); + setMap(map); + } + + @GwtIncompatible // not needed in emulated source + private static final long serialVersionUID = 0; + } + + /** + * Creates a new {@code SortedSetMultimap} that uses the provided map and factory. It can generate + * a multimap based on arbitrary {@link Map} and {@link SortedSet} classes. + * + *

The {@code factory}-generated and {@code map} classes determine the multimap iteration + * order. They also specify the behavior of the {@code equals}, {@code hashCode}, and {@code + * toString} methods for the multimap and its returned views. However, the multimap's {@code get} + * method returns instances of a different class than {@code factory.get()} does. + * + *

The multimap is serializable if {@code map}, {@code factory}, the sets generated by {@code + * factory}, and the multimap contents are all serializable. + * + *

The multimap is not threadsafe when any concurrent operations update the multimap, even if + * {@code map} and the instances generated by {@code factory} are. Concurrent read operations will + * work correctly. To allow concurrent update operations, wrap the multimap with a call to {@link + * #synchronizedSortedSetMultimap}. + * + *

Call this method only when the simpler methods {@link TreeMultimap#create()} and {@link + * TreeMultimap#create(Comparator, Comparator)} won't suffice. + * + *

Note: the multimap assumes complete ownership over of {@code map} and the sets returned by + * {@code factory}. Those objects should not be manually updated and they should not use soft, + * weak, or phantom references. + * + * @param map place to store the mapping from each key to its corresponding values + * @param factory supplier of new, empty sorted sets that will each hold all values for a given + * key + * @throws IllegalArgumentException if {@code map} is not empty + */ + public static SortedSetMultimap newSortedSetMultimap( + Map> map, final Supplier> factory) { + return new CustomSortedSetMultimap<>(map, factory); + } + + private static class CustomSortedSetMultimap extends AbstractSortedSetMultimap { + transient Supplier> factory; + transient Comparator valueComparator; + + CustomSortedSetMultimap(Map> map, Supplier> factory) { + super(map); + this.factory = checkNotNull(factory); + valueComparator = factory.get().comparator(); + } + + @Override + Set createKeySet() { + return createMaybeNavigableKeySet(); + } + + @Override + Map> createAsMap() { + return createMaybeNavigableAsMap(); + } + + @Override + protected SortedSet createCollection() { + return factory.get(); + } + + @Override + public Comparator valueComparator() { + return valueComparator; + } + + /** @serialData the factory and the backing map */ + @GwtIncompatible // java.io.ObjectOutputStream + private void writeObject(ObjectOutputStream stream) throws IOException { + stream.defaultWriteObject(); + stream.writeObject(factory); + stream.writeObject(backingMap()); + } + + @GwtIncompatible // java.io.ObjectInputStream + @SuppressWarnings("unchecked") // reading data stored by writeObject + private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException { + stream.defaultReadObject(); + factory = (Supplier>) stream.readObject(); + valueComparator = factory.get().comparator(); + Map> map = (Map>) stream.readObject(); + setMap(map); + } + + @GwtIncompatible // not needed in emulated source + private static final long serialVersionUID = 0; + } + + /** + * Copies each key-value mapping in {@code source} into {@code dest}, with its key and value + * reversed. + * + *

If {@code source} is an {@link ImmutableMultimap}, consider using {@link + * ImmutableMultimap#inverse} instead. + * + * @param source any multimap + * @param dest the multimap to copy into; usually empty + * @return {@code dest} + */ + + public static > M invertFrom( + Multimap source, M dest) { + checkNotNull(dest); + for (Map.Entry entry : source.entries()) { + dest.put(entry.getValue(), entry.getKey()); + } + return dest; + } + + /** + * Returns a synchronized (thread-safe) multimap backed by the specified multimap. In order to + * guarantee serial access, it is critical that all access to the backing multimap is + * accomplished through the returned multimap. + * + *

It is imperative that the user manually synchronize on the returned multimap when accessing + * any of its collection views: + * + *

{@code
+   * Multimap multimap = Multimaps.synchronizedMultimap(
+   *     HashMultimap.create());
+   * ...
+   * Collection values = multimap.get(key);  // Needn't be in synchronized block
+   * ...
+   * synchronized (multimap) {  // Synchronizing on multimap, not values!
+   *   Iterator i = values.iterator(); // Must be in synchronized block
+   *   while (i.hasNext()) {
+   *     foo(i.next());
+   *   }
+   * }
+   * }
+ * + *

Failure to follow this advice may result in non-deterministic behavior. + * + *

Note that the generated multimap's {@link Multimap#removeAll} and {@link + * Multimap#replaceValues} methods return collections that aren't synchronized. + * + *

The returned multimap will be serializable if the specified multimap is serializable. + * + * @param multimap the multimap to be wrapped in a synchronized view + * @return a synchronized view of the specified multimap + */ + public static Multimap synchronizedMultimap(Multimap multimap) { + return Synchronized.multimap(multimap, null); + } + + /** + * Returns an unmodifiable view of the specified multimap. Query operations on the returned + * multimap "read through" to the specified multimap, and attempts to modify the returned + * multimap, either directly or through the multimap's views, result in an {@code + * UnsupportedOperationException}. + * + *

The returned multimap will be serializable if the specified multimap is serializable. + * + * @param delegate the multimap for which an unmodifiable view is to be returned + * @return an unmodifiable view of the specified multimap + */ + public static Multimap unmodifiableMultimap(Multimap delegate) { + if (delegate instanceof UnmodifiableMultimap || delegate instanceof ImmutableMultimap) { + return delegate; + } + return new UnmodifiableMultimap<>(delegate); + } + + /** + * Simply returns its argument. + * + * @deprecated no need to use this + * @since 10.0 + */ + @Deprecated + public static Multimap unmodifiableMultimap(ImmutableMultimap delegate) { + return checkNotNull(delegate); + } + + private static class UnmodifiableMultimap extends ForwardingMultimap + implements Serializable { + final Multimap delegate; + transient Collection> entries; + transient Multiset keys; + transient Set keySet; + transient Collection values; + transient Map> map; + + UnmodifiableMultimap(final Multimap delegate) { + this.delegate = checkNotNull(delegate); + } + + @Override + protected Multimap delegate() { + return delegate; + } + + @Override + public void clear() { + throw new UnsupportedOperationException(); + } + + @Override + public Map> asMap() { + Map> result = map; + if (result == null) { + result = + map = + Collections.unmodifiableMap( + Maps.transformValues( + delegate.asMap(), + new Function, Collection>() { + @Override + public Collection apply(Collection collection) { + return unmodifiableValueCollection(collection); + } + })); + } + return result; + } + + @Override + public Collection> entries() { + Collection> result = entries; + if (result == null) { + entries = result = unmodifiableEntries(delegate.entries()); + } + return result; + } + + @Override + public Collection get(K key) { + return unmodifiableValueCollection(delegate.get(key)); + } + + @Override + public Multiset keys() { + Multiset result = keys; + if (result == null) { + keys = result = Multisets.unmodifiableMultiset(delegate.keys()); + } + return result; + } + + @Override + public Set keySet() { + Set result = keySet; + if (result == null) { + keySet = result = Collections.unmodifiableSet(delegate.keySet()); + } + return result; + } + + @Override + public boolean put(K key, V value) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean putAll(K key, Iterable values) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean putAll(Multimap multimap) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean remove(Object key, Object value) { + throw new UnsupportedOperationException(); + } + + @Override + public Collection removeAll(Object key) { + throw new UnsupportedOperationException(); + } + + @Override + public Collection replaceValues(K key, Iterable values) { + throw new UnsupportedOperationException(); + } + + @Override + public Collection values() { + Collection result = values; + if (result == null) { + values = result = Collections.unmodifiableCollection(delegate.values()); + } + return result; + } + + private static final long serialVersionUID = 0; + } + + private static class UnmodifiableListMultimap extends UnmodifiableMultimap + implements ListMultimap { + UnmodifiableListMultimap(ListMultimap delegate) { + super(delegate); + } + + @Override + public ListMultimap delegate() { + return (ListMultimap) super.delegate(); + } + + @Override + public List get(K key) { + return Collections.unmodifiableList(delegate().get(key)); + } + + @Override + public List removeAll(Object key) { + throw new UnsupportedOperationException(); + } + + @Override + public List replaceValues(K key, Iterable values) { + throw new UnsupportedOperationException(); + } + + private static final long serialVersionUID = 0; + } + + private static class UnmodifiableSetMultimap extends UnmodifiableMultimap + implements SetMultimap { + UnmodifiableSetMultimap(SetMultimap delegate) { + super(delegate); + } + + @Override + public SetMultimap delegate() { + return (SetMultimap) super.delegate(); + } + + @Override + public Set get(K key) { + /* + * Note that this doesn't return a SortedSet when delegate is a + * SortedSetMultiset, unlike (SortedSet) super.get(). + */ + return Collections.unmodifiableSet(delegate().get(key)); + } + + @Override + public Set> entries() { + return Maps.unmodifiableEntrySet(delegate().entries()); + } + + @Override + public Set removeAll(Object key) { + throw new UnsupportedOperationException(); + } + + @Override + public Set replaceValues(K key, Iterable values) { + throw new UnsupportedOperationException(); + } + + private static final long serialVersionUID = 0; + } + + private static class UnmodifiableSortedSetMultimap extends UnmodifiableSetMultimap + implements SortedSetMultimap { + UnmodifiableSortedSetMultimap(SortedSetMultimap delegate) { + super(delegate); + } + + @Override + public SortedSetMultimap delegate() { + return (SortedSetMultimap) super.delegate(); + } + + @Override + public SortedSet get(K key) { + return Collections.unmodifiableSortedSet(delegate().get(key)); + } + + @Override + public SortedSet removeAll(Object key) { + throw new UnsupportedOperationException(); + } + + @Override + public SortedSet replaceValues(K key, Iterable values) { + throw new UnsupportedOperationException(); + } + + @Override + public Comparator valueComparator() { + return delegate().valueComparator(); + } + + private static final long serialVersionUID = 0; + } + + /** + * Returns a synchronized (thread-safe) {@code SetMultimap} backed by the specified multimap. + * + *

You must follow the warnings described in {@link #synchronizedMultimap}. + * + *

The returned multimap will be serializable if the specified multimap is serializable. + * + * @param multimap the multimap to be wrapped + * @return a synchronized view of the specified multimap + */ + public static SetMultimap synchronizedSetMultimap(SetMultimap multimap) { + return Synchronized.setMultimap(multimap, null); + } + + /** + * Returns an unmodifiable view of the specified {@code SetMultimap}. Query operations on the + * returned multimap "read through" to the specified multimap, and attempts to modify the returned + * multimap, either directly or through the multimap's views, result in an {@code + * UnsupportedOperationException}. + * + *

The returned multimap will be serializable if the specified multimap is serializable. + * + * @param delegate the multimap for which an unmodifiable view is to be returned + * @return an unmodifiable view of the specified multimap + */ + public static SetMultimap unmodifiableSetMultimap(SetMultimap delegate) { + if (delegate instanceof UnmodifiableSetMultimap || delegate instanceof ImmutableSetMultimap) { + return delegate; + } + return new UnmodifiableSetMultimap<>(delegate); + } + + /** + * Simply returns its argument. + * + * @deprecated no need to use this + * @since 10.0 + */ + @Deprecated + public static SetMultimap unmodifiableSetMultimap( + ImmutableSetMultimap delegate) { + return checkNotNull(delegate); + } + + /** + * Returns a synchronized (thread-safe) {@code SortedSetMultimap} backed by the specified + * multimap. + * + *

You must follow the warnings described in {@link #synchronizedMultimap}. + * + *

The returned multimap will be serializable if the specified multimap is serializable. + * + * @param multimap the multimap to be wrapped + * @return a synchronized view of the specified multimap + */ + public static SortedSetMultimap synchronizedSortedSetMultimap( + SortedSetMultimap multimap) { + return Synchronized.sortedSetMultimap(multimap, null); + } + + /** + * Returns an unmodifiable view of the specified {@code SortedSetMultimap}. Query operations on + * the returned multimap "read through" to the specified multimap, and attempts to modify the + * returned multimap, either directly or through the multimap's views, result in an {@code + * UnsupportedOperationException}. + * + *

The returned multimap will be serializable if the specified multimap is serializable. + * + * @param delegate the multimap for which an unmodifiable view is to be returned + * @return an unmodifiable view of the specified multimap + */ + public static SortedSetMultimap unmodifiableSortedSetMultimap( + SortedSetMultimap delegate) { + if (delegate instanceof UnmodifiableSortedSetMultimap) { + return delegate; + } + return new UnmodifiableSortedSetMultimap<>(delegate); + } + + /** + * Returns a synchronized (thread-safe) {@code ListMultimap} backed by the specified multimap. + * + *

You must follow the warnings described in {@link #synchronizedMultimap}. + * + * @param multimap the multimap to be wrapped + * @return a synchronized view of the specified multimap + */ + public static ListMultimap synchronizedListMultimap(ListMultimap multimap) { + return Synchronized.listMultimap(multimap, null); + } + + /** + * Returns an unmodifiable view of the specified {@code ListMultimap}. Query operations on the + * returned multimap "read through" to the specified multimap, and attempts to modify the returned + * multimap, either directly or through the multimap's views, result in an {@code + * UnsupportedOperationException}. + * + *

The returned multimap will be serializable if the specified multimap is serializable. + * + * @param delegate the multimap for which an unmodifiable view is to be returned + * @return an unmodifiable view of the specified multimap + */ + public static ListMultimap unmodifiableListMultimap(ListMultimap delegate) { + if (delegate instanceof UnmodifiableListMultimap || delegate instanceof ImmutableListMultimap) { + return delegate; + } + return new UnmodifiableListMultimap<>(delegate); + } + + /** + * Simply returns its argument. + * + * @deprecated no need to use this + * @since 10.0 + */ + @Deprecated + public static ListMultimap unmodifiableListMultimap( + ImmutableListMultimap delegate) { + return checkNotNull(delegate); + } + + /** + * Returns an unmodifiable view of the specified collection, preserving the interface for + * instances of {@code SortedSet}, {@code Set}, {@code List} and {@code Collection}, in that order + * of preference. + * + * @param collection the collection for which to return an unmodifiable view + * @return an unmodifiable view of the collection + */ + private static Collection unmodifiableValueCollection(Collection collection) { + if (collection instanceof SortedSet) { + return Collections.unmodifiableSortedSet((SortedSet) collection); + } else if (collection instanceof Set) { + return Collections.unmodifiableSet((Set) collection); + } else if (collection instanceof List) { + return Collections.unmodifiableList((List) collection); + } + return Collections.unmodifiableCollection(collection); + } + + /** + * Returns an unmodifiable view of the specified collection of entries. The {@link Entry#setValue} + * operation throws an {@link UnsupportedOperationException}. If the specified collection is a + * {@code Set}, the returned collection is also a {@code Set}. + * + * @param entries the entries for which to return an unmodifiable view + * @return an unmodifiable view of the entries + */ + private static Collection> unmodifiableEntries( + Collection> entries) { + if (entries instanceof Set) { + return Maps.unmodifiableEntrySet((Set>) entries); + } + return new Maps.UnmodifiableEntries<>(Collections.unmodifiableCollection(entries)); + } + + /** + * Returns {@link ListMultimap#asMap multimap.asMap()}, with its type corrected from {@code Map>} to {@code Map>}. + * + * @since 15.0 + */ + @Beta + @SuppressWarnings("unchecked") + // safe by specification of ListMultimap.asMap() + public static Map> asMap(ListMultimap multimap) { + return (Map>) (Map) multimap.asMap(); + } + + /** + * Returns {@link SetMultimap#asMap multimap.asMap()}, with its type corrected from {@code Map>} to {@code Map>}. + * + * @since 15.0 + */ + @Beta + @SuppressWarnings("unchecked") + // safe by specification of SetMultimap.asMap() + public static Map> asMap(SetMultimap multimap) { + return (Map>) (Map) multimap.asMap(); + } + + /** + * Returns {@link SortedSetMultimap#asMap multimap.asMap()}, with its type corrected from {@code + * Map>} to {@code Map>}. + * + * @since 15.0 + */ + @Beta + @SuppressWarnings("unchecked") + // safe by specification of SortedSetMultimap.asMap() + public static Map> asMap(SortedSetMultimap multimap) { + return (Map>) (Map) multimap.asMap(); + } + + /** + * Returns {@link Multimap#asMap multimap.asMap()}. This is provided for parity with the other + * more strongly-typed {@code asMap()} implementations. + * + * @since 15.0 + */ + @Beta + public static Map> asMap(Multimap multimap) { + return multimap.asMap(); + } + + /** + * Returns a multimap view of the specified map. The multimap is backed by the map, so changes to + * the map are reflected in the multimap, and vice versa. If the map is modified while an + * iteration over one of the multimap's collection views is in progress (except through the + * iterator's own {@code remove} operation, or through the {@code setValue} operation on a map + * entry returned by the iterator), the results of the iteration are undefined. + * + *

The multimap supports mapping removal, which removes the corresponding mapping from the map. + * It does not support any operations which might add mappings, such as {@code put}, {@code + * putAll} or {@code replaceValues}. + * + *

The returned multimap will be serializable if the specified map is serializable. + * + * @param map the backing map for the returned multimap view + */ + public static SetMultimap forMap(Map map) { + return new MapMultimap<>(map); + } + + /** @see Multimaps#forMap */ + private static class MapMultimap extends AbstractMultimap + implements SetMultimap, Serializable { + final Map map; + + MapMultimap(Map map) { + this.map = checkNotNull(map); + } + + @Override + public int size() { + return map.size(); + } + + @Override + public boolean containsKey(Object key) { + return map.containsKey(key); + } + + @Override + public boolean containsValue(Object value) { + return map.containsValue(value); + } + + @Override + public boolean containsEntry(Object key, Object value) { + return map.entrySet().contains(Maps.immutableEntry(key, value)); + } + + @Override + public Set get(final K key) { + return new Sets.ImprovedAbstractSet() { + @Override + public Iterator iterator() { + return new Iterator() { + int i; + + @Override + public boolean hasNext() { + return (i == 0) && map.containsKey(key); + } + + @Override + public V next() { + if (!hasNext()) { + throw new NoSuchElementException(); + } + i++; + return map.get(key); + } + + @Override + public void remove() { + checkRemove(i == 1); + i = -1; + map.remove(key); + } + }; + } + + @Override + public int size() { + return map.containsKey(key) ? 1 : 0; + } + }; + } + + @Override + public boolean put(K key, V value) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean putAll(K key, Iterable values) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean putAll(Multimap multimap) { + throw new UnsupportedOperationException(); + } + + @Override + public Set replaceValues(K key, Iterable values) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean remove(Object key, Object value) { + return map.entrySet().remove(Maps.immutableEntry(key, value)); + } + + @Override + public Set removeAll(Object key) { + Set values = new HashSet(2); + if (!map.containsKey(key)) { + return values; + } + values.add(map.remove(key)); + return values; + } + + @Override + public void clear() { + map.clear(); + } + + @Override + Set createKeySet() { + return map.keySet(); + } + + @Override + Collection createValues() { + return map.values(); + } + + @Override + public Set> entries() { + return map.entrySet(); + } + + @Override + Collection> createEntries() { + throw new AssertionError("unreachable"); + } + + @Override + Multiset createKeys() { + return new Multimaps.Keys(this); + } + + @Override + Iterator> entryIterator() { + return map.entrySet().iterator(); + } + + @Override + Map> createAsMap() { + return new AsMap<>(this); + } + + @Override + public int hashCode() { + return map.hashCode(); + } + + private static final long serialVersionUID = 7845222491160860175L; + } + + /** + * Returns a view of a multimap where each value is transformed by a function. All other + * properties of the multimap, such as iteration order, are left intact. For example, the code: + * + *

{@code
+   * Multimap multimap =
+   *     ImmutableSetMultimap.of("a", 2, "b", -3, "b", -3, "a", 4, "c", 6);
+   * Function square = new Function() {
+   *     public String apply(Integer in) {
+   *       return Integer.toString(in * in);
+   *     }
+   * };
+   * Multimap transformed =
+   *     Multimaps.transformValues(multimap, square);
+   *   System.out.println(transformed);
+   * }
+ * + * ... prints {@code {a=[4, 16], b=[9, 9], c=[36]}}. + * + *

Changes in the underlying multimap are reflected in this view. Conversely, this view + * supports removal operations, and these are reflected in the underlying multimap. + * + *

It's acceptable for the underlying multimap to contain null keys, and even null values + * provided that the function is capable of accepting null input. The transformed multimap might + * contain null values, if the function sometimes gives a null result. + * + *

The returned multimap is not thread-safe or serializable, even if the underlying multimap + * is. The {@code equals} and {@code hashCode} methods of the returned multimap are meaningless, + * since there is not a definition of {@code equals} or {@code hashCode} for general collections, + * and {@code get()} will return a general {@code Collection} as opposed to a {@code List} or a + * {@code Set}. + * + *

The function is applied lazily, invoked when needed. This is necessary for the returned + * multimap to be a view, but it means that the function will be applied many times for bulk + * operations like {@link Multimap#containsValue} and {@code Multimap.toString()}. For this to + * perform well, {@code function} should be fast. To avoid lazy evaluation when the returned + * multimap doesn't need to be a view, copy the returned multimap into a new multimap of your + * choosing. + * + * @since 7.0 + */ + public static Multimap transformValues( + Multimap fromMultimap, final Function function) { + checkNotNull(function); + EntryTransformer transformer = Maps.asEntryTransformer(function); + return transformEntries(fromMultimap, transformer); + } + + /** + * Returns a view of a {@code ListMultimap} where each value is transformed by a function. All + * other properties of the multimap, such as iteration order, are left intact. For example, the + * code: + * + *

{@code
+   * ListMultimap multimap
+   *      = ImmutableListMultimap.of("a", 4, "a", 16, "b", 9);
+   * Function sqrt =
+   *     new Function() {
+   *       public Double apply(Integer in) {
+   *         return Math.sqrt((int) in);
+   *       }
+   *     };
+   * ListMultimap transformed = Multimaps.transformValues(map,
+   *     sqrt);
+   * System.out.println(transformed);
+   * }
+ * + * ... prints {@code {a=[2.0, 4.0], b=[3.0]}}. + * + *

Changes in the underlying multimap are reflected in this view. Conversely, this view + * supports removal operations, and these are reflected in the underlying multimap. + * + *

It's acceptable for the underlying multimap to contain null keys, and even null values + * provided that the function is capable of accepting null input. The transformed multimap might + * contain null values, if the function sometimes gives a null result. + * + *

The returned multimap is not thread-safe or serializable, even if the underlying multimap + * is. + * + *

The function is applied lazily, invoked when needed. This is necessary for the returned + * multimap to be a view, but it means that the function will be applied many times for bulk + * operations like {@link Multimap#containsValue} and {@code Multimap.toString()}. For this to + * perform well, {@code function} should be fast. To avoid lazy evaluation when the returned + * multimap doesn't need to be a view, copy the returned multimap into a new multimap of your + * choosing. + * + * @since 7.0 + */ + public static ListMultimap transformValues( + ListMultimap fromMultimap, final Function function) { + checkNotNull(function); + EntryTransformer transformer = Maps.asEntryTransformer(function); + return transformEntries(fromMultimap, transformer); + } + + /** + * Returns a view of a multimap whose values are derived from the original multimap's entries. In + * contrast to {@link #transformValues}, this method's entry-transformation logic may depend on + * the key as well as the value. + * + *

All other properties of the transformed multimap, such as iteration order, are left intact. + * For example, the code: + * + *

{@code
+   * SetMultimap multimap =
+   *     ImmutableSetMultimap.of("a", 1, "a", 4, "b", -6);
+   * EntryTransformer transformer =
+   *     new EntryTransformer() {
+   *       public String transformEntry(String key, Integer value) {
+   *          return (value >= 0) ? key : "no" + key;
+   *       }
+   *     };
+   * Multimap transformed =
+   *     Multimaps.transformEntries(multimap, transformer);
+   * System.out.println(transformed);
+   * }
+ * + * ... prints {@code {a=[a, a], b=[nob]}}. + * + *

Changes in the underlying multimap are reflected in this view. Conversely, this view + * supports removal operations, and these are reflected in the underlying multimap. + * + *

It's acceptable for the underlying multimap to contain null keys and null values provided + * that the transformer is capable of accepting null inputs. The transformed multimap might + * contain null values if the transformer sometimes gives a null result. + * + *

The returned multimap is not thread-safe or serializable, even if the underlying multimap + * is. The {@code equals} and {@code hashCode} methods of the returned multimap are meaningless, + * since there is not a definition of {@code equals} or {@code hashCode} for general collections, + * and {@code get()} will return a general {@code Collection} as opposed to a {@code List} or a + * {@code Set}. + * + *

The transformer is applied lazily, invoked when needed. This is necessary for the returned + * multimap to be a view, but it means that the transformer will be applied many times for bulk + * operations like {@link Multimap#containsValue} and {@link Object#toString}. For this to perform + * well, {@code transformer} should be fast. To avoid lazy evaluation when the returned multimap + * doesn't need to be a view, copy the returned multimap into a new multimap of your choosing. + * + *

Warning: This method assumes that for any instance {@code k} of {@code + * EntryTransformer} key type {@code K}, {@code k.equals(k2)} implies that {@code k2} is also of + * type {@code K}. Using an {@code EntryTransformer} key type for which this may not hold, such as + * {@code ArrayList}, may risk a {@code ClassCastException} when calling methods on the + * transformed multimap. + * + * @since 7.0 + */ + public static Multimap transformEntries( + Multimap fromMap, EntryTransformer transformer) { + return new TransformedEntriesMultimap<>(fromMap, transformer); + } + + /** + * Returns a view of a {@code ListMultimap} whose values are derived from the original multimap's + * entries. In contrast to {@link #transformValues(ListMultimap, Function)}, this method's + * entry-transformation logic may depend on the key as well as the value. + * + *

All other properties of the transformed multimap, such as iteration order, are left intact. + * For example, the code: + * + *

{@code
+   * Multimap multimap =
+   *     ImmutableMultimap.of("a", 1, "a", 4, "b", 6);
+   * EntryTransformer transformer =
+   *     new EntryTransformer() {
+   *       public String transformEntry(String key, Integer value) {
+   *         return key + value;
+   *       }
+   *     };
+   * Multimap transformed =
+   *     Multimaps.transformEntries(multimap, transformer);
+   * System.out.println(transformed);
+   * }
+ * + * ... prints {@code {"a"=["a1", "a4"], "b"=["b6"]}}. + * + *

Changes in the underlying multimap are reflected in this view. Conversely, this view + * supports removal operations, and these are reflected in the underlying multimap. + * + *

It's acceptable for the underlying multimap to contain null keys and null values provided + * that the transformer is capable of accepting null inputs. The transformed multimap might + * contain null values if the transformer sometimes gives a null result. + * + *

The returned multimap is not thread-safe or serializable, even if the underlying multimap + * is. + * + *

The transformer is applied lazily, invoked when needed. This is necessary for the returned + * multimap to be a view, but it means that the transformer will be applied many times for bulk + * operations like {@link Multimap#containsValue} and {@link Object#toString}. For this to perform + * well, {@code transformer} should be fast. To avoid lazy evaluation when the returned multimap + * doesn't need to be a view, copy the returned multimap into a new multimap of your choosing. + * + *

Warning: This method assumes that for any instance {@code k} of {@code + * EntryTransformer} key type {@code K}, {@code k.equals(k2)} implies that {@code k2} is also of + * type {@code K}. Using an {@code EntryTransformer} key type for which this may not hold, such as + * {@code ArrayList}, may risk a {@code ClassCastException} when calling methods on the + * transformed multimap. + * + * @since 7.0 + */ + public static ListMultimap transformEntries( + ListMultimap fromMap, EntryTransformer transformer) { + return new TransformedEntriesListMultimap<>(fromMap, transformer); + } + + private static class TransformedEntriesMultimap extends AbstractMultimap { + final Multimap fromMultimap; + final EntryTransformer transformer; + + TransformedEntriesMultimap( + Multimap fromMultimap, + final EntryTransformer transformer) { + this.fromMultimap = checkNotNull(fromMultimap); + this.transformer = checkNotNull(transformer); + } + + Collection transform(K key, Collection values) { + Function function = Maps.asValueToValueFunction(transformer, key); + if (values instanceof List) { + return Lists.transform((List) values, function); + } else { + return Collections2.transform(values, function); + } + } + + @Override + Map> createAsMap() { + return Maps.transformEntries( + fromMultimap.asMap(), + new EntryTransformer, Collection>() { + @Override + public Collection transformEntry(K key, Collection value) { + return transform(key, value); + } + }); + } + + @Override + public void clear() { + fromMultimap.clear(); + } + + @Override + public boolean containsKey(Object key) { + return fromMultimap.containsKey(key); + } + + @Override + Collection> createEntries() { + return new Entries(); + } + + @Override + Iterator> entryIterator() { + return Iterators.transform( + fromMultimap.entries().iterator(), Maps.asEntryToEntryFunction(transformer)); + } + + @Override + public Collection get(final K key) { + return transform(key, fromMultimap.get(key)); + } + + @Override + public boolean isEmpty() { + return fromMultimap.isEmpty(); + } + + @Override + Set createKeySet() { + return fromMultimap.keySet(); + } + + @Override + Multiset createKeys() { + return fromMultimap.keys(); + } + + @Override + public boolean put(K key, V2 value) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean putAll(K key, Iterable values) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean putAll(Multimap multimap) { + throw new UnsupportedOperationException(); + } + + @SuppressWarnings("unchecked") + @Override + public boolean remove(Object key, Object value) { + return get((K) key).remove(value); + } + + @SuppressWarnings("unchecked") + @Override + public Collection removeAll(Object key) { + return transform((K) key, fromMultimap.removeAll(key)); + } + + @Override + public Collection replaceValues(K key, Iterable values) { + throw new UnsupportedOperationException(); + } + + @Override + public int size() { + return fromMultimap.size(); + } + + @Override + Collection createValues() { + return Collections2.transform( + fromMultimap.entries(), Maps.asEntryToValueFunction(transformer)); + } + } + + private static final class TransformedEntriesListMultimap + extends TransformedEntriesMultimap implements ListMultimap { + + TransformedEntriesListMultimap( + ListMultimap fromMultimap, EntryTransformer transformer) { + super(fromMultimap, transformer); + } + + @Override + List transform(K key, Collection values) { + return Lists.transform((List) values, Maps.asValueToValueFunction(transformer, key)); + } + + @Override + public List get(K key) { + return transform(key, fromMultimap.get(key)); + } + + @SuppressWarnings("unchecked") + @Override + public List removeAll(Object key) { + return transform((K) key, fromMultimap.removeAll(key)); + } + + @Override + public List replaceValues(K key, Iterable values) { + throw new UnsupportedOperationException(); + } + } + + /** + * Creates an index {@code ImmutableListMultimap} that contains the results of applying a + * specified function to each item in an {@code Iterable} of values. Each value will be stored as + * a value in the resulting multimap, yielding a multimap with the same size as the input + * iterable. The key used to store that value in the multimap will be the result of calling the + * function on that value. The resulting multimap is created as an immutable snapshot. In the + * returned multimap, keys appear in the order they are first encountered, and the values + * corresponding to each key appear in the same order as they are encountered. + * + *

For example, + * + *

{@code
+   * List badGuys =
+   *     Arrays.asList("Inky", "Blinky", "Pinky", "Pinky", "Clyde");
+   * Function stringLengthFunction = ...;
+   * Multimap index =
+   *     Multimaps.index(badGuys, stringLengthFunction);
+   * System.out.println(index);
+   * }
+ * + *

prints + * + *

{@code
+   * {4=[Inky], 6=[Blinky], 5=[Pinky, Pinky, Clyde]}
+   * }
+ * + *

The returned multimap is serializable if its keys and values are all serializable. + * + * @param values the values to use when constructing the {@code ImmutableListMultimap} + * @param keyFunction the function used to produce the key for each value + * @return {@code ImmutableListMultimap} mapping the result of evaluating the function {@code + * keyFunction} on each value in the input collection to that value + * @throws NullPointerException if any element of {@code values} is {@code null}, or if {@code + * keyFunction} produces {@code null} for any key + */ + public static ImmutableListMultimap index( + Iterable values, Function keyFunction) { + return index(values.iterator(), keyFunction); + } + + /** + * Creates an index {@code ImmutableListMultimap} that contains the results of applying a + * specified function to each item in an {@code Iterator} of values. Each value will be stored as + * a value in the resulting multimap, yielding a multimap with the same size as the input + * iterator. The key used to store that value in the multimap will be the result of calling the + * function on that value. The resulting multimap is created as an immutable snapshot. In the + * returned multimap, keys appear in the order they are first encountered, and the values + * corresponding to each key appear in the same order as they are encountered. + * + *

For example, + * + *

{@code
+   * List badGuys =
+   *     Arrays.asList("Inky", "Blinky", "Pinky", "Pinky", "Clyde");
+   * Function stringLengthFunction = ...;
+   * Multimap index =
+   *     Multimaps.index(badGuys.iterator(), stringLengthFunction);
+   * System.out.println(index);
+   * }
+ * + *

prints + * + *

{@code
+   * {4=[Inky], 6=[Blinky], 5=[Pinky, Pinky, Clyde]}
+   * }
+ * + *

The returned multimap is serializable if its keys and values are all serializable. + * + * @param values the values to use when constructing the {@code ImmutableListMultimap} + * @param keyFunction the function used to produce the key for each value + * @return {@code ImmutableListMultimap} mapping the result of evaluating the function {@code + * keyFunction} on each value in the input collection to that value + * @throws NullPointerException if any element of {@code values} is {@code null}, or if {@code + * keyFunction} produces {@code null} for any key + * @since 10.0 + */ + public static ImmutableListMultimap index( + Iterator values, Function keyFunction) { + checkNotNull(keyFunction); + ImmutableListMultimap.Builder builder = ImmutableListMultimap.builder(); + while (values.hasNext()) { + V value = values.next(); + checkNotNull(value, values); + builder.put(keyFunction.apply(value), value); + } + return builder.build(); + } + + static class Keys extends AbstractMultiset { + final Multimap multimap; + + Keys(Multimap multimap) { + this.multimap = multimap; + } + + @Override + Iterator> entryIterator() { + return new TransformedIterator>, Multiset.Entry>( + multimap.asMap().entrySet().iterator()) { + @Override + Multiset.Entry transform(final Map.Entry> backingEntry) { + return new Multisets.AbstractEntry() { + @Override + public K getElement() { + return backingEntry.getKey(); + } + + @Override + public int getCount() { + return backingEntry.getValue().size(); + } + }; + } + }; + } + + @Override + public Spliterator spliterator() { + return CollectSpliterators.map(multimap.entries().spliterator(), Map.Entry::getKey); + } + + @Override + public void forEach(Consumer consumer) { + checkNotNull(consumer); + multimap.entries().forEach(entry -> consumer.accept(entry.getKey())); + } + + @Override + int distinctElements() { + return multimap.asMap().size(); + } + + @Override + public int size() { + return multimap.size(); + } + + @Override + public boolean contains(Object element) { + return multimap.containsKey(element); + } + + @Override + public Iterator iterator() { + return Maps.keyIterator(multimap.entries().iterator()); + } + + @Override + public int count(Object element) { + Collection values = Maps.safeGet(multimap.asMap(), element); + return (values == null) ? 0 : values.size(); + } + + @Override + public int remove(Object element, int occurrences) { + checkNonnegative(occurrences, "occurrences"); + if (occurrences == 0) { + return count(element); + } + + Collection values = Maps.safeGet(multimap.asMap(), element); + + if (values == null) { + return 0; + } + + int oldCount = values.size(); + if (occurrences >= oldCount) { + values.clear(); + } else { + Iterator iterator = values.iterator(); + for (int i = 0; i < occurrences; i++) { + iterator.next(); + iterator.remove(); + } + } + return oldCount; + } + + @Override + public void clear() { + multimap.clear(); + } + + @Override + public Set elementSet() { + return multimap.keySet(); + } + + @Override + Iterator elementIterator() { + throw new AssertionError("should never be called"); + } + } + + /** A skeleton implementation of {@link Multimap#entries()}. */ + abstract static class Entries extends AbstractCollection> { + abstract Multimap multimap(); + + @Override + public int size() { + return multimap().size(); + } + + @Override + public boolean contains(Object o) { + if (o instanceof Map.Entry) { + Map.Entry entry = (Map.Entry) o; + return multimap().containsEntry(entry.getKey(), entry.getValue()); + } + return false; + } + + @Override + public boolean remove(Object o) { + if (o instanceof Map.Entry) { + Map.Entry entry = (Map.Entry) o; + return multimap().remove(entry.getKey(), entry.getValue()); + } + return false; + } + + @Override + public void clear() { + multimap().clear(); + } + } + + /** A skeleton implementation of {@link Multimap#asMap()}. */ + static final class AsMap extends Maps.ViewCachingAbstractMap> { + private final Multimap multimap; + + AsMap(Multimap multimap) { + this.multimap = checkNotNull(multimap); + } + + @Override + public int size() { + return multimap.keySet().size(); + } + + @Override + protected Set>> createEntrySet() { + return new EntrySet(); + } + + void removeValuesForKey(Object key) { + multimap.keySet().remove(key); + } + + + class EntrySet extends Maps.EntrySet> { + @Override + Map> map() { + return AsMap.this; + } + + @Override + public Iterator>> iterator() { + return Maps.asMapEntryIterator( + multimap.keySet(), + new Function>() { + @Override + public Collection apply(K key) { + return multimap.get(key); + } + }); + } + + @Override + public boolean remove(Object o) { + if (!contains(o)) { + return false; + } + Map.Entry entry = (Map.Entry) o; + removeValuesForKey(entry.getKey()); + return true; + } + } + + @SuppressWarnings("unchecked") + @Override + public Collection get(Object key) { + return containsKey(key) ? multimap.get((K) key) : null; + } + + @Override + public Collection remove(Object key) { + return containsKey(key) ? multimap.removeAll(key) : null; + } + + @Override + public Set keySet() { + return multimap.keySet(); + } + + @Override + public boolean isEmpty() { + return multimap.isEmpty(); + } + + @Override + public boolean containsKey(Object key) { + return multimap.containsKey(key); + } + + @Override + public void clear() { + multimap.clear(); + } + } + + /** + * Returns a multimap containing the mappings in {@code unfiltered} whose keys satisfy a + * predicate. The returned multimap is a live view of {@code unfiltered}; changes to one affect + * the other. + * + *

The resulting multimap's views have iterators that don't support {@code remove()}, but all + * other methods are supported by the multimap and its views. When adding a key that doesn't + * satisfy the predicate, the multimap's {@code put()}, {@code putAll()}, and {@code + * replaceValues()} methods throw an {@link IllegalArgumentException}. + * + *

When methods such as {@code removeAll()} and {@code clear()} are called on the filtered + * multimap or its views, only mappings whose keys satisfy the filter will be removed from the + * underlying multimap. + * + *

The returned multimap isn't threadsafe or serializable, even if {@code unfiltered} is. + * + *

Many of the filtered multimap's methods, such as {@code size()}, iterate across every + * key/value mapping in the underlying multimap and determine which satisfy the filter. When a + * live view is not needed, it may be faster to copy the filtered multimap and use the + * copy. + * + *

Warning: {@code keyPredicate} must be consistent with equals, as documented at + * {@link Predicate#apply}. Do not provide a predicate such as {@code + * Predicates.instanceOf(ArrayList.class)}, which is inconsistent with equals. + * + * @since 11.0 + */ + public static Multimap filterKeys( + Multimap unfiltered, final Predicate keyPredicate) { + if (unfiltered instanceof SetMultimap) { + return filterKeys((SetMultimap) unfiltered, keyPredicate); + } else if (unfiltered instanceof ListMultimap) { + return filterKeys((ListMultimap) unfiltered, keyPredicate); + } else if (unfiltered instanceof FilteredKeyMultimap) { + FilteredKeyMultimap prev = (FilteredKeyMultimap) unfiltered; + return new FilteredKeyMultimap<>( + prev.unfiltered, Predicates.and(prev.keyPredicate, keyPredicate)); + } else if (unfiltered instanceof FilteredMultimap) { + FilteredMultimap prev = (FilteredMultimap) unfiltered; + return filterFiltered(prev, Maps.keyPredicateOnEntries(keyPredicate)); + } else { + return new FilteredKeyMultimap<>(unfiltered, keyPredicate); + } + } + + /** + * Returns a multimap containing the mappings in {@code unfiltered} whose keys satisfy a + * predicate. The returned multimap is a live view of {@code unfiltered}; changes to one affect + * the other. + * + *

The resulting multimap's views have iterators that don't support {@code remove()}, but all + * other methods are supported by the multimap and its views. When adding a key that doesn't + * satisfy the predicate, the multimap's {@code put()}, {@code putAll()}, and {@code + * replaceValues()} methods throw an {@link IllegalArgumentException}. + * + *

When methods such as {@code removeAll()} and {@code clear()} are called on the filtered + * multimap or its views, only mappings whose keys satisfy the filter will be removed from the + * underlying multimap. + * + *

The returned multimap isn't threadsafe or serializable, even if {@code unfiltered} is. + * + *

Many of the filtered multimap's methods, such as {@code size()}, iterate across every + * key/value mapping in the underlying multimap and determine which satisfy the filter. When a + * live view is not needed, it may be faster to copy the filtered multimap and use the + * copy. + * + *

Warning: {@code keyPredicate} must be consistent with equals, as documented at + * {@link Predicate#apply}. Do not provide a predicate such as {@code + * Predicates.instanceOf(ArrayList.class)}, which is inconsistent with equals. + * + * @since 14.0 + */ + public static SetMultimap filterKeys( + SetMultimap unfiltered, final Predicate keyPredicate) { + if (unfiltered instanceof FilteredKeySetMultimap) { + FilteredKeySetMultimap prev = (FilteredKeySetMultimap) unfiltered; + return new FilteredKeySetMultimap<>( + prev.unfiltered(), Predicates.and(prev.keyPredicate, keyPredicate)); + } else if (unfiltered instanceof FilteredSetMultimap) { + FilteredSetMultimap prev = (FilteredSetMultimap) unfiltered; + return filterFiltered(prev, Maps.keyPredicateOnEntries(keyPredicate)); + } else { + return new FilteredKeySetMultimap<>(unfiltered, keyPredicate); + } + } + + /** + * Returns a multimap containing the mappings in {@code unfiltered} whose keys satisfy a + * predicate. The returned multimap is a live view of {@code unfiltered}; changes to one affect + * the other. + * + *

The resulting multimap's views have iterators that don't support {@code remove()}, but all + * other methods are supported by the multimap and its views. When adding a key that doesn't + * satisfy the predicate, the multimap's {@code put()}, {@code putAll()}, and {@code + * replaceValues()} methods throw an {@link IllegalArgumentException}. + * + *

When methods such as {@code removeAll()} and {@code clear()} are called on the filtered + * multimap or its views, only mappings whose keys satisfy the filter will be removed from the + * underlying multimap. + * + *

The returned multimap isn't threadsafe or serializable, even if {@code unfiltered} is. + * + *

Many of the filtered multimap's methods, such as {@code size()}, iterate across every + * key/value mapping in the underlying multimap and determine which satisfy the filter. When a + * live view is not needed, it may be faster to copy the filtered multimap and use the + * copy. + * + *

Warning: {@code keyPredicate} must be consistent with equals, as documented at + * {@link Predicate#apply}. Do not provide a predicate such as {@code + * Predicates.instanceOf(ArrayList.class)}, which is inconsistent with equals. + * + * @since 14.0 + */ + public static ListMultimap filterKeys( + ListMultimap unfiltered, final Predicate keyPredicate) { + if (unfiltered instanceof FilteredKeyListMultimap) { + FilteredKeyListMultimap prev = (FilteredKeyListMultimap) unfiltered; + return new FilteredKeyListMultimap<>( + prev.unfiltered(), Predicates.and(prev.keyPredicate, keyPredicate)); + } else { + return new FilteredKeyListMultimap<>(unfiltered, keyPredicate); + } + } + + /** + * Returns a multimap containing the mappings in {@code unfiltered} whose values satisfy a + * predicate. The returned multimap is a live view of {@code unfiltered}; changes to one affect + * the other. + * + *

The resulting multimap's views have iterators that don't support {@code remove()}, but all + * other methods are supported by the multimap and its views. When adding a value that doesn't + * satisfy the predicate, the multimap's {@code put()}, {@code putAll()}, and {@code + * replaceValues()} methods throw an {@link IllegalArgumentException}. + * + *

When methods such as {@code removeAll()} and {@code clear()} are called on the filtered + * multimap or its views, only mappings whose value satisfy the filter will be removed from the + * underlying multimap. + * + *

The returned multimap isn't threadsafe or serializable, even if {@code unfiltered} is. + * + *

Many of the filtered multimap's methods, such as {@code size()}, iterate across every + * key/value mapping in the underlying multimap and determine which satisfy the filter. When a + * live view is not needed, it may be faster to copy the filtered multimap and use the + * copy. + * + *

Warning: {@code valuePredicate} must be consistent with equals, as documented + * at {@link Predicate#apply}. Do not provide a predicate such as {@code + * Predicates.instanceOf(ArrayList.class)}, which is inconsistent with equals. + * + * @since 11.0 + */ + public static Multimap filterValues( + Multimap unfiltered, final Predicate valuePredicate) { + return filterEntries(unfiltered, Maps.valuePredicateOnEntries(valuePredicate)); + } + + /** + * Returns a multimap containing the mappings in {@code unfiltered} whose values satisfy a + * predicate. The returned multimap is a live view of {@code unfiltered}; changes to one affect + * the other. + * + *

The resulting multimap's views have iterators that don't support {@code remove()}, but all + * other methods are supported by the multimap and its views. When adding a value that doesn't + * satisfy the predicate, the multimap's {@code put()}, {@code putAll()}, and {@code + * replaceValues()} methods throw an {@link IllegalArgumentException}. + * + *

When methods such as {@code removeAll()} and {@code clear()} are called on the filtered + * multimap or its views, only mappings whose value satisfy the filter will be removed from the + * underlying multimap. + * + *

The returned multimap isn't threadsafe or serializable, even if {@code unfiltered} is. + * + *

Many of the filtered multimap's methods, such as {@code size()}, iterate across every + * key/value mapping in the underlying multimap and determine which satisfy the filter. When a + * live view is not needed, it may be faster to copy the filtered multimap and use the + * copy. + * + *

Warning: {@code valuePredicate} must be consistent with equals, as documented + * at {@link Predicate#apply}. Do not provide a predicate such as {@code + * Predicates.instanceOf(ArrayList.class)}, which is inconsistent with equals. + * + * @since 14.0 + */ + public static SetMultimap filterValues( + SetMultimap unfiltered, final Predicate valuePredicate) { + return filterEntries(unfiltered, Maps.valuePredicateOnEntries(valuePredicate)); + } + + /** + * Returns a multimap containing the mappings in {@code unfiltered} that satisfy a predicate. The + * returned multimap is a live view of {@code unfiltered}; changes to one affect the other. + * + *

The resulting multimap's views have iterators that don't support {@code remove()}, but all + * other methods are supported by the multimap and its views. When adding a key/value pair that + * doesn't satisfy the predicate, multimap's {@code put()}, {@code putAll()}, and {@code + * replaceValues()} methods throw an {@link IllegalArgumentException}. + * + *

When methods such as {@code removeAll()} and {@code clear()} are called on the filtered + * multimap or its views, only mappings whose keys satisfy the filter will be removed from the + * underlying multimap. + * + *

The returned multimap isn't threadsafe or serializable, even if {@code unfiltered} is. + * + *

Many of the filtered multimap's methods, such as {@code size()}, iterate across every + * key/value mapping in the underlying multimap and determine which satisfy the filter. When a + * live view is not needed, it may be faster to copy the filtered multimap and use the + * copy. + * + *

Warning: {@code entryPredicate} must be consistent with equals, as documented + * at {@link Predicate#apply}. + * + * @since 11.0 + */ + public static Multimap filterEntries( + Multimap unfiltered, Predicate> entryPredicate) { + checkNotNull(entryPredicate); + if (unfiltered instanceof SetMultimap) { + return filterEntries((SetMultimap) unfiltered, entryPredicate); + } + return (unfiltered instanceof FilteredMultimap) + ? filterFiltered((FilteredMultimap) unfiltered, entryPredicate) + : new FilteredEntryMultimap(checkNotNull(unfiltered), entryPredicate); + } + + /** + * Returns a multimap containing the mappings in {@code unfiltered} that satisfy a predicate. The + * returned multimap is a live view of {@code unfiltered}; changes to one affect the other. + * + *

The resulting multimap's views have iterators that don't support {@code remove()}, but all + * other methods are supported by the multimap and its views. When adding a key/value pair that + * doesn't satisfy the predicate, multimap's {@code put()}, {@code putAll()}, and {@code + * replaceValues()} methods throw an {@link IllegalArgumentException}. + * + *

When methods such as {@code removeAll()} and {@code clear()} are called on the filtered + * multimap or its views, only mappings whose keys satisfy the filter will be removed from the + * underlying multimap. + * + *

The returned multimap isn't threadsafe or serializable, even if {@code unfiltered} is. + * + *

Many of the filtered multimap's methods, such as {@code size()}, iterate across every + * key/value mapping in the underlying multimap and determine which satisfy the filter. When a + * live view is not needed, it may be faster to copy the filtered multimap and use the + * copy. + * + *

Warning: {@code entryPredicate} must be consistent with equals, as documented + * at {@link Predicate#apply}. + * + * @since 14.0 + */ + public static SetMultimap filterEntries( + SetMultimap unfiltered, Predicate> entryPredicate) { + checkNotNull(entryPredicate); + return (unfiltered instanceof FilteredSetMultimap) + ? filterFiltered((FilteredSetMultimap) unfiltered, entryPredicate) + : new FilteredEntrySetMultimap(checkNotNull(unfiltered), entryPredicate); + } + + /** + * Support removal operations when filtering a filtered multimap. Since a filtered multimap has + * iterators that don't support remove, passing one to the FilteredEntryMultimap constructor would + * lead to a multimap whose removal operations would fail. This method combines the predicates to + * avoid that problem. + */ + private static Multimap filterFiltered( + FilteredMultimap multimap, Predicate> entryPredicate) { + Predicate> predicate = + Predicates.>and(multimap.entryPredicate(), entryPredicate); + return new FilteredEntryMultimap<>(multimap.unfiltered(), predicate); + } + + /** + * Support removal operations when filtering a filtered multimap. Since a filtered multimap has + * iterators that don't support remove, passing one to the FilteredEntryMultimap constructor would + * lead to a multimap whose removal operations would fail. This method combines the predicates to + * avoid that problem. + */ + private static SetMultimap filterFiltered( + FilteredSetMultimap multimap, Predicate> entryPredicate) { + Predicate> predicate = + Predicates.>and(multimap.entryPredicate(), entryPredicate); + return new FilteredEntrySetMultimap<>(multimap.unfiltered(), predicate); + } + + static boolean equalsImpl(Multimap multimap, Object object) { + if (object == multimap) { + return true; + } + if (object instanceof Multimap) { + Multimap that = (Multimap) object; + return multimap.asMap().equals(that.asMap()); + } + return false; + } + + // TODO(jlevy): Create methods that filter a SortedSetMultimap. +} diff --git a/src/main/java/com/google/common/collect/Multiset.java b/src/main/java/com/google/common/collect/Multiset.java new file mode 100644 index 0000000..a855bd0 --- /dev/null +++ b/src/main/java/com/google/common/collect/Multiset.java @@ -0,0 +1,475 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.annotations.Beta; +import com.google.common.annotations.GwtCompatible; + + +import java.util.Collection; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.Set; +import java.util.Spliterator; +import java.util.function.Consumer; +import java.util.function.ObjIntConsumer; + + +/** + * A collection that supports order-independent equality, like {@link Set}, but may have duplicate + * elements. A multiset is also sometimes called a bag. + * + *

Elements of a multiset that are equal to one another are referred to as occurrences of + * the same single element. The total number of occurrences of an element in a multiset is called + * the count of that element (the terms "frequency" and "multiplicity" are equivalent, but + * not used in this API). Since the count of an element is represented as an {@code int}, a multiset + * may never contain more than {@link Integer#MAX_VALUE} occurrences of any one element. + * + *

{@code Multiset} refines the specifications of several methods from {@code Collection}. It + * also defines an additional query operation, {@link #count}, which returns the count of an + * element. There are five new bulk-modification operations, for example {@link #add(Object, int)}, + * to add or remove multiple occurrences of an element at once, or to set the count of an element to + * a specific value. These modification operations are optional, but implementations which support + * the standard collection operations {@link #add(Object)} or {@link #remove(Object)} are encouraged + * to implement the related methods as well. Finally, two collection views are provided: {@link + * #elementSet} contains the distinct elements of the multiset "with duplicates collapsed", and + * {@link #entrySet} is similar but contains {@link Entry Multiset.Entry} instances, each providing + * both a distinct element and the count of that element. + * + *

In addition to these required methods, implementations of {@code Multiset} are expected to + * provide two {@code static} creation methods: {@code create()}, returning an empty multiset, and + * {@code create(Iterable)}, returning a multiset containing the given initial + * elements. This is simply a refinement of {@code Collection}'s constructor recommendations, + * reflecting the new developments of Java 5. + * + *

As with other collection types, the modification operations are optional, and should throw + * {@link UnsupportedOperationException} when they are not implemented. Most implementations should + * support either all add operations or none of them, all removal operations or none of them, and if + * and only if all of these are supported, the {@code setCount} methods as well. + * + *

A multiset uses {@link Object#equals} to determine whether two instances should be considered + * "the same," unless specified otherwise by the implementation. + * + *

Common implementations include {@link ImmutableMultiset}, {@link HashMultiset}, and {@link + * ConcurrentHashMultiset}. + * + *

If your values may be zero, negative, or outside the range of an int, you may wish to use + * {@link com.google.common.util.concurrent.AtomicLongMap} instead. Note, however, that unlike + * {@code Multiset}, {@code AtomicLongMap} does not automatically remove zeros. + * + *

See the Guava User Guide article on {@code + * Multiset}. + * + * @author Kevin Bourrillion + * @since 2.0 + */ +@GwtCompatible +public interface Multiset extends Collection { + // Query Operations + + /** + * Returns the total number of all occurrences of all elements in this multiset. + * + *

Note: this method does not return the number of distinct elements in the + * multiset, which is given by {@code entrySet().size()}. + */ + @Override + int size(); + + /** + * Returns the number of occurrences of an element in this multiset (the count of the + * element). Note that for an {@link Object#equals}-based multiset, this gives the same result as + * {@link Collections#frequency} (which would presumably perform more poorly). + * + *

Note: the utility method {@link Iterables#frequency} generalizes this operation; it + * correctly delegates to this method when dealing with a multiset, but it can also accept any + * other iterable type. + * + * @param element the element to count occurrences of + * @return the number of occurrences of the element in this multiset; possibly zero but never + * negative + */ + int count(Object element); + + // Bulk Operations + + /** + * Adds a number of occurrences of an element to this multiset. Note that if {@code occurrences == + * 1}, this method has the identical effect to {@link #add(Object)}. This method is functionally + * equivalent (except in the case of overflow) to the call {@code + * addAll(Collections.nCopies(element, occurrences))}, which would presumably perform much more + * poorly. + * + * @param element the element to add occurrences of; may be null only if explicitly allowed by the + * implementation + * @param occurrences the number of occurrences of the element to add. May be zero, in which case + * no change will be made. + * @return the count of the element before the operation; possibly zero + * @throws IllegalArgumentException if {@code occurrences} is negative, or if this operation would + * result in more than {@link Integer#MAX_VALUE} occurrences of the element + * @throws NullPointerException if {@code element} is null and this implementation does not permit + * null elements. Note that if {@code occurrences} is zero, the implementation may opt to + * return normally. + */ + + int add(E element, int occurrences); + + /** + * Adds a single occurrence of the specified element to this multiset. + * + *

This method refines {@link Collection#add}, which only ensures the presence of the + * element, to further specify that a successful call must always increment the count of the + * element, and the overall size of the collection, by one. + * + *

To both add the element and obtain the previous count of that element, use {@link + * #add(Object, int) add}{@code (element, 1)} instead. + * + * @param element the element to add one occurrence of; may be null only if explicitly allowed by + * the implementation + * @return {@code true} always, since this call is required to modify the multiset, unlike other + * {@link Collection} types + * @throws NullPointerException if {@code element} is null and this implementation does not permit + * null elements + * @throws IllegalArgumentException if {@link Integer#MAX_VALUE} occurrences of {@code element} + * are already contained in this multiset + */ + + @Override + boolean add(E element); + + /** + * Removes a number of occurrences of the specified element from this multiset. If the multiset + * contains fewer than this number of occurrences to begin with, all occurrences will be removed. + * Note that if {@code occurrences == 1}, this is functionally equivalent to the call {@code + * remove(element)}. + * + * @param element the element to conditionally remove occurrences of + * @param occurrences the number of occurrences of the element to remove. May be zero, in which + * case no change will be made. + * @return the count of the element before the operation; possibly zero + * @throws IllegalArgumentException if {@code occurrences} is negative + */ + + int remove(Object element, int occurrences); + + /** + * Removes a single occurrence of the specified element from this multiset, if present. + * + *

This method refines {@link Collection#remove} to further specify that it may not + * throw an exception in response to {@code element} being null or of the wrong type. + * + *

To both remove the element and obtain the previous count of that element, use {@link + * #remove(Object, int) remove}{@code (element, 1)} instead. + * + * @param element the element to remove one occurrence of + * @return {@code true} if an occurrence was found and removed + */ + + @Override + boolean remove(Object element); + + /** + * Adds or removes the necessary occurrences of an element such that the element attains the + * desired count. + * + * @param element the element to add or remove occurrences of; may be null only if explicitly + * allowed by the implementation + * @param count the desired count of the element in this multiset + * @return the count of the element before the operation; possibly zero + * @throws IllegalArgumentException if {@code count} is negative + * @throws NullPointerException if {@code element} is null and this implementation does not permit + * null elements. Note that if {@code count} is zero, the implementor may optionally return + * zero instead. + */ + + int setCount(E element, int count); + + /** + * Conditionally sets the count of an element to a new value, as described in {@link + * #setCount(Object, int)}, provided that the element has the expected current count. If the + * current count is not {@code oldCount}, no change is made. + * + * @param element the element to conditionally set the count of; may be null only if explicitly + * allowed by the implementation + * @param oldCount the expected present count of the element in this multiset + * @param newCount the desired count of the element in this multiset + * @return {@code true} if the condition for modification was met. This implies that the multiset + * was indeed modified, unless {@code oldCount == newCount}. + * @throws IllegalArgumentException if {@code oldCount} or {@code newCount} is negative + * @throws NullPointerException if {@code element} is null and the implementation does not permit + * null elements. Note that if {@code oldCount} and {@code newCount} are both zero, the + * implementor may optionally return {@code true} instead. + */ + + boolean setCount(E element, int oldCount, int newCount); + + // Views + + /** + * Returns the set of distinct elements contained in this multiset. The element set is backed by + * the same data as the multiset, so any change to either is immediately reflected in the other. + * The order of the elements in the element set is unspecified. + * + *

If the element set supports any removal operations, these necessarily cause all + * occurrences of the removed element(s) to be removed from the multiset. Implementations are not + * expected to support the add operations, although this is possible. + * + *

A common use for the element set is to find the number of distinct elements in the multiset: + * {@code elementSet().size()}. + * + * @return a view of the set of distinct elements in this multiset + */ + Set elementSet(); + + /** + * Returns a view of the contents of this multiset, grouped into {@code Multiset.Entry} instances, + * each providing an element of the multiset and the count of that element. This set contains + * exactly one entry for each distinct element in the multiset (thus it always has the same size + * as the {@link #elementSet}). The order of the elements in the element set is unspecified. + * + *

The entry set is backed by the same data as the multiset, so any change to either is + * immediately reflected in the other. However, multiset changes may or may not be reflected in + * any {@code Entry} instances already retrieved from the entry set (this is + * implementation-dependent). Furthermore, implementations are not required to support + * modifications to the entry set at all, and the {@code Entry} instances themselves don't even + * have methods for modification. See the specific implementation class for more details on how + * its entry set handles modifications. + * + * @return a set of entries representing the data of this multiset + */ + Set> entrySet(); + + /** + * An unmodifiable element-count pair for a multiset. The {@link Multiset#entrySet} method returns + * a view of the multiset whose elements are of this class. A multiset implementation may return + * Entry instances that are either live "read-through" views to the Multiset, or immutable + * snapshots. Note that this type is unrelated to the similarly-named type {@code Map.Entry}. + * + * @since 2.0 + */ + interface Entry { + + /** + * Returns the multiset element corresponding to this entry. Multiple calls to this method + * always return the same instance. + * + * @return the element corresponding to this entry + */ + E getElement(); + + /** + * Returns the count of the associated element in the underlying multiset. This count may either + * be an unchanging snapshot of the count at the time the entry was retrieved, or a live view of + * the current count of the element in the multiset, depending on the implementation. Note that + * in the former case, this method can never return zero, while in the latter, it will return + * zero if all occurrences of the element were since removed from the multiset. + * + * @return the count of the element; never negative + */ + int getCount(); + + /** + * {@inheritDoc} + * + *

Returns {@code true} if the given object is also a multiset entry and the two entries + * represent the same element and count. That is, two entries {@code a} and {@code b} are equal + * if: + * + *

{@code
+     * Objects.equal(a.getElement(), b.getElement())
+     *     && a.getCount() == b.getCount()
+     * }
+ */ + @Override + // TODO(kevinb): check this wrt TreeMultiset? + boolean equals(Object o); + + /** + * {@inheritDoc} + * + *

The hash code of a multiset entry for element {@code element} and count {@code count} is + * defined as: + * + *

{@code
+     * ((element == null) ? 0 : element.hashCode()) ^ count
+     * }
+ */ + @Override + int hashCode(); + + /** + * Returns the canonical string representation of this entry, defined as follows. If the count + * for this entry is one, this is simply the string representation of the corresponding element. + * Otherwise, it is the string representation of the element, followed by the three characters + * {@code " x "} (space, letter x, space), followed by the count. + */ + @Override + String toString(); + } + + /** + * Runs the specified action for each distinct element in this multiset, and the number of + * occurrences of that element. For some {@code Multiset} implementations, this may be more + * efficient than iterating over the {@link #entrySet()} either explicitly or with {@code + * entrySet().forEach(action)}. + * + * @since 21.0 + */ + @Beta + default void forEachEntry(ObjIntConsumer action) { + checkNotNull(action); + entrySet().forEach(entry -> action.accept(entry.getElement(), entry.getCount())); + } + + // Comparison and hashing + + /** + * Compares the specified object with this multiset for equality. Returns {@code true} if the + * given object is also a multiset and contains equal elements with equal counts, regardless of + * order. + */ + @Override + // TODO(kevinb): caveats about equivalence-relation? + boolean equals(Object object); + + /** + * Returns the hash code for this multiset. This is defined as the sum of + * + *
{@code
+   * ((element == null) ? 0 : element.hashCode()) ^ count(element)
+   * }
+ * + *

over all distinct elements in the multiset. It follows that a multiset and its entry set + * always have the same hash code. + */ + @Override + int hashCode(); + + /** + * {@inheritDoc} + * + *

It is recommended, though not mandatory, that this method return the result of invoking + * {@link #toString} on the {@link #entrySet}, yielding a result such as {@code [a x 3, c, d x 2, + * e]}. + */ + @Override + String toString(); + + // Refined Collection Methods + + /** + * {@inheritDoc} + * + *

Elements that occur multiple times in the multiset will appear multiple times in this + * iterator, though not necessarily sequentially. + */ + @Override + Iterator iterator(); + + /** + * Determines whether this multiset contains the specified element. + * + *

This method refines {@link Collection#contains} to further specify that it may not + * throw an exception in response to {@code element} being null or of the wrong type. + * + * @param element the element to check for + * @return {@code true} if this multiset contains at least one occurrence of the element + */ + @Override + boolean contains(Object element); + + /** + * Returns {@code true} if this multiset contains at least one occurrence of each element in the + * specified collection. + * + *

This method refines {@link Collection#containsAll} to further specify that it may not + * throw an exception in response to any of {@code elements} being null or of the wrong type. + * + *

Note: this method does not take into account the occurrence count of an element in + * the two collections; it may still return {@code true} even if {@code elements} contains several + * occurrences of an element and this multiset contains only one. This is no different than any + * other collection type like {@link List}, but it may be unexpected to the user of a multiset. + * + * @param elements the collection of elements to be checked for containment in this multiset + * @return {@code true} if this multiset contains at least one occurrence of each element + * contained in {@code elements} + * @throws NullPointerException if {@code elements} is null + */ + @Override + boolean containsAll(Collection elements); + + /** + * {@inheritDoc} + * + *

Note: This method ignores how often any element might appear in {@code c}, and only + * cares whether or not an element appears at all. If you wish to remove one occurrence in this + * multiset for every occurrence in {@code c}, see {@link Multisets#removeOccurrences(Multiset, + * Multiset)}. + * + *

This method refines {@link Collection#removeAll} to further specify that it may not + * throw an exception in response to any of {@code elements} being null or of the wrong type. + */ + + @Override + boolean removeAll(Collection c); + + /** + * {@inheritDoc} + * + *

Note: This method ignores how often any element might appear in {@code c}, and only + * cares whether or not an element appears at all. If you wish to remove one occurrence in this + * multiset for every occurrence in {@code c}, see {@link Multisets#retainOccurrences(Multiset, + * Multiset)}. + * + *

This method refines {@link Collection#retainAll} to further specify that it may not + * throw an exception in response to any of {@code elements} being null or of the wrong type. + * + * @see Multisets#retainOccurrences(Multiset, Multiset) + */ + + @Override + boolean retainAll(Collection c); + + /** + * {@inheritDoc} + * + *

Elements that occur multiple times in the multiset will be passed to the {@code Consumer} + * correspondingly many times, though not necessarily sequentially. + */ + @Override + default void forEach(Consumer action) { + checkNotNull(action); + entrySet() + .forEach( + entry -> { + E elem = entry.getElement(); + int count = entry.getCount(); + for (int i = 0; i < count; i++) { + action.accept(elem); + } + }); + } + + @Override + default Spliterator spliterator() { + return Multisets.spliteratorImpl(this); + } +} diff --git a/src/main/java/com/google/common/collect/Multisets.java b/src/main/java/com/google/common/collect/Multisets.java new file mode 100644 index 0000000..0a91c79 --- /dev/null +++ b/src/main/java/com/google/common/collect/Multisets.java @@ -0,0 +1,1178 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.collect.CollectPreconditions.checkNonnegative; +import static com.google.common.collect.CollectPreconditions.checkRemove; + +import com.google.common.annotations.Beta; +import com.google.common.annotations.GwtCompatible; +import com.google.common.base.Objects; +import com.google.common.base.Predicate; +import com.google.common.base.Predicates; +import com.google.common.collect.Multiset.Entry; +import com.google.common.math.IntMath; +import com.google.common.primitives.Ints; + +import java.io.Serializable; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.Iterator; +import java.util.NoSuchElementException; +import java.util.Set; +import java.util.Spliterator; +import java.util.stream.Collector; + + + +/** + * Provides static utility methods for creating and working with {@link Multiset} instances. + * + *

See the Guava User Guide article on {@code + * Multisets}. + * + * @author Kevin Bourrillion + * @author Mike Bostock + * @author Louis Wasserman + * @since 2.0 + */ +@GwtCompatible +public final class Multisets { + private Multisets() {} + + /** + * Returns a {@code Collector} that accumulates elements into a multiset created via the specified + * {@code Supplier}, whose elements are the result of applying {@code elementFunction} to the + * inputs, with counts equal to the result of applying {@code countFunction} to the inputs. + * Elements are added in encounter order. + * + *

If the mapped elements contain duplicates (according to {@link Object#equals}), the element + * will be added more than once, with the count summed over all appearances of the element. + * + *

Note that {@code stream.collect(toMultiset(function, e -> 1, supplier))} is equivalent to + * {@code stream.map(function).collect(Collectors.toCollection(supplier))}. + * + * @since 22.0 + */ + public static > Collector toMultiset( + java.util.function.Function elementFunction, + java.util.function.ToIntFunction countFunction, + java.util.function.Supplier multisetSupplier) { + checkNotNull(elementFunction); + checkNotNull(countFunction); + checkNotNull(multisetSupplier); + return Collector.of( + multisetSupplier, + (ms, t) -> ms.add(elementFunction.apply(t), countFunction.applyAsInt(t)), + (ms1, ms2) -> { + ms1.addAll(ms2); + return ms1; + }); + } + + /** + * Returns an unmodifiable view of the specified multiset. Query operations on the returned + * multiset "read through" to the specified multiset, and attempts to modify the returned multiset + * result in an {@link UnsupportedOperationException}. + * + *

The returned multiset will be serializable if the specified multiset is serializable. + * + * @param multiset the multiset for which an unmodifiable view is to be generated + * @return an unmodifiable view of the multiset + */ + public static Multiset unmodifiableMultiset(Multiset multiset) { + if (multiset instanceof UnmodifiableMultiset || multiset instanceof ImmutableMultiset) { + @SuppressWarnings("unchecked") // Since it's unmodifiable, the covariant cast is safe + Multiset result = (Multiset) multiset; + return result; + } + return new UnmodifiableMultiset(checkNotNull(multiset)); + } + + /** + * Simply returns its argument. + * + * @deprecated no need to use this + * @since 10.0 + */ + @Deprecated + public static Multiset unmodifiableMultiset(ImmutableMultiset multiset) { + return checkNotNull(multiset); + } + + static class UnmodifiableMultiset extends ForwardingMultiset implements Serializable { + final Multiset delegate; + + UnmodifiableMultiset(Multiset delegate) { + this.delegate = delegate; + } + + @SuppressWarnings("unchecked") + @Override + protected Multiset delegate() { + // This is safe because all non-covariant methods are overridden + return (Multiset) delegate; + } + + transient Set elementSet; + + Set createElementSet() { + return Collections.unmodifiableSet(delegate.elementSet()); + } + + @Override + public Set elementSet() { + Set es = elementSet; + return (es == null) ? elementSet = createElementSet() : es; + } + + transient Set> entrySet; + + @SuppressWarnings("unchecked") + @Override + public Set> entrySet() { + Set> es = entrySet; + return (es == null) + // Safe because the returned set is made unmodifiable and Entry + // itself is readonly + ? entrySet = (Set) Collections.unmodifiableSet(delegate.entrySet()) + : es; + } + + @Override + public Iterator iterator() { + return Iterators.unmodifiableIterator(delegate.iterator()); + } + + @Override + public boolean add(E element) { + throw new UnsupportedOperationException(); + } + + @Override + public int add(E element, int occurences) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean addAll(Collection elementsToAdd) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean remove(Object element) { + throw new UnsupportedOperationException(); + } + + @Override + public int remove(Object element, int occurrences) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean removeAll(Collection elementsToRemove) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean retainAll(Collection elementsToRetain) { + throw new UnsupportedOperationException(); + } + + @Override + public void clear() { + throw new UnsupportedOperationException(); + } + + @Override + public int setCount(E element, int count) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean setCount(E element, int oldCount, int newCount) { + throw new UnsupportedOperationException(); + } + + private static final long serialVersionUID = 0; + } + + /** + * Returns an unmodifiable view of the specified sorted multiset. Query operations on the returned + * multiset "read through" to the specified multiset, and attempts to modify the returned multiset + * result in an {@link UnsupportedOperationException}. + * + *

The returned multiset will be serializable if the specified multiset is serializable. + * + * @param sortedMultiset the sorted multiset for which an unmodifiable view is to be generated + * @return an unmodifiable view of the multiset + * @since 11.0 + */ + @Beta + public static SortedMultiset unmodifiableSortedMultiset(SortedMultiset sortedMultiset) { + // it's in its own file so it can be emulated for GWT + return new UnmodifiableSortedMultiset(checkNotNull(sortedMultiset)); + } + + /** + * Returns an immutable multiset entry with the specified element and count. The entry will be + * serializable if {@code e} is. + * + * @param e the element to be associated with the returned entry + * @param n the count to be associated with the returned entry + * @throws IllegalArgumentException if {@code n} is negative + */ + public static Multiset.Entry immutableEntry(E e, int n) { + return new ImmutableEntry(e, n); + } + + static class ImmutableEntry extends AbstractEntry implements Serializable { + private final E element; + private final int count; + + ImmutableEntry(E element, int count) { + this.element = element; + this.count = count; + checkNonnegative(count, "count"); + } + + @Override + public final E getElement() { + return element; + } + + @Override + public final int getCount() { + return count; + } + + public ImmutableEntry nextInBucket() { + return null; + } + + private static final long serialVersionUID = 0; + } + + /** + * Returns a view of the elements of {@code unfiltered} that satisfy a predicate. The returned + * multiset is a live view of {@code unfiltered}; changes to one affect the other. + * + *

The resulting multiset's iterators, and those of its {@code entrySet()} and {@code + * elementSet()}, do not support {@code remove()}. However, all other multiset methods supported + * by {@code unfiltered} are supported by the returned multiset. When given an element that + * doesn't satisfy the predicate, the multiset's {@code add()} and {@code addAll()} methods throw + * an {@link IllegalArgumentException}. When methods such as {@code removeAll()} and {@code + * clear()} are called on the filtered multiset, only elements that satisfy the filter will be + * removed from the underlying multiset. + * + *

The returned multiset isn't threadsafe or serializable, even if {@code unfiltered} is. + * + *

Many of the filtered multiset's methods, such as {@code size()}, iterate across every + * element in the underlying multiset and determine which elements satisfy the filter. When a live + * view is not needed, it may be faster to copy the returned multiset and use the copy. + * + *

Warning: {@code predicate} must be consistent with equals, as documented at + * {@link Predicate#apply}. Do not provide a predicate such as {@code + * Predicates.instanceOf(ArrayList.class)}, which is inconsistent with equals. (See {@link + * Iterables#filter(Iterable, Class)} for related functionality.) + * + * @since 14.0 + */ + @Beta + public static Multiset filter(Multiset unfiltered, Predicate predicate) { + if (unfiltered instanceof FilteredMultiset) { + // Support clear(), removeAll(), and retainAll() when filtering a filtered + // collection. + FilteredMultiset filtered = (FilteredMultiset) unfiltered; + Predicate combinedPredicate = Predicates.and(filtered.predicate, predicate); + return new FilteredMultiset(filtered.unfiltered, combinedPredicate); + } + return new FilteredMultiset(unfiltered, predicate); + } + + private static final class FilteredMultiset extends ViewMultiset { + final Multiset unfiltered; + final Predicate predicate; + + FilteredMultiset(Multiset unfiltered, Predicate predicate) { + this.unfiltered = checkNotNull(unfiltered); + this.predicate = checkNotNull(predicate); + } + + @Override + public UnmodifiableIterator iterator() { + return Iterators.filter(unfiltered.iterator(), predicate); + } + + @Override + Set createElementSet() { + return Sets.filter(unfiltered.elementSet(), predicate); + } + + @Override + Iterator elementIterator() { + throw new AssertionError("should never be called"); + } + + @Override + Set> createEntrySet() { + return Sets.filter( + unfiltered.entrySet(), + new Predicate>() { + @Override + public boolean apply(Entry entry) { + return predicate.apply(entry.getElement()); + } + }); + } + + @Override + Iterator> entryIterator() { + throw new AssertionError("should never be called"); + } + + @Override + public int count(Object element) { + int count = unfiltered.count(element); + if (count > 0) { + @SuppressWarnings("unchecked") // element is equal to an E + E e = (E) element; + return predicate.apply(e) ? count : 0; + } + return 0; + } + + @Override + public int add(E element, int occurrences) { + checkArgument( + predicate.apply(element), "Element %s does not match predicate %s", element, predicate); + return unfiltered.add(element, occurrences); + } + + @Override + public int remove(Object element, int occurrences) { + checkNonnegative(occurrences, "occurrences"); + if (occurrences == 0) { + return count(element); + } else { + return contains(element) ? unfiltered.remove(element, occurrences) : 0; + } + } + } + + /** + * Returns the expected number of distinct elements given the specified elements. The number of + * distinct elements is only computed if {@code elements} is an instance of {@code Multiset}; + * otherwise the default value of 11 is returned. + */ + static int inferDistinctElements(Iterable elements) { + if (elements instanceof Multiset) { + return ((Multiset) elements).elementSet().size(); + } + return 11; // initial capacity will be rounded up to 16 + } + + /** + * Returns an unmodifiable view of the union of two multisets. In the returned multiset, the count + * of each element is the maximum of its counts in the two backing multisets. The iteration + * order of the returned multiset matches that of the element set of {@code multiset1} followed by + * the members of the element set of {@code multiset2} that are not contained in {@code + * multiset1}, with repeated occurrences of the same element appearing consecutively. + * + *

Results are undefined if {@code multiset1} and {@code multiset2} are based on different + * equivalence relations (as {@code HashMultiset} and {@code TreeMultiset} are). + * + * @since 14.0 + */ + @Beta + public static Multiset union( + final Multiset multiset1, final Multiset multiset2) { + checkNotNull(multiset1); + checkNotNull(multiset2); + + return new ViewMultiset() { + @Override + public boolean contains(Object element) { + return multiset1.contains(element) || multiset2.contains(element); + } + + @Override + public boolean isEmpty() { + return multiset1.isEmpty() && multiset2.isEmpty(); + } + + @Override + public int count(Object element) { + return Math.max(multiset1.count(element), multiset2.count(element)); + } + + @Override + Set createElementSet() { + return Sets.union(multiset1.elementSet(), multiset2.elementSet()); + } + + @Override + Iterator elementIterator() { + throw new AssertionError("should never be called"); + } + + @Override + Iterator> entryIterator() { + final Iterator> iterator1 = multiset1.entrySet().iterator(); + final Iterator> iterator2 = multiset2.entrySet().iterator(); + // TODO(lowasser): consider making the entries live views + return new AbstractIterator>() { + @Override + protected Entry computeNext() { + if (iterator1.hasNext()) { + Entry entry1 = iterator1.next(); + E element = entry1.getElement(); + int count = Math.max(entry1.getCount(), multiset2.count(element)); + return immutableEntry(element, count); + } + while (iterator2.hasNext()) { + Entry entry2 = iterator2.next(); + E element = entry2.getElement(); + if (!multiset1.contains(element)) { + return immutableEntry(element, entry2.getCount()); + } + } + return endOfData(); + } + }; + } + }; + } + + /** + * Returns an unmodifiable view of the intersection of two multisets. In the returned multiset, + * the count of each element is the minimum of its counts in the two backing multisets, + * with elements that would have a count of 0 not included. The iteration order of the returned + * multiset matches that of the element set of {@code multiset1}, with repeated occurrences of the + * same element appearing consecutively. + * + *

Results are undefined if {@code multiset1} and {@code multiset2} are based on different + * equivalence relations (as {@code HashMultiset} and {@code TreeMultiset} are). + * + * @since 2.0 + */ + public static Multiset intersection( + final Multiset multiset1, final Multiset multiset2) { + checkNotNull(multiset1); + checkNotNull(multiset2); + + return new ViewMultiset() { + @Override + public int count(Object element) { + int count1 = multiset1.count(element); + return (count1 == 0) ? 0 : Math.min(count1, multiset2.count(element)); + } + + @Override + Set createElementSet() { + return Sets.intersection(multiset1.elementSet(), multiset2.elementSet()); + } + + @Override + Iterator elementIterator() { + throw new AssertionError("should never be called"); + } + + @Override + Iterator> entryIterator() { + final Iterator> iterator1 = multiset1.entrySet().iterator(); + // TODO(lowasser): consider making the entries live views + return new AbstractIterator>() { + @Override + protected Entry computeNext() { + while (iterator1.hasNext()) { + Entry entry1 = iterator1.next(); + E element = entry1.getElement(); + int count = Math.min(entry1.getCount(), multiset2.count(element)); + if (count > 0) { + return immutableEntry(element, count); + } + } + return endOfData(); + } + }; + } + }; + } + + /** + * Returns an unmodifiable view of the sum of two multisets. In the returned multiset, the count + * of each element is the sum of its counts in the two backing multisets. The iteration + * order of the returned multiset matches that of the element set of {@code multiset1} followed by + * the members of the element set of {@code multiset2} that are not contained in {@code + * multiset1}, with repeated occurrences of the same element appearing consecutively. + * + *

Results are undefined if {@code multiset1} and {@code multiset2} are based on different + * equivalence relations (as {@code HashMultiset} and {@code TreeMultiset} are). + * + * @since 14.0 + */ + @Beta + public static Multiset sum( + final Multiset multiset1, final Multiset multiset2) { + checkNotNull(multiset1); + checkNotNull(multiset2); + + // TODO(lowasser): consider making the entries live views + return new ViewMultiset() { + @Override + public boolean contains(Object element) { + return multiset1.contains(element) || multiset2.contains(element); + } + + @Override + public boolean isEmpty() { + return multiset1.isEmpty() && multiset2.isEmpty(); + } + + @Override + public int size() { + return IntMath.saturatedAdd(multiset1.size(), multiset2.size()); + } + + @Override + public int count(Object element) { + return multiset1.count(element) + multiset2.count(element); + } + + @Override + Set createElementSet() { + return Sets.union(multiset1.elementSet(), multiset2.elementSet()); + } + + @Override + Iterator elementIterator() { + throw new AssertionError("should never be called"); + } + + @Override + Iterator> entryIterator() { + final Iterator> iterator1 = multiset1.entrySet().iterator(); + final Iterator> iterator2 = multiset2.entrySet().iterator(); + return new AbstractIterator>() { + @Override + protected Entry computeNext() { + if (iterator1.hasNext()) { + Entry entry1 = iterator1.next(); + E element = entry1.getElement(); + int count = entry1.getCount() + multiset2.count(element); + return immutableEntry(element, count); + } + while (iterator2.hasNext()) { + Entry entry2 = iterator2.next(); + E element = entry2.getElement(); + if (!multiset1.contains(element)) { + return immutableEntry(element, entry2.getCount()); + } + } + return endOfData(); + } + }; + } + }; + } + + /** + * Returns an unmodifiable view of the difference of two multisets. In the returned multiset, the + * count of each element is the result of the zero-truncated subtraction of its count in + * the second multiset from its count in the first multiset, with elements that would have a count + * of 0 not included. The iteration order of the returned multiset matches that of the element set + * of {@code multiset1}, with repeated occurrences of the same element appearing consecutively. + * + *

Results are undefined if {@code multiset1} and {@code multiset2} are based on different + * equivalence relations (as {@code HashMultiset} and {@code TreeMultiset} are). + * + * @since 14.0 + */ + @Beta + public static Multiset difference( + final Multiset multiset1, final Multiset multiset2) { + checkNotNull(multiset1); + checkNotNull(multiset2); + + // TODO(lowasser): consider making the entries live views + return new ViewMultiset() { + @Override + public int count(Object element) { + int count1 = multiset1.count(element); + return (count1 == 0) ? 0 : Math.max(0, count1 - multiset2.count(element)); + } + + @Override + public void clear() { + throw new UnsupportedOperationException(); + } + + @Override + Iterator elementIterator() { + final Iterator> iterator1 = multiset1.entrySet().iterator(); + return new AbstractIterator() { + @Override + protected E computeNext() { + while (iterator1.hasNext()) { + Entry entry1 = iterator1.next(); + E element = entry1.getElement(); + if (entry1.getCount() > multiset2.count(element)) { + return element; + } + } + return endOfData(); + } + }; + } + + @Override + Iterator> entryIterator() { + final Iterator> iterator1 = multiset1.entrySet().iterator(); + return new AbstractIterator>() { + @Override + protected Entry computeNext() { + while (iterator1.hasNext()) { + Entry entry1 = iterator1.next(); + E element = entry1.getElement(); + int count = entry1.getCount() - multiset2.count(element); + if (count > 0) { + return immutableEntry(element, count); + } + } + return endOfData(); + } + }; + } + + @Override + int distinctElements() { + return Iterators.size(entryIterator()); + } + }; + } + + /** + * Returns {@code true} if {@code subMultiset.count(o) <= superMultiset.count(o)} for all {@code + * o}. + * + * @since 10.0 + */ + + public static boolean containsOccurrences(Multiset superMultiset, Multiset subMultiset) { + checkNotNull(superMultiset); + checkNotNull(subMultiset); + for (Entry entry : subMultiset.entrySet()) { + int superCount = superMultiset.count(entry.getElement()); + if (superCount < entry.getCount()) { + return false; + } + } + return true; + } + + /** + * Modifies {@code multisetToModify} so that its count for an element {@code e} is at most {@code + * multisetToRetain.count(e)}. + * + *

To be precise, {@code multisetToModify.count(e)} is set to {@code + * Math.min(multisetToModify.count(e), multisetToRetain.count(e))}. This is similar to {@link + * #intersection(Multiset, Multiset) intersection} {@code (multisetToModify, multisetToRetain)}, + * but mutates {@code multisetToModify} instead of returning a view. + * + *

In contrast, {@code multisetToModify.retainAll(multisetToRetain)} keeps all occurrences of + * elements that appear at all in {@code multisetToRetain}, and deletes all occurrences of all + * other elements. + * + * @return {@code true} if {@code multisetToModify} was changed as a result of this operation + * @since 10.0 + */ + + public static boolean retainOccurrences( + Multiset multisetToModify, Multiset multisetToRetain) { + return retainOccurrencesImpl(multisetToModify, multisetToRetain); + } + + /** Delegate implementation which cares about the element type. */ + private static boolean retainOccurrencesImpl( + Multiset multisetToModify, Multiset occurrencesToRetain) { + checkNotNull(multisetToModify); + checkNotNull(occurrencesToRetain); + // Avoiding ConcurrentModificationExceptions is tricky. + Iterator> entryIterator = multisetToModify.entrySet().iterator(); + boolean changed = false; + while (entryIterator.hasNext()) { + Entry entry = entryIterator.next(); + int retainCount = occurrencesToRetain.count(entry.getElement()); + if (retainCount == 0) { + entryIterator.remove(); + changed = true; + } else if (retainCount < entry.getCount()) { + multisetToModify.setCount(entry.getElement(), retainCount); + changed = true; + } + } + return changed; + } + + /** + * For each occurrence of an element {@code e} in {@code occurrencesToRemove}, removes one + * occurrence of {@code e} in {@code multisetToModify}. + * + *

Equivalently, this method modifies {@code multisetToModify} so that {@code + * multisetToModify.count(e)} is set to {@code Math.max(0, multisetToModify.count(e) - + * Iterables.frequency(occurrencesToRemove, e))}. + * + *

This is not the same as {@code multisetToModify.} {@link Multiset#removeAll + * removeAll}{@code (occurrencesToRemove)}, which removes all occurrences of elements that appear + * in {@code occurrencesToRemove}. However, this operation is equivalent to, albeit + * sometimes more efficient than, the following: + * + *

{@code
+   * for (E e : occurrencesToRemove) {
+   *   multisetToModify.remove(e);
+   * }
+   * }
+ * + * @return {@code true} if {@code multisetToModify} was changed as a result of this operation + * @since 18.0 (present in 10.0 with a requirement that the second parameter be a {@code + * Multiset}) + */ + + public static boolean removeOccurrences( + Multiset multisetToModify, Iterable occurrencesToRemove) { + if (occurrencesToRemove instanceof Multiset) { + return removeOccurrences(multisetToModify, (Multiset) occurrencesToRemove); + } else { + checkNotNull(multisetToModify); + checkNotNull(occurrencesToRemove); + boolean changed = false; + for (Object o : occurrencesToRemove) { + changed |= multisetToModify.remove(o); + } + return changed; + } + } + + /** + * For each occurrence of an element {@code e} in {@code occurrencesToRemove}, removes one + * occurrence of {@code e} in {@code multisetToModify}. + * + *

Equivalently, this method modifies {@code multisetToModify} so that {@code + * multisetToModify.count(e)} is set to {@code Math.max(0, multisetToModify.count(e) - + * occurrencesToRemove.count(e))}. + * + *

This is not the same as {@code multisetToModify.} {@link Multiset#removeAll + * removeAll}{@code (occurrencesToRemove)}, which removes all occurrences of elements that appear + * in {@code occurrencesToRemove}. However, this operation is equivalent to, albeit + * sometimes more efficient than, the following: + * + *

{@code
+   * for (E e : occurrencesToRemove) {
+   *   multisetToModify.remove(e);
+   * }
+   * }
+ * + * @return {@code true} if {@code multisetToModify} was changed as a result of this operation + * @since 10.0 (missing in 18.0 when only the overload taking an {@code Iterable} was present) + */ + + public static boolean removeOccurrences( + Multiset multisetToModify, Multiset occurrencesToRemove) { + checkNotNull(multisetToModify); + checkNotNull(occurrencesToRemove); + + boolean changed = false; + Iterator> entryIterator = multisetToModify.entrySet().iterator(); + while (entryIterator.hasNext()) { + Entry entry = entryIterator.next(); + int removeCount = occurrencesToRemove.count(entry.getElement()); + if (removeCount >= entry.getCount()) { + entryIterator.remove(); + changed = true; + } else if (removeCount > 0) { + multisetToModify.remove(entry.getElement(), removeCount); + changed = true; + } + } + return changed; + } + + /** + * Implementation of the {@code equals}, {@code hashCode}, and {@code toString} methods of {@link + * Multiset.Entry}. + */ + abstract static class AbstractEntry implements Multiset.Entry { + /** + * Indicates whether an object equals this entry, following the behavior specified in {@link + * Multiset.Entry#equals}. + */ + @Override + public boolean equals(Object object) { + if (object instanceof Multiset.Entry) { + Multiset.Entry that = (Multiset.Entry) object; + return this.getCount() == that.getCount() + && Objects.equal(this.getElement(), that.getElement()); + } + return false; + } + + /** + * Return this entry's hash code, following the behavior specified in {@link + * Multiset.Entry#hashCode}. + */ + @Override + public int hashCode() { + E e = getElement(); + return ((e == null) ? 0 : e.hashCode()) ^ getCount(); + } + + /** + * Returns a string representation of this multiset entry. The string representation consists of + * the associated element if the associated count is one, and otherwise the associated element + * followed by the characters " x " (space, x and space) followed by the count. Elements and + * counts are converted to strings as by {@code String.valueOf}. + */ + @Override + public String toString() { + String text = String.valueOf(getElement()); + int n = getCount(); + return (n == 1) ? text : (text + " x " + n); + } + } + + /** An implementation of {@link Multiset#equals}. */ + static boolean equalsImpl(Multiset multiset, Object object) { + if (object == multiset) { + return true; + } + if (object instanceof Multiset) { + Multiset that = (Multiset) object; + /* + * We can't simply check whether the entry sets are equal, since that + * approach fails when a TreeMultiset has a comparator that returns 0 + * when passed unequal elements. + */ + + if (multiset.size() != that.size() || multiset.entrySet().size() != that.entrySet().size()) { + return false; + } + for (Entry entry : that.entrySet()) { + if (multiset.count(entry.getElement()) != entry.getCount()) { + return false; + } + } + return true; + } + return false; + } + + /** An implementation of {@link Multiset#addAll}. */ + static boolean addAllImpl(Multiset self, Collection elements) { + checkNotNull(self); + checkNotNull(elements); + if (elements instanceof Multiset) { + return addAllImpl(self, cast(elements)); + } else if (elements.isEmpty()) { + return false; + } else { + return Iterators.addAll(self, elements.iterator()); + } + } + + /** A specialization of {@code addAllImpl} for when {@code elements} is itself a Multiset. */ + private static boolean addAllImpl(Multiset self, Multiset elements) { + if (elements.isEmpty()) { + return false; + } + elements.forEachEntry(self::add); + return true; + } + + /** An implementation of {@link Multiset#removeAll}. */ + static boolean removeAllImpl(Multiset self, Collection elementsToRemove) { + Collection collection = + (elementsToRemove instanceof Multiset) + ? ((Multiset) elementsToRemove).elementSet() + : elementsToRemove; + + return self.elementSet().removeAll(collection); + } + + /** An implementation of {@link Multiset#retainAll}. */ + static boolean retainAllImpl(Multiset self, Collection elementsToRetain) { + checkNotNull(elementsToRetain); + Collection collection = + (elementsToRetain instanceof Multiset) + ? ((Multiset) elementsToRetain).elementSet() + : elementsToRetain; + + return self.elementSet().retainAll(collection); + } + + /** An implementation of {@link Multiset#setCount(Object, int)}. */ + static int setCountImpl(Multiset self, E element, int count) { + checkNonnegative(count, "count"); + + int oldCount = self.count(element); + + int delta = count - oldCount; + if (delta > 0) { + self.add(element, delta); + } else if (delta < 0) { + self.remove(element, -delta); + } + + return oldCount; + } + + /** An implementation of {@link Multiset#setCount(Object, int, int)}. */ + static boolean setCountImpl(Multiset self, E element, int oldCount, int newCount) { + checkNonnegative(oldCount, "oldCount"); + checkNonnegative(newCount, "newCount"); + + if (self.count(element) == oldCount) { + self.setCount(element, newCount); + return true; + } else { + return false; + } + } + + static Iterator elementIterator(Iterator> entryIterator) { + return new TransformedIterator, E>(entryIterator) { + @Override + E transform(Entry entry) { + return entry.getElement(); + } + }; + } + + abstract static class ElementSet extends Sets.ImprovedAbstractSet { + abstract Multiset multiset(); + + @Override + public void clear() { + multiset().clear(); + } + + @Override + public boolean contains(Object o) { + return multiset().contains(o); + } + + @Override + public boolean containsAll(Collection c) { + return multiset().containsAll(c); + } + + @Override + public boolean isEmpty() { + return multiset().isEmpty(); + } + + @Override + public abstract Iterator iterator(); + + @Override + public boolean remove(Object o) { + return multiset().remove(o, Integer.MAX_VALUE) > 0; + } + + @Override + public int size() { + return multiset().entrySet().size(); + } + } + + abstract static class EntrySet extends Sets.ImprovedAbstractSet> { + abstract Multiset multiset(); + + @Override + public boolean contains(Object o) { + if (o instanceof Entry) { + /* + * The GWT compiler wrongly issues a warning here. + */ + @SuppressWarnings("cast") + Entry entry = (Entry) o; + if (entry.getCount() <= 0) { + return false; + } + int count = multiset().count(entry.getElement()); + return count == entry.getCount(); + } + return false; + } + + // GWT compiler warning; see contains(). + @SuppressWarnings("cast") + @Override + public boolean remove(Object object) { + if (object instanceof Multiset.Entry) { + Entry entry = (Entry) object; + Object element = entry.getElement(); + int entryCount = entry.getCount(); + if (entryCount != 0) { + // Safe as long as we never add a new entry, which we won't. + @SuppressWarnings("unchecked") + Multiset multiset = (Multiset) multiset(); + return multiset.setCount(element, entryCount, 0); + } + } + return false; + } + + @Override + public void clear() { + multiset().clear(); + } + } + + /** An implementation of {@link Multiset#iterator}. */ + static Iterator iteratorImpl(Multiset multiset) { + return new MultisetIteratorImpl(multiset, multiset.entrySet().iterator()); + } + + static final class MultisetIteratorImpl implements Iterator { + private final Multiset multiset; + private final Iterator> entryIterator; + private Entry currentEntry; + + /** Count of subsequent elements equal to current element */ + private int laterCount; + + /** Count of all elements equal to current element */ + private int totalCount; + + private boolean canRemove; + + MultisetIteratorImpl(Multiset multiset, Iterator> entryIterator) { + this.multiset = multiset; + this.entryIterator = entryIterator; + } + + @Override + public boolean hasNext() { + return laterCount > 0 || entryIterator.hasNext(); + } + + @Override + public E next() { + if (!hasNext()) { + throw new NoSuchElementException(); + } + if (laterCount == 0) { + currentEntry = entryIterator.next(); + totalCount = laterCount = currentEntry.getCount(); + } + laterCount--; + canRemove = true; + return currentEntry.getElement(); + } + + @Override + public void remove() { + checkRemove(canRemove); + if (totalCount == 1) { + entryIterator.remove(); + } else { + multiset.remove(currentEntry.getElement()); + } + totalCount--; + canRemove = false; + } + } + + static Spliterator spliteratorImpl(Multiset multiset) { + Spliterator> entrySpliterator = multiset.entrySet().spliterator(); + return CollectSpliterators.flatMap( + entrySpliterator, + entry -> Collections.nCopies(entry.getCount(), entry.getElement()).spliterator(), + Spliterator.SIZED + | (entrySpliterator.characteristics() + & (Spliterator.ORDERED | Spliterator.NONNULL | Spliterator.IMMUTABLE)), + multiset.size()); + } + + /** An implementation of {@link Multiset#size}. */ + static int linearTimeSizeImpl(Multiset multiset) { + long size = 0; + for (Entry entry : multiset.entrySet()) { + size += entry.getCount(); + } + return Ints.saturatedCast(size); + } + + /** Used to avoid http://bugs.sun.com/view_bug.do?bug_id=6558557 */ + static Multiset cast(Iterable iterable) { + return (Multiset) iterable; + } + + /** + * Returns a copy of {@code multiset} as an {@link ImmutableMultiset} whose iteration order is + * highest count first, with ties broken by the iteration order of the original multiset. + * + * @since 11.0 + */ + @Beta + public static ImmutableMultiset copyHighestCountFirst(Multiset multiset) { + Entry[] entries = (Entry[]) multiset.entrySet().toArray(new Entry[0]); + Arrays.sort(entries, DecreasingCount.INSTANCE); + return ImmutableMultiset.copyFromEntries(Arrays.asList(entries)); + } + + private static final class DecreasingCount implements Comparator> { + static final DecreasingCount INSTANCE = new DecreasingCount(); + + @Override + public int compare(Entry entry1, Entry entry2) { + return entry2.getCount() - entry1.getCount(); // subtracting two nonnegative integers + } + } + + /** + * An {@link AbstractMultiset} with additional default implementations, some of them linear-time + * implementations in terms of {@code elementSet} and {@code entrySet}. + */ + private abstract static class ViewMultiset extends AbstractMultiset { + @Override + public int size() { + return linearTimeSizeImpl(this); + } + + @Override + public void clear() { + elementSet().clear(); + } + + @Override + public Iterator iterator() { + return iteratorImpl(this); + } + + @Override + int distinctElements() { + return elementSet().size(); + } + } +} diff --git a/src/main/java/com/google/common/collect/MutableClassToInstanceMap.java b/src/main/java/com/google/common/collect/MutableClassToInstanceMap.java new file mode 100644 index 0000000..aaa6f27 --- /dev/null +++ b/src/main/java/com/google/common/collect/MutableClassToInstanceMap.java @@ -0,0 +1,181 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.annotations.GwtIncompatible; +import com.google.common.primitives.Primitives; + +import java.io.Serializable; +import java.util.HashMap; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Set; +import java.util.Spliterator; + +/** + * A mutable class-to-instance map backed by an arbitrary user-provided map. See also {@link + * ImmutableClassToInstanceMap}. + * + *

See the Guava User Guide article on {@code + * ClassToInstanceMap}. + * + * @author Kevin Bourrillion + * @since 2.0 + */ +@GwtIncompatible +@SuppressWarnings("serial") // using writeReplace instead of standard serialization +public final class MutableClassToInstanceMap extends ForwardingMap, B> + implements ClassToInstanceMap, Serializable { + + /** + * Returns a new {@code MutableClassToInstanceMap} instance backed by a {@link HashMap} using the + * default initial capacity and load factor. + */ + public static MutableClassToInstanceMap create() { + return new MutableClassToInstanceMap(new HashMap, B>()); + } + + /** + * Returns a new {@code MutableClassToInstanceMap} instance backed by a given empty {@code + * backingMap}. The caller surrenders control of the backing map, and thus should not allow any + * direct references to it to remain accessible. + */ + public static MutableClassToInstanceMap create(Map, B> backingMap) { + return new MutableClassToInstanceMap(backingMap); + } + + private final Map, B> delegate; + + private MutableClassToInstanceMap(Map, B> delegate) { + this.delegate = checkNotNull(delegate); + } + + @Override + protected Map, B> delegate() { + return delegate; + } + + /** + * Wraps the {@code setValue} implementation of an {@code Entry} to enforce the class constraint. + */ + private static Entry, B> checkedEntry( + final Entry, B> entry) { + return new ForwardingMapEntry, B>() { + @Override + protected Entry, B> delegate() { + return entry; + } + + @Override + public B setValue(B value) { + return super.setValue(cast(getKey(), value)); + } + }; + } + + @Override + public Set, B>> entrySet() { + return new ForwardingSet, B>>() { + + @Override + protected Set, B>> delegate() { + return MutableClassToInstanceMap.this.delegate().entrySet(); + } + + @Override + public Spliterator, B>> spliterator() { + return CollectSpliterators.map( + delegate().spliterator(), MutableClassToInstanceMap::checkedEntry); + } + + @Override + public Iterator, B>> iterator() { + return new TransformedIterator, B>, Entry, B>>( + delegate().iterator()) { + @Override + Entry, B> transform(Entry, B> from) { + return checkedEntry(from); + } + }; + } + + @Override + public Object[] toArray() { + return standardToArray(); + } + + @Override + public T[] toArray(T[] array) { + return standardToArray(array); + } + }; + } + + @Override + + public B put(Class key, B value) { + return super.put(key, cast(key, value)); + } + + @Override + public void putAll(Map, ? extends B> map) { + Map, B> copy = new LinkedHashMap<>(map); + for (Entry, B> entry : copy.entrySet()) { + cast(entry.getKey(), entry.getValue()); + } + super.putAll(copy); + } + + + @Override + public T putInstance(Class type, T value) { + return cast(type, put(type, value)); + } + + @Override + public T getInstance(Class type) { + return cast(type, get(type)); + } + + + private static T cast(Class type, B value) { + return Primitives.wrap(type).cast(value); + } + + private Object writeReplace() { + return new SerializedForm(delegate()); + } + + /** Serialized form of the map, to avoid serializing the constraint. */ + private static final class SerializedForm implements Serializable { + private final Map, B> backingMap; + + SerializedForm(Map, B> backingMap) { + this.backingMap = backingMap; + } + + Object readResolve() { + return create(backingMap); + } + + private static final long serialVersionUID = 0; + } +} diff --git a/src/main/java/com/google/common/collect/NaturalOrdering.java b/src/main/java/com/google/common/collect/NaturalOrdering.java new file mode 100644 index 0000000..695cd6c --- /dev/null +++ b/src/main/java/com/google/common/collect/NaturalOrdering.java @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.annotations.GwtCompatible; +import java.io.Serializable; + + +/** An ordering that uses the natural order of the values. */ +@GwtCompatible(serializable = true) +@SuppressWarnings({"unchecked", "rawtypes"}) // TODO(kevinb): the right way to explain this?? +final class NaturalOrdering extends Ordering implements Serializable { + static final NaturalOrdering INSTANCE = new NaturalOrdering(); + + private transient Ordering nullsFirst; + private transient Ordering nullsLast; + + @Override + public int compare(Comparable left, Comparable right) { + checkNotNull(left); // for GWT + checkNotNull(right); + return left.compareTo(right); + } + + @Override + public Ordering nullsFirst() { + Ordering result = nullsFirst; + if (result == null) { + result = nullsFirst = super.nullsFirst(); + } + return (Ordering) result; + } + + @Override + public Ordering nullsLast() { + Ordering result = nullsLast; + if (result == null) { + result = nullsLast = super.nullsLast(); + } + return (Ordering) result; + } + + @Override + public Ordering reverse() { + return (Ordering) ReverseNaturalOrdering.INSTANCE; + } + + // preserving singleton-ness gives equals()/hashCode() for free + private Object readResolve() { + return INSTANCE; + } + + @Override + public String toString() { + return "Ordering.natural()"; + } + + private NaturalOrdering() {} + + private static final long serialVersionUID = 0; +} diff --git a/src/main/java/com/google/common/collect/NullsFirstOrdering.java b/src/main/java/com/google/common/collect/NullsFirstOrdering.java new file mode 100644 index 0000000..0d2b308 --- /dev/null +++ b/src/main/java/com/google/common/collect/NullsFirstOrdering.java @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import com.google.common.annotations.GwtCompatible; +import java.io.Serializable; + + +/** An ordering that treats {@code null} as less than all other values. */ +@GwtCompatible(serializable = true) +final class NullsFirstOrdering extends Ordering implements Serializable { + final Ordering ordering; + + NullsFirstOrdering(Ordering ordering) { + this.ordering = ordering; + } + + @Override + public int compare(T left, T right) { + if (left == right) { + return 0; + } + if (left == null) { + return RIGHT_IS_GREATER; + } + if (right == null) { + return LEFT_IS_GREATER; + } + return ordering.compare(left, right); + } + + @Override + public Ordering reverse() { + // ordering.reverse() might be optimized, so let it do its thing + return ordering.reverse().nullsLast(); + } + + @SuppressWarnings("unchecked") // still need the right way to explain this + @Override + public Ordering nullsFirst() { + return (Ordering) this; + } + + @Override + public Ordering nullsLast() { + return ordering.nullsLast(); + } + + @Override + public boolean equals(Object object) { + if (object == this) { + return true; + } + if (object instanceof NullsFirstOrdering) { + NullsFirstOrdering that = (NullsFirstOrdering) object; + return this.ordering.equals(that.ordering); + } + return false; + } + + @Override + public int hashCode() { + return ordering.hashCode() ^ 957692532; // meaningless + } + + @Override + public String toString() { + return ordering + ".nullsFirst()"; + } + + private static final long serialVersionUID = 0; +} diff --git a/src/main/java/com/google/common/collect/NullsLastOrdering.java b/src/main/java/com/google/common/collect/NullsLastOrdering.java new file mode 100644 index 0000000..1c95694 --- /dev/null +++ b/src/main/java/com/google/common/collect/NullsLastOrdering.java @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import com.google.common.annotations.GwtCompatible; +import java.io.Serializable; + + +/** An ordering that treats {@code null} as greater than all other values. */ +@GwtCompatible(serializable = true) +final class NullsLastOrdering extends Ordering implements Serializable { + final Ordering ordering; + + NullsLastOrdering(Ordering ordering) { + this.ordering = ordering; + } + + @Override + public int compare(T left, T right) { + if (left == right) { + return 0; + } + if (left == null) { + return LEFT_IS_GREATER; + } + if (right == null) { + return RIGHT_IS_GREATER; + } + return ordering.compare(left, right); + } + + @Override + public Ordering reverse() { + // ordering.reverse() might be optimized, so let it do its thing + return ordering.reverse().nullsFirst(); + } + + @Override + public Ordering nullsFirst() { + return ordering.nullsFirst(); + } + + @SuppressWarnings("unchecked") // still need the right way to explain this + @Override + public Ordering nullsLast() { + return (Ordering) this; + } + + @Override + public boolean equals(Object object) { + if (object == this) { + return true; + } + if (object instanceof NullsLastOrdering) { + NullsLastOrdering that = (NullsLastOrdering) object; + return this.ordering.equals(that.ordering); + } + return false; + } + + @Override + public int hashCode() { + return ordering.hashCode() ^ -921210296; // meaningless + } + + @Override + public String toString() { + return ordering + ".nullsLast()"; + } + + private static final long serialVersionUID = 0; +} diff --git a/src/main/java/com/google/common/collect/ObjectArrays.java b/src/main/java/com/google/common/collect/ObjectArrays.java new file mode 100644 index 0000000..0249b94 --- /dev/null +++ b/src/main/java/com/google/common/collect/ObjectArrays.java @@ -0,0 +1,229 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import static com.google.common.base.Preconditions.checkPositionIndexes; + +import com.google.common.annotations.GwtCompatible; +import com.google.common.annotations.GwtIncompatible; + +import java.lang.reflect.Array; +import java.util.Arrays; +import java.util.Collection; + + +/** + * Static utility methods pertaining to object arrays. + * + * @author Kevin Bourrillion + * @since 2.0 + */ +@GwtCompatible(emulated = true) +public final class ObjectArrays { + + private ObjectArrays() {} + + /** + * Returns a new array of the given length with the specified component type. + * + * @param type the component type + * @param length the length of the new array + */ + @GwtIncompatible // Array.newInstance(Class, int) + @SuppressWarnings("unchecked") + public static T[] newArray(Class type, int length) { + return (T[]) Array.newInstance(type, length); + } + + /** + * Returns a new array of the given length with the same type as a reference array. + * + * @param reference any array of the desired type + * @param length the length of the new array + */ + public static T[] newArray(T[] reference, int length) { + return Platform.newArray(reference, length); + } + + /** + * Returns a new array that contains the concatenated contents of two arrays. + * + * @param first the first array of elements to concatenate + * @param second the second array of elements to concatenate + * @param type the component type of the returned array + */ + @GwtIncompatible // Array.newInstance(Class, int) + public static T[] concat(T[] first, T[] second, Class type) { + T[] result = newArray(type, first.length + second.length); + System.arraycopy(first, 0, result, 0, first.length); + System.arraycopy(second, 0, result, first.length, second.length); + return result; + } + + /** + * Returns a new array that prepends {@code element} to {@code array}. + * + * @param element the element to prepend to the front of {@code array} + * @param array the array of elements to append + * @return an array whose size is one larger than {@code array}, with {@code element} occupying + * the first position, and the elements of {@code array} occupying the remaining elements. + */ + public static T[] concat(T element, T[] array) { + T[] result = newArray(array, array.length + 1); + result[0] = element; + System.arraycopy(array, 0, result, 1, array.length); + return result; + } + + /** + * Returns a new array that appends {@code element} to {@code array}. + * + * @param array the array of elements to prepend + * @param element the element to append to the end + * @return an array whose size is one larger than {@code array}, with the same contents as {@code + * array}, plus {@code element} occupying the last position. + */ + public static T[] concat(T[] array, T element) { + T[] result = Arrays.copyOf(array, array.length + 1); + result[array.length] = element; + return result; + } + + /** + * Returns an array containing all of the elements in the specified collection; the runtime type + * of the returned array is that of the specified array. If the collection fits in the specified + * array, it is returned therein. Otherwise, a new array is allocated with the runtime type of the + * specified array and the size of the specified collection. + * + *

If the collection fits in the specified array with room to spare (i.e., the array has more + * elements than the collection), the element in the array immediately following the end of the + * collection is set to {@code null}. This is useful in determining the length of the collection + * only if the caller knows that the collection does not contain any null elements. + * + *

This method returns the elements in the order they are returned by the collection's + * iterator. + * + *

TODO(kevinb): support concurrently modified collections? + * + * @param c the collection for which to return an array of elements + * @param array the array in which to place the collection elements + * @throws ArrayStoreException if the runtime type of the specified array is not a supertype of + * the runtime type of every element in the specified collection + */ + static T[] toArrayImpl(Collection c, T[] array) { + int size = c.size(); + if (array.length < size) { + array = newArray(array, size); + } + fillArray(c, array); + if (array.length > size) { + array[size] = null; + } + return array; + } + + /** + * Implementation of {@link Collection#toArray(Object[])} for collections backed by an object + * array. the runtime type of the returned array is that of the specified array. If the collection + * fits in the specified array, it is returned therein. Otherwise, a new array is allocated with + * the runtime type of the specified array and the size of the specified collection. + * + *

If the collection fits in the specified array with room to spare (i.e., the array has more + * elements than the collection), the element in the array immediately following the end of the + * collection is set to {@code null}. This is useful in determining the length of the collection + * only if the caller knows that the collection does not contain any null elements. + */ + static T[] toArrayImpl(Object[] src, int offset, int len, T[] dst) { + checkPositionIndexes(offset, offset + len, src.length); + if (dst.length < len) { + dst = newArray(dst, len); + } else if (dst.length > len) { + dst[len] = null; + } + System.arraycopy(src, offset, dst, 0, len); + return dst; + } + + /** + * Returns an array containing all of the elements in the specified collection. This method + * returns the elements in the order they are returned by the collection's iterator. The returned + * array is "safe" in that no references to it are maintained by the collection. The caller is + * thus free to modify the returned array. + * + *

This method assumes that the collection size doesn't change while the method is running. + * + *

TODO(kevinb): support concurrently modified collections? + * + * @param c the collection for which to return an array of elements + */ + static Object[] toArrayImpl(Collection c) { + return fillArray(c, new Object[c.size()]); + } + + /** + * Returns a copy of the specified subrange of the specified array that is literally an Object[], + * and not e.g. a {@code String[]}. + */ + static Object[] copyAsObjectArray(Object[] elements, int offset, int length) { + checkPositionIndexes(offset, offset + length, elements.length); + if (length == 0) { + return new Object[0]; + } + Object[] result = new Object[length]; + System.arraycopy(elements, offset, result, 0, length); + return result; + } + + + private static Object[] fillArray(Iterable elements, Object[] array) { + int i = 0; + for (Object element : elements) { + array[i++] = element; + } + return array; + } + + /** Swaps {@code array[i]} with {@code array[j]}. */ + static void swap(Object[] array, int i, int j) { + Object temp = array[i]; + array[i] = array[j]; + array[j] = temp; + } + + + static Object[] checkElementsNotNull(Object... array) { + return checkElementsNotNull(array, array.length); + } + + + static Object[] checkElementsNotNull(Object[] array, int length) { + for (int i = 0; i < length; i++) { + checkElementNotNull(array[i], i); + } + return array; + } + + // We do this instead of Preconditions.checkNotNull to save boxing and array + // creation cost. + + static Object checkElementNotNull(Object element, int index) { + if (element == null) { + throw new NullPointerException("at index " + index); + } + return element; + } +} diff --git a/src/main/java/com/google/common/collect/Ordering.java b/src/main/java/com/google/common/collect/Ordering.java new file mode 100644 index 0000000..869887e --- /dev/null +++ b/src/main/java/com/google/common/collect/Ordering.java @@ -0,0 +1,947 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.collect.CollectPreconditions.checkNonnegative; + +import com.google.common.annotations.GwtCompatible; +import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.Function; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map.Entry; +import java.util.NoSuchElementException; +import java.util.SortedMap; +import java.util.SortedSet; +import java.util.TreeSet; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.atomic.AtomicInteger; + + +/** + * A comparator, with additional methods to support common operations. This is an "enriched" version + * of {@code Comparator} for pre-Java-8 users, in the same sense that {@link FluentIterable} is an + * enriched {@link Iterable} for pre-Java-8 users. + * + *

Three types of methods

+ * + * Like other fluent types, there are three types of methods present: methods for acquiring, + * chaining, and using. + * + *

Acquiring

+ * + *

The common ways to get an instance of {@code Ordering} are: + * + *

    + *
  • Subclass it and implement {@link #compare} instead of implementing {@link Comparator} + * directly + *
  • Pass a pre-existing {@link Comparator} instance to {@link #from(Comparator)} + *
  • Use the natural ordering, {@link Ordering#natural} + *
+ * + *

Chaining

+ * + *

Then you can use the chaining methods to get an altered version of that {@code + * Ordering}, including: + * + *

    + *
  • {@link #reverse} + *
  • {@link #compound(Comparator)} + *
  • {@link #onResultOf(Function)} + *
  • {@link #nullsFirst} / {@link #nullsLast} + *
+ * + *

Using

+ * + *

Finally, use the resulting {@code Ordering} anywhere a {@link Comparator} is required, or use + * any of its special operations, such as: + * + *

    + *
  • {@link #immutableSortedCopy} + *
  • {@link #isOrdered} / {@link #isStrictlyOrdered} + *
  • {@link #min} / {@link #max} + *
+ * + *

Understanding complex orderings

+ * + *

Complex chained orderings like the following example can be challenging to understand. + * + *

{@code
+ * Ordering ordering =
+ *     Ordering.natural()
+ *         .nullsFirst()
+ *         .onResultOf(getBarFunction)
+ *         .nullsLast();
+ * }
+ * + * Note that each chaining method returns a new ordering instance which is backed by the previous + * instance, but has the chance to act on values before handing off to that backing instance. + * As a result, it usually helps to read chained ordering expressions backwards. For example, + * when {@code compare} is called on the above ordering: + * + *
    + *
  1. First, if only one {@code Foo} is null, that null value is treated as greater + *
  2. Next, non-null {@code Foo} values are passed to {@code getBarFunction} (we will be + * comparing {@code Bar} values from now on) + *
  3. Next, if only one {@code Bar} is null, that null value is treated as lesser + *
  4. Finally, natural ordering is used (i.e. the result of {@code Bar.compareTo(Bar)} is + * returned) + *
+ * + *

Alas, {@link #reverse} is a little different. As you read backwards through a chain and + * encounter a call to {@code reverse}, continue working backwards until a result is determined, and + * then reverse that result. + * + *

Additional notes

+ * + *

Except as noted, the orderings returned by the factory methods of this class are serializable + * if and only if the provided instances that back them are. For example, if {@code ordering} and + * {@code function} can themselves be serialized, then {@code ordering.onResultOf(function)} can as + * well. + * + *

For Java 8 users

+ * + *

If you are using Java 8, this class is now obsolete. Most of its functionality is now provided + * by {@link java.util.stream.Stream Stream} and by {@link Comparator} itself, and the rest can now + * be found as static methods in our new {@link Comparators} class. See each method below for + * further instructions. Whenever possible, you should change any references of type {@code + * Ordering} to be of type {@code Comparator} instead. However, at this time we have no plan to + * deprecate this class. + * + *

Many replacements involve adopting {@code Stream}, and these changes can sometimes make your + * code verbose. Whenever following this advice, you should check whether {@code Stream} could be + * adopted more comprehensively in your code; the end result may be quite a bit simpler. + * + *

See also

+ * + *

See the Guava User Guide article on {@code Ordering}. + * + * @author Jesse Wilson + * @author Kevin Bourrillion + * @since 2.0 + */ +@GwtCompatible +public abstract class Ordering implements Comparator { + // Natural order + + /** + * Returns a serializable ordering that uses the natural order of the values. The ordering throws + * a {@link NullPointerException} when passed a null parameter. + * + *

The type specification is {@code }, instead of the technically correct + * {@code >}, to support legacy types from before Java 5. + * + *

Java 8 users: use {@link Comparator#naturalOrder} instead. + */ + @GwtCompatible(serializable = true) + @SuppressWarnings("unchecked") // TODO(kevinb): right way to explain this?? + public static Ordering natural() { + return (Ordering) NaturalOrdering.INSTANCE; + } + + // Static factories + + /** + * Returns an ordering based on an existing comparator instance. Note that it is + * unnecessary to create a new anonymous inner class implementing {@code Comparator} just + * to pass it in here. Instead, simply subclass {@code Ordering} and implement its {@code compare} + * method directly. + * + *

Java 8 users: this class is now obsolete as explained in the class documentation, so + * there is no need to use this method. + * + * @param comparator the comparator that defines the order + * @return comparator itself if it is already an {@code Ordering}; otherwise an ordering that + * wraps that comparator + */ + @GwtCompatible(serializable = true) + public static Ordering from(Comparator comparator) { + return (comparator instanceof Ordering) + ? (Ordering) comparator + : new ComparatorOrdering(comparator); + } + + /** + * Simply returns its argument. + * + * @deprecated no need to use this + */ + @GwtCompatible(serializable = true) + @Deprecated + public static Ordering from(Ordering ordering) { + return checkNotNull(ordering); + } + + /** + * Returns an ordering that compares objects according to the order in which they appear in the + * given list. Only objects present in the list (according to {@link Object#equals}) may be + * compared. This comparator imposes a "partial ordering" over the type {@code T}. Subsequent + * changes to the {@code valuesInOrder} list will have no effect on the returned comparator. Null + * values in the list are not supported. + * + *

The returned comparator throws a {@link ClassCastException} when it receives an input + * parameter that isn't among the provided values. + * + *

The generated comparator is serializable if all the provided values are serializable. + * + * @param valuesInOrder the values that the returned comparator will be able to compare, in the + * order the comparator should induce + * @return the comparator described above + * @throws NullPointerException if any of the provided values is null + * @throws IllegalArgumentException if {@code valuesInOrder} contains any duplicate values + * (according to {@link Object#equals}) + */ + // TODO(kevinb): provide replacement + @GwtCompatible(serializable = true) + public static Ordering explicit(List valuesInOrder) { + return new ExplicitOrdering(valuesInOrder); + } + + /** + * Returns an ordering that compares objects according to the order in which they are given to + * this method. Only objects present in the argument list (according to {@link Object#equals}) may + * be compared. This comparator imposes a "partial ordering" over the type {@code T}. Null values + * in the argument list are not supported. + * + *

The returned comparator throws a {@link ClassCastException} when it receives an input + * parameter that isn't among the provided values. + * + *

The generated comparator is serializable if all the provided values are serializable. + * + * @param leastValue the value which the returned comparator should consider the "least" of all + * values + * @param remainingValuesInOrder the rest of the values that the returned comparator will be able + * to compare, in the order the comparator should follow + * @return the comparator described above + * @throws NullPointerException if any of the provided values is null + * @throws IllegalArgumentException if any duplicate values (according to {@link + * Object#equals(Object)}) are present among the method arguments + */ + // TODO(kevinb): provide replacement + @GwtCompatible(serializable = true) + public static Ordering explicit(T leastValue, T... remainingValuesInOrder) { + return explicit(Lists.asList(leastValue, remainingValuesInOrder)); + } + + // Ordering singletons + + /** + * Returns an ordering which treats all values as equal, indicating "no ordering." Passing this + * ordering to any stable sort algorithm results in no change to the order of elements. + * Note especially that {@link #sortedCopy} and {@link #immutableSortedCopy} are stable, and in + * the returned instance these are implemented by simply copying the source list. + * + *

Example: + * + *

{@code
+   * Ordering.allEqual().nullsLast().sortedCopy(
+   *     asList(t, null, e, s, null, t, null))
+   * }
+ * + *

Assuming {@code t}, {@code e} and {@code s} are non-null, this returns {@code [t, e, s, t, + * null, null, null]} regardless of the true comparison order of those three values (which might + * not even implement {@link Comparable} at all). + * + *

Warning: by definition, this comparator is not consistent with equals (as + * defined {@linkplain Comparator here}). Avoid its use in APIs, such as {@link + * TreeSet#TreeSet(Comparator)}, where such consistency is expected. + * + *

The returned comparator is serializable. + * + *

Java 8 users: Use the lambda expression {@code (a, b) -> 0} instead (in certain cases + * you may need to cast that to {@code Comparator}). + * + * @since 13.0 + */ + @GwtCompatible(serializable = true) + @SuppressWarnings("unchecked") + public static Ordering allEqual() { + return AllEqualOrdering.INSTANCE; + } + + /** + * Returns an ordering that compares objects by the natural ordering of their string + * representations as returned by {@code toString()}. It does not support null values. + * + *

The comparator is serializable. + * + *

Java 8 users: Use {@code Comparator.comparing(Object::toString)} instead. + */ + @GwtCompatible(serializable = true) + public static Ordering usingToString() { + return UsingToStringOrdering.INSTANCE; + } + + /** + * Returns an arbitrary ordering over all objects, for which {@code compare(a, b) == 0} implies + * {@code a == b} (identity equality). There is no meaning whatsoever to the order imposed, but it + * is constant for the life of the VM. + * + *

Because the ordering is identity-based, it is not "consistent with {@link + * Object#equals(Object)}" as defined by {@link Comparator}. Use caution when building a {@link + * SortedSet} or {@link SortedMap} from it, as the resulting collection will not behave exactly + * according to spec. + * + *

This ordering is not serializable, as its implementation relies on {@link + * System#identityHashCode(Object)}, so its behavior cannot be preserved across serialization. + * + * @since 2.0 + */ + // TODO(kevinb): copy to Comparators, etc. + public static Ordering arbitrary() { + return ArbitraryOrderingHolder.ARBITRARY_ORDERING; + } + + private static class ArbitraryOrderingHolder { + static final Ordering ARBITRARY_ORDERING = new ArbitraryOrdering(); + } + + @VisibleForTesting + static class ArbitraryOrdering extends Ordering { + + private final AtomicInteger counter = new AtomicInteger(0); + private final ConcurrentMap uids = + Platform.tryWeakKeys(new MapMaker()).makeMap(); + + private Integer getUid(Object obj) { + Integer uid = uids.get(obj); + if (uid == null) { + // One or more integer values could be skipped in the event of a race + // to generate a UID for the same object from multiple threads, but + // that shouldn't be a problem. + uid = counter.getAndIncrement(); + Integer alreadySet = uids.putIfAbsent(obj, uid); + if (alreadySet != null) { + uid = alreadySet; + } + } + return uid; + } + + @Override + public int compare(Object left, Object right) { + if (left == right) { + return 0; + } else if (left == null) { + return -1; + } else if (right == null) { + return 1; + } + int leftCode = identityHashCode(left); + int rightCode = identityHashCode(right); + if (leftCode != rightCode) { + return leftCode < rightCode ? -1 : 1; + } + + // identityHashCode collision (rare, but not as rare as you'd think) + int result = getUid(left).compareTo(getUid(right)); + if (result == 0) { + throw new AssertionError(); // extremely, extremely unlikely. + } + return result; + } + + @Override + public String toString() { + return "Ordering.arbitrary()"; + } + + /* + * We need to be able to mock identityHashCode() calls for tests, because it + * can take 1-10 seconds to find colliding objects. Mocking frameworks that + * can do magic to mock static method calls still can't do so for a system + * class, so we need the indirection. In production, Hotspot should still + * recognize that the call is 1-morphic and should still be willing to + * inline it if necessary. + */ + int identityHashCode(Object object) { + return System.identityHashCode(object); + } + } + + // Constructor + + /** + * Constructs a new instance of this class (only invokable by the subclass constructor, typically + * implicit). + */ + protected Ordering() {} + + // Instance-based factories (and any static equivalents) + + /** + * Returns the reverse of this ordering; the {@code Ordering} equivalent to {@link + * Collections#reverseOrder(Comparator)}. + * + *

Java 8 users: Use {@code thisComparator.reversed()} instead. + */ + // type parameter lets us avoid the extra in statements like: + // Ordering o = Ordering.natural().reverse(); + @GwtCompatible(serializable = true) + public Ordering reverse() { + return new ReverseOrdering(this); + } + + /** + * Returns an ordering that treats {@code null} as less than all other values and uses {@code + * this} to compare non-null values. + * + *

Java 8 users: Use {@code Comparator.nullsFirst(thisComparator)} instead. + */ + // type parameter lets us avoid the extra in statements like: + // Ordering o = Ordering.natural().nullsFirst(); + @GwtCompatible(serializable = true) + public Ordering nullsFirst() { + return new NullsFirstOrdering(this); + } + + /** + * Returns an ordering that treats {@code null} as greater than all other values and uses this + * ordering to compare non-null values. + * + *

Java 8 users: Use {@code Comparator.nullsLast(thisComparator)} instead. + */ + // type parameter lets us avoid the extra in statements like: + // Ordering o = Ordering.natural().nullsLast(); + @GwtCompatible(serializable = true) + public Ordering nullsLast() { + return new NullsLastOrdering(this); + } + + /** + * Returns a new ordering on {@code F} which orders elements by first applying a function to them, + * then comparing those results using {@code this}. For example, to compare objects by their + * string forms, in a case-insensitive manner, use: + * + *

{@code
+   * Ordering.from(String.CASE_INSENSITIVE_ORDER)
+   *     .onResultOf(Functions.toStringFunction())
+   * }
+ * + *

Java 8 users: Use {@code Comparator.comparing(function, thisComparator)} instead (you + * can omit the comparator if it is the natural order). + */ + @GwtCompatible(serializable = true) + public Ordering onResultOf(Function function) { + return new ByFunctionOrdering<>(function, this); + } + + Ordering> onKeys() { + return onResultOf(Maps.keyFunction()); + } + + /** + * Returns an ordering which first uses the ordering {@code this}, but which in the event of a + * "tie", then delegates to {@code secondaryComparator}. For example, to sort a bug list first by + * status and second by priority, you might use {@code byStatus.compound(byPriority)}. For a + * compound ordering with three or more components, simply chain multiple calls to this method. + * + *

An ordering produced by this method, or a chain of calls to this method, is equivalent to + * one created using {@link Ordering#compound(Iterable)} on the same component comparators. + * + *

Java 8 users: Use {@code thisComparator.thenComparing(secondaryComparator)} instead. + * Depending on what {@code secondaryComparator} is, one of the other overloads of {@code + * thenComparing} may be even more useful. + */ + @GwtCompatible(serializable = true) + public Ordering compound(Comparator secondaryComparator) { + return new CompoundOrdering(this, checkNotNull(secondaryComparator)); + } + + /** + * Returns an ordering which tries each given comparator in order until a non-zero result is + * found, returning that result, and returning zero only if all comparators return zero. The + * returned ordering is based on the state of the {@code comparators} iterable at the time it was + * provided to this method. + * + *

The returned ordering is equivalent to that produced using {@code + * Ordering.from(comp1).compound(comp2).compound(comp3) . . .}. + * + *

Warning: Supplying an argument with undefined iteration order, such as a {@link + * HashSet}, will produce non-deterministic results. + * + *

Java 8 users: Use a chain of calls to {@link Comparator#thenComparing(Comparator)}, + * or {@code comparatorCollection.stream().reduce(Comparator::thenComparing).get()} (if the + * collection might be empty, also provide a default comparator as the {@code identity} parameter + * to {@code reduce}). + * + * @param comparators the comparators to try in order + */ + @GwtCompatible(serializable = true) + public static Ordering compound(Iterable> comparators) { + return new CompoundOrdering(comparators); + } + + /** + * Returns a new ordering which sorts iterables by comparing corresponding elements pairwise until + * a nonzero result is found; imposes "dictionary order". If the end of one iterable is reached, + * but not the other, the shorter iterable is considered to be less than the longer one. For + * example, a lexicographical natural ordering over integers considers {@code [] < [1] < [1, 1] < + * [1, 2] < [2]}. + * + *

Note that {@code ordering.lexicographical().reverse()} is not equivalent to {@code + * ordering.reverse().lexicographical()} (consider how each would order {@code [1]} and {@code [1, + * 1]}). + * + *

Java 8 users: Use {@link Comparators#lexicographical(Comparator)} instead. + * + * @since 2.0 + */ + @GwtCompatible(serializable = true) + // type parameter lets us avoid the extra in statements like: + // Ordering> o = + // Ordering.natural().lexicographical(); + public Ordering> lexicographical() { + /* + * Note that technically the returned ordering should be capable of + * handling not just {@code Iterable} instances, but also any {@code + * Iterable}. However, the need for this comes up so rarely + * that it doesn't justify making everyone else deal with the very ugly + * wildcard. + */ + return new LexicographicalOrdering(this); + } + + // Regular instance methods + + // Override to add + // TODO(kak): Consider removing this + @Override + public abstract int compare(T left, T right); + + /** + * Returns the least of the specified values according to this ordering. If there are multiple + * least values, the first of those is returned. The iterator will be left exhausted: its {@code + * hasNext()} method will return {@code false}. + * + *

Java 8 users: Use {@code Streams.stream(iterator).min(thisComparator).get()} instead + * (but note that it does not guarantee which tied minimum element is returned). + * + * @param iterator the iterator whose minimum element is to be determined + * @throws NoSuchElementException if {@code iterator} is empty + * @throws ClassCastException if the parameters are not mutually comparable under this + * ordering. + * @since 11.0 + */ + public E min(Iterator iterator) { + // let this throw NoSuchElementException as necessary + E minSoFar = iterator.next(); + + while (iterator.hasNext()) { + minSoFar = min(minSoFar, iterator.next()); + } + + return minSoFar; + } + + /** + * Returns the least of the specified values according to this ordering. If there are multiple + * least values, the first of those is returned. + * + *

Java 8 users: If {@code iterable} is a {@link Collection}, use {@code + * Collections.min(collection, thisComparator)} instead. Otherwise, use {@code + * Streams.stream(iterable).min(thisComparator).get()} instead. Note that these alternatives do + * not guarantee which tied minimum element is returned) + * + * @param iterable the iterable whose minimum element is to be determined + * @throws NoSuchElementException if {@code iterable} is empty + * @throws ClassCastException if the parameters are not mutually comparable under this + * ordering. + */ + public E min(Iterable iterable) { + return min(iterable.iterator()); + } + + /** + * Returns the lesser of the two values according to this ordering. If the values compare as 0, + * the first is returned. + * + *

Implementation note: this method is invoked by the default implementations of the + * other {@code min} overloads, so overriding it will affect their behavior. + * + *

Java 8 users: Use {@code Collections.min(Arrays.asList(a, b), thisComparator)} + * instead (but note that it does not guarantee which tied minimum element is returned). + * + * @param a value to compare, returned if less than or equal to b. + * @param b value to compare. + * @throws ClassCastException if the parameters are not mutually comparable under this + * ordering. + */ + public E min(E a, E b) { + return (compare(a, b) <= 0) ? a : b; + } + + /** + * Returns the least of the specified values according to this ordering. If there are multiple + * least values, the first of those is returned. + * + *

Java 8 users: Use {@code Collections.min(Arrays.asList(a, b, c...), thisComparator)} + * instead (but note that it does not guarantee which tied minimum element is returned). + * + * @param a value to compare, returned if less than or equal to the rest. + * @param b value to compare + * @param c value to compare + * @param rest values to compare + * @throws ClassCastException if the parameters are not mutually comparable under this + * ordering. + */ + public E min(E a, E b, E c, E... rest) { + E minSoFar = min(min(a, b), c); + + for (E r : rest) { + minSoFar = min(minSoFar, r); + } + + return minSoFar; + } + + /** + * Returns the greatest of the specified values according to this ordering. If there are multiple + * greatest values, the first of those is returned. The iterator will be left exhausted: its + * {@code hasNext()} method will return {@code false}. + * + *

Java 8 users: Use {@code Streams.stream(iterator).max(thisComparator).get()} instead + * (but note that it does not guarantee which tied maximum element is returned). + * + * @param iterator the iterator whose maximum element is to be determined + * @throws NoSuchElementException if {@code iterator} is empty + * @throws ClassCastException if the parameters are not mutually comparable under this + * ordering. + * @since 11.0 + */ + public E max(Iterator iterator) { + // let this throw NoSuchElementException as necessary + E maxSoFar = iterator.next(); + + while (iterator.hasNext()) { + maxSoFar = max(maxSoFar, iterator.next()); + } + + return maxSoFar; + } + + /** + * Returns the greatest of the specified values according to this ordering. If there are multiple + * greatest values, the first of those is returned. + * + *

Java 8 users: If {@code iterable} is a {@link Collection}, use {@code + * Collections.max(collection, thisComparator)} instead. Otherwise, use {@code + * Streams.stream(iterable).max(thisComparator).get()} instead. Note that these alternatives do + * not guarantee which tied maximum element is returned) + * + * @param iterable the iterable whose maximum element is to be determined + * @throws NoSuchElementException if {@code iterable} is empty + * @throws ClassCastException if the parameters are not mutually comparable under this + * ordering. + */ + public E max(Iterable iterable) { + return max(iterable.iterator()); + } + + /** + * Returns the greater of the two values according to this ordering. If the values compare as 0, + * the first is returned. + * + *

Implementation note: this method is invoked by the default implementations of the + * other {@code max} overloads, so overriding it will affect their behavior. + * + *

Java 8 users: Use {@code Collections.max(Arrays.asList(a, b), thisComparator)} + * instead (but note that it does not guarantee which tied maximum element is returned). + * + * @param a value to compare, returned if greater than or equal to b. + * @param b value to compare. + * @throws ClassCastException if the parameters are not mutually comparable under this + * ordering. + */ + public E max(E a, E b) { + return (compare(a, b) >= 0) ? a : b; + } + + /** + * Returns the greatest of the specified values according to this ordering. If there are multiple + * greatest values, the first of those is returned. + * + *

Java 8 users: Use {@code Collections.max(Arrays.asList(a, b, c...), thisComparator)} + * instead (but note that it does not guarantee which tied maximum element is returned). + * + * @param a value to compare, returned if greater than or equal to the rest. + * @param b value to compare + * @param c value to compare + * @param rest values to compare + * @throws ClassCastException if the parameters are not mutually comparable under this + * ordering. + */ + public E max(E a, E b, E c, E... rest) { + E maxSoFar = max(max(a, b), c); + + for (E r : rest) { + maxSoFar = max(maxSoFar, r); + } + + return maxSoFar; + } + + /** + * Returns the {@code k} least elements of the given iterable according to this ordering, in order + * from least to greatest. If there are fewer than {@code k} elements present, all will be + * included. + * + *

The implementation does not necessarily use a stable sorting algorithm; when multiple + * elements are equivalent, it is undefined which will come first. + * + *

Java 8 users: Use {@code Streams.stream(iterable).collect(Comparators.least(k, + * thisComparator))} instead. + * + * @return an immutable {@code RandomAccess} list of the {@code k} least elements in ascending + * order + * @throws IllegalArgumentException if {@code k} is negative + * @since 8.0 + */ + public List leastOf(Iterable iterable, int k) { + if (iterable instanceof Collection) { + Collection collection = (Collection) iterable; + if (collection.size() <= 2L * k) { + // In this case, just dumping the collection to an array and sorting is + // faster than using the implementation for Iterator, which is + // specialized for k much smaller than n. + + @SuppressWarnings("unchecked") // c only contains E's and doesn't escape + E[] array = (E[]) collection.toArray(); + Arrays.sort(array, this); + if (array.length > k) { + array = Arrays.copyOf(array, k); + } + return Collections.unmodifiableList(Arrays.asList(array)); + } + } + return leastOf(iterable.iterator(), k); + } + + /** + * Returns the {@code k} least elements from the given iterator according to this ordering, in + * order from least to greatest. If there are fewer than {@code k} elements present, all will be + * included. + * + *

The implementation does not necessarily use a stable sorting algorithm; when multiple + * elements are equivalent, it is undefined which will come first. + * + *

Java 8 users: Use {@code Streams.stream(iterator).collect(Comparators.least(k, + * thisComparator))} instead. + * + * @return an immutable {@code RandomAccess} list of the {@code k} least elements in ascending + * order + * @throws IllegalArgumentException if {@code k} is negative + * @since 14.0 + */ + public List leastOf(Iterator iterator, int k) { + checkNotNull(iterator); + checkNonnegative(k, "k"); + + if (k == 0 || !iterator.hasNext()) { + return Collections.emptyList(); + } else if (k >= Integer.MAX_VALUE / 2) { + // k is really large; just do a straightforward sorted-copy-and-sublist + ArrayList list = Lists.newArrayList(iterator); + Collections.sort(list, this); + if (list.size() > k) { + list.subList(k, list.size()).clear(); + } + list.trimToSize(); + return Collections.unmodifiableList(list); + } else { + TopKSelector selector = TopKSelector.least(k, this); + selector.offerAll(iterator); + return selector.topK(); + } + } + + /** + * Returns the {@code k} greatest elements of the given iterable according to this ordering, in + * order from greatest to least. If there are fewer than {@code k} elements present, all will be + * included. + * + *

The implementation does not necessarily use a stable sorting algorithm; when multiple + * elements are equivalent, it is undefined which will come first. + * + *

Java 8 users: Use {@code Streams.stream(iterable).collect(Comparators.greatest(k, + * thisComparator))} instead. + * + * @return an immutable {@code RandomAccess} list of the {@code k} greatest elements in + * descending order + * @throws IllegalArgumentException if {@code k} is negative + * @since 8.0 + */ + public List greatestOf(Iterable iterable, int k) { + // TODO(kevinb): see if delegation is hurting performance noticeably + // TODO(kevinb): if we change this implementation, add full unit tests. + return reverse().leastOf(iterable, k); + } + + /** + * Returns the {@code k} greatest elements from the given iterator according to this ordering, in + * order from greatest to least. If there are fewer than {@code k} elements present, all will be + * included. + * + *

The implementation does not necessarily use a stable sorting algorithm; when multiple + * elements are equivalent, it is undefined which will come first. + * + *

Java 8 users: Use {@code Streams.stream(iterator).collect(Comparators.greatest(k, + * thisComparator))} instead. + * + * @return an immutable {@code RandomAccess} list of the {@code k} greatest elements in + * descending order + * @throws IllegalArgumentException if {@code k} is negative + * @since 14.0 + */ + public List greatestOf(Iterator iterator, int k) { + return reverse().leastOf(iterator, k); + } + + /** + * Returns a mutable list containing {@code elements} sorted by this ordering; use this + * only when the resulting list may need further modification, or may contain {@code null}. The + * input is not modified. The returned list is serializable and has random access. + * + *

Unlike {@link Sets#newTreeSet(Iterable)}, this method does not discard elements that are + * duplicates according to the comparator. The sort performed is stable, meaning that such + * elements will appear in the returned list in the same order they appeared in {@code elements}. + * + *

Performance note: According to our + * benchmarking + * on Open JDK 7, {@link #immutableSortedCopy} generally performs better (in both time and space) + * than this method, and this method in turn generally performs better than copying the list and + * calling {@link Collections#sort(List)}. + */ + // TODO(kevinb): rerun benchmarks including new options + public List sortedCopy(Iterable elements) { + @SuppressWarnings("unchecked") // does not escape, and contains only E's + E[] array = (E[]) Iterables.toArray(elements); + Arrays.sort(array, this); + return Lists.newArrayList(Arrays.asList(array)); + } + + /** + * Returns an immutable list containing {@code elements} sorted by this ordering. The input + * is not modified. + * + *

Unlike {@link Sets#newTreeSet(Iterable)}, this method does not discard elements that are + * duplicates according to the comparator. The sort performed is stable, meaning that such + * elements will appear in the returned list in the same order they appeared in {@code elements}. + * + *

Performance note: According to our + * benchmarking + * on Open JDK 7, this method is the most efficient way to make a sorted copy of a collection. + * + * @throws NullPointerException if any element of {@code elements} is {@code null} + * @since 3.0 + */ + // TODO(kevinb): rerun benchmarks including new options + public ImmutableList immutableSortedCopy(Iterable elements) { + return ImmutableList.sortedCopyOf(this, elements); + } + + /** + * Returns {@code true} if each element in {@code iterable} after the first is greater than or + * equal to the element that preceded it, according to this ordering. Note that this is always + * true when the iterable has fewer than two elements. + * + *

Java 8 users: Use the equivalent {@link Comparators#isInOrder(Iterable, Comparator)} + * instead, since the rest of {@code Ordering} is mostly obsolete (as explained in the class + * documentation). + */ + public boolean isOrdered(Iterable iterable) { + Iterator it = iterable.iterator(); + if (it.hasNext()) { + T prev = it.next(); + while (it.hasNext()) { + T next = it.next(); + if (compare(prev, next) > 0) { + return false; + } + prev = next; + } + } + return true; + } + + /** + * Returns {@code true} if each element in {@code iterable} after the first is strictly + * greater than the element that preceded it, according to this ordering. Note that this is always + * true when the iterable has fewer than two elements. + * + *

Java 8 users: Use the equivalent {@link Comparators#isInStrictOrder(Iterable, + * Comparator)} instead, since the rest of {@code Ordering} is mostly obsolete (as explained in + * the class documentation). + */ + public boolean isStrictlyOrdered(Iterable iterable) { + Iterator it = iterable.iterator(); + if (it.hasNext()) { + T prev = it.next(); + while (it.hasNext()) { + T next = it.next(); + if (compare(prev, next) >= 0) { + return false; + } + prev = next; + } + } + return true; + } + + /** + * {@link Collections#binarySearch(List, Object, Comparator) Searches} {@code sortedList} for + * {@code key} using the binary search algorithm. The list must be sorted using this ordering. + * + * @param sortedList the list to be searched + * @param key the key to be searched for + * @deprecated Use {@link Collections#binarySearch(List, Object, Comparator)} directly. + */ + @Deprecated + public int binarySearch(List sortedList, T key) { + return Collections.binarySearch(sortedList, key, this); + } + + /** + * Exception thrown by a {@link Ordering#explicit(List)} or {@link Ordering#explicit(Object, + * Object[])} comparator when comparing a value outside the set of values it can compare. + * Extending {@link ClassCastException} may seem odd, but it is required. + */ + @VisibleForTesting + static class IncomparableValueException extends ClassCastException { + final Object value; + + IncomparableValueException(Object value) { + super("Cannot compare value: " + value); + this.value = value; + } + + private static final long serialVersionUID = 0; + } + + // Never make these public + static final int LEFT_IS_GREATER = 1; + static final int RIGHT_IS_GREATER = -1; +} diff --git a/src/main/java/com/google/common/collect/PeekingIterator.java b/src/main/java/com/google/common/collect/PeekingIterator.java new file mode 100644 index 0000000..12a80d3 --- /dev/null +++ b/src/main/java/com/google/common/collect/PeekingIterator.java @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2008 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import com.google.common.annotations.GwtCompatible; + +import java.util.Iterator; +import java.util.NoSuchElementException; + +/** + * An iterator that supports a one-element lookahead while iterating. + * + *

See the Guava User Guide article on {@code + * PeekingIterator}. + * + * @author Mick Killianey + * @since 2.0 + */ +@GwtCompatible +public interface PeekingIterator extends Iterator { + /** + * Returns the next element in the iteration, without advancing the iteration. + * + *

Calls to {@code peek()} should not change the state of the iteration, except that it + * may prevent removal of the most recent element via {@link #remove()}. + * + * @throws NoSuchElementException if the iteration has no more elements according to {@link + * #hasNext()} + */ + E peek(); + + /** + * {@inheritDoc} + * + *

The objects returned by consecutive calls to {@link #peek()} then {@link #next()} are + * guaranteed to be equal to each other. + */ + + @Override + E next(); + + /** + * {@inheritDoc} + * + *

Implementations may or may not support removal when a call to {@link #peek()} has occurred + * since the most recent call to {@link #next()}. + * + * @throws IllegalStateException if there has been a call to {@link #peek()} since the most recent + * call to {@link #next()} and this implementation does not support this sequence of calls + * (optional) + */ + @Override + void remove(); +} diff --git a/src/main/java/com/google/common/collect/Platform.java b/src/main/java/com/google/common/collect/Platform.java new file mode 100644 index 0000000..d3b994f --- /dev/null +++ b/src/main/java/com/google/common/collect/Platform.java @@ -0,0 +1,141 @@ +/* + * Copyright (C) 2008 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import static com.google.common.base.Strings.lenientFormat; +import static java.lang.Boolean.parseBoolean; + +import com.google.common.annotations.GwtCompatible; +import java.lang.reflect.Array; +import java.util.Arrays; +import java.util.Map; +import java.util.Set; + +/** + * Methods factored out so that they can be emulated differently in GWT. + * + * @author Hayward Chan + */ +@GwtCompatible(emulated = true) +final class Platform { + private static final java.util.logging.Logger logger = + java.util.logging.Logger.getLogger(Platform.class.getName()); + + /** Returns the platform preferred implementation of a map based on a hash table. */ + static Map newHashMapWithExpectedSize(int expectedSize) { + return Maps.newHashMapWithExpectedSize(expectedSize); + } + + /** + * Returns the platform preferred implementation of an insertion ordered map based on a hash + * table. + */ + static Map newLinkedHashMapWithExpectedSize(int expectedSize) { + return Maps.newLinkedHashMapWithExpectedSize(expectedSize); + } + + /** Returns the platform preferred implementation of a set based on a hash table. */ + static Set newHashSetWithExpectedSize(int expectedSize) { + return Sets.newHashSetWithExpectedSize(expectedSize); + } + + /** + * Returns the platform preferred implementation of an insertion ordered set based on a hash + * table. + */ + static Set newLinkedHashSetWithExpectedSize(int expectedSize) { + return Sets.newLinkedHashSetWithExpectedSize(expectedSize); + } + + /** + * Returns the platform preferred map implementation that preserves insertion order when used only + * for insertions. + */ + static Map preservesInsertionOrderOnPutsMap() { + return Maps.newLinkedHashMap(); + } + + /** + * Returns the platform preferred set implementation that preserves insertion order when used only + * for insertions. + */ + static Set preservesInsertionOrderOnAddsSet() { + return Sets.newLinkedHashSet(); + } + + /** + * Returns a new array of the given length with the same type as a reference array. + * + * @param reference any array of the desired type + * @param length the length of the new array + */ + static T[] newArray(T[] reference, int length) { + Class type = reference.getClass().getComponentType(); + + // the cast is safe because + // result.getClass() == reference.getClass().getComponentType() + @SuppressWarnings("unchecked") + T[] result = (T[]) Array.newInstance(type, length); + return result; + } + + /** Equivalent to Arrays.copyOfRange(source, from, to, arrayOfType.getClass()). */ + static T[] copy(Object[] source, int from, int to, T[] arrayOfType) { + return Arrays.copyOfRange(source, from, to, (Class) arrayOfType.getClass()); + } + + /** + * Configures the given map maker to use weak keys, if possible; does nothing otherwise (i.e., in + * GWT). This is sometimes acceptable, when only server-side code could generate enough volume + * that reclamation becomes important. + */ + static MapMaker tryWeakKeys(MapMaker mapMaker) { + return mapMaker.weakKeys(); + } + + static int reduceIterationsIfGwt(int iterations) { + return iterations; + } + + static int reduceExponentIfGwt(int exponent) { + return exponent; + } + + private static final String GWT_RPC_PROPERTY_NAME = "guava.gwt.emergency_reenable_rpc"; + + static void checkGwtRpcEnabled() { + if (!parseBoolean(System.getProperty(GWT_RPC_PROPERTY_NAME, "true"))) { + throw new UnsupportedOperationException( + lenientFormat( + "We are removing GWT-RPC support for Guava types. You can temporarily reenable" + + " support by setting the system property %s to true. For more about system" + + " properties, see %s. For more about Guava's GWT-RPC support, see %s.", + GWT_RPC_PROPERTY_NAME, + "https://stackoverflow.com/q/5189914/28465", + "https://groups.google.com/d/msg/guava-announce/zHZTFg7YF3o/rQNnwdHeEwAJ")); + } + logger.log( + java.util.logging.Level.WARNING, + "In January 2020, we will remove GWT-RPC support for Guava types. You are seeing this" + + " warning because you are sending a Guava type over GWT-RPC, which will break. You" + + " can identify which type by looking at the class name in the attached stack trace.", + new Throwable()); + + } + + private Platform() {} +} diff --git a/src/main/java/com/google/common/collect/Queues.java b/src/main/java/com/google/common/collect/Queues.java new file mode 100644 index 0000000..738e3fa --- /dev/null +++ b/src/main/java/com/google/common/collect/Queues.java @@ -0,0 +1,471 @@ +/* + * Copyright (C) 2011 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.collect; + +import com.google.common.annotations.Beta; +import com.google.common.annotations.GwtCompatible; +import com.google.common.annotations.GwtIncompatible; +import com.google.common.base.Preconditions; + +import java.util.ArrayDeque; +import java.util.Collection; +import java.util.Deque; +import java.util.PriorityQueue; +import java.util.Queue; +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.LinkedBlockingDeque; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.PriorityBlockingQueue; +import java.util.concurrent.SynchronousQueue; +import java.util.concurrent.TimeUnit; + +/** + * Static utility methods pertaining to {@link Queue} and {@link Deque} instances. Also see this + * class's counterparts {@link Lists}, {@link Sets}, and {@link Maps}. + * + * @author Kurt Alfred Kluever + * @since 11.0 + */ +@GwtCompatible(emulated = true) +public final class Queues { + private Queues() {} + + // ArrayBlockingQueue + + /** + * Creates an empty {@code ArrayBlockingQueue} with the given (fixed) capacity and nonfair access + * policy. + */ + @GwtIncompatible // ArrayBlockingQueue + public static ArrayBlockingQueue newArrayBlockingQueue(int capacity) { + return new ArrayBlockingQueue(capacity); + } + + // ArrayDeque + + /** + * Creates an empty {@code ArrayDeque}. + * + * @since 12.0 + */ + public static ArrayDeque newArrayDeque() { + return new ArrayDeque(); + } + + /** + * Creates an {@code ArrayDeque} containing the elements of the specified iterable, in the order + * they are returned by the iterable's iterator. + * + * @since 12.0 + */ + public static ArrayDeque newArrayDeque(Iterable elements) { + if (elements instanceof Collection) { + return new ArrayDeque(Collections2.cast(elements)); + } + ArrayDeque deque = new ArrayDeque(); + Iterables.addAll(deque, elements); + return deque; + } + + // ConcurrentLinkedQueue + + /** Creates an empty {@code ConcurrentLinkedQueue}. */ + @GwtIncompatible // ConcurrentLinkedQueue + public static ConcurrentLinkedQueue newConcurrentLinkedQueue() { + return new ConcurrentLinkedQueue(); + } + + /** + * Creates a {@code ConcurrentLinkedQueue} containing the elements of the specified iterable, in + * the order they are returned by the iterable's iterator. + */ + @GwtIncompatible // ConcurrentLinkedQueue + public static ConcurrentLinkedQueue newConcurrentLinkedQueue( + Iterable elements) { + if (elements instanceof Collection) { + return new ConcurrentLinkedQueue(Collections2.cast(elements)); + } + ConcurrentLinkedQueue queue = new ConcurrentLinkedQueue(); + Iterables.addAll(queue, elements); + return queue; + } + + // LinkedBlockingDeque + + /** + * Creates an empty {@code LinkedBlockingDeque} with a capacity of {@link Integer#MAX_VALUE}. + * + * @since 12.0 + */ + @GwtIncompatible // LinkedBlockingDeque + public static LinkedBlockingDeque newLinkedBlockingDeque() { + return new LinkedBlockingDeque(); + } + + /** + * Creates an empty {@code LinkedBlockingDeque} with the given (fixed) capacity. + * + * @throws IllegalArgumentException if {@code capacity} is less than 1 + * @since 12.0 + */ + @GwtIncompatible // LinkedBlockingDeque + public static LinkedBlockingDeque newLinkedBlockingDeque(int capacity) { + return new LinkedBlockingDeque(capacity); + } + + /** + * Creates a {@code LinkedBlockingDeque} with a capacity of {@link Integer#MAX_VALUE}, containing + * the elements of the specified iterable, in the order they are returned by the iterable's + * iterator. + * + * @since 12.0 + */ + @GwtIncompatible // LinkedBlockingDeque + public static LinkedBlockingDeque newLinkedBlockingDeque(Iterable elements) { + if (elements instanceof Collection) { + return new LinkedBlockingDeque(Collections2.cast(elements)); + } + LinkedBlockingDeque deque = new LinkedBlockingDeque(); + Iterables.addAll(deque, elements); + return deque; + } + + // LinkedBlockingQueue + + /** Creates an empty {@code LinkedBlockingQueue} with a capacity of {@link Integer#MAX_VALUE}. */ + @GwtIncompatible // LinkedBlockingQueue + public static LinkedBlockingQueue newLinkedBlockingQueue() { + return new LinkedBlockingQueue(); + } + + /** + * Creates an empty {@code LinkedBlockingQueue} with the given (fixed) capacity. + * + * @throws IllegalArgumentException if {@code capacity} is less than 1 + */ + @GwtIncompatible // LinkedBlockingQueue + public static LinkedBlockingQueue newLinkedBlockingQueue(int capacity) { + return new LinkedBlockingQueue(capacity); + } + + /** + * Creates a {@code LinkedBlockingQueue} with a capacity of {@link Integer#MAX_VALUE}, containing + * the elements of the specified iterable, in the order they are returned by the iterable's + * iterator. + * + * @param elements the elements that the queue should contain, in order + * @return a new {@code LinkedBlockingQueue} containing those elements + */ + @GwtIncompatible // LinkedBlockingQueue + public static LinkedBlockingQueue newLinkedBlockingQueue(Iterable elements) { + if (elements instanceof Collection) { + return new LinkedBlockingQueue(Collections2.cast(elements)); + } + LinkedBlockingQueue queue = new LinkedBlockingQueue(); + Iterables.addAll(queue, elements); + return queue; + } + + // LinkedList: see {@link com.google.common.collect.Lists} + + // PriorityBlockingQueue + + /** + * Creates an empty {@code PriorityBlockingQueue} with the ordering given by its elements' natural + * ordering. + * + * @since 11.0 (requires that {@code E} be {@code Comparable} since 15.0). + */ + @GwtIncompatible // PriorityBlockingQueue + public static PriorityBlockingQueue newPriorityBlockingQueue() { + return new PriorityBlockingQueue(); + } + + /** + * Creates a {@code PriorityBlockingQueue} containing the given elements. + * + *

Note: If the specified iterable is a {@code SortedSet} or a {@code PriorityQueue}, + * this priority queue will be ordered according to the same ordering. + * + * @since 11.0 (requires that {@code E} be {@code Comparable} since 15.0). + */ + @GwtIncompatible // PriorityBlockingQueue + public static PriorityBlockingQueue newPriorityBlockingQueue( + Iterable elements) { + if (elements instanceof Collection) { + return new PriorityBlockingQueue(Collections2.cast(elements)); + } + PriorityBlockingQueue queue = new PriorityBlockingQueue(); + Iterables.addAll(queue, elements); + return queue; + } + + // PriorityQueue + + /** + * Creates an empty {@code PriorityQueue} with the ordering given by its elements' natural + * ordering. + * + * @since 11.0 (requires that {@code E} be {@code Comparable} since 15.0). + */ + public static PriorityQueue newPriorityQueue() { + return new PriorityQueue(); + } + + /** + * Creates a {@code PriorityQueue} containing the given elements. + * + *

Note: If the specified iterable is a {@code SortedSet} or a {@code PriorityQueue}, + * this priority queue will be ordered according to the same ordering. + * + * @since 11.0 (requires that {@code E} be {@code Comparable} since 15.0). + */ + public static PriorityQueue newPriorityQueue( + Iterable elements) { + if (elements instanceof Collection) { + return new PriorityQueue(Collections2.cast(elements)); + } + PriorityQueue queue = new PriorityQueue(); + Iterables.addAll(queue, elements); + return queue; + } + + // SynchronousQueue + + /** Creates an empty {@code SynchronousQueue} with nonfair access policy. */ + @GwtIncompatible // SynchronousQueue + public static SynchronousQueue newSynchronousQueue() { + return new SynchronousQueue(); + } + + /** + * Drains the queue as {@link BlockingQueue#drainTo(Collection, int)}, but if the requested {@code + * numElements} elements are not available, it will wait for them up to the specified timeout. + * + * @param q the blocking queue to be drained + * @param buffer where to add the transferred elements + * @param numElements the number of elements to be waited for + * @param timeout how long to wait before giving up + * @return the number of elements transferred + * @throws InterruptedException if interrupted while waiting + * @since 28.0 + */ + @Beta + + @GwtIncompatible // BlockingQueue + public static int drain( + BlockingQueue q, Collection buffer, int numElements, java.time.Duration timeout) + throws InterruptedException { + // TODO(b/126049426): Consider using saturateToNanos(timeout) instead. + return drain(q, buffer, numElements, timeout.toNanos(), TimeUnit.NANOSECONDS); + } + + /** + * Drains the queue as {@link BlockingQueue#drainTo(Collection, int)}, but if the requested {@code + * numElements} elements are not available, it will wait for them up to the specified timeout. + * + * @param q the blocking queue to be drained + * @param buffer where to add the transferred elements + * @param numElements the number of elements to be waited for + * @param timeout how long to wait before giving up, in units of {@code unit} + * @param unit a {@code TimeUnit} determining how to interpret the timeout parameter + * @return the number of elements transferred + * @throws InterruptedException if interrupted while waiting + */ + @Beta + + @GwtIncompatible // BlockingQueue + @SuppressWarnings("GoodTime") // should accept a java.time.Duration + public static int drain( + BlockingQueue q, + Collection buffer, + int numElements, + long timeout, + TimeUnit unit) + throws InterruptedException { + Preconditions.checkNotNull(buffer); + /* + * This code performs one System.nanoTime() more than necessary, and in return, the time to + * execute Queue#drainTo is not added *on top* of waiting for the timeout (which could make + * the timeout arbitrarily inaccurate, given a queue that is slow to drain). + */ + long deadline = System.nanoTime() + unit.toNanos(timeout); + int added = 0; + while (added < numElements) { + // we could rely solely on #poll, but #drainTo might be more efficient when there are multiple + // elements already available (e.g. LinkedBlockingQueue#drainTo locks only once) + added += q.drainTo(buffer, numElements - added); + if (added < numElements) { // not enough elements immediately available; will have to poll + E e = q.poll(deadline - System.nanoTime(), TimeUnit.NANOSECONDS); + if (e == null) { + break; // we already waited enough, and there are no more elements in sight + } + buffer.add(e); + added++; + } + } + return added; + } + + /** + * Drains the queue as {@linkplain #drain(BlockingQueue, Collection, int, Duration)}, but with a + * different behavior in case it is interrupted while waiting. In that case, the operation will + * continue as usual, and in the end the thread's interruption status will be set (no {@code + * InterruptedException} is thrown). + * + * @param q the blocking queue to be drained + * @param buffer where to add the transferred elements + * @param numElements the number of elements to be waited for + * @param timeout how long to wait before giving up + * @return the number of elements transferred + * @since 28.0 + */ + @Beta + + @GwtIncompatible // BlockingQueue + public static int drainUninterruptibly( + BlockingQueue q, + Collection buffer, + int numElements, + java.time.Duration timeout) { + // TODO(b/126049426): Consider using saturateToNanos(timeout) instead. + return drainUninterruptibly(q, buffer, numElements, timeout.toNanos(), TimeUnit.NANOSECONDS); + } + + /** + * Drains the queue as {@linkplain #drain(BlockingQueue, Collection, int, long, TimeUnit)}, but + * with a different behavior in case it is interrupted while waiting. In that case, the operation + * will continue as usual, and in the end the thread's interruption status will be set (no {@code + * InterruptedException} is thrown). + * + * @param q the blocking queue to be drained + * @param buffer where to add the transferred elements + * @param numElements the number of elements to be waited for + * @param timeout how long to wait before giving up, in units of {@code unit} + * @param unit a {@code TimeUnit} determining how to interpret the timeout parameter + * @return the number of elements transferred + */ + @Beta + + @GwtIncompatible // BlockingQueue + @SuppressWarnings("GoodTime") // should accept a java.time.Duration + public static int drainUninterruptibly( + BlockingQueue q, + Collection buffer, + int numElements, + long timeout, + TimeUnit unit) { + Preconditions.checkNotNull(buffer); + long deadline = System.nanoTime() + unit.toNanos(timeout); + int added = 0; + boolean interrupted = false; + try { + while (added < numElements) { + // we could rely solely on #poll, but #drainTo might be more efficient when there are + // multiple elements already available (e.g. LinkedBlockingQueue#drainTo locks only once) + added += q.drainTo(buffer, numElements - added); + if (added < numElements) { // not enough elements immediately available; will have to poll + E e; // written exactly once, by a successful (uninterrupted) invocation of #poll + while (true) { + try { + e = q.poll(deadline - System.nanoTime(), TimeUnit.NANOSECONDS); + break; + } catch (InterruptedException ex) { + interrupted = true; // note interruption and retry + } + } + if (e == null) { + break; // we already waited enough, and there are no more elements in sight + } + buffer.add(e); + added++; + } + } + } finally { + if (interrupted) { + Thread.currentThread().interrupt(); + } + } + return added; + } + + /** + * Returns a synchronized (thread-safe) queue backed by the specified queue. In order to guarantee + * serial access, it is critical that all access to the backing queue is accomplished + * through the returned queue. + * + *

It is imperative that the user manually synchronize on the returned queue when accessing the + * queue's iterator: + * + *

{@code
+   * Queue queue = Queues.synchronizedQueue(MinMaxPriorityQueue.create());
+   * ...
+   * queue.add(element);  // Needn't be in synchronized block
+   * ...
+   * synchronized (queue) {  // Must synchronize on queue!
+   *   Iterator i = queue.iterator(); // Must be in synchronized block
+   *   while (i.hasNext()) {
+   *     foo(i.next());
+   *   }
+   * }
+   * }
+ * + *

Failure to follow this advice may result in non-deterministic behavior. + * + *

The returned queue will be serializable if the specified queue is serializable. + * + * @param queue the queue to be wrapped in a synchronized view + * @return a synchronized view of the specified queue + * @since 14.0 + */ + public static Queue synchronizedQueue(Queue queue) { + return Synchronized.queue(queue, null); + } + + /** + * Returns a synchronized (thread-safe) deque backed by the specified deque. In order to guarantee + * serial access, it is critical that all access to the backing deque is accomplished + * through the returned deque. + * + *

It is imperative that the user manually synchronize on the returned deque when accessing any + * of the deque's iterators: + * + *

{@code
+   * Deque deque = Queues.synchronizedDeque(Queues.newArrayDeque());
+   * ...
+   * deque.add(element);  // Needn't be in synchronized block
+   * ...
+   * synchronized (deque) {  // Must synchronize on deque!
+   *   Iterator i = deque.iterator(); // Must be in synchronized block
+   *   while (i.hasNext()) {
+   *     foo(i.next());
+   *   }
+   * }
+   * }
+ * + *

Failure to follow this advice may result in non-deterministic behavior. + * + *

The returned deque will be serializable if the specified deque is serializable. + * + * @param deque the deque to be wrapped in a synchronized view + * @return a synchronized view of the specified deque + * @since 15.0 + */ + public static Deque synchronizedDeque(Deque deque) { + return Synchronized.deque(deque, null); + } +} diff --git a/src/main/java/com/google/common/collect/Range.java b/src/main/java/com/google/common/collect/Range.java new file mode 100644 index 0000000..9c32f14 --- /dev/null +++ b/src/main/java/com/google/common/collect/Range.java @@ -0,0 +1,714 @@ +/* + * Copyright (C) 2008 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.annotations.GwtCompatible; +import com.google.common.base.Equivalence; +import com.google.common.base.Function; +import com.google.common.base.Predicate; +import java.io.Serializable; +import java.util.Comparator; +import java.util.Iterator; +import java.util.NoSuchElementException; +import java.util.SortedSet; + + +/** + * A range (or "interval") defines the boundaries around a contiguous span of values of some + * {@code Comparable} type; for example, "integers from 1 to 100 inclusive." Note that it is not + * possible to iterate over these contained values. To do so, pass this range instance and an + * appropriate {@link DiscreteDomain} to {@link ContiguousSet#create}. + * + *

Types of ranges

+ * + *

Each end of the range may be bounded or unbounded. If bounded, there is an associated + * endpoint value, and the range is considered to be either open (does not include the + * endpoint) or closed (includes the endpoint) on that side. With three possibilities on each + * side, this yields nine basic types of ranges, enumerated below. (Notation: a square bracket + * ({@code [ ]}) indicates that the range is closed on that side; a parenthesis ({@code ( )}) means + * it is either open or unbounded. The construct {@code {x | statement}} is read "the set of all + * x such that statement.") + * + *

+ * + * + * + *
Range Types
Notation Definition Factory method + *
{@code (a..b)} {@code {x | a < x < b}} {@link Range#open open} + *
{@code [a..b]} {@code {x | a <= x <= b}}{@link Range#closed closed} + *
{@code (a..b]} {@code {x | a < x <= b}} {@link Range#openClosed openClosed} + *
{@code [a..b)} {@code {x | a <= x < b}} {@link Range#closedOpen closedOpen} + *
{@code (a..+∞)} {@code {x | x > a}} {@link Range#greaterThan greaterThan} + *
{@code [a..+∞)} {@code {x | x >= a}} {@link Range#atLeast atLeast} + *
{@code (-∞..b)} {@code {x | x < b}} {@link Range#lessThan lessThan} + *
{@code (-∞..b]} {@code {x | x <= b}} {@link Range#atMost atMost} + *
{@code (-∞..+∞)}{@code {x}} {@link Range#all all} + *
+ * + *
+ * + *

When both endpoints exist, the upper endpoint may not be less than the lower. The endpoints + * may be equal only if at least one of the bounds is closed: + * + *

    + *
  • {@code [a..a]} : a singleton range + *
  • {@code [a..a); (a..a]} : {@linkplain #isEmpty empty} ranges; also valid + *
  • {@code (a..a)} : invalid; an exception will be thrown + *
+ * + *

Warnings

+ * + *
    + *
  • Use immutable value types only, if at all possible. If you must use a mutable type, do + * not allow the endpoint instances to mutate after the range is created! + *
  • Your value type's comparison method should be {@linkplain Comparable consistent with + * equals} if at all possible. Otherwise, be aware that concepts used throughout this + * documentation such as "equal", "same", "unique" and so on actually refer to whether {@link + * Comparable#compareTo compareTo} returns zero, not whether {@link Object#equals equals} + * returns {@code true}. + *
  • A class which implements {@code Comparable} is very broken, and will cause + * undefined horrible things to happen in {@code Range}. For now, the Range API does not + * prevent its use, because this would also rule out all ungenerified (pre-JDK1.5) data types. + * This may change in the future. + *
+ * + *

Other notes

+ * + *
    + *
  • Instances of this type are obtained using the static factory methods in this class. + *
  • Ranges are convex: whenever two values are contained, all values in between them + * must also be contained. More formally, for any {@code c1 <= c2 <= c3} of type {@code C}, + * {@code r.contains(c1) && r.contains(c3)} implies {@code r.contains(c2)}). This means that a + * {@code Range} can never be used to represent, say, "all prime numbers from + * 1 to 100." + *
  • When evaluated as a {@link Predicate}, a range yields the same result as invoking {@link + * #contains}. + *
  • Terminology note: a range {@code a} is said to be the maximal range having property + * P if, for all ranges {@code b} also having property P, {@code a.encloses(b)}. + * Likewise, {@code a} is minimal when {@code b.encloses(a)} for all {@code b} having + * property P. See, for example, the definition of {@link #intersection intersection}. + *
+ * + *

Further reading

+ * + *

See the Guava User Guide article on {@code Range}. + * + * @author Kevin Bourrillion + * @author Gregory Kick + * @since 10.0 + */ +@GwtCompatible +@SuppressWarnings("rawtypes") +public final class Range extends RangeGwtSerializationDependencies + implements Predicate, Serializable { + + static class LowerBoundFn implements Function { + static final LowerBoundFn INSTANCE = new LowerBoundFn(); + + @Override + public Cut apply(Range range) { + return range.lowerBound; + } + } + + static class UpperBoundFn implements Function { + static final UpperBoundFn INSTANCE = new UpperBoundFn(); + + @Override + public Cut apply(Range range) { + return range.upperBound; + } + } + + @SuppressWarnings("unchecked") + static > Function, Cut> lowerBoundFn() { + return (Function) LowerBoundFn.INSTANCE; + } + + @SuppressWarnings("unchecked") + static > Function, Cut> upperBoundFn() { + return (Function) UpperBoundFn.INSTANCE; + } + + static > Ordering> rangeLexOrdering() { + return (Ordering>) (Ordering) RangeLexOrdering.INSTANCE; + } + + static > Range create(Cut lowerBound, Cut upperBound) { + return new Range(lowerBound, upperBound); + } + + /** + * Returns a range that contains all values strictly greater than {@code lower} and strictly less + * than {@code upper}. + * + * @throws IllegalArgumentException if {@code lower} is greater than or equal to {@code + * upper} + * @throws ClassCastException if {@code lower} and {@code upper} are not mutually comparable + * @since 14.0 + */ + public static > Range open(C lower, C upper) { + return create(Cut.aboveValue(lower), Cut.belowValue(upper)); + } + + /** + * Returns a range that contains all values greater than or equal to {@code lower} and less than + * or equal to {@code upper}. + * + * @throws IllegalArgumentException if {@code lower} is greater than {@code upper} + * @throws ClassCastException if {@code lower} and {@code upper} are not mutually comparable + * @since 14.0 + */ + public static > Range closed(C lower, C upper) { + return create(Cut.belowValue(lower), Cut.aboveValue(upper)); + } + + /** + * Returns a range that contains all values greater than or equal to {@code lower} and strictly + * less than {@code upper}. + * + * @throws IllegalArgumentException if {@code lower} is greater than {@code upper} + * @throws ClassCastException if {@code lower} and {@code upper} are not mutually comparable + * @since 14.0 + */ + public static > Range closedOpen(C lower, C upper) { + return create(Cut.belowValue(lower), Cut.belowValue(upper)); + } + + /** + * Returns a range that contains all values strictly greater than {@code lower} and less than or + * equal to {@code upper}. + * + * @throws IllegalArgumentException if {@code lower} is greater than {@code upper} + * @throws ClassCastException if {@code lower} and {@code upper} are not mutually comparable + * @since 14.0 + */ + public static > Range openClosed(C lower, C upper) { + return create(Cut.aboveValue(lower), Cut.aboveValue(upper)); + } + + /** + * Returns a range that contains any value from {@code lower} to {@code upper}, where each + * endpoint may be either inclusive (closed) or exclusive (open). + * + * @throws IllegalArgumentException if {@code lower} is greater than {@code upper} + * @throws ClassCastException if {@code lower} and {@code upper} are not mutually comparable + * @since 14.0 + */ + public static > Range range( + C lower, BoundType lowerType, C upper, BoundType upperType) { + checkNotNull(lowerType); + checkNotNull(upperType); + + Cut lowerBound = + (lowerType == BoundType.OPEN) ? Cut.aboveValue(lower) : Cut.belowValue(lower); + Cut upperBound = + (upperType == BoundType.OPEN) ? Cut.belowValue(upper) : Cut.aboveValue(upper); + return create(lowerBound, upperBound); + } + + /** + * Returns a range that contains all values strictly less than {@code endpoint}. + * + * @since 14.0 + */ + public static > Range lessThan(C endpoint) { + return create(Cut.belowAll(), Cut.belowValue(endpoint)); + } + + /** + * Returns a range that contains all values less than or equal to {@code endpoint}. + * + * @since 14.0 + */ + public static > Range atMost(C endpoint) { + return create(Cut.belowAll(), Cut.aboveValue(endpoint)); + } + + /** + * Returns a range with no lower bound up to the given endpoint, which may be either inclusive + * (closed) or exclusive (open). + * + * @since 14.0 + */ + public static > Range upTo(C endpoint, BoundType boundType) { + switch (boundType) { + case OPEN: + return lessThan(endpoint); + case CLOSED: + return atMost(endpoint); + default: + throw new AssertionError(); + } + } + + /** + * Returns a range that contains all values strictly greater than {@code endpoint}. + * + * @since 14.0 + */ + public static > Range greaterThan(C endpoint) { + return create(Cut.aboveValue(endpoint), Cut.aboveAll()); + } + + /** + * Returns a range that contains all values greater than or equal to {@code endpoint}. + * + * @since 14.0 + */ + public static > Range atLeast(C endpoint) { + return create(Cut.belowValue(endpoint), Cut.aboveAll()); + } + + /** + * Returns a range from the given endpoint, which may be either inclusive (closed) or exclusive + * (open), with no upper bound. + * + * @since 14.0 + */ + public static > Range downTo(C endpoint, BoundType boundType) { + switch (boundType) { + case OPEN: + return greaterThan(endpoint); + case CLOSED: + return atLeast(endpoint); + default: + throw new AssertionError(); + } + } + + private static final Range ALL = new Range<>(Cut.belowAll(), Cut.aboveAll()); + + /** + * Returns a range that contains every value of type {@code C}. + * + * @since 14.0 + */ + @SuppressWarnings("unchecked") + public static > Range all() { + return (Range) ALL; + } + + /** + * Returns a range that {@linkplain Range#contains(Comparable) contains} only the given value. The + * returned range is {@linkplain BoundType#CLOSED closed} on both ends. + * + * @since 14.0 + */ + public static > Range singleton(C value) { + return closed(value, value); + } + + /** + * Returns the minimal range that {@linkplain Range#contains(Comparable) contains} all of the + * given values. The returned range is {@linkplain BoundType#CLOSED closed} on both ends. + * + * @throws ClassCastException if the values are not mutually comparable + * @throws NoSuchElementException if {@code values} is empty + * @throws NullPointerException if any of {@code values} is null + * @since 14.0 + */ + public static > Range encloseAll(Iterable values) { + checkNotNull(values); + if (values instanceof SortedSet) { + SortedSet set = cast(values); + Comparator comparator = set.comparator(); + if (Ordering.natural().equals(comparator) || comparator == null) { + return closed(set.first(), set.last()); + } + } + Iterator valueIterator = values.iterator(); + C min = checkNotNull(valueIterator.next()); + C max = min; + while (valueIterator.hasNext()) { + C value = checkNotNull(valueIterator.next()); + min = Ordering.natural().min(min, value); + max = Ordering.natural().max(max, value); + } + return closed(min, max); + } + + final Cut lowerBound; + final Cut upperBound; + + private Range(Cut lowerBound, Cut upperBound) { + this.lowerBound = checkNotNull(lowerBound); + this.upperBound = checkNotNull(upperBound); + if (lowerBound.compareTo(upperBound) > 0 + || lowerBound == Cut.aboveAll() + || upperBound == Cut.belowAll()) { + throw new IllegalArgumentException("Invalid range: " + toString(lowerBound, upperBound)); + } + } + + /** Returns {@code true} if this range has a lower endpoint. */ + public boolean hasLowerBound() { + return lowerBound != Cut.belowAll(); + } + + /** + * Returns the lower endpoint of this range. + * + * @throws IllegalStateException if this range is unbounded below (that is, {@link + * #hasLowerBound()} returns {@code false}) + */ + public C lowerEndpoint() { + return lowerBound.endpoint(); + } + + /** + * Returns the type of this range's lower bound: {@link BoundType#CLOSED} if the range includes + * its lower endpoint, {@link BoundType#OPEN} if it does not. + * + * @throws IllegalStateException if this range is unbounded below (that is, {@link + * #hasLowerBound()} returns {@code false}) + */ + public BoundType lowerBoundType() { + return lowerBound.typeAsLowerBound(); + } + + /** Returns {@code true} if this range has an upper endpoint. */ + public boolean hasUpperBound() { + return upperBound != Cut.aboveAll(); + } + + /** + * Returns the upper endpoint of this range. + * + * @throws IllegalStateException if this range is unbounded above (that is, {@link + * #hasUpperBound()} returns {@code false}) + */ + public C upperEndpoint() { + return upperBound.endpoint(); + } + + /** + * Returns the type of this range's upper bound: {@link BoundType#CLOSED} if the range includes + * its upper endpoint, {@link BoundType#OPEN} if it does not. + * + * @throws IllegalStateException if this range is unbounded above (that is, {@link + * #hasUpperBound()} returns {@code false}) + */ + public BoundType upperBoundType() { + return upperBound.typeAsUpperBound(); + } + + /** + * Returns {@code true} if this range is of the form {@code [v..v)} or {@code (v..v]}. (This does + * not encompass ranges of the form {@code (v..v)}, because such ranges are invalid and + * can't be constructed at all.) + * + *

Note that certain discrete ranges such as the integer range {@code (3..4)} are not + * considered empty, even though they contain no actual values. In these cases, it may be helpful + * to preprocess ranges with {@link #canonical(DiscreteDomain)}. + */ + public boolean isEmpty() { + return lowerBound.equals(upperBound); + } + + /** + * Returns {@code true} if {@code value} is within the bounds of this range. For example, on the + * range {@code [0..2)}, {@code contains(1)} returns {@code true}, while {@code contains(2)} + * returns {@code false}. + */ + public boolean contains(C value) { + checkNotNull(value); + // let this throw CCE if there is some trickery going on + return lowerBound.isLessThan(value) && !upperBound.isLessThan(value); + } + + /** + * @deprecated Provided only to satisfy the {@link Predicate} interface; use {@link #contains} + * instead. + */ + @Deprecated + @Override + public boolean apply(C input) { + return contains(input); + } + + /** + * Returns {@code true} if every element in {@code values} is {@linkplain #contains contained} in + * this range. + */ + public boolean containsAll(Iterable values) { + if (Iterables.isEmpty(values)) { + return true; + } + + // this optimizes testing equality of two range-backed sets + if (values instanceof SortedSet) { + SortedSet set = cast(values); + Comparator comparator = set.comparator(); + if (Ordering.natural().equals(comparator) || comparator == null) { + return contains(set.first()) && contains(set.last()); + } + } + + for (C value : values) { + if (!contains(value)) { + return false; + } + } + return true; + } + + /** + * Returns {@code true} if the bounds of {@code other} do not extend outside the bounds of this + * range. Examples: + * + *

    + *
  • {@code [3..6]} encloses {@code [4..5]} + *
  • {@code (3..6)} encloses {@code (3..6)} + *
  • {@code [3..6]} encloses {@code [4..4)} (even though the latter is empty) + *
  • {@code (3..6]} does not enclose {@code [3..6]} + *
  • {@code [4..5]} does not enclose {@code (3..6)} (even though it contains every value + * contained by the latter range) + *
  • {@code [3..6]} does not enclose {@code (1..1]} (even though it contains every value + * contained by the latter range) + *
+ * + *

Note that if {@code a.encloses(b)}, then {@code b.contains(v)} implies {@code + * a.contains(v)}, but as the last two examples illustrate, the converse is not always true. + * + *

Being reflexive, antisymmetric and transitive, the {@code encloses} relation defines a + * partial order over ranges. There exists a unique {@linkplain Range#all maximal} range + * according to this relation, and also numerous {@linkplain #isEmpty minimal} ranges. Enclosure + * also implies {@linkplain #isConnected connectedness}. + */ + public boolean encloses(Range other) { + return lowerBound.compareTo(other.lowerBound) <= 0 + && upperBound.compareTo(other.upperBound) >= 0; + } + + /** + * Returns {@code true} if there exists a (possibly empty) range which is {@linkplain #encloses + * enclosed} by both this range and {@code other}. + * + *

For example, + * + *

    + *
  • {@code [2, 4)} and {@code [5, 7)} are not connected + *
  • {@code [2, 4)} and {@code [3, 5)} are connected, because both enclose {@code [3, 4)} + *
  • {@code [2, 4)} and {@code [4, 6)} are connected, because both enclose the empty range + * {@code [4, 4)} + *
+ * + *

Note that this range and {@code other} have a well-defined {@linkplain #span union} and + * {@linkplain #intersection intersection} (as a single, possibly-empty range) if and only if this + * method returns {@code true}. + * + *

The connectedness relation is both reflexive and symmetric, but does not form an {@linkplain + * Equivalence equivalence relation} as it is not transitive. + * + *

Note that certain discrete ranges are not considered connected, even though there are no + * elements "between them." For example, {@code [3, 5]} is not considered connected to {@code [6, + * 10]}. In these cases, it may be desirable for both input ranges to be preprocessed with {@link + * #canonical(DiscreteDomain)} before testing for connectedness. + */ + public boolean isConnected(Range other) { + return lowerBound.compareTo(other.upperBound) <= 0 + && other.lowerBound.compareTo(upperBound) <= 0; + } + + /** + * Returns the maximal range {@linkplain #encloses enclosed} by both this range and {@code + * connectedRange}, if such a range exists. + * + *

For example, the intersection of {@code [1..5]} and {@code (3..7)} is {@code (3..5]}. The + * resulting range may be empty; for example, {@code [1..5)} intersected with {@code [5..7)} + * yields the empty range {@code [5..5)}. + * + *

The intersection exists if and only if the two ranges are {@linkplain #isConnected + * connected}. + * + *

The intersection operation is commutative, associative and idempotent, and its identity + * element is {@link Range#all}). + * + * @throws IllegalArgumentException if {@code isConnected(connectedRange)} is {@code false} + */ + public Range intersection(Range connectedRange) { + int lowerCmp = lowerBound.compareTo(connectedRange.lowerBound); + int upperCmp = upperBound.compareTo(connectedRange.upperBound); + if (lowerCmp >= 0 && upperCmp <= 0) { + return this; + } else if (lowerCmp <= 0 && upperCmp >= 0) { + return connectedRange; + } else { + Cut newLower = (lowerCmp >= 0) ? lowerBound : connectedRange.lowerBound; + Cut newUpper = (upperCmp <= 0) ? upperBound : connectedRange.upperBound; + return create(newLower, newUpper); + } + } + + /** + * Returns the maximal range lying between this range and {@code otherRange}, if such a range + * exists. The resulting range may be empty if the two ranges are adjacent but non-overlapping. + * + *

For example, the gap of {@code [1..5]} and {@code (7..10)} is {@code (5..7]}. The resulting + * range may be empty; for example, the gap between {@code [1..5)} {@code [5..7)} yields the empty + * range {@code [5..5)}. + * + *

The gap exists if and only if the two ranges are either disconnected or immediately adjacent + * (any intersection must be an empty range). + * + *

The gap operation is commutative. + * + * @throws IllegalArgumentException if this range and {@code otherRange} have a nonempty + * intersection + * @since 27.0 + */ + public Range gap(Range otherRange) { + boolean isThisFirst = this.lowerBound.compareTo(otherRange.lowerBound) < 0; + Range firstRange = isThisFirst ? this : otherRange; + Range secondRange = isThisFirst ? otherRange : this; + return create(firstRange.upperBound, secondRange.lowerBound); + } + + /** + * Returns the minimal range that {@linkplain #encloses encloses} both this range and {@code + * other}. For example, the span of {@code [1..3]} and {@code (5..7)} is {@code [1..7)}. + * + *

If the input ranges are {@linkplain #isConnected connected}, the returned range can + * also be called their union. If they are not, note that the span might contain values + * that are not contained in either input range. + * + *

Like {@link #intersection(Range) intersection}, this operation is commutative, associative + * and idempotent. Unlike it, it is always well-defined for any two input ranges. + */ + public Range span(Range other) { + int lowerCmp = lowerBound.compareTo(other.lowerBound); + int upperCmp = upperBound.compareTo(other.upperBound); + if (lowerCmp <= 0 && upperCmp >= 0) { + return this; + } else if (lowerCmp >= 0 && upperCmp <= 0) { + return other; + } else { + Cut newLower = (lowerCmp <= 0) ? lowerBound : other.lowerBound; + Cut newUpper = (upperCmp >= 0) ? upperBound : other.upperBound; + return create(newLower, newUpper); + } + } + + /** + * Returns the canonical form of this range in the given domain. The canonical form has the + * following properties: + * + *

    + *
  • equivalence: {@code a.canonical().contains(v) == a.contains(v)} for all {@code v} (in + * other words, {@code ContiguousSet.create(a.canonical(domain), domain).equals( + * ContiguousSet.create(a, domain))} + *
  • uniqueness: unless {@code a.isEmpty()}, {@code ContiguousSet.create(a, + * domain).equals(ContiguousSet.create(b, domain))} implies {@code + * a.canonical(domain).equals(b.canonical(domain))} + *
  • idempotence: {@code a.canonical(domain).canonical(domain).equals(a.canonical(domain))} + *
+ * + *

Furthermore, this method guarantees that the range returned will be one of the following + * canonical forms: + * + *

    + *
  • [start..end) + *
  • [start..+∞) + *
  • (-∞..end) (only if type {@code C} is unbounded below) + *
  • (-∞..+∞) (only if type {@code C} is unbounded below) + *
+ */ + public Range canonical(DiscreteDomain domain) { + checkNotNull(domain); + Cut lower = lowerBound.canonical(domain); + Cut upper = upperBound.canonical(domain); + return (lower == lowerBound && upper == upperBound) ? this : create(lower, upper); + } + + /** + * Returns {@code true} if {@code object} is a range having the same endpoints and bound types as + * this range. Note that discrete ranges such as {@code (1..4)} and {@code [2..3]} are not + * equal to one another, despite the fact that they each contain precisely the same set of values. + * Similarly, empty ranges are not equal unless they have exactly the same representation, so + * {@code [3..3)}, {@code (3..3]}, {@code (4..4]} are all unequal. + */ + @Override + public boolean equals(Object object) { + if (object instanceof Range) { + Range other = (Range) object; + return lowerBound.equals(other.lowerBound) && upperBound.equals(other.upperBound); + } + return false; + } + + /** Returns a hash code for this range. */ + @Override + public int hashCode() { + return lowerBound.hashCode() * 31 + upperBound.hashCode(); + } + + /** + * Returns a string representation of this range, such as {@code "[3..5)"} (other examples are + * listed in the class documentation). + */ + @Override + public String toString() { + return toString(lowerBound, upperBound); + } + + private static String toString(Cut lowerBound, Cut upperBound) { + StringBuilder sb = new StringBuilder(16); + lowerBound.describeAsLowerBound(sb); + sb.append(".."); + upperBound.describeAsUpperBound(sb); + return sb.toString(); + } + + /** Used to avoid http://bugs.sun.com/view_bug.do?bug_id=6558557 */ + private static SortedSet cast(Iterable iterable) { + return (SortedSet) iterable; + } + + Object readResolve() { + if (this.equals(ALL)) { + return all(); + } else { + return this; + } + } + + @SuppressWarnings("unchecked") // this method may throw CCE + static int compareOrThrow(Comparable left, Comparable right) { + return left.compareTo(right); + } + + /** Needed to serialize sorted collections of Ranges. */ + private static class RangeLexOrdering extends Ordering> implements Serializable { + static final Ordering> INSTANCE = new RangeLexOrdering(); + + @Override + public int compare(Range left, Range right) { + return ComparisonChain.start() + .compare(left.lowerBound, right.lowerBound) + .compare(left.upperBound, right.upperBound) + .result(); + } + + private static final long serialVersionUID = 0; + } + + private static final long serialVersionUID = 0; +} diff --git a/src/main/java/com/google/common/collect/RangeGwtSerializationDependencies.java b/src/main/java/com/google/common/collect/RangeGwtSerializationDependencies.java new file mode 100644 index 0000000..222c128 --- /dev/null +++ b/src/main/java/com/google/common/collect/RangeGwtSerializationDependencies.java @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2016 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import com.google.common.annotations.GwtCompatible; +import java.io.Serializable; + +/** + * A dummy superclass to support GWT serialization of the element type of a {@link Range}. The GWT + * supersource for this class contains a field of type {@code C}. + * + *

For details about this hack, see {@code GwtSerializationDependencies}, which takes the same + * approach but with a subclass rather than a superclass. + * + *

TODO(cpovirk): Consider applying this subclass approach to our other types. + */ +@GwtCompatible(emulated = true) +abstract class RangeGwtSerializationDependencies implements Serializable {} diff --git a/src/main/java/com/google/common/collect/RangeMap.java b/src/main/java/com/google/common/collect/RangeMap.java new file mode 100644 index 0000000..2104020 --- /dev/null +++ b/src/main/java/com/google/common/collect/RangeMap.java @@ -0,0 +1,185 @@ +/* + * Copyright (C) 2012 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import com.google.common.annotations.Beta; +import com.google.common.annotations.GwtIncompatible; +import java.util.Collection; +import java.util.Map; +import java.util.Map.Entry; +import java.util.NoSuchElementException; +import java.util.function.BiFunction; + + +/** + * A mapping from disjoint nonempty ranges to non-null values. Queries look up the value associated + * with the range (if any) that contains a specified key. + * + *

In contrast to {@link RangeSet}, no "coalescing" is done of {@linkplain + * Range#isConnected(Range) connected} ranges, even if they are mapped to the same value. + * + * @author Louis Wasserman + * @since 14.0 + */ +@Beta +@GwtIncompatible +public interface RangeMap { + /** + * Returns the value associated with the specified key, or {@code null} if there is no such value. + * + *

Specifically, if any range in this range map contains the specified key, the value + * associated with that range is returned. + */ + + V get(K key); + + /** + * Returns the range containing this key and its associated value, if such a range is present in + * the range map, or {@code null} otherwise. + */ + + Entry, V> getEntry(K key); + + /** + * Returns the minimal range {@linkplain Range#encloses(Range) enclosing} the ranges in this + * {@code RangeMap}. + * + * @throws NoSuchElementException if this range map is empty + */ + Range span(); + + /** + * Maps a range to a specified value (optional operation). + * + *

Specifically, after a call to {@code put(range, value)}, if {@link + * Range#contains(Comparable) range.contains(k)}, then {@link #get(Comparable) get(k)} will return + * {@code value}. + * + *

If {@code range} {@linkplain Range#isEmpty() is empty}, then this is a no-op. + */ + void put(Range range, V value); + + /** + * Maps a range to a specified value, coalescing this range with any existing ranges with the same + * value that are {@linkplain Range#isConnected connected} to this range. + * + *

The behavior of {@link #get(Comparable) get(k)} after calling this method is identical to + * the behavior described in {@link #put(Range, Object) put(range, value)}, however the ranges + * returned from {@link #asMapOfRanges} will be different if there were existing entries which + * connect to the given range and value. + * + *

Even if the input range is empty, if it is connected on both sides by ranges mapped to the + * same value those two ranges will be coalesced. + * + *

Note: coalescing requires calling {@code .equals()} on any connected values, which + * may be expensive depending on the value type. Using this method on range maps with large values + * such as {@link Collection} types is discouraged. + * + * @since 22.0 + */ + void putCoalescing(Range range, V value); + + /** Puts all the associations from {@code rangeMap} into this range map (optional operation). */ + void putAll(RangeMap rangeMap); + + /** Removes all associations from this range map (optional operation). */ + void clear(); + + /** + * Removes all associations from this range map in the specified range (optional operation). + * + *

If {@code !range.contains(k)}, {@link #get(Comparable) get(k)} will return the same result + * before and after a call to {@code remove(range)}. If {@code range.contains(k)}, then after a + * call to {@code remove(range)}, {@code get(k)} will return {@code null}. + */ + void remove(Range range); + + /** + * Merges a value into the map over a range by applying a remapping function. + * + *

If any parts of the range are already present in this range map, those parts are mapped to + * new values by applying the remapping function. Any parts of the range not already present in + * this range map are mapped to the specified value, unless the value is {@code null}. + * + *

Any existing map entry spanning either range boundary may be split at the boundary, even if + * the merge does not affect its value. + * + *

For example, if {@code rangeMap} had one entry {@code [1, 5] => 3} then {@code + * rangeMap.merge(Range.closed(0,2), 3, Math::max)} could yield a range map with the entries + * {@code [0, 1) => 3, [1, 2] => 3, (2, 5] => 3}. + * + * @since 28.1 + */ + void merge( + Range range, + V value, + BiFunction remappingFunction); + + /** + * Returns a view of this range map as an unmodifiable {@code Map, V>}. Modifications to + * this range map are guaranteed to read through to the returned {@code Map}. + * + *

The returned {@code Map} iterates over entries in ascending order of the bounds of the + * {@code Range} entries. + * + *

It is guaranteed that no empty ranges will be in the returned {@code Map}. + */ + Map, V> asMapOfRanges(); + + /** + * Returns a view of this range map as an unmodifiable {@code Map, V>}. Modifications to + * this range map are guaranteed to read through to the returned {@code Map}. + * + *

The returned {@code Map} iterates over entries in descending order of the bounds of the + * {@code Range} entries. + * + *

It is guaranteed that no empty ranges will be in the returned {@code Map}. + * + * @since 19.0 + */ + Map, V> asDescendingMapOfRanges(); + + /** + * Returns a view of the part of this range map that intersects with {@code range}. + * + *

For example, if {@code rangeMap} had the entries {@code [1, 5] => "foo", (6, 8) => "bar", + * (10, ∞) => "baz"} then {@code rangeMap.subRangeMap(Range.open(3, 12))} would return a range map + * with the entries {@code (3, 5] => "foo", (6, 8) => "bar", (10, 12) => "baz"}. + * + *

The returned range map supports all optional operations that this range map supports, except + * for {@code asMapOfRanges().iterator().remove()}. + * + *

The returned range map will throw an {@link IllegalArgumentException} on an attempt to + * insert a range not {@linkplain Range#encloses(Range) enclosed} by {@code range}. + */ + RangeMap subRangeMap(Range range); + + /** + * Returns {@code true} if {@code obj} is another {@code RangeMap} that has an equivalent {@link + * #asMapOfRanges()}. + */ + @Override + boolean equals(Object o); + + /** Returns {@code asMapOfRanges().hashCode()}. */ + @Override + int hashCode(); + + /** Returns a readable string representation of this range map. */ + @Override + String toString(); +} diff --git a/src/main/java/com/google/common/collect/RangeSet.java b/src/main/java/com/google/common/collect/RangeSet.java new file mode 100644 index 0000000..d81f039 --- /dev/null +++ b/src/main/java/com/google/common/collect/RangeSet.java @@ -0,0 +1,276 @@ +/* + * Copyright (C) 2011 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the + * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import com.google.common.annotations.Beta; +import com.google.common.annotations.GwtIncompatible; +import java.util.NoSuchElementException; +import java.util.Set; + + +/** + * A set comprising zero or more {@linkplain Range#isEmpty nonempty}, {@linkplain + * Range#isConnected(Range) disconnected} ranges of type {@code C}. + * + *

Implementations that choose to support the {@link #add(Range)} operation are required to + * ignore empty ranges and coalesce connected ranges. For example: + * + *

{@code
+ * RangeSet rangeSet = TreeRangeSet.create();
+ * rangeSet.add(Range.closed(1, 10)); // {[1, 10]}
+ * rangeSet.add(Range.closedOpen(11, 15)); // disconnected range; {[1, 10], [11, 15)}
+ * rangeSet.add(Range.closedOpen(15, 20)); // connected range; {[1, 10], [11, 20)}
+ * rangeSet.add(Range.openClosed(0, 0)); // empty range; {[1, 10], [11, 20)}
+ * rangeSet.remove(Range.open(5, 10)); // splits [1, 10]; {[1, 5], [10, 10], [11, 20)}
+ * }
+ * + *

Note that the behavior of {@link Range#isEmpty()} and {@link Range#isConnected(Range)} may not + * be as expected on discrete ranges. See the Javadoc of those methods for details. + * + *

For a {@link Set} whose contents are specified by a {@link Range}, see {@link ContiguousSet}. + * + *

See the Guava User Guide article on RangeSets. + * + * @author Kevin Bourrillion + * @author Louis Wasserman + * @since 14.0 + */ +@Beta +@GwtIncompatible +public interface RangeSet { + // TODO(lowasser): consider adding default implementations of some of these methods + + // Query methods + + /** Determines whether any of this range set's member ranges contains {@code value}. */ + boolean contains(C value); + + /** + * Returns the unique range from this range set that {@linkplain Range#contains contains} {@code + * value}, or {@code null} if this range set does not contain {@code value}. + */ + Range rangeContaining(C value); + + /** + * Returns {@code true} if there exists a non-empty range enclosed by both a member range in this + * range set and the specified range. This is equivalent to calling {@code + * subRangeSet(otherRange)} and testing whether the resulting range set is non-empty. + * + * @since 20.0 + */ + boolean intersects(Range otherRange); + + /** + * Returns {@code true} if there exists a member range in this range set which {@linkplain + * Range#encloses encloses} the specified range. + */ + boolean encloses(Range otherRange); + + /** + * Returns {@code true} if for each member range in {@code other} there exists a member range in + * this range set which {@linkplain Range#encloses encloses} it. It follows that {@code + * this.contains(value)} whenever {@code other.contains(value)}. Returns {@code true} if {@code + * other} is empty. + * + *

This is equivalent to checking if this range set {@link #encloses} each of the ranges in + * {@code other}. + */ + boolean enclosesAll(RangeSet other); + + /** + * Returns {@code true} if for each range in {@code other} there exists a member range in this + * range set which {@linkplain Range#encloses encloses} it. Returns {@code true} if {@code other} + * is empty. + * + *

This is equivalent to checking if this range set {@link #encloses} each range in {@code + * other}. + * + * @since 21.0 + */ + default boolean enclosesAll(Iterable> other) { + for (Range range : other) { + if (!encloses(range)) { + return false; + } + } + return true; + } + + /** Returns {@code true} if this range set contains no ranges. */ + boolean isEmpty(); + + /** + * Returns the minimal range which {@linkplain Range#encloses(Range) encloses} all ranges in this + * range set. + * + * @throws NoSuchElementException if this range set is {@linkplain #isEmpty() empty} + */ + Range span(); + + // Views + + /** + * Returns a view of the {@linkplain Range#isConnected disconnected} ranges that make up this + * range set. The returned set may be empty. The iterators returned by its {@link + * Iterable#iterator} method return the ranges in increasing order of lower bound (equivalently, + * of upper bound). + */ + Set> asRanges(); + + /** + * Returns a descending view of the {@linkplain Range#isConnected disconnected} ranges that make + * up this range set. The returned set may be empty. The iterators returned by its {@link + * Iterable#iterator} method return the ranges in decreasing order of lower bound (equivalently, + * of upper bound). + * + * @since 19.0 + */ + Set> asDescendingSetOfRanges(); + + /** + * Returns a view of the complement of this {@code RangeSet}. + * + *

The returned view supports the {@link #add} operation if this {@code RangeSet} supports + * {@link #remove}, and vice versa. + */ + RangeSet complement(); + + /** + * Returns a view of the intersection of this {@code RangeSet} with the specified range. + * + *

The returned view supports all optional operations supported by this {@code RangeSet}, with + * the caveat that an {@link IllegalArgumentException} is thrown on an attempt to {@linkplain + * #add(Range) add} any range not {@linkplain Range#encloses(Range) enclosed} by {@code view}. + */ + RangeSet subRangeSet(Range view); + + // Modification + + /** + * Adds the specified range to this {@code RangeSet} (optional operation). That is, for equal + * range sets a and b, the result of {@code a.add(range)} is that {@code a} will be the minimal + * range set for which both {@code a.enclosesAll(b)} and {@code a.encloses(range)}. + * + *

Note that {@code range} will be {@linkplain Range#span(Range) coalesced} with any ranges in + * the range set that are {@linkplain Range#isConnected(Range) connected} with it. Moreover, if + * {@code range} is empty, this is a no-op. + * + * @throws UnsupportedOperationException if this range set does not support the {@code add} + * operation + */ + void add(Range range); + + /** + * Removes the specified range from this {@code RangeSet} (optional operation). After this + * operation, if {@code range.contains(c)}, {@code this.contains(c)} will return {@code false}. + * + *

If {@code range} is empty, this is a no-op. + * + * @throws UnsupportedOperationException if this range set does not support the {@code remove} + * operation + */ + void remove(Range range); + + /** + * Removes all ranges from this {@code RangeSet} (optional operation). After this operation, + * {@code this.contains(c)} will return false for all {@code c}. + * + *

This is equivalent to {@code remove(Range.all())}. + * + * @throws UnsupportedOperationException if this range set does not support the {@code clear} + * operation + */ + void clear(); + + /** + * Adds all of the ranges from the specified range set to this range set (optional operation). + * After this operation, this range set is the minimal range set that {@linkplain + * #enclosesAll(RangeSet) encloses} both the original range set and {@code other}. + * + *

This is equivalent to calling {@link #add} on each of the ranges in {@code other} in turn. + * + * @throws UnsupportedOperationException if this range set does not support the {@code addAll} + * operation + */ + void addAll(RangeSet other); + + /** + * Adds all of the specified ranges to this range set (optional operation). After this operation, + * this range set is the minimal range set that {@linkplain #enclosesAll(RangeSet) encloses} both + * the original range set and each range in {@code other}. + * + *

This is equivalent to calling {@link #add} on each of the ranges in {@code other} in turn. + * + * @throws UnsupportedOperationException if this range set does not support the {@code addAll} + * operation + * @since 21.0 + */ + default void addAll(Iterable> ranges) { + for (Range range : ranges) { + add(range); + } + } + + /** + * Removes all of the ranges from the specified range set from this range set (optional + * operation). After this operation, if {@code other.contains(c)}, {@code this.contains(c)} will + * return {@code false}. + * + *

This is equivalent to calling {@link #remove} on each of the ranges in {@code other} in + * turn. + * + * @throws UnsupportedOperationException if this range set does not support the {@code removeAll} + * operation + */ + void removeAll(RangeSet other); + + /** + * Removes all of the specified ranges from this range set (optional operation). + * + *

This is equivalent to calling {@link #remove} on each of the ranges in {@code other} in + * turn. + * + * @throws UnsupportedOperationException if this range set does not support the {@code removeAll} + * operation + * @since 21.0 + */ + default void removeAll(Iterable> ranges) { + for (Range range : ranges) { + remove(range); + } + } + + // Object methods + + /** + * Returns {@code true} if {@code obj} is another {@code RangeSet} that contains the same ranges + * according to {@link Range#equals(Object)}. + */ + @Override + boolean equals(Object obj); + + /** Returns {@code asRanges().hashCode()}. */ + @Override + int hashCode(); + + /** + * Returns a readable string representation of this range set. For example, if this {@code + * RangeSet} consisted of {@code Range.closed(1, 3)} and {@code Range.greaterThan(4)}, this might + * return {@code " [1..3](4..+∞)}"}. + */ + @Override + String toString(); +} diff --git a/src/main/java/com/google/common/collect/RegularContiguousSet.java b/src/main/java/com/google/common/collect/RegularContiguousSet.java new file mode 100644 index 0000000..b1f9840 --- /dev/null +++ b/src/main/java/com/google/common/collect/RegularContiguousSet.java @@ -0,0 +1,238 @@ +/* + * Copyright (C) 2011 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.collect; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkElementIndex; +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.collect.BoundType.CLOSED; + +import com.google.common.annotations.GwtCompatible; +import com.google.common.annotations.GwtIncompatible; +import java.io.Serializable; +import java.util.Collection; + + +/** + * An implementation of {@link ContiguousSet} that contains one or more elements. + * + * @author Gregory Kick + */ +@GwtCompatible(emulated = true) +@SuppressWarnings("unchecked") // allow ungenerified Comparable types +final class RegularContiguousSet extends ContiguousSet { + private final Range range; + + RegularContiguousSet(Range range, DiscreteDomain domain) { + super(domain); + this.range = range; + } + + private ContiguousSet intersectionInCurrentDomain(Range other) { + return range.isConnected(other) + ? ContiguousSet.create(range.intersection(other), domain) + : new EmptyContiguousSet(domain); + } + + @Override + ContiguousSet headSetImpl(C toElement, boolean inclusive) { + return intersectionInCurrentDomain(Range.upTo(toElement, BoundType.forBoolean(inclusive))); + } + + @Override + ContiguousSet subSetImpl( + C fromElement, boolean fromInclusive, C toElement, boolean toInclusive) { + if (fromElement.compareTo(toElement) == 0 && !fromInclusive && !toInclusive) { + // Range would reject our attempt to create (x, x). + return new EmptyContiguousSet(domain); + } + return intersectionInCurrentDomain( + Range.range( + fromElement, BoundType.forBoolean(fromInclusive), + toElement, BoundType.forBoolean(toInclusive))); + } + + @Override + ContiguousSet tailSetImpl(C fromElement, boolean inclusive) { + return intersectionInCurrentDomain(Range.downTo(fromElement, BoundType.forBoolean(inclusive))); + } + + @GwtIncompatible // not used by GWT emulation + @Override + int indexOf(Object target) { + return contains(target) ? (int) domain.distance(first(), (C) target) : -1; + } + + @Override + public UnmodifiableIterator iterator() { + return new AbstractSequentialIterator(first()) { + final C last = last(); + + @Override + protected C computeNext(C previous) { + return equalsOrThrow(previous, last) ? null : domain.next(previous); + } + }; + } + + @GwtIncompatible // NavigableSet + @Override + public UnmodifiableIterator descendingIterator() { + return new AbstractSequentialIterator(last()) { + final C first = first(); + + @Override + protected C computeNext(C previous) { + return equalsOrThrow(previous, first) ? null : domain.previous(previous); + } + }; + } + + private static boolean equalsOrThrow(Comparable left, Comparable right) { + return right != null && Range.compareOrThrow(left, right) == 0; + } + + @Override + boolean isPartialView() { + return false; + } + + @Override + public C first() { + return range.lowerBound.leastValueAbove(domain); + } + + @Override + public C last() { + return range.upperBound.greatestValueBelow(domain); + } + + @Override + ImmutableList createAsList() { + if (domain.supportsFastOffset) { + return new ImmutableAsList() { + @Override + ImmutableSortedSet delegateCollection() { + return RegularContiguousSet.this; + } + + @Override + public C get(int i) { + checkElementIndex(i, size()); + return domain.offset(first(), i); + } + }; + } else { + return super.createAsList(); + } + } + + @Override + public int size() { + long distance = domain.distance(first(), last()); + return (distance >= Integer.MAX_VALUE) ? Integer.MAX_VALUE : (int) distance + 1; + } + + @Override + public boolean contains(Object object) { + if (object == null) { + return false; + } + try { + return range.contains((C) object); + } catch (ClassCastException e) { + return false; + } + } + + @Override + public boolean containsAll(Collection targets) { + return Collections2.containsAllImpl(this, targets); + } + + @Override + public boolean isEmpty() { + return false; + } + + @Override + public ContiguousSet intersection(ContiguousSet other) { + checkNotNull(other); + checkArgument(this.domain.equals(other.domain)); + if (other.isEmpty()) { + return other; + } else { + C lowerEndpoint = Ordering.natural().max(this.first(), other.first()); + C upperEndpoint = Ordering.natural().min(this.last(), other.last()); + return (lowerEndpoint.compareTo(upperEndpoint) <= 0) + ? ContiguousSet.create(Range.closed(lowerEndpoint, upperEndpoint), domain) + : new EmptyContiguousSet(domain); + } + } + + @Override + public Range range() { + return range(CLOSED, CLOSED); + } + + @Override + public Range range(BoundType lowerBoundType, BoundType upperBoundType) { + return Range.create( + range.lowerBound.withLowerBoundType(lowerBoundType, domain), + range.upperBound.withUpperBoundType(upperBoundType, domain)); + } + + @Override + public boolean equals(Object object) { + if (object == this) { + return true; + } else if (object instanceof RegularContiguousSet) { + RegularContiguousSet that = (RegularContiguousSet) object; + if (this.domain.equals(that.domain)) { + return this.first().equals(that.first()) && this.last().equals(that.last()); + } + } + return super.equals(object); + } + + // copied to make sure not to use the GWT-emulated version + @Override + public int hashCode() { + return Sets.hashCodeImpl(this); + } + + @GwtIncompatible // serialization + private static final class SerializedForm implements Serializable { + final Range range; + final DiscreteDomain domain; + + private SerializedForm(Range range, DiscreteDomain domain) { + this.range = range; + this.domain = domain; + } + + private Object readResolve() { + return new RegularContiguousSet(range, domain); + } + } + + @GwtIncompatible // serialization + @Override + Object writeReplace() { + return new SerializedForm(range, domain); + } + + private static final long serialVersionUID = 0; +} diff --git a/src/main/java/com/google/common/collect/RegularImmutableAsList.java b/src/main/java/com/google/common/collect/RegularImmutableAsList.java new file mode 100644 index 0000000..df17b6e --- /dev/null +++ b/src/main/java/com/google/common/collect/RegularImmutableAsList.java @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2012 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import com.google.common.annotations.GwtCompatible; +import com.google.common.annotations.GwtIncompatible; +import java.util.function.Consumer; + +/** + * An {@link ImmutableAsList} implementation specialized for when the delegate collection is already + * backed by an {@code ImmutableList} or array. + * + * @author Louis Wasserman + */ +@GwtCompatible(emulated = true) +@SuppressWarnings("serial") // uses writeReplace, not default serialization +class RegularImmutableAsList extends ImmutableAsList { + private final ImmutableCollection delegate; + private final ImmutableList delegateList; + + RegularImmutableAsList(ImmutableCollection delegate, ImmutableList delegateList) { + this.delegate = delegate; + this.delegateList = delegateList; + } + + RegularImmutableAsList(ImmutableCollection delegate, Object[] array) { + this(delegate, ImmutableList.asImmutableList(array)); + } + + @Override + ImmutableCollection delegateCollection() { + return delegate; + } + + ImmutableList delegateList() { + return delegateList; + } + + @SuppressWarnings("unchecked") // safe covariant cast! + @Override + public UnmodifiableListIterator listIterator(int index) { + return (UnmodifiableListIterator) delegateList.listIterator(index); + } + + @GwtIncompatible // not present in emulated superclass + @Override + public void forEach(Consumer action) { + delegateList.forEach(action); + } + + @GwtIncompatible // not present in emulated superclass + @Override + int copyIntoArray(Object[] dst, int offset) { + return delegateList.copyIntoArray(dst, offset); + } + + @Override + Object[] internalArray() { + return delegateList.internalArray(); + } + + @Override + int internalArrayStart() { + return delegateList.internalArrayStart(); + } + + @Override + int internalArrayEnd() { + return delegateList.internalArrayEnd(); + } + + @Override + public E get(int index) { + return delegateList.get(index); + } +} diff --git a/src/main/java/com/google/common/collect/RegularImmutableBiMap.java b/src/main/java/com/google/common/collect/RegularImmutableBiMap.java new file mode 100644 index 0000000..8bca4fc --- /dev/null +++ b/src/main/java/com/google/common/collect/RegularImmutableBiMap.java @@ -0,0 +1,303 @@ +/* + * Copyright (C) 2008 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.base.Preconditions.checkPositionIndex; +import static com.google.common.collect.CollectPreconditions.checkEntryNotNull; +import static com.google.common.collect.ImmutableMapEntry.createEntryArray; +import static com.google.common.collect.RegularImmutableMap.checkNoConflictInKeyBucket; + +import com.google.common.annotations.GwtCompatible; +import com.google.common.annotations.VisibleForTesting; +import com.google.common.collect.ImmutableMapEntry.NonTerminalImmutableBiMapEntry; + + + +import java.io.Serializable; +import java.util.function.BiConsumer; +import java.util.function.Consumer; + + +/** + * Bimap with zero or more mappings. + * + * @author Louis Wasserman + */ +@GwtCompatible(serializable = true, emulated = true) +@SuppressWarnings("serial") // uses writeReplace(), not default serialization +class RegularImmutableBiMap extends ImmutableBiMap { + static final RegularImmutableBiMap EMPTY = + new RegularImmutableBiMap<>( + null, null, (Entry[]) ImmutableMap.EMPTY_ENTRY_ARRAY, 0, 0); + + static final double MAX_LOAD_FACTOR = 1.2; + + private final transient ImmutableMapEntry[] keyTable; + private final transient ImmutableMapEntry[] valueTable; + @VisibleForTesting final transient Entry[] entries; + private final transient int mask; + private final transient int hashCode; + + static ImmutableBiMap fromEntries(Entry... entries) { + return fromEntryArray(entries.length, entries); + } + + static ImmutableBiMap fromEntryArray(int n, Entry[] entryArray) { + checkPositionIndex(n, entryArray.length); + int tableSize = Hashing.closedTableSize(n, MAX_LOAD_FACTOR); + int mask = tableSize - 1; + ImmutableMapEntry[] keyTable = createEntryArray(tableSize); + ImmutableMapEntry[] valueTable = createEntryArray(tableSize); + Entry[] entries; + if (n == entryArray.length) { + entries = entryArray; + } else { + entries = createEntryArray(n); + } + int hashCode = 0; + + for (int i = 0; i < n; i++) { + @SuppressWarnings("unchecked") + Entry entry = entryArray[i]; + K key = entry.getKey(); + V value = entry.getValue(); + checkEntryNotNull(key, value); + int keyHash = key.hashCode(); + int valueHash = value.hashCode(); + int keyBucket = Hashing.smear(keyHash) & mask; + int valueBucket = Hashing.smear(valueHash) & mask; + + ImmutableMapEntry nextInKeyBucket = keyTable[keyBucket]; + int keyBucketLength = checkNoConflictInKeyBucket(key, entry, nextInKeyBucket); + ImmutableMapEntry nextInValueBucket = valueTable[valueBucket]; + int valueBucketLength = checkNoConflictInValueBucket(value, entry, nextInValueBucket); + if (keyBucketLength > RegularImmutableMap.MAX_HASH_BUCKET_LENGTH + || valueBucketLength > RegularImmutableMap.MAX_HASH_BUCKET_LENGTH) { + return JdkBackedImmutableBiMap.create(n, entryArray); + } + ImmutableMapEntry newEntry = + (nextInValueBucket == null && nextInKeyBucket == null) + ? RegularImmutableMap.makeImmutable(entry, key, value) + : new NonTerminalImmutableBiMapEntry<>( + key, value, nextInKeyBucket, nextInValueBucket); + keyTable[keyBucket] = newEntry; + valueTable[valueBucket] = newEntry; + entries[i] = newEntry; + hashCode += keyHash ^ valueHash; + } + return new RegularImmutableBiMap<>(keyTable, valueTable, entries, mask, hashCode); + } + + private RegularImmutableBiMap( + ImmutableMapEntry[] keyTable, + ImmutableMapEntry[] valueTable, + Entry[] entries, + int mask, + int hashCode) { + this.keyTable = keyTable; + this.valueTable = valueTable; + this.entries = entries; + this.mask = mask; + this.hashCode = hashCode; + } + + // checkNoConflictInKeyBucket is static imported from RegularImmutableMap + + /** + * @return number of entries in this bucket + * @throws IllegalArgumentException if another entry in the bucket has the same key + */ + + private static int checkNoConflictInValueBucket( + Object value, Entry entry, ImmutableMapEntry valueBucketHead) { + int bucketSize = 0; + for (; valueBucketHead != null; valueBucketHead = valueBucketHead.getNextInValueBucket()) { + checkNoConflict(!value.equals(valueBucketHead.getValue()), "value", entry, valueBucketHead); + bucketSize++; + } + return bucketSize; + } + + @Override + public V get(Object key) { + return (keyTable == null) ? null : RegularImmutableMap.get(key, keyTable, mask); + } + + @Override + ImmutableSet> createEntrySet() { + return isEmpty() + ? ImmutableSet.>of() + : new ImmutableMapEntrySet.RegularEntrySet(this, entries); + } + + @Override + ImmutableSet createKeySet() { + return new ImmutableMapKeySet<>(this); + } + + @Override + public void forEach(BiConsumer action) { + checkNotNull(action); + for (Entry entry : entries) { + action.accept(entry.getKey(), entry.getValue()); + } + } + + @Override + boolean isHashCodeFast() { + return true; + } + + @Override + public int hashCode() { + return hashCode; + } + + @Override + boolean isPartialView() { + return false; + } + + @Override + public int size() { + return entries.length; + } + + private transient ImmutableBiMap inverse; + + @Override + public ImmutableBiMap inverse() { + if (isEmpty()) { + return ImmutableBiMap.of(); + } + ImmutableBiMap result = inverse; + return (result == null) ? inverse = new Inverse() : result; + } + + private final class Inverse extends ImmutableBiMap { + + @Override + public int size() { + return inverse().size(); + } + + @Override + public ImmutableBiMap inverse() { + return RegularImmutableBiMap.this; + } + + @Override + public void forEach(BiConsumer action) { + checkNotNull(action); + RegularImmutableBiMap.this.forEach((k, v) -> action.accept(v, k)); + } + + @Override + public K get(Object value) { + if (value == null || valueTable == null) { + return null; + } + int bucket = Hashing.smear(value.hashCode()) & mask; + for (ImmutableMapEntry entry = valueTable[bucket]; + entry != null; + entry = entry.getNextInValueBucket()) { + if (value.equals(entry.getValue())) { + return entry.getKey(); + } + } + return null; + } + + @Override + ImmutableSet createKeySet() { + return new ImmutableMapKeySet<>(this); + } + + @Override + ImmutableSet> createEntrySet() { + return new InverseEntrySet(); + } + + final class InverseEntrySet extends ImmutableMapEntrySet { + @Override + ImmutableMap map() { + return Inverse.this; + } + + @Override + boolean isHashCodeFast() { + return true; + } + + @Override + public int hashCode() { + return hashCode; + } + + @Override + public UnmodifiableIterator> iterator() { + return asList().iterator(); + } + + @Override + public void forEach(Consumer> action) { + asList().forEach(action); + } + + @Override + ImmutableList> createAsList() { + return new ImmutableAsList>() { + @Override + public Entry get(int index) { + Entry entry = entries[index]; + return Maps.immutableEntry(entry.getValue(), entry.getKey()); + } + + @Override + ImmutableCollection> delegateCollection() { + return InverseEntrySet.this; + } + }; + } + } + + @Override + boolean isPartialView() { + return false; + } + + @Override + Object writeReplace() { + return new InverseSerializedForm<>(RegularImmutableBiMap.this); + } + } + + private static class InverseSerializedForm implements Serializable { + private final ImmutableBiMap forward; + + InverseSerializedForm(ImmutableBiMap forward) { + this.forward = forward; + } + + Object readResolve() { + return forward.inverse(); + } + + private static final long serialVersionUID = 1; + } +} diff --git a/src/main/java/com/google/common/collect/RegularImmutableList.java b/src/main/java/com/google/common/collect/RegularImmutableList.java new file mode 100644 index 0000000..47f42d5 --- /dev/null +++ b/src/main/java/com/google/common/collect/RegularImmutableList.java @@ -0,0 +1,92 @@ +/* + * Copyright (C) 2009 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import com.google.common.annotations.GwtCompatible; +import com.google.common.annotations.VisibleForTesting; +import java.util.Spliterator; +import java.util.Spliterators; + +/** + * Implementation of {@link ImmutableList} backed by a simple array. + * + * @author Kevin Bourrillion + */ +@GwtCompatible(serializable = true, emulated = true) +@SuppressWarnings("serial") // uses writeReplace(), not default serialization +class RegularImmutableList extends ImmutableList { + static final ImmutableList EMPTY = new RegularImmutableList<>(new Object[0]); + + @VisibleForTesting final transient Object[] array; + + RegularImmutableList(Object[] array) { + this.array = array; + } + + @Override + public int size() { + return array.length; + } + + @Override + boolean isPartialView() { + return false; + } + + @Override + Object[] internalArray() { + return array; + } + + @Override + int internalArrayStart() { + return 0; + } + + @Override + int internalArrayEnd() { + return array.length; + } + + @Override + int copyIntoArray(Object[] dst, int dstOff) { + System.arraycopy(array, 0, dst, dstOff, array.length); + return dstOff + array.length; + } + + // The fake cast to E is safe because the creation methods only allow E's + @Override + @SuppressWarnings("unchecked") + public E get(int index) { + return (E) array[index]; + } + + @SuppressWarnings("unchecked") + @Override + public UnmodifiableListIterator listIterator(int index) { + // for performance + // The fake cast to E is safe because the creation methods only allow E's + return (UnmodifiableListIterator) Iterators.forArray(array, 0, array.length, index); + } + + @Override + public Spliterator spliterator() { + return Spliterators.spliterator(array, SPLITERATOR_CHARACTERISTICS); + } + + // TODO(lowasser): benchmark optimizations for equals() and see if they're worthwhile +} diff --git a/src/main/java/com/google/common/collect/RegularImmutableMap.java b/src/main/java/com/google/common/collect/RegularImmutableMap.java new file mode 100644 index 0000000..4639bfd --- /dev/null +++ b/src/main/java/com/google/common/collect/RegularImmutableMap.java @@ -0,0 +1,312 @@ +/* + * Copyright (C) 2008 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.base.Preconditions.checkPositionIndex; +import static com.google.common.collect.CollectPreconditions.checkEntryNotNull; +import static com.google.common.collect.ImmutableMapEntry.createEntryArray; + +import com.google.common.annotations.GwtCompatible; +import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.VisibleForTesting; +import com.google.common.collect.ImmutableMapEntry.NonTerminalImmutableMapEntry; + +import java.io.Serializable; +import java.util.function.BiConsumer; + + +/** + * Implementation of {@link ImmutableMap} with two or more entries. + * + * @author Jesse Wilson + * @author Kevin Bourrillion + * @author Gregory Kick + */ +@GwtCompatible(serializable = true, emulated = true) +final class RegularImmutableMap extends ImmutableMap { + @SuppressWarnings("unchecked") + static final ImmutableMap EMPTY = + new RegularImmutableMap<>((Entry[]) ImmutableMap.EMPTY_ENTRY_ARRAY, null, 0); + + /** + * Closed addressing tends to perform well even with high load factors. Being conservative here + * ensures that the table is still likely to be relatively sparse (hence it misses fast) while + * saving space. + */ + @VisibleForTesting static final double MAX_LOAD_FACTOR = 1.2; + + /** + * Maximum allowed false positive probability of detecting a hash flooding attack given random + * input. + */ + @VisibleForTesting static final double HASH_FLOODING_FPP = 0.001; + + /** + * Maximum allowed length of a hash table bucket before falling back to a j.u.HashMap based + * implementation. Experimentally determined. + */ + @VisibleForTesting static final int MAX_HASH_BUCKET_LENGTH = 8; + + // entries in insertion order + @VisibleForTesting final transient Entry[] entries; + // array of linked lists of entries + private final transient ImmutableMapEntry[] table; + // 'and' with an int to get a table index + private final transient int mask; + + static ImmutableMap fromEntries(Entry... entries) { + return fromEntryArray(entries.length, entries); + } + + /** + * Creates an ImmutableMap from the first n entries in entryArray. This implementation may replace + * the entries in entryArray with its own entry objects (though they will have the same key/value + * contents), and may take ownership of entryArray. + */ + static ImmutableMap fromEntryArray(int n, Entry[] entryArray) { + checkPositionIndex(n, entryArray.length); + if (n == 0) { + return (RegularImmutableMap) EMPTY; + } + Entry[] entries; + if (n == entryArray.length) { + entries = entryArray; + } else { + entries = createEntryArray(n); + } + int tableSize = Hashing.closedTableSize(n, MAX_LOAD_FACTOR); + ImmutableMapEntry[] table = createEntryArray(tableSize); + int mask = tableSize - 1; + for (int entryIndex = 0; entryIndex < n; entryIndex++) { + Entry entry = entryArray[entryIndex]; + K key = entry.getKey(); + V value = entry.getValue(); + checkEntryNotNull(key, value); + int tableIndex = Hashing.smear(key.hashCode()) & mask; + ImmutableMapEntry existing = table[tableIndex]; + // prepend, not append, so the entries can be immutable + ImmutableMapEntry newEntry = + (existing == null) + ? makeImmutable(entry, key, value) + : new NonTerminalImmutableMapEntry(key, value, existing); + table[tableIndex] = newEntry; + entries[entryIndex] = newEntry; + int bucketSize = checkNoConflictInKeyBucket(key, newEntry, existing); + if (bucketSize > MAX_HASH_BUCKET_LENGTH) { + // probable hash flooding attack, fall back to j.u.HM based implementation and use its + // implementation of hash flooding protection + return JdkBackedImmutableMap.create(n, entryArray); + } + } + return new RegularImmutableMap<>(entries, table, mask); + } + + /** Makes an entry usable internally by a new ImmutableMap without rereading its contents. */ + static ImmutableMapEntry makeImmutable(Entry entry, K key, V value) { + boolean reusable = + entry instanceof ImmutableMapEntry && ((ImmutableMapEntry) entry).isReusable(); + return reusable ? (ImmutableMapEntry) entry : new ImmutableMapEntry(key, value); + } + + /** Makes an entry usable internally by a new ImmutableMap. */ + static ImmutableMapEntry makeImmutable(Entry entry) { + return makeImmutable(entry, entry.getKey(), entry.getValue()); + } + + private RegularImmutableMap(Entry[] entries, ImmutableMapEntry[] table, int mask) { + this.entries = entries; + this.table = table; + this.mask = mask; + } + + /** + * @return number of entries in this bucket + * @throws IllegalArgumentException if another entry in the bucket has the same key + */ + + static int checkNoConflictInKeyBucket( + Object key, Entry entry, ImmutableMapEntry keyBucketHead) { + int bucketSize = 0; + for (; keyBucketHead != null; keyBucketHead = keyBucketHead.getNextInKeyBucket()) { + checkNoConflict(!key.equals(keyBucketHead.getKey()), "key", entry, keyBucketHead); + bucketSize++; + } + return bucketSize; + } + + @Override + public V get(Object key) { + return get(key, table, mask); + } + + static V get( + Object key, ImmutableMapEntry [] keyTable, int mask) { + if (key == null || keyTable == null) { + return null; + } + int index = Hashing.smear(key.hashCode()) & mask; + for (ImmutableMapEntry entry = keyTable[index]; + entry != null; + entry = entry.getNextInKeyBucket()) { + Object candidateKey = entry.getKey(); + + /* + * Assume that equals uses the == optimization when appropriate, and that + * it would check hash codes as an optimization when appropriate. If we + * did these things, it would just make things worse for the most + * performance-conscious users. + */ + if (key.equals(candidateKey)) { + return entry.getValue(); + } + } + return null; + } + + @Override + public void forEach(BiConsumer action) { + checkNotNull(action); + for (Entry entry : entries) { + action.accept(entry.getKey(), entry.getValue()); + } + } + + @Override + public int size() { + return entries.length; + } + + @Override + boolean isPartialView() { + return false; + } + + @Override + ImmutableSet> createEntrySet() { + return new ImmutableMapEntrySet.RegularEntrySet<>(this, entries); + } + + @Override + ImmutableSet createKeySet() { + return new KeySet<>(this); + } + + @GwtCompatible(emulated = true) + private static final class KeySet extends IndexedImmutableSet { + private final RegularImmutableMap map; + + KeySet(RegularImmutableMap map) { + this.map = map; + } + + @Override + K get(int index) { + return map.entries[index].getKey(); + } + + @Override + public boolean contains(Object object) { + return map.containsKey(object); + } + + @Override + boolean isPartialView() { + return true; + } + + @Override + public int size() { + return map.size(); + } + + @GwtIncompatible // serialization + @Override + Object writeReplace() { + return new SerializedForm(map); + } + + @GwtIncompatible // serialization + private static class SerializedForm implements Serializable { + final ImmutableMap map; + + SerializedForm(ImmutableMap map) { + this.map = map; + } + + Object readResolve() { + return map.keySet(); + } + + private static final long serialVersionUID = 0; + } + } + + @Override + ImmutableCollection createValues() { + return new Values<>(this); + } + + @GwtCompatible(emulated = true) + private static final class Values extends ImmutableList { + final RegularImmutableMap map; + + Values(RegularImmutableMap map) { + this.map = map; + } + + @Override + public V get(int index) { + return map.entries[index].getValue(); + } + + @Override + public int size() { + return map.size(); + } + + @Override + boolean isPartialView() { + return true; + } + + @GwtIncompatible // serialization + @Override + Object writeReplace() { + return new SerializedForm(map); + } + + @GwtIncompatible // serialization + private static class SerializedForm implements Serializable { + final ImmutableMap map; + + SerializedForm(ImmutableMap map) { + this.map = map; + } + + Object readResolve() { + return map.values(); + } + + private static final long serialVersionUID = 0; + } + } + + // This class is never actually serialized directly, but we have to make the + // warning go away (and suppressing would suppress for all nested classes too) + private static final long serialVersionUID = 0; +} diff --git a/src/main/java/com/google/common/collect/RegularImmutableMultiset.java b/src/main/java/com/google/common/collect/RegularImmutableMultiset.java new file mode 100644 index 0000000..09ff486 --- /dev/null +++ b/src/main/java/com/google/common/collect/RegularImmutableMultiset.java @@ -0,0 +1,195 @@ +/* + * Copyright (C) 2011 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.collect; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.annotations.GwtCompatible; +import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.Objects; +import com.google.common.collect.Multisets.ImmutableEntry; +import com.google.common.primitives.Ints; + +import java.util.Arrays; +import java.util.Collection; + + +/** + * Implementation of {@link ImmutableMultiset} with zero or more elements. + * + * @author Jared Levy + * @author Louis Wasserman + */ +@GwtCompatible(emulated = true, serializable = true) +@SuppressWarnings("serial") // uses writeReplace(), not default serialization +class RegularImmutableMultiset extends ImmutableMultiset { + static final ImmutableMultiset EMPTY = create(ImmutableList.>of()); + + static ImmutableMultiset create(Collection> entries) { + int distinct = entries.size(); + @SuppressWarnings("unchecked") + Multisets.ImmutableEntry[] entryArray = new Multisets.ImmutableEntry[distinct]; + if (distinct == 0) { + return new RegularImmutableMultiset<>(entryArray, null, 0, 0, ImmutableSet.of()); + } + int tableSize = Hashing.closedTableSize(distinct, MAX_LOAD_FACTOR); + int mask = tableSize - 1; + @SuppressWarnings("unchecked") + Multisets.ImmutableEntry[] hashTable = new Multisets.ImmutableEntry[tableSize]; + + int index = 0; + int hashCode = 0; + long size = 0; + for (Entry entry : entries) { + E element = checkNotNull(entry.getElement()); + int count = entry.getCount(); + int hash = element.hashCode(); + int bucket = Hashing.smear(hash) & mask; + Multisets.ImmutableEntry bucketHead = hashTable[bucket]; + Multisets.ImmutableEntry newEntry; + if (bucketHead == null) { + boolean canReuseEntry = + entry instanceof Multisets.ImmutableEntry && !(entry instanceof NonTerminalEntry); + newEntry = + canReuseEntry + ? (Multisets.ImmutableEntry) entry + : new Multisets.ImmutableEntry(element, count); + } else { + newEntry = new NonTerminalEntry(element, count, bucketHead); + } + hashCode += hash ^ count; + entryArray[index++] = newEntry; + hashTable[bucket] = newEntry; + size += count; + } + + return hashFloodingDetected(hashTable) + ? JdkBackedImmutableMultiset.create(ImmutableList.asImmutableList(entryArray)) + : new RegularImmutableMultiset( + entryArray, hashTable, Ints.saturatedCast(size), hashCode, null); + } + + private static boolean hashFloodingDetected(Multisets.ImmutableEntry[] hashTable) { + for (int i = 0; i < hashTable.length; i++) { + int bucketLength = 0; + for (Multisets.ImmutableEntry entry = hashTable[i]; + entry != null; + entry = entry.nextInBucket()) { + bucketLength++; + if (bucketLength > MAX_HASH_BUCKET_LENGTH) { + return true; + } + } + } + return false; + } + + /** + * Closed addressing tends to perform well even with high load factors. Being conservative here + * ensures that the table is still likely to be relatively sparse (hence it misses fast) while + * saving space. + */ + @VisibleForTesting static final double MAX_LOAD_FACTOR = 1.0; + + /** + * Maximum allowed false positive probability of detecting a hash flooding attack given random + * input. + */ + @VisibleForTesting static final double HASH_FLOODING_FPP = 0.001; + + /** + * Maximum allowed length of a hash table bucket before falling back to a j.u.HashMap based + * implementation. Experimentally determined. + */ + @VisibleForTesting static final int MAX_HASH_BUCKET_LENGTH = 9; + + private final transient Multisets.ImmutableEntry[] entries; + private final transient Multisets.ImmutableEntry [] hashTable; + private final transient int size; + private final transient int hashCode; + + private transient ImmutableSet elementSet; + + private RegularImmutableMultiset( + ImmutableEntry[] entries, + ImmutableEntry[] hashTable, + int size, + int hashCode, + ImmutableSet elementSet) { + this.entries = entries; + this.hashTable = hashTable; + this.size = size; + this.hashCode = hashCode; + this.elementSet = elementSet; + } + + private static final class NonTerminalEntry extends Multisets.ImmutableEntry { + private final Multisets.ImmutableEntry nextInBucket; + + NonTerminalEntry(E element, int count, ImmutableEntry nextInBucket) { + super(element, count); + this.nextInBucket = nextInBucket; + } + + @Override + public ImmutableEntry nextInBucket() { + return nextInBucket; + } + } + + @Override + boolean isPartialView() { + return false; + } + + @Override + public int count(Object element) { + Multisets.ImmutableEntry[] hashTable = this.hashTable; + if (element == null || hashTable == null) { + return 0; + } + int hash = Hashing.smearedHash(element); + int mask = hashTable.length - 1; + for (Multisets.ImmutableEntry entry = hashTable[hash & mask]; + entry != null; + entry = entry.nextInBucket()) { + if (Objects.equal(element, entry.getElement())) { + return entry.getCount(); + } + } + return 0; + } + + @Override + public int size() { + return size; + } + + @Override + public ImmutableSet elementSet() { + ImmutableSet result = elementSet; + return (result == null) ? elementSet = new ElementSet(Arrays.asList(entries), this) : result; + } + + @Override + Entry getEntry(int index) { + return entries[index]; + } + + @Override + public int hashCode() { + return hashCode; + } +} diff --git a/src/main/java/com/google/common/collect/RegularImmutableSet.java b/src/main/java/com/google/common/collect/RegularImmutableSet.java new file mode 100644 index 0000000..c3a1b4a --- /dev/null +++ b/src/main/java/com/google/common/collect/RegularImmutableSet.java @@ -0,0 +1,122 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import com.google.common.annotations.GwtCompatible; +import com.google.common.annotations.VisibleForTesting; +import java.util.Spliterator; +import java.util.Spliterators; + + +/** + * Implementation of {@link ImmutableSet} with two or more elements. + * + * @author Kevin Bourrillion + */ +@GwtCompatible(serializable = true, emulated = true) +@SuppressWarnings("serial") // uses writeReplace(), not default serialization +final class RegularImmutableSet extends ImmutableSet { + static final RegularImmutableSet EMPTY = + new RegularImmutableSet<>(new Object[0], 0, null, 0); + + private final transient Object[] elements; + // the same elements in hashed positions (plus nulls) + @VisibleForTesting final transient Object[] table; + // 'and' with an int to get a valid table index. + private final transient int mask; + private final transient int hashCode; + + RegularImmutableSet(Object[] elements, int hashCode, Object[] table, int mask) { + this.elements = elements; + this.table = table; + this.mask = mask; + this.hashCode = hashCode; + } + + @Override + public boolean contains(Object target) { + Object[] table = this.table; + if (target == null || table == null) { + return false; + } + for (int i = Hashing.smearedHash(target); ; i++) { + i &= mask; + Object candidate = table[i]; + if (candidate == null) { + return false; + } else if (candidate.equals(target)) { + return true; + } + } + } + + @Override + public int size() { + return elements.length; + } + + @Override + public UnmodifiableIterator iterator() { + return (UnmodifiableIterator) Iterators.forArray(elements); + } + + @Override + public Spliterator spliterator() { + return Spliterators.spliterator(elements, SPLITERATOR_CHARACTERISTICS); + } + + @Override + Object[] internalArray() { + return elements; + } + + @Override + int internalArrayStart() { + return 0; + } + + @Override + int internalArrayEnd() { + return elements.length; + } + + @Override + int copyIntoArray(Object[] dst, int offset) { + System.arraycopy(elements, 0, dst, offset, elements.length); + return offset + elements.length; + } + + @Override + ImmutableList createAsList() { + return (table == null) ? ImmutableList.of() : new RegularImmutableAsList(this, elements); + } + + @Override + boolean isPartialView() { + return false; + } + + @Override + public int hashCode() { + return hashCode; + } + + @Override + boolean isHashCodeFast() { + return true; + } +} diff --git a/src/main/java/com/google/common/collect/RegularImmutableSortedMultiset.java b/src/main/java/com/google/common/collect/RegularImmutableSortedMultiset.java new file mode 100644 index 0000000..b24deae --- /dev/null +++ b/src/main/java/com/google/common/collect/RegularImmutableSortedMultiset.java @@ -0,0 +1,133 @@ +/* + * Copyright (C) 2011 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the + * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.base.Preconditions.checkPositionIndexes; +import static com.google.common.collect.BoundType.CLOSED; + +import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.VisibleForTesting; +import com.google.common.primitives.Ints; +import java.util.Comparator; +import java.util.function.ObjIntConsumer; + + +/** + * An immutable sorted multiset with one or more distinct elements. + * + * @author Louis Wasserman + */ +@SuppressWarnings("serial") // uses writeReplace, not default serialization +@GwtIncompatible +final class RegularImmutableSortedMultiset extends ImmutableSortedMultiset { + private static final long[] ZERO_CUMULATIVE_COUNTS = {0}; + + static final ImmutableSortedMultiset NATURAL_EMPTY_MULTISET = + new RegularImmutableSortedMultiset<>(Ordering.natural()); + + @VisibleForTesting final transient RegularImmutableSortedSet elementSet; + private final transient long[] cumulativeCounts; + private final transient int offset; + private final transient int length; + + RegularImmutableSortedMultiset(Comparator comparator) { + this.elementSet = ImmutableSortedSet.emptySet(comparator); + this.cumulativeCounts = ZERO_CUMULATIVE_COUNTS; + this.offset = 0; + this.length = 0; + } + + RegularImmutableSortedMultiset( + RegularImmutableSortedSet elementSet, long[] cumulativeCounts, int offset, int length) { + this.elementSet = elementSet; + this.cumulativeCounts = cumulativeCounts; + this.offset = offset; + this.length = length; + } + + private int getCount(int index) { + return (int) (cumulativeCounts[offset + index + 1] - cumulativeCounts[offset + index]); + } + + @Override + Entry getEntry(int index) { + return Multisets.immutableEntry(elementSet.asList().get(index), getCount(index)); + } + + @Override + public void forEachEntry(ObjIntConsumer action) { + checkNotNull(action); + for (int i = 0; i < length; i++) { + action.accept(elementSet.asList().get(i), getCount(i)); + } + } + + @Override + public Entry firstEntry() { + return isEmpty() ? null : getEntry(0); + } + + @Override + public Entry lastEntry() { + return isEmpty() ? null : getEntry(length - 1); + } + + @Override + public int count(Object element) { + int index = elementSet.indexOf(element); + return (index >= 0) ? getCount(index) : 0; + } + + @Override + public int size() { + long size = cumulativeCounts[offset + length] - cumulativeCounts[offset]; + return Ints.saturatedCast(size); + } + + @Override + public ImmutableSortedSet elementSet() { + return elementSet; + } + + @Override + public ImmutableSortedMultiset headMultiset(E upperBound, BoundType boundType) { + return getSubMultiset(0, elementSet.headIndex(upperBound, checkNotNull(boundType) == CLOSED)); + } + + @Override + public ImmutableSortedMultiset tailMultiset(E lowerBound, BoundType boundType) { + return getSubMultiset( + elementSet.tailIndex(lowerBound, checkNotNull(boundType) == CLOSED), length); + } + + ImmutableSortedMultiset getSubMultiset(int from, int to) { + checkPositionIndexes(from, to, length); + if (from == to) { + return emptyMultiset(comparator()); + } else if (from == 0 && to == length) { + return this; + } else { + RegularImmutableSortedSet subElementSet = elementSet.getSubSet(from, to); + return new RegularImmutableSortedMultiset( + subElementSet, cumulativeCounts, offset + from, to - from); + } + } + + @Override + boolean isPartialView() { + return offset > 0 || length < cumulativeCounts.length - 1; + } +} diff --git a/src/main/java/com/google/common/collect/RegularImmutableSortedSet.java b/src/main/java/com/google/common/collect/RegularImmutableSortedSet.java new file mode 100644 index 0000000..2381f92 --- /dev/null +++ b/src/main/java/com/google/common/collect/RegularImmutableSortedSet.java @@ -0,0 +1,325 @@ +/* + * Copyright (C) 2009 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.annotations.GwtCompatible; +import com.google.common.annotations.GwtIncompatible; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.Iterator; +import java.util.NoSuchElementException; +import java.util.Set; +import java.util.Spliterator; +import java.util.function.Consumer; + + +/** + * An immutable sorted set with one or more elements. TODO(jlevy): Consider separate class for a + * single-element sorted set. + * + * @author Jared Levy + * @author Louis Wasserman + */ +@GwtCompatible(serializable = true, emulated = true) +@SuppressWarnings({"serial", "rawtypes"}) +final class RegularImmutableSortedSet extends ImmutableSortedSet { + static final RegularImmutableSortedSet NATURAL_EMPTY_SET = + new RegularImmutableSortedSet<>(ImmutableList.of(), Ordering.natural()); + + private final transient ImmutableList elements; + + RegularImmutableSortedSet(ImmutableList elements, Comparator comparator) { + super(comparator); + this.elements = elements; + } + + @Override + Object[] internalArray() { + return elements.internalArray(); + } + + @Override + int internalArrayStart() { + return elements.internalArrayStart(); + } + + @Override + int internalArrayEnd() { + return elements.internalArrayEnd(); + } + + @Override + public UnmodifiableIterator iterator() { + return elements.iterator(); + } + + @GwtIncompatible // NavigableSet + @Override + public UnmodifiableIterator descendingIterator() { + return elements.reverse().iterator(); + } + + @Override + public Spliterator spliterator() { + return asList().spliterator(); + } + + @Override + public void forEach(Consumer action) { + elements.forEach(action); + } + + @Override + public int size() { + return elements.size(); + } + + @Override + public boolean contains(Object o) { + try { + return o != null && unsafeBinarySearch(o) >= 0; + } catch (ClassCastException e) { + return false; + } + } + + @Override + public boolean containsAll(Collection targets) { + // TODO(jlevy): For optimal performance, use a binary search when + // targets.size() < size() / log(size()) + // TODO(kevinb): see if we can share code with OrderedIterator after it + // graduates from labs. + if (targets instanceof Multiset) { + targets = ((Multiset) targets).elementSet(); + } + if (!SortedIterables.hasSameComparator(comparator(), targets) || (targets.size() <= 1)) { + return super.containsAll(targets); + } + + /* + * If targets is a sorted set with the same comparator, containsAll can run + * in O(n) time stepping through the two collections. + */ + Iterator thisIterator = iterator(); + + Iterator thatIterator = targets.iterator(); + // known nonempty since we checked targets.size() > 1 + + if (!thisIterator.hasNext()) { + return false; + } + + Object target = thatIterator.next(); + E current = thisIterator.next(); + try { + while (true) { + int cmp = unsafeCompare(current, target); + + if (cmp < 0) { + if (!thisIterator.hasNext()) { + return false; + } + current = thisIterator.next(); + } else if (cmp == 0) { + if (!thatIterator.hasNext()) { + return true; + } + target = thatIterator.next(); + + } else if (cmp > 0) { + return false; + } + } + } catch (NullPointerException | ClassCastException e) { + return false; + } + } + + private int unsafeBinarySearch(Object key) throws ClassCastException { + return Collections.binarySearch(elements, key, unsafeComparator()); + } + + @Override + boolean isPartialView() { + return elements.isPartialView(); + } + + @Override + int copyIntoArray(Object[] dst, int offset) { + return elements.copyIntoArray(dst, offset); + } + + @Override + public boolean equals(Object object) { + if (object == this) { + return true; + } + if (!(object instanceof Set)) { + return false; + } + + Set that = (Set) object; + if (size() != that.size()) { + return false; + } else if (isEmpty()) { + return true; + } + + if (SortedIterables.hasSameComparator(comparator, that)) { + Iterator otherIterator = that.iterator(); + try { + Iterator iterator = iterator(); + while (iterator.hasNext()) { + Object element = iterator.next(); + Object otherElement = otherIterator.next(); + if (otherElement == null || unsafeCompare(element, otherElement) != 0) { + return false; + } + } + return true; + } catch (ClassCastException e) { + return false; + } catch (NoSuchElementException e) { + return false; // concurrent change to other set + } + } + return containsAll(that); + } + + @Override + public E first() { + if (isEmpty()) { + throw new NoSuchElementException(); + } + return elements.get(0); + } + + @Override + public E last() { + if (isEmpty()) { + throw new NoSuchElementException(); + } + return elements.get(size() - 1); + } + + @Override + public E lower(E element) { + int index = headIndex(element, false) - 1; + return (index == -1) ? null : elements.get(index); + } + + @Override + public E floor(E element) { + int index = headIndex(element, true) - 1; + return (index == -1) ? null : elements.get(index); + } + + @Override + public E ceiling(E element) { + int index = tailIndex(element, true); + return (index == size()) ? null : elements.get(index); + } + + @Override + public E higher(E element) { + int index = tailIndex(element, false); + return (index == size()) ? null : elements.get(index); + } + + @Override + ImmutableSortedSet headSetImpl(E toElement, boolean inclusive) { + return getSubSet(0, headIndex(toElement, inclusive)); + } + + int headIndex(E toElement, boolean inclusive) { + int index = Collections.binarySearch(elements, checkNotNull(toElement), comparator()); + if (index >= 0) { + return inclusive ? index + 1 : index; + } else { + return ~index; + } + } + + @Override + ImmutableSortedSet subSetImpl( + E fromElement, boolean fromInclusive, E toElement, boolean toInclusive) { + return tailSetImpl(fromElement, fromInclusive).headSetImpl(toElement, toInclusive); + } + + @Override + ImmutableSortedSet tailSetImpl(E fromElement, boolean inclusive) { + return getSubSet(tailIndex(fromElement, inclusive), size()); + } + + int tailIndex(E fromElement, boolean inclusive) { + int index = Collections.binarySearch(elements, checkNotNull(fromElement), comparator()); + if (index >= 0) { + return inclusive ? index : index + 1; + } else { + return ~index; + } + } + + // Pretend the comparator can compare anything. If it turns out it can't + // compare two elements, it'll throw a CCE. Only methods that are specified to + // throw CCE should call this. + @SuppressWarnings("unchecked") + Comparator unsafeComparator() { + return (Comparator) comparator; + } + + RegularImmutableSortedSet getSubSet(int newFromIndex, int newToIndex) { + if (newFromIndex == 0 && newToIndex == size()) { + return this; + } else if (newFromIndex < newToIndex) { + return new RegularImmutableSortedSet( + elements.subList(newFromIndex, newToIndex), comparator); + } else { + return emptySet(comparator); + } + } + + @Override + int indexOf(Object target) { + if (target == null) { + return -1; + } + int position; + try { + position = Collections.binarySearch(elements, target, unsafeComparator()); + } catch (ClassCastException e) { + return -1; + } + return (position >= 0) ? position : -1; + } + + @Override + ImmutableList createAsList() { + return (size() <= 1) ? elements : new ImmutableSortedAsList(this, elements); + } + + @Override + ImmutableSortedSet createDescendingSet() { + Comparator reversedOrder = Collections.reverseOrder(comparator); + return isEmpty() + ? emptySet(reversedOrder) + : new RegularImmutableSortedSet(elements.reverse(), reversedOrder); + } +} diff --git a/src/main/java/com/google/common/collect/RegularImmutableTable.java b/src/main/java/com/google/common/collect/RegularImmutableTable.java new file mode 100644 index 0000000..ca1b2be --- /dev/null +++ b/src/main/java/com/google/common/collect/RegularImmutableTable.java @@ -0,0 +1,186 @@ +/* + * Copyright (C) 2009 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.collect; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.annotations.GwtCompatible; + +import java.util.Collections; +import java.util.Comparator; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; + + +/** + * An implementation of {@link ImmutableTable} holding an arbitrary number of cells. + * + * @author Gregory Kick + */ +@GwtCompatible +abstract class RegularImmutableTable extends ImmutableTable { + RegularImmutableTable() {} + + abstract Cell getCell(int iterationIndex); + + @Override + final ImmutableSet> createCellSet() { + return isEmpty() ? ImmutableSet.>of() : new CellSet(); + } + + + private final class CellSet extends IndexedImmutableSet> { + @Override + public int size() { + return RegularImmutableTable.this.size(); + } + + @Override + Cell get(int index) { + return getCell(index); + } + + @Override + public boolean contains(Object object) { + if (object instanceof Cell) { + Cell cell = (Cell) object; + Object value = RegularImmutableTable.this.get(cell.getRowKey(), cell.getColumnKey()); + return value != null && value.equals(cell.getValue()); + } + return false; + } + + @Override + boolean isPartialView() { + return false; + } + } + + abstract V getValue(int iterationIndex); + + @Override + final ImmutableCollection createValues() { + return isEmpty() ? ImmutableList.of() : new Values(); + } + + + private final class Values extends ImmutableList { + @Override + public int size() { + return RegularImmutableTable.this.size(); + } + + @Override + public V get(int index) { + return getValue(index); + } + + @Override + boolean isPartialView() { + return true; + } + } + + static RegularImmutableTable forCells( + List> cells, + final Comparator rowComparator, + final Comparator columnComparator) { + checkNotNull(cells); + if (rowComparator != null || columnComparator != null) { + /* + * This sorting logic leads to a cellSet() ordering that may not be expected and that isn't + * documented in the Javadoc. If a row Comparator is provided, cellSet() iterates across the + * columns in the first row, the columns in the second row, etc. If a column Comparator is + * provided but a row Comparator isn't, cellSet() iterates across the rows in the first + * column, the rows in the second column, etc. + */ + Comparator> comparator = + new Comparator>() { + @Override + public int compare(Cell cell1, Cell cell2) { + int rowCompare = + (rowComparator == null) + ? 0 + : rowComparator.compare(cell1.getRowKey(), cell2.getRowKey()); + if (rowCompare != 0) { + return rowCompare; + } + return (columnComparator == null) + ? 0 + : columnComparator.compare(cell1.getColumnKey(), cell2.getColumnKey()); + } + }; + Collections.sort(cells, comparator); + } + return forCellsInternal(cells, rowComparator, columnComparator); + } + + static RegularImmutableTable forCells(Iterable> cells) { + return forCellsInternal(cells, null, null); + } + + private static RegularImmutableTable forCellsInternal( + Iterable> cells, + Comparator rowComparator, + Comparator columnComparator) { + Set rowSpaceBuilder = new LinkedHashSet<>(); + Set columnSpaceBuilder = new LinkedHashSet<>(); + ImmutableList> cellList = ImmutableList.copyOf(cells); + for (Cell cell : cells) { + rowSpaceBuilder.add(cell.getRowKey()); + columnSpaceBuilder.add(cell.getColumnKey()); + } + + ImmutableSet rowSpace = + (rowComparator == null) + ? ImmutableSet.copyOf(rowSpaceBuilder) + : ImmutableSet.copyOf(ImmutableList.sortedCopyOf(rowComparator, rowSpaceBuilder)); + ImmutableSet columnSpace = + (columnComparator == null) + ? ImmutableSet.copyOf(columnSpaceBuilder) + : ImmutableSet.copyOf(ImmutableList.sortedCopyOf(columnComparator, columnSpaceBuilder)); + + return forOrderedComponents(cellList, rowSpace, columnSpace); + } + + /** A factory that chooses the most space-efficient representation of the table. */ + static RegularImmutableTable forOrderedComponents( + ImmutableList> cellList, + ImmutableSet rowSpace, + ImmutableSet columnSpace) { + // use a dense table if more than half of the cells have values + // TODO(gak): tune this condition based on empirical evidence + return (cellList.size() > (((long) rowSpace.size() * columnSpace.size()) / 2)) + ? new DenseImmutableTable(cellList, rowSpace, columnSpace) + : new SparseImmutableTable(cellList, rowSpace, columnSpace); + } + + /** @throws IllegalArgumentException if {@code existingValue} is not null. */ + /* + * We could have declared this method 'static' but the additional compile-time checks achieved by + * referencing the type variables seem worthwhile. + */ + final void checkNoDuplicate(R rowKey, C columnKey, V existingValue, V newValue) { + checkArgument( + existingValue == null, + "Duplicate key: (row=%s, column=%s), values: [%s, %s].", + rowKey, + columnKey, + newValue, + existingValue); + } +} diff --git a/src/main/java/com/google/common/collect/ReverseNaturalOrdering.java b/src/main/java/com/google/common/collect/ReverseNaturalOrdering.java new file mode 100644 index 0000000..612d846 --- /dev/null +++ b/src/main/java/com/google/common/collect/ReverseNaturalOrdering.java @@ -0,0 +1,101 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.annotations.GwtCompatible; +import java.io.Serializable; +import java.util.Iterator; + +/** An ordering that uses the reverse of the natural order of the values. */ +@GwtCompatible(serializable = true) +@SuppressWarnings({"unchecked", "rawtypes"}) // TODO(kevinb): the right way to explain this?? +final class ReverseNaturalOrdering extends Ordering implements Serializable { + static final ReverseNaturalOrdering INSTANCE = new ReverseNaturalOrdering(); + + @Override + public int compare(Comparable left, Comparable right) { + checkNotNull(left); // right null is caught later + if (left == right) { + return 0; + } + + return right.compareTo(left); + } + + @Override + public Ordering reverse() { + return Ordering.natural(); + } + + // Override the min/max methods to "hoist" delegation outside loops + + @Override + public E min(E a, E b) { + return NaturalOrdering.INSTANCE.max(a, b); + } + + @Override + public E min(E a, E b, E c, E... rest) { + return NaturalOrdering.INSTANCE.max(a, b, c, rest); + } + + @Override + public E min(Iterator iterator) { + return NaturalOrdering.INSTANCE.max(iterator); + } + + @Override + public E min(Iterable iterable) { + return NaturalOrdering.INSTANCE.max(iterable); + } + + @Override + public E max(E a, E b) { + return NaturalOrdering.INSTANCE.min(a, b); + } + + @Override + public E max(E a, E b, E c, E... rest) { + return NaturalOrdering.INSTANCE.min(a, b, c, rest); + } + + @Override + public E max(Iterator iterator) { + return NaturalOrdering.INSTANCE.min(iterator); + } + + @Override + public E max(Iterable iterable) { + return NaturalOrdering.INSTANCE.min(iterable); + } + + // preserving singleton-ness gives equals()/hashCode() for free + private Object readResolve() { + return INSTANCE; + } + + @Override + public String toString() { + return "Ordering.natural().reverse()"; + } + + private ReverseNaturalOrdering() {} + + private static final long serialVersionUID = 0; +} diff --git a/src/main/java/com/google/common/collect/ReverseOrdering.java b/src/main/java/com/google/common/collect/ReverseOrdering.java new file mode 100644 index 0000000..a64e2d6 --- /dev/null +++ b/src/main/java/com/google/common/collect/ReverseOrdering.java @@ -0,0 +1,111 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.annotations.GwtCompatible; +import java.io.Serializable; +import java.util.Iterator; + + +/** An ordering that uses the reverse of a given order. */ +@GwtCompatible(serializable = true) +final class ReverseOrdering extends Ordering implements Serializable { + final Ordering forwardOrder; + + ReverseOrdering(Ordering forwardOrder) { + this.forwardOrder = checkNotNull(forwardOrder); + } + + @Override + public int compare(T a, T b) { + return forwardOrder.compare(b, a); + } + + @SuppressWarnings("unchecked") // how to explain? + @Override + public Ordering reverse() { + return (Ordering) forwardOrder; + } + + // Override the min/max methods to "hoist" delegation outside loops + + @Override + public E min(E a, E b) { + return forwardOrder.max(a, b); + } + + @Override + public E min(E a, E b, E c, E... rest) { + return forwardOrder.max(a, b, c, rest); + } + + @Override + public E min(Iterator iterator) { + return forwardOrder.max(iterator); + } + + @Override + public E min(Iterable iterable) { + return forwardOrder.max(iterable); + } + + @Override + public E max(E a, E b) { + return forwardOrder.min(a, b); + } + + @Override + public E max(E a, E b, E c, E... rest) { + return forwardOrder.min(a, b, c, rest); + } + + @Override + public E max(Iterator iterator) { + return forwardOrder.min(iterator); + } + + @Override + public E max(Iterable iterable) { + return forwardOrder.min(iterable); + } + + @Override + public int hashCode() { + return -forwardOrder.hashCode(); + } + + @Override + public boolean equals(Object object) { + if (object == this) { + return true; + } + if (object instanceof ReverseOrdering) { + ReverseOrdering that = (ReverseOrdering) object; + return this.forwardOrder.equals(that.forwardOrder); + } + return false; + } + + @Override + public String toString() { + return forwardOrder + ".reverse()"; + } + + private static final long serialVersionUID = 0; +} diff --git a/src/main/java/com/google/common/collect/RowSortedTable.java b/src/main/java/com/google/common/collect/RowSortedTable.java new file mode 100644 index 0000000..9cdae79 --- /dev/null +++ b/src/main/java/com/google/common/collect/RowSortedTable.java @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2010 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import com.google.common.annotations.GwtCompatible; +import java.util.Map; +import java.util.Set; +import java.util.SortedMap; +import java.util.SortedSet; + +/** + * Interface that extends {@code Table} and whose rows are sorted. + * + *

The {@link #rowKeySet} method returns a {@link SortedSet} and the {@link #rowMap} method + * returns a {@link SortedMap}, instead of the {@link Set} and {@link Map} specified by the {@link + * Table} interface. + * + * @author Warren Dukes + * @since 8.0 + */ +@GwtCompatible +public interface RowSortedTable extends Table { + /** + * {@inheritDoc} + * + *

This method returns a {@link SortedSet}, instead of the {@code Set} specified in the {@link + * Table} interface. + */ + @Override + SortedSet rowKeySet(); + + /** + * {@inheritDoc} + * + *

This method returns a {@link SortedMap}, instead of the {@code Map} specified in the {@link + * Table} interface. + */ + @Override + SortedMap> rowMap(); +} diff --git a/src/main/java/com/google/common/collect/Serialization.java b/src/main/java/com/google/common/collect/Serialization.java new file mode 100644 index 0000000..929a48f --- /dev/null +++ b/src/main/java/com/google/common/collect/Serialization.java @@ -0,0 +1,219 @@ +/* + * Copyright (C) 2008 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import com.google.common.annotations.GwtIncompatible; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.lang.reflect.Field; +import java.util.Collection; +import java.util.Map; + +/** + * Provides static methods for serializing collection classes. + * + *

This class assists the implementation of collection classes. Do not use this class to + * serialize collections that are defined elsewhere. + * + * @author Jared Levy + */ +@GwtIncompatible +final class Serialization { + private Serialization() {} + + /** + * Reads a count corresponding to a serialized map, multiset, or multimap. It returns the size of + * a map serialized by {@link #writeMap(Map, ObjectOutputStream)}, the number of distinct elements + * in a multiset serialized by {@link #writeMultiset(Multiset, ObjectOutputStream)}, or the number + * of distinct keys in a multimap serialized by {@link #writeMultimap(Multimap, + * ObjectOutputStream)}. + */ + static int readCount(ObjectInputStream stream) throws IOException { + return stream.readInt(); + } + + /** + * Stores the contents of a map in an output stream, as part of serialization. It does not support + * concurrent maps whose content may change while the method is running. + * + *

The serialized output consists of the number of entries, first key, first value, second key, + * second value, and so on. + */ + static void writeMap(Map map, ObjectOutputStream stream) throws IOException { + stream.writeInt(map.size()); + for (Map.Entry entry : map.entrySet()) { + stream.writeObject(entry.getKey()); + stream.writeObject(entry.getValue()); + } + } + + /** + * Populates a map by reading an input stream, as part of deserialization. See {@link #writeMap} + * for the data format. + */ + static void populateMap(Map map, ObjectInputStream stream) + throws IOException, ClassNotFoundException { + int size = stream.readInt(); + populateMap(map, stream, size); + } + + /** + * Populates a map by reading an input stream, as part of deserialization. See {@link #writeMap} + * for the data format. The size is determined by a prior call to {@link #readCount}. + */ + static void populateMap(Map map, ObjectInputStream stream, int size) + throws IOException, ClassNotFoundException { + for (int i = 0; i < size; i++) { + @SuppressWarnings("unchecked") // reading data stored by writeMap + K key = (K) stream.readObject(); + @SuppressWarnings("unchecked") // reading data stored by writeMap + V value = (V) stream.readObject(); + map.put(key, value); + } + } + + /** + * Stores the contents of a multiset in an output stream, as part of serialization. It does not + * support concurrent multisets whose content may change while the method is running. + * + *

The serialized output consists of the number of distinct elements, the first element, its + * count, the second element, its count, and so on. + */ + static void writeMultiset(Multiset multiset, ObjectOutputStream stream) + throws IOException { + int entryCount = multiset.entrySet().size(); + stream.writeInt(entryCount); + for (Multiset.Entry entry : multiset.entrySet()) { + stream.writeObject(entry.getElement()); + stream.writeInt(entry.getCount()); + } + } + + /** + * Populates a multiset by reading an input stream, as part of deserialization. See {@link + * #writeMultiset} for the data format. + */ + static void populateMultiset(Multiset multiset, ObjectInputStream stream) + throws IOException, ClassNotFoundException { + int distinctElements = stream.readInt(); + populateMultiset(multiset, stream, distinctElements); + } + + /** + * Populates a multiset by reading an input stream, as part of deserialization. See {@link + * #writeMultiset} for the data format. The number of distinct elements is determined by a prior + * call to {@link #readCount}. + */ + static void populateMultiset( + Multiset multiset, ObjectInputStream stream, int distinctElements) + throws IOException, ClassNotFoundException { + for (int i = 0; i < distinctElements; i++) { + @SuppressWarnings("unchecked") // reading data stored by writeMultiset + E element = (E) stream.readObject(); + int count = stream.readInt(); + multiset.add(element, count); + } + } + + /** + * Stores the contents of a multimap in an output stream, as part of serialization. It does not + * support concurrent multimaps whose content may change while the method is running. The {@link + * Multimap#asMap} view determines the ordering in which data is written to the stream. + * + *

The serialized output consists of the number of distinct keys, and then for each distinct + * key: the key, the number of values for that key, and the key's values. + */ + static void writeMultimap(Multimap multimap, ObjectOutputStream stream) + throws IOException { + stream.writeInt(multimap.asMap().size()); + for (Map.Entry> entry : multimap.asMap().entrySet()) { + stream.writeObject(entry.getKey()); + stream.writeInt(entry.getValue().size()); + for (V value : entry.getValue()) { + stream.writeObject(value); + } + } + } + + /** + * Populates a multimap by reading an input stream, as part of deserialization. See {@link + * #writeMultimap} for the data format. + */ + static void populateMultimap(Multimap multimap, ObjectInputStream stream) + throws IOException, ClassNotFoundException { + int distinctKeys = stream.readInt(); + populateMultimap(multimap, stream, distinctKeys); + } + + /** + * Populates a multimap by reading an input stream, as part of deserialization. See {@link + * #writeMultimap} for the data format. The number of distinct keys is determined by a prior call + * to {@link #readCount}. + */ + static void populateMultimap( + Multimap multimap, ObjectInputStream stream, int distinctKeys) + throws IOException, ClassNotFoundException { + for (int i = 0; i < distinctKeys; i++) { + @SuppressWarnings("unchecked") // reading data stored by writeMultimap + K key = (K) stream.readObject(); + Collection values = multimap.get(key); + int valueCount = stream.readInt(); + for (int j = 0; j < valueCount; j++) { + @SuppressWarnings("unchecked") // reading data stored by writeMultimap + V value = (V) stream.readObject(); + values.add(value); + } + } + } + + // Secret sauce for setting final fields; don't make it public. + static FieldSetter getFieldSetter(final Class clazz, String fieldName) { + try { + Field field = clazz.getDeclaredField(fieldName); + return new FieldSetter(field); + } catch (NoSuchFieldException e) { + throw new AssertionError(e); // programmer error + } + } + + // Secret sauce for setting final fields; don't make it public. + static final class FieldSetter { + private final Field field; + + private FieldSetter(Field field) { + this.field = field; + field.setAccessible(true); + } + + void set(T instance, Object value) { + try { + field.set(instance, value); + } catch (IllegalAccessException impossible) { + throw new AssertionError(impossible); + } + } + + void set(T instance, int value) { + try { + field.set(instance, value); + } catch (IllegalAccessException impossible) { + throw new AssertionError(impossible); + } + } + } +} diff --git a/src/main/java/com/google/common/collect/SetMultimap.java b/src/main/java/com/google/common/collect/SetMultimap.java new file mode 100644 index 0000000..b27d1a1 --- /dev/null +++ b/src/main/java/com/google/common/collect/SetMultimap.java @@ -0,0 +1,118 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import com.google.common.annotations.GwtCompatible; + +import java.util.Collection; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; + + +/** + * A {@code Multimap} that cannot hold duplicate key-value pairs. Adding a key-value pair that's + * already in the multimap has no effect. See the {@link Multimap} documentation for information + * common to all multimaps. + * + *

The {@link #get}, {@link #removeAll}, and {@link #replaceValues} methods each return a {@link + * Set} of values, while {@link #entries} returns a {@code Set} of map entries. Though the method + * signature doesn't say so explicitly, the map returned by {@link #asMap} has {@code Set} values. + * + *

If the values corresponding to a single key should be ordered according to a {@link + * java.util.Comparator} (or the natural order), see the {@link SortedSetMultimap} subinterface. + * + *

Since the value collections are sets, the behavior of a {@code SetMultimap} is not specified + * if key or value objects already present in the multimap change in a manner that affects + * {@code equals} comparisons. Use caution if mutable objects are used as keys or values in a {@code + * SetMultimap}. + * + *

See the Guava User Guide article on {@code + * Multimap}. + * + * @author Jared Levy + * @since 2.0 + */ +@GwtCompatible +public interface SetMultimap extends Multimap { + /** + * {@inheritDoc} + * + *

Because a {@code SetMultimap} has unique values for a given key, this method returns a + * {@link Set}, instead of the {@link java.util.Collection} specified in the {@link Multimap} + * interface. + */ + @Override + Set get(K key); + + /** + * {@inheritDoc} + * + *

Because a {@code SetMultimap} has unique values for a given key, this method returns a + * {@link Set}, instead of the {@link java.util.Collection} specified in the {@link Multimap} + * interface. + */ + + @Override + Set removeAll(Object key); + + /** + * {@inheritDoc} + * + *

Because a {@code SetMultimap} has unique values for a given key, this method returns a + * {@link Set}, instead of the {@link java.util.Collection} specified in the {@link Multimap} + * interface. + * + *

Any duplicates in {@code values} will be stored in the multimap once. + */ + + @Override + Set replaceValues(K key, Iterable values); + + /** + * {@inheritDoc} + * + *

Because a {@code SetMultimap} has unique values for a given key, this method returns a + * {@link Set}, instead of the {@link java.util.Collection} specified in the {@link Multimap} + * interface. + */ + @Override + Set> entries(); + + /** + * {@inheritDoc} + * + *

Note: The returned map's values are guaranteed to be of type {@link Set}. To obtain + * this map with the more specific generic type {@code Map>}, call {@link + * Multimaps#asMap(SetMultimap)} instead. + */ + @Override + Map> asMap(); + + /** + * Compares the specified object to this multimap for equality. + * + *

Two {@code SetMultimap} instances are equal if, for each key, they contain the same values. + * Equality does not depend on the ordering of keys or values. + * + *

An empty {@code SetMultimap} is equal to any other empty {@code Multimap}, including an + * empty {@code ListMultimap}. + */ + @Override + boolean equals(Object obj); +} diff --git a/src/main/java/com/google/common/collect/Sets.java b/src/main/java/com/google/common/collect/Sets.java new file mode 100644 index 0000000..a80e1a2 --- /dev/null +++ b/src/main/java/com/google/common/collect/Sets.java @@ -0,0 +1,2126 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.collect.CollectPreconditions.checkNonnegative; + +import com.google.common.annotations.Beta; +import com.google.common.annotations.GwtCompatible; +import com.google.common.annotations.GwtIncompatible; +import com.google.common.base.Predicate; +import com.google.common.base.Predicates; +import com.google.common.collect.Collections2.FilteredCollection; +import com.google.common.math.IntMath; + +import java.io.Serializable; +import java.util.AbstractSet; +import java.util.Arrays; +import java.util.BitSet; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.EnumSet; +import java.util.HashSet; +import java.util.Iterator; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.NavigableSet; +import java.util.NoSuchElementException; +import java.util.Set; +import java.util.SortedSet; +import java.util.TreeSet; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.CopyOnWriteArraySet; +import java.util.function.Consumer; +import java.util.stream.Collector; +import java.util.stream.Stream; + + + +/** + * Static utility methods pertaining to {@link Set} instances. Also see this class's counterparts + * {@link Lists}, {@link Maps} and {@link Queues}. + * + *

See the Guava User Guide article on {@code Sets}. + * + * @author Kevin Bourrillion + * @author Jared Levy + * @author Chris Povirk + * @since 2.0 + */ +@GwtCompatible(emulated = true) +public final class Sets { + private Sets() {} + + /** + * {@link AbstractSet} substitute without the potentially-quadratic {@code removeAll} + * implementation. + */ + abstract static class ImprovedAbstractSet extends AbstractSet { + @Override + public boolean removeAll(Collection c) { + return removeAllImpl(this, c); + } + + @Override + public boolean retainAll(Collection c) { + return super.retainAll(checkNotNull(c)); // GWT compatibility + } + } + + /** + * Returns an immutable set instance containing the given enum elements. Internally, the returned + * set will be backed by an {@link EnumSet}. + * + *

The iteration order of the returned set follows the enum's iteration order, not the order in + * which the elements are provided to the method. + * + * @param anElement one of the elements the set should contain + * @param otherElements the rest of the elements the set should contain + * @return an immutable set containing those elements, minus duplicates + */ + // http://code.google.com/p/google-web-toolkit/issues/detail?id=3028 + @GwtCompatible(serializable = true) + public static > ImmutableSet immutableEnumSet( + E anElement, E... otherElements) { + return ImmutableEnumSet.asImmutable(EnumSet.of(anElement, otherElements)); + } + + /** + * Returns an immutable set instance containing the given enum elements. Internally, the returned + * set will be backed by an {@link EnumSet}. + * + *

The iteration order of the returned set follows the enum's iteration order, not the order in + * which the elements appear in the given collection. + * + * @param elements the elements, all of the same {@code enum} type, that the set should contain + * @return an immutable set containing those elements, minus duplicates + */ + // http://code.google.com/p/google-web-toolkit/issues/detail?id=3028 + @GwtCompatible(serializable = true) + public static > ImmutableSet immutableEnumSet(Iterable elements) { + if (elements instanceof ImmutableEnumSet) { + return (ImmutableEnumSet) elements; + } else if (elements instanceof Collection) { + Collection collection = (Collection) elements; + if (collection.isEmpty()) { + return ImmutableSet.of(); + } else { + return ImmutableEnumSet.asImmutable(EnumSet.copyOf(collection)); + } + } else { + Iterator itr = elements.iterator(); + if (itr.hasNext()) { + EnumSet enumSet = EnumSet.of(itr.next()); + Iterators.addAll(enumSet, itr); + return ImmutableEnumSet.asImmutable(enumSet); + } else { + return ImmutableSet.of(); + } + } + } + + private static final class Accumulator> { + static final Collector, ?, ImmutableSet>> TO_IMMUTABLE_ENUM_SET = + (Collector) + Collector.>of( + Accumulator::new, + Accumulator::add, + Accumulator::combine, + Accumulator::toImmutableSet, + Collector.Characteristics.UNORDERED); + + private EnumSet set; + + void add(E e) { + if (set == null) { + set = EnumSet.of(e); + } else { + set.add(e); + } + } + + Accumulator combine(Accumulator other) { + if (this.set == null) { + return other; + } else if (other.set == null) { + return this; + } else { + this.set.addAll(other.set); + return this; + } + } + + ImmutableSet toImmutableSet() { + return (set == null) ? ImmutableSet.of() : ImmutableEnumSet.asImmutable(set); + } + } + + /** + * Returns a {@code Collector} that accumulates the input elements into a new {@code ImmutableSet} + * with an implementation specialized for enums. Unlike {@link ImmutableSet#toImmutableSet}, the + * resulting set will iterate over elements in their enum definition order, not encounter order. + * + * @since 21.0 + */ + public static > Collector> toImmutableEnumSet() { + return (Collector) Accumulator.TO_IMMUTABLE_ENUM_SET; + } + + /** + * Returns a new, mutable {@code EnumSet} instance containing the given elements in their + * natural order. This method behaves identically to {@link EnumSet#copyOf(Collection)}, but also + * accepts non-{@code Collection} iterables and empty iterables. + */ + public static > EnumSet newEnumSet( + Iterable iterable, Class elementType) { + EnumSet set = EnumSet.noneOf(elementType); + Iterables.addAll(set, iterable); + return set; + } + + // HashSet + + /** + * Creates a mutable, initially empty {@code HashSet} instance. + * + *

Note: if mutability is not required, use {@link ImmutableSet#of()} instead. If {@code + * E} is an {@link Enum} type, use {@link EnumSet#noneOf} instead. Otherwise, strongly consider + * using a {@code LinkedHashSet} instead, at the cost of increased memory footprint, to get + * deterministic iteration behavior. + * + *

Note for Java 7 and later: this method is now unnecessary and should be treated as + * deprecated. Instead, use the {@code HashSet} constructor directly, taking advantage of the new + * "diamond" syntax. + */ + public static HashSet newHashSet() { + return new HashSet(); + } + + /** + * Creates a mutable {@code HashSet} instance initially containing the given elements. + * + *

Note: if elements are non-null and won't be added or removed after this point, use + * {@link ImmutableSet#of()} or {@link ImmutableSet#copyOf(Object[])} instead. If {@code E} is an + * {@link Enum} type, use {@link EnumSet#of(Enum, Enum[])} instead. Otherwise, strongly consider + * using a {@code LinkedHashSet} instead, at the cost of increased memory footprint, to get + * deterministic iteration behavior. + * + *

This method is just a small convenience, either for {@code newHashSet(}{@link Arrays#asList + * asList}{@code (...))}, or for creating an empty set then calling {@link Collections#addAll}. + * This method is not actually very useful and will likely be deprecated in the future. + */ + public static HashSet newHashSet(E... elements) { + HashSet set = newHashSetWithExpectedSize(elements.length); + Collections.addAll(set, elements); + return set; + } + + /** + * Creates a mutable {@code HashSet} instance containing the given elements. A very thin + * convenience for creating an empty set then calling {@link Collection#addAll} or {@link + * Iterables#addAll}. + * + *

Note: if mutability is not required and the elements are non-null, use {@link + * ImmutableSet#copyOf(Iterable)} instead. (Or, change {@code elements} to be a {@link + * FluentIterable} and call {@code elements.toSet()}.) + * + *

Note: if {@code E} is an {@link Enum} type, use {@link #newEnumSet(Iterable, Class)} + * instead. + * + *

Note for Java 7 and later: if {@code elements} is a {@link Collection}, you don't + * need this method. Instead, use the {@code HashSet} constructor directly, taking advantage of + * the new "diamond" syntax. + * + *

Overall, this method is not very useful and will likely be deprecated in the future. + */ + public static HashSet newHashSet(Iterable elements) { + return (elements instanceof Collection) + ? new HashSet(Collections2.cast(elements)) + : newHashSet(elements.iterator()); + } + + /** + * Creates a mutable {@code HashSet} instance containing the given elements. A very thin + * convenience for creating an empty set and then calling {@link Iterators#addAll}. + * + *

Note: if mutability is not required and the elements are non-null, use {@link + * ImmutableSet#copyOf(Iterator)} instead. + * + *

Note: if {@code E} is an {@link Enum} type, you should create an {@link EnumSet} + * instead. + * + *

Overall, this method is not very useful and will likely be deprecated in the future. + */ + public static HashSet newHashSet(Iterator elements) { + HashSet set = newHashSet(); + Iterators.addAll(set, elements); + return set; + } + + /** + * Returns a new hash set using the smallest initial table size that can hold {@code expectedSize} + * elements without resizing. Note that this is not what {@link HashSet#HashSet(int)} does, but it + * is what most users want and expect it to do. + * + *

This behavior can't be broadly guaranteed, but has been tested with OpenJDK 1.7 and 1.8. + * + * @param expectedSize the number of elements you expect to add to the returned set + * @return a new, empty hash set with enough capacity to hold {@code expectedSize} elements + * without resizing + * @throws IllegalArgumentException if {@code expectedSize} is negative + */ + public static HashSet newHashSetWithExpectedSize(int expectedSize) { + return new HashSet(Maps.capacity(expectedSize)); + } + + /** + * Creates a thread-safe set backed by a hash map. The set is backed by a {@link + * ConcurrentHashMap} instance, and thus carries the same concurrency guarantees. + * + *

Unlike {@code HashSet}, this class does NOT allow {@code null} to be used as an element. The + * set is serializable. + * + * @return a new, empty thread-safe {@code Set} + * @since 15.0 + */ + public static Set newConcurrentHashSet() { + return Collections.newSetFromMap(new ConcurrentHashMap()); + } + + /** + * Creates a thread-safe set backed by a hash map and containing the given elements. The set is + * backed by a {@link ConcurrentHashMap} instance, and thus carries the same concurrency + * guarantees. + * + *

Unlike {@code HashSet}, this class does NOT allow {@code null} to be used as an element. The + * set is serializable. + * + * @param elements the elements that the set should contain + * @return a new thread-safe set containing those elements (minus duplicates) + * @throws NullPointerException if {@code elements} or any of its contents is null + * @since 15.0 + */ + public static Set newConcurrentHashSet(Iterable elements) { + Set set = newConcurrentHashSet(); + Iterables.addAll(set, elements); + return set; + } + + // LinkedHashSet + + /** + * Creates a mutable, empty {@code LinkedHashSet} instance. + * + *

Note: if mutability is not required, use {@link ImmutableSet#of()} instead. + * + *

Note for Java 7 and later: this method is now unnecessary and should be treated as + * deprecated. Instead, use the {@code LinkedHashSet} constructor directly, taking advantage of + * the new "diamond" syntax. + * + * @return a new, empty {@code LinkedHashSet} + */ + public static LinkedHashSet newLinkedHashSet() { + return new LinkedHashSet(); + } + + /** + * Creates a mutable {@code LinkedHashSet} instance containing the given elements in order. + * + *

Note: if mutability is not required and the elements are non-null, use {@link + * ImmutableSet#copyOf(Iterable)} instead. + * + *

Note for Java 7 and later: if {@code elements} is a {@link Collection}, you don't + * need this method. Instead, use the {@code LinkedHashSet} constructor directly, taking advantage + * of the new "diamond" syntax. + * + *

Overall, this method is not very useful and will likely be deprecated in the future. + * + * @param elements the elements that the set should contain, in order + * @return a new {@code LinkedHashSet} containing those elements (minus duplicates) + */ + public static LinkedHashSet newLinkedHashSet(Iterable elements) { + if (elements instanceof Collection) { + return new LinkedHashSet(Collections2.cast(elements)); + } + LinkedHashSet set = newLinkedHashSet(); + Iterables.addAll(set, elements); + return set; + } + + /** + * Creates a {@code LinkedHashSet} instance, with a high enough "initial capacity" that it + * should hold {@code expectedSize} elements without growth. This behavior cannot be + * broadly guaranteed, but it is observed to be true for OpenJDK 1.7. It also can't be guaranteed + * that the method isn't inadvertently oversizing the returned set. + * + * @param expectedSize the number of elements you expect to add to the returned set + * @return a new, empty {@code LinkedHashSet} with enough capacity to hold {@code expectedSize} + * elements without resizing + * @throws IllegalArgumentException if {@code expectedSize} is negative + * @since 11.0 + */ + public static LinkedHashSet newLinkedHashSetWithExpectedSize(int expectedSize) { + return new LinkedHashSet(Maps.capacity(expectedSize)); + } + + // TreeSet + + /** + * Creates a mutable, empty {@code TreeSet} instance sorted by the natural sort ordering of + * its elements. + * + *

Note: if mutability is not required, use {@link ImmutableSortedSet#of()} instead. + * + *

Note for Java 7 and later: this method is now unnecessary and should be treated as + * deprecated. Instead, use the {@code TreeSet} constructor directly, taking advantage of the new + * "diamond" syntax. + * + * @return a new, empty {@code TreeSet} + */ + public static TreeSet newTreeSet() { + return new TreeSet(); + } + + /** + * Creates a mutable {@code TreeSet} instance containing the given elements sorted by their + * natural ordering. + * + *

Note: if mutability is not required, use {@link ImmutableSortedSet#copyOf(Iterable)} + * instead. + * + *

Note: If {@code elements} is a {@code SortedSet} with an explicit comparator, this + * method has different behavior than {@link TreeSet#TreeSet(SortedSet)}, which returns a {@code + * TreeSet} with that comparator. + * + *

Note for Java 7 and later: this method is now unnecessary and should be treated as + * deprecated. Instead, use the {@code TreeSet} constructor directly, taking advantage of the new + * "diamond" syntax. + * + *

This method is just a small convenience for creating an empty set and then calling {@link + * Iterables#addAll}. This method is not very useful and will likely be deprecated in the future. + * + * @param elements the elements that the set should contain + * @return a new {@code TreeSet} containing those elements (minus duplicates) + */ + public static TreeSet newTreeSet(Iterable elements) { + TreeSet set = newTreeSet(); + Iterables.addAll(set, elements); + return set; + } + + /** + * Creates a mutable, empty {@code TreeSet} instance with the given comparator. + * + *

Note: if mutability is not required, use {@code + * ImmutableSortedSet.orderedBy(comparator).build()} instead. + * + *

Note for Java 7 and later: this method is now unnecessary and should be treated as + * deprecated. Instead, use the {@code TreeSet} constructor directly, taking advantage of the new + * "diamond" syntax. One caveat to this is that the {@code + * TreeSet} constructor uses a null {@code Comparator} to mean "natural ordering," whereas this + * factory rejects null. Clean your code accordingly. + * + * @param comparator the comparator to use to sort the set + * @return a new, empty {@code TreeSet} + * @throws NullPointerException if {@code comparator} is null + */ + public static TreeSet newTreeSet(Comparator comparator) { + return new TreeSet(checkNotNull(comparator)); + } + + /** + * Creates an empty {@code Set} that uses identity to determine equality. It compares object + * references, instead of calling {@code equals}, to determine whether a provided object matches + * an element in the set. For example, {@code contains} returns {@code false} when passed an + * object that equals a set member, but isn't the same instance. This behavior is similar to the + * way {@code IdentityHashMap} handles key lookups. + * + * @since 8.0 + */ + public static Set newIdentityHashSet() { + return Collections.newSetFromMap(Maps.newIdentityHashMap()); + } + + /** + * Creates an empty {@code CopyOnWriteArraySet} instance. + * + *

Note: if you need an immutable empty {@link Set}, use {@link Collections#emptySet} + * instead. + * + * @return a new, empty {@code CopyOnWriteArraySet} + * @since 12.0 + */ + @GwtIncompatible // CopyOnWriteArraySet + public static CopyOnWriteArraySet newCopyOnWriteArraySet() { + return new CopyOnWriteArraySet(); + } + + /** + * Creates a {@code CopyOnWriteArraySet} instance containing the given elements. + * + * @param elements the elements that the set should contain, in order + * @return a new {@code CopyOnWriteArraySet} containing those elements + * @since 12.0 + */ + @GwtIncompatible // CopyOnWriteArraySet + public static CopyOnWriteArraySet newCopyOnWriteArraySet(Iterable elements) { + // We copy elements to an ArrayList first, rather than incurring the + // quadratic cost of adding them to the COWAS directly. + Collection elementsCollection = + (elements instanceof Collection) + ? Collections2.cast(elements) + : Lists.newArrayList(elements); + return new CopyOnWriteArraySet(elementsCollection); + } + + /** + * Creates an {@code EnumSet} consisting of all enum values that are not in the specified + * collection. If the collection is an {@link EnumSet}, this method has the same behavior as + * {@link EnumSet#complementOf}. Otherwise, the specified collection must contain at least one + * element, in order to determine the element type. If the collection could be empty, use {@link + * #complementOf(Collection, Class)} instead of this method. + * + * @param collection the collection whose complement should be stored in the enum set + * @return a new, modifiable {@code EnumSet} containing all values of the enum that aren't present + * in the given collection + * @throws IllegalArgumentException if {@code collection} is not an {@code EnumSet} instance and + * contains no elements + */ + public static > EnumSet complementOf(Collection collection) { + if (collection instanceof EnumSet) { + return EnumSet.complementOf((EnumSet) collection); + } + checkArgument( + !collection.isEmpty(), "collection is empty; use the other version of this method"); + Class type = collection.iterator().next().getDeclaringClass(); + return makeComplementByHand(collection, type); + } + + /** + * Creates an {@code EnumSet} consisting of all enum values that are not in the specified + * collection. This is equivalent to {@link EnumSet#complementOf}, but can act on any input + * collection, as long as the elements are of enum type. + * + * @param collection the collection whose complement should be stored in the {@code EnumSet} + * @param type the type of the elements in the set + * @return a new, modifiable {@code EnumSet} initially containing all the values of the enum not + * present in the given collection + */ + public static > EnumSet complementOf( + Collection collection, Class type) { + checkNotNull(collection); + return (collection instanceof EnumSet) + ? EnumSet.complementOf((EnumSet) collection) + : makeComplementByHand(collection, type); + } + + private static > EnumSet makeComplementByHand( + Collection collection, Class type) { + EnumSet result = EnumSet.allOf(type); + result.removeAll(collection); + return result; + } + + /** + * Returns a set backed by the specified map. The resulting set displays the same ordering, + * concurrency, and performance characteristics as the backing map. In essence, this factory + * method provides a {@link Set} implementation corresponding to any {@link Map} implementation. + * There is no need to use this method on a {@link Map} implementation that already has a + * corresponding {@link Set} implementation (such as {@link java.util.HashMap} or {@link + * java.util.TreeMap}). + * + *

Each method invocation on the set returned by this method results in exactly one method + * invocation on the backing map or its {@code keySet} view, with one exception. The {@code + * addAll} method is implemented as a sequence of {@code put} invocations on the backing map. + * + *

The specified map must be empty at the time this method is invoked, and should not be + * accessed directly after this method returns. These conditions are ensured if the map is created + * empty, passed directly to this method, and no reference to the map is retained, as illustrated + * in the following code fragment: + * + *

{@code
+   * Set identityHashSet = Sets.newSetFromMap(
+   *     new IdentityHashMap());
+   * }
+   *
+   * 

The returned set is serializable if the backing map is. + * + * @param map the backing map + * @return the set backed by the map + * @throws IllegalArgumentException if {@code map} is not empty + * @deprecated Use {@link Collections#newSetFromMap} instead. + */ + @Deprecated + public static Set newSetFromMap(Map map) { + return Collections.newSetFromMap(map); + } + + /** + * An unmodifiable view of a set which may be backed by other sets; this view will change as the + * backing sets do. Contains methods to copy the data into a new set which will then remain + * stable. There is usually no reason to retain a reference of type {@code SetView}; typically, + * you either use it as a plain {@link Set}, or immediately invoke {@link #immutableCopy} or + * {@link #copyInto} and forget the {@code SetView} itself. + * + * @since 2.0 + */ + public abstract static class SetView extends AbstractSet { + private SetView() {} // no subclasses but our own + + /** + * Returns an immutable copy of the current contents of this set view. Does not support null + * elements. + * + *

Warning: this may have unexpected results if a backing set of this view uses a + * nonstandard notion of equivalence, for example if it is a {@link TreeSet} using a comparator + * that is inconsistent with {@link Object#equals(Object)}. + */ + public ImmutableSet immutableCopy() { + return ImmutableSet.copyOf(this); + } + + /** + * Copies the current contents of this set view into an existing set. This method has equivalent + * behavior to {@code set.addAll(this)}, assuming that all the sets involved are based on the + * same notion of equivalence. + * + * @return a reference to {@code set}, for convenience + */ + // Note: S should logically extend Set but can't due to either + // some javac bug or some weirdness in the spec, not sure which. + + public > S copyInto(S set) { + set.addAll(this); + return set; + } + + /** + * Guaranteed to throw an exception and leave the collection unmodified. + * + * @throws UnsupportedOperationException always + * @deprecated Unsupported operation. + */ + + @Deprecated + @Override + public final boolean add(E e) { + throw new UnsupportedOperationException(); + } + + /** + * Guaranteed to throw an exception and leave the collection unmodified. + * + * @throws UnsupportedOperationException always + * @deprecated Unsupported operation. + */ + + @Deprecated + @Override + public final boolean remove(Object object) { + throw new UnsupportedOperationException(); + } + + /** + * Guaranteed to throw an exception and leave the collection unmodified. + * + * @throws UnsupportedOperationException always + * @deprecated Unsupported operation. + */ + + @Deprecated + @Override + public final boolean addAll(Collection newElements) { + throw new UnsupportedOperationException(); + } + + /** + * Guaranteed to throw an exception and leave the collection unmodified. + * + * @throws UnsupportedOperationException always + * @deprecated Unsupported operation. + */ + + @Deprecated + @Override + public final boolean removeAll(Collection oldElements) { + throw new UnsupportedOperationException(); + } + + /** + * Guaranteed to throw an exception and leave the collection unmodified. + * + * @throws UnsupportedOperationException always + * @deprecated Unsupported operation. + */ + + @Deprecated + @Override + public final boolean removeIf(java.util.function.Predicate filter) { + throw new UnsupportedOperationException(); + } + + /** + * Guaranteed to throw an exception and leave the collection unmodified. + * + * @throws UnsupportedOperationException always + * @deprecated Unsupported operation. + */ + + @Deprecated + @Override + public final boolean retainAll(Collection elementsToKeep) { + throw new UnsupportedOperationException(); + } + + /** + * Guaranteed to throw an exception and leave the collection unmodified. + * + * @throws UnsupportedOperationException always + * @deprecated Unsupported operation. + */ + @Deprecated + @Override + public final void clear() { + throw new UnsupportedOperationException(); + } + + /** + * Scope the return type to {@link UnmodifiableIterator} to ensure this is an unmodifiable view. + * + * @since 20.0 (present with return type {@link Iterator} since 2.0) + */ + @Override + public abstract UnmodifiableIterator iterator(); + } + + /** + * Returns an unmodifiable view of the union of two sets. The returned set contains all + * elements that are contained in either backing set. Iterating over the returned set iterates + * first over all the elements of {@code set1}, then over each element of {@code set2}, in order, + * that is not contained in {@code set1}. + * + *

Results are undefined if {@code set1} and {@code set2} are sets based on different + * equivalence relations (as {@link HashSet}, {@link TreeSet}, and the {@link Map#keySet} of an + * {@code IdentityHashMap} all are). + */ + public static SetView union(final Set set1, final Set set2) { + checkNotNull(set1, "set1"); + checkNotNull(set2, "set2"); + + return new SetView() { + @Override + public int size() { + int size = set1.size(); + for (E e : set2) { + if (!set1.contains(e)) { + size++; + } + } + return size; + } + + @Override + public boolean isEmpty() { + return set1.isEmpty() && set2.isEmpty(); + } + + @Override + public UnmodifiableIterator iterator() { + return new AbstractIterator() { + final Iterator itr1 = set1.iterator(); + final Iterator itr2 = set2.iterator(); + + @Override + protected E computeNext() { + if (itr1.hasNext()) { + return itr1.next(); + } + while (itr2.hasNext()) { + E e = itr2.next(); + if (!set1.contains(e)) { + return e; + } + } + return endOfData(); + } + }; + } + + @Override + public Stream stream() { + return Stream.concat(set1.stream(), set2.stream().filter(e -> !set1.contains(e))); + } + + @Override + public Stream parallelStream() { + return stream().parallel(); + } + + @Override + public boolean contains(Object object) { + return set1.contains(object) || set2.contains(object); + } + + @Override + public > S copyInto(S set) { + set.addAll(set1); + set.addAll(set2); + return set; + } + + @Override + public ImmutableSet immutableCopy() { + return new ImmutableSet.Builder().addAll(set1).addAll(set2).build(); + } + }; + } + + /** + * Returns an unmodifiable view of the intersection of two sets. The returned set contains + * all elements that are contained by both backing sets. The iteration order of the returned set + * matches that of {@code set1}. + * + *

Results are undefined if {@code set1} and {@code set2} are sets based on different + * equivalence relations (as {@code HashSet}, {@code TreeSet}, and the keySet of an {@code + * IdentityHashMap} all are). + * + *

Note: The returned view performs slightly better when {@code set1} is the smaller of + * the two sets. If you have reason to believe one of your sets will generally be smaller than the + * other, pass it first. Unfortunately, since this method sets the generic type of the returned + * set based on the type of the first set passed, this could in rare cases force you to make a + * cast, for example: + * + *

{@code
+   * Set aFewBadObjects = ...
+   * Set manyBadStrings = ...
+   *
+   * // impossible for a non-String to be in the intersection
+   * SuppressWarnings("unchecked")
+   * Set badStrings = (Set) Sets.intersection(
+   *     aFewBadObjects, manyBadStrings);
+   * }
+   *
+   * 

This is unfortunate, but should come up only very rarely. + */ + public static SetView intersection(final Set set1, final Set set2) { + checkNotNull(set1, "set1"); + checkNotNull(set2, "set2"); + + return new SetView() { + @Override + public UnmodifiableIterator iterator() { + return new AbstractIterator() { + final Iterator itr = set1.iterator(); + + @Override + protected E computeNext() { + while (itr.hasNext()) { + E e = itr.next(); + if (set2.contains(e)) { + return e; + } + } + return endOfData(); + } + }; + } + + @Override + public Stream stream() { + return set1.stream().filter(set2::contains); + } + + @Override + public Stream parallelStream() { + return set1.parallelStream().filter(set2::contains); + } + + @Override + public int size() { + int size = 0; + for (E e : set1) { + if (set2.contains(e)) { + size++; + } + } + return size; + } + + @Override + public boolean isEmpty() { + return Collections.disjoint(set2, set1); + } + + @Override + public boolean contains(Object object) { + return set1.contains(object) && set2.contains(object); + } + + @Override + public boolean containsAll(Collection collection) { + return set1.containsAll(collection) && set2.containsAll(collection); + } + }; + } + + /** + * Returns an unmodifiable view of the difference of two sets. The returned set contains + * all elements that are contained by {@code set1} and not contained by {@code set2}. {@code set2} + * may also contain elements not present in {@code set1}; these are simply ignored. The iteration + * order of the returned set matches that of {@code set1}. + * + *

Results are undefined if {@code set1} and {@code set2} are sets based on different + * equivalence relations (as {@code HashSet}, {@code TreeSet}, and the keySet of an {@code + * IdentityHashMap} all are). + */ + public static SetView difference(final Set set1, final Set set2) { + checkNotNull(set1, "set1"); + checkNotNull(set2, "set2"); + + return new SetView() { + @Override + public UnmodifiableIterator iterator() { + return new AbstractIterator() { + final Iterator itr = set1.iterator(); + + @Override + protected E computeNext() { + while (itr.hasNext()) { + E e = itr.next(); + if (!set2.contains(e)) { + return e; + } + } + return endOfData(); + } + }; + } + + @Override + public Stream stream() { + return set1.stream().filter(e -> !set2.contains(e)); + } + + @Override + public Stream parallelStream() { + return set1.parallelStream().filter(e -> !set2.contains(e)); + } + + @Override + public int size() { + int size = 0; + for (E e : set1) { + if (!set2.contains(e)) { + size++; + } + } + return size; + } + + @Override + public boolean isEmpty() { + return set2.containsAll(set1); + } + + @Override + public boolean contains(Object element) { + return set1.contains(element) && !set2.contains(element); + } + }; + } + + /** + * Returns an unmodifiable view of the symmetric difference of two sets. The returned set + * contains all elements that are contained in either {@code set1} or {@code set2} but not in + * both. The iteration order of the returned set is undefined. + * + *

Results are undefined if {@code set1} and {@code set2} are sets based on different + * equivalence relations (as {@code HashSet}, {@code TreeSet}, and the keySet of an {@code + * IdentityHashMap} all are). + * + * @since 3.0 + */ + public static SetView symmetricDifference( + final Set set1, final Set set2) { + checkNotNull(set1, "set1"); + checkNotNull(set2, "set2"); + + return new SetView() { + @Override + public UnmodifiableIterator iterator() { + final Iterator itr1 = set1.iterator(); + final Iterator itr2 = set2.iterator(); + return new AbstractIterator() { + @Override + public E computeNext() { + while (itr1.hasNext()) { + E elem1 = itr1.next(); + if (!set2.contains(elem1)) { + return elem1; + } + } + while (itr2.hasNext()) { + E elem2 = itr2.next(); + if (!set1.contains(elem2)) { + return elem2; + } + } + return endOfData(); + } + }; + } + + @Override + public int size() { + int size = 0; + for (E e : set1) { + if (!set2.contains(e)) { + size++; + } + } + for (E e : set2) { + if (!set1.contains(e)) { + size++; + } + } + return size; + } + + @Override + public boolean isEmpty() { + return set1.equals(set2); + } + + @Override + public boolean contains(Object element) { + return set1.contains(element) ^ set2.contains(element); + } + }; + } + + /** + * Returns the elements of {@code unfiltered} that satisfy a predicate. The returned set is a live + * view of {@code unfiltered}; changes to one affect the other. + * + *

The resulting set's iterator does not support {@code remove()}, but all other set methods + * are supported. When given an element that doesn't satisfy the predicate, the set's {@code + * add()} and {@code addAll()} methods throw an {@link IllegalArgumentException}. When methods + * such as {@code removeAll()} and {@code clear()} are called on the filtered set, only elements + * that satisfy the filter will be removed from the underlying set. + * + *

The returned set isn't threadsafe or serializable, even if {@code unfiltered} is. + * + *

Many of the filtered set's methods, such as {@code size()}, iterate across every element in + * the underlying set and determine which elements satisfy the filter. When a live view is + * not needed, it may be faster to copy {@code Iterables.filter(unfiltered, predicate)} and + * use the copy. + * + *

Warning: {@code predicate} must be consistent with equals, as documented at + * {@link Predicate#apply}. Do not provide a predicate such as {@code + * Predicates.instanceOf(ArrayList.class)}, which is inconsistent with equals. (See {@link + * Iterables#filter(Iterable, Class)} for related functionality.) + * + *

Java 8 users: many use cases for this method are better addressed by {@link + * java.util.stream.Stream#filter}. This method is not being deprecated, but we gently encourage + * you to migrate to streams. + */ + // TODO(kevinb): how to omit that last sentence when building GWT javadoc? + public static Set filter(Set unfiltered, Predicate predicate) { + if (unfiltered instanceof SortedSet) { + return filter((SortedSet) unfiltered, predicate); + } + if (unfiltered instanceof FilteredSet) { + // Support clear(), removeAll(), and retainAll() when filtering a filtered + // collection. + FilteredSet filtered = (FilteredSet) unfiltered; + Predicate combinedPredicate = Predicates.and(filtered.predicate, predicate); + return new FilteredSet((Set) filtered.unfiltered, combinedPredicate); + } + + return new FilteredSet(checkNotNull(unfiltered), checkNotNull(predicate)); + } + + /** + * Returns the elements of a {@code SortedSet}, {@code unfiltered}, that satisfy a predicate. The + * returned set is a live view of {@code unfiltered}; changes to one affect the other. + * + *

The resulting set's iterator does not support {@code remove()}, but all other set methods + * are supported. When given an element that doesn't satisfy the predicate, the set's {@code + * add()} and {@code addAll()} methods throw an {@link IllegalArgumentException}. When methods + * such as {@code removeAll()} and {@code clear()} are called on the filtered set, only elements + * that satisfy the filter will be removed from the underlying set. + * + *

The returned set isn't threadsafe or serializable, even if {@code unfiltered} is. + * + *

Many of the filtered set's methods, such as {@code size()}, iterate across every element in + * the underlying set and determine which elements satisfy the filter. When a live view is + * not needed, it may be faster to copy {@code Iterables.filter(unfiltered, predicate)} and + * use the copy. + * + *

Warning: {@code predicate} must be consistent with equals, as documented at + * {@link Predicate#apply}. Do not provide a predicate such as {@code + * Predicates.instanceOf(ArrayList.class)}, which is inconsistent with equals. (See {@link + * Iterables#filter(Iterable, Class)} for related functionality.) + * + * @since 11.0 + */ + public static SortedSet filter(SortedSet unfiltered, Predicate predicate) { + if (unfiltered instanceof FilteredSet) { + // Support clear(), removeAll(), and retainAll() when filtering a filtered + // collection. + FilteredSet filtered = (FilteredSet) unfiltered; + Predicate combinedPredicate = Predicates.and(filtered.predicate, predicate); + return new FilteredSortedSet((SortedSet) filtered.unfiltered, combinedPredicate); + } + + return new FilteredSortedSet(checkNotNull(unfiltered), checkNotNull(predicate)); + } + + /** + * Returns the elements of a {@code NavigableSet}, {@code unfiltered}, that satisfy a predicate. + * The returned set is a live view of {@code unfiltered}; changes to one affect the other. + * + *

The resulting set's iterator does not support {@code remove()}, but all other set methods + * are supported. When given an element that doesn't satisfy the predicate, the set's {@code + * add()} and {@code addAll()} methods throw an {@link IllegalArgumentException}. When methods + * such as {@code removeAll()} and {@code clear()} are called on the filtered set, only elements + * that satisfy the filter will be removed from the underlying set. + * + *

The returned set isn't threadsafe or serializable, even if {@code unfiltered} is. + * + *

Many of the filtered set's methods, such as {@code size()}, iterate across every element in + * the underlying set and determine which elements satisfy the filter. When a live view is + * not needed, it may be faster to copy {@code Iterables.filter(unfiltered, predicate)} and + * use the copy. + * + *

Warning: {@code predicate} must be consistent with equals, as documented at + * {@link Predicate#apply}. Do not provide a predicate such as {@code + * Predicates.instanceOf(ArrayList.class)}, which is inconsistent with equals. (See {@link + * Iterables#filter(Iterable, Class)} for related functionality.) + * + * @since 14.0 + */ + @GwtIncompatible // NavigableSet + @SuppressWarnings("unchecked") + public static NavigableSet filter( + NavigableSet unfiltered, Predicate predicate) { + if (unfiltered instanceof FilteredSet) { + // Support clear(), removeAll(), and retainAll() when filtering a filtered + // collection. + FilteredSet filtered = (FilteredSet) unfiltered; + Predicate combinedPredicate = Predicates.and(filtered.predicate, predicate); + return new FilteredNavigableSet((NavigableSet) filtered.unfiltered, combinedPredicate); + } + + return new FilteredNavigableSet(checkNotNull(unfiltered), checkNotNull(predicate)); + } + + private static class FilteredSet extends FilteredCollection implements Set { + FilteredSet(Set unfiltered, Predicate predicate) { + super(unfiltered, predicate); + } + + @Override + public boolean equals(Object object) { + return equalsImpl(this, object); + } + + @Override + public int hashCode() { + return hashCodeImpl(this); + } + } + + private static class FilteredSortedSet extends FilteredSet implements SortedSet { + + FilteredSortedSet(SortedSet unfiltered, Predicate predicate) { + super(unfiltered, predicate); + } + + @Override + public Comparator comparator() { + return ((SortedSet) unfiltered).comparator(); + } + + @Override + public SortedSet subSet(E fromElement, E toElement) { + return new FilteredSortedSet( + ((SortedSet) unfiltered).subSet(fromElement, toElement), predicate); + } + + @Override + public SortedSet headSet(E toElement) { + return new FilteredSortedSet(((SortedSet) unfiltered).headSet(toElement), predicate); + } + + @Override + public SortedSet tailSet(E fromElement) { + return new FilteredSortedSet(((SortedSet) unfiltered).tailSet(fromElement), predicate); + } + + @Override + public E first() { + return Iterators.find(unfiltered.iterator(), predicate); + } + + @Override + public E last() { + SortedSet sortedUnfiltered = (SortedSet) unfiltered; + while (true) { + E element = sortedUnfiltered.last(); + if (predicate.apply(element)) { + return element; + } + sortedUnfiltered = sortedUnfiltered.headSet(element); + } + } + } + + @GwtIncompatible // NavigableSet + private static class FilteredNavigableSet extends FilteredSortedSet + implements NavigableSet { + FilteredNavigableSet(NavigableSet unfiltered, Predicate predicate) { + super(unfiltered, predicate); + } + + NavigableSet unfiltered() { + return (NavigableSet) unfiltered; + } + + @Override + public E lower(E e) { + return Iterators.find(unfiltered().headSet(e, false).descendingIterator(), predicate, null); + } + + @Override + public E floor(E e) { + return Iterators.find(unfiltered().headSet(e, true).descendingIterator(), predicate, null); + } + + @Override + public E ceiling(E e) { + return Iterables.find(unfiltered().tailSet(e, true), predicate, null); + } + + @Override + public E higher(E e) { + return Iterables.find(unfiltered().tailSet(e, false), predicate, null); + } + + @Override + public E pollFirst() { + return Iterables.removeFirstMatching(unfiltered(), predicate); + } + + @Override + public E pollLast() { + return Iterables.removeFirstMatching(unfiltered().descendingSet(), predicate); + } + + @Override + public NavigableSet descendingSet() { + return Sets.filter(unfiltered().descendingSet(), predicate); + } + + @Override + public Iterator descendingIterator() { + return Iterators.filter(unfiltered().descendingIterator(), predicate); + } + + @Override + public E last() { + return Iterators.find(unfiltered().descendingIterator(), predicate); + } + + @Override + public NavigableSet subSet( + E fromElement, boolean fromInclusive, E toElement, boolean toInclusive) { + return filter( + unfiltered().subSet(fromElement, fromInclusive, toElement, toInclusive), predicate); + } + + @Override + public NavigableSet headSet(E toElement, boolean inclusive) { + return filter(unfiltered().headSet(toElement, inclusive), predicate); + } + + @Override + public NavigableSet tailSet(E fromElement, boolean inclusive) { + return filter(unfiltered().tailSet(fromElement, inclusive), predicate); + } + } + + /** + * Returns every possible list that can be formed by choosing one element from each of the given + * sets in order; the "n-ary Cartesian + * product" of the sets. For example: + * + *

{@code
+   * Sets.cartesianProduct(ImmutableList.of(
+   *     ImmutableSet.of(1, 2),
+   *     ImmutableSet.of("A", "B", "C")))
+   * }
+ * + *

returns a set containing six lists: + * + *

    + *
  • {@code ImmutableList.of(1, "A")} + *
  • {@code ImmutableList.of(1, "B")} + *
  • {@code ImmutableList.of(1, "C")} + *
  • {@code ImmutableList.of(2, "A")} + *
  • {@code ImmutableList.of(2, "B")} + *
  • {@code ImmutableList.of(2, "C")} + *
+ * + *

The result is guaranteed to be in the "traditional", lexicographical order for Cartesian + * products that you would get from nesting for loops: + * + *

{@code
+   * for (B b0 : sets.get(0)) {
+   *   for (B b1 : sets.get(1)) {
+   *     ...
+   *     ImmutableList tuple = ImmutableList.of(b0, b1, ...);
+   *     // operate on tuple
+   *   }
+   * }
+   * }
+ * + *

Note that if any input set is empty, the Cartesian product will also be empty. If no sets at + * all are provided (an empty list), the resulting Cartesian product has one element, an empty + * list (counter-intuitive, but mathematically consistent). + * + *

Performance notes: while the cartesian product of sets of size {@code m, n, p} is a + * set of size {@code m x n x p}, its actual memory consumption is much smaller. When the + * cartesian set is constructed, the input sets are merely copied. Only as the resulting set is + * iterated are the individual lists created, and these are not retained after iteration. + * + * @param sets the sets to choose elements from, in the order that the elements chosen from those + * sets should appear in the resulting lists + * @param any common base class shared by all axes (often just {@link Object}) + * @return the Cartesian product, as an immutable set containing immutable lists + * @throws NullPointerException if {@code sets}, any one of the {@code sets}, or any element of a + * provided set is null + * @since 2.0 + */ + public static Set> cartesianProduct(List> sets) { + return CartesianSet.create(sets); + } + + /** + * Returns every possible list that can be formed by choosing one element from each of the given + * sets in order; the "n-ary Cartesian + * product" of the sets. For example: + * + *

{@code
+   * Sets.cartesianProduct(
+   *     ImmutableSet.of(1, 2),
+   *     ImmutableSet.of("A", "B", "C"))
+   * }
+ * + *

returns a set containing six lists: + * + *

    + *
  • {@code ImmutableList.of(1, "A")} + *
  • {@code ImmutableList.of(1, "B")} + *
  • {@code ImmutableList.of(1, "C")} + *
  • {@code ImmutableList.of(2, "A")} + *
  • {@code ImmutableList.of(2, "B")} + *
  • {@code ImmutableList.of(2, "C")} + *
+ * + *

The result is guaranteed to be in the "traditional", lexicographical order for Cartesian + * products that you would get from nesting for loops: + * + *

{@code
+   * for (B b0 : sets.get(0)) {
+   *   for (B b1 : sets.get(1)) {
+   *     ...
+   *     ImmutableList tuple = ImmutableList.of(b0, b1, ...);
+   *     // operate on tuple
+   *   }
+   * }
+   * }
+ * + *

Note that if any input set is empty, the Cartesian product will also be empty. If no sets at + * all are provided (an empty list), the resulting Cartesian product has one element, an empty + * list (counter-intuitive, but mathematically consistent). + * + *

Performance notes: while the cartesian product of sets of size {@code m, n, p} is a + * set of size {@code m x n x p}, its actual memory consumption is much smaller. When the + * cartesian set is constructed, the input sets are merely copied. Only as the resulting set is + * iterated are the individual lists created, and these are not retained after iteration. + * + * @param sets the sets to choose elements from, in the order that the elements chosen from those + * sets should appear in the resulting lists + * @param any common base class shared by all axes (often just {@link Object}) + * @return the Cartesian product, as an immutable set containing immutable lists + * @throws NullPointerException if {@code sets}, any one of the {@code sets}, or any element of a + * provided set is null + * @since 2.0 + */ + @SafeVarargs + public static Set> cartesianProduct(Set... sets) { + return cartesianProduct(Arrays.asList(sets)); + } + + private static final class CartesianSet extends ForwardingCollection> + implements Set> { + private final transient ImmutableList> axes; + private final transient CartesianList delegate; + + static Set> create(List> sets) { + ImmutableList.Builder> axesBuilder = new ImmutableList.Builder<>(sets.size()); + for (Set set : sets) { + ImmutableSet copy = ImmutableSet.copyOf(set); + if (copy.isEmpty()) { + return ImmutableSet.of(); + } + axesBuilder.add(copy); + } + final ImmutableList> axes = axesBuilder.build(); + ImmutableList> listAxes = + new ImmutableList>() { + @Override + public int size() { + return axes.size(); + } + + @Override + public List get(int index) { + return axes.get(index).asList(); + } + + @Override + boolean isPartialView() { + return true; + } + }; + return new CartesianSet(axes, new CartesianList(listAxes)); + } + + private CartesianSet(ImmutableList> axes, CartesianList delegate) { + this.axes = axes; + this.delegate = delegate; + } + + @Override + protected Collection> delegate() { + return delegate; + } + + @Override + public boolean equals(Object object) { + // Warning: this is broken if size() == 0, so it is critical that we + // substitute an empty ImmutableSet to the user in place of this + if (object instanceof CartesianSet) { + CartesianSet that = (CartesianSet) object; + return this.axes.equals(that.axes); + } + return super.equals(object); + } + + @Override + public int hashCode() { + // Warning: this is broken if size() == 0, so it is critical that we + // substitute an empty ImmutableSet to the user in place of this + + // It's a weird formula, but tests prove it works. + int adjust = size() - 1; + for (int i = 0; i < axes.size(); i++) { + adjust *= 31; + adjust = ~~adjust; + // in GWT, we have to deal with integer overflow carefully + } + int hash = 1; + for (Set axis : axes) { + hash = 31 * hash + (size() / axis.size() * axis.hashCode()); + + hash = ~~hash; + } + hash += adjust; + return ~~hash; + } + } + + /** + * Returns the set of all possible subsets of {@code set}. For example, {@code + * powerSet(ImmutableSet.of(1, 2))} returns the set {@code {{}, {1}, {2}, {1, 2}}}. + * + *

Elements appear in these subsets in the same iteration order as they appeared in the input + * set. The order in which these subsets appear in the outer set is undefined. Note that the power + * set of the empty set is not the empty set, but a one-element set containing the empty set. + * + *

The returned set and its constituent sets use {@code equals} to decide whether two elements + * are identical, even if the input set uses a different concept of equivalence. + * + *

Performance notes: while the power set of a set with size {@code n} is of size {@code + * 2^n}, its memory usage is only {@code O(n)}. When the power set is constructed, the input set + * is merely copied. Only as the power set is iterated are the individual subsets created, and + * these subsets themselves occupy only a small constant amount of memory. + * + * @param set the set of elements to construct a power set from + * @return the power set, as an immutable set of immutable sets + * @throws IllegalArgumentException if {@code set} has more than 30 unique elements (causing the + * power set size to exceed the {@code int} range) + * @throws NullPointerException if {@code set} is or contains {@code null} + * @see Power set article at Wikipedia + * @since 4.0 + */ + @GwtCompatible(serializable = false) + public static Set> powerSet(Set set) { + return new PowerSet(set); + } + + private static final class SubSet extends AbstractSet { + private final ImmutableMap inputSet; + private final int mask; + + SubSet(ImmutableMap inputSet, int mask) { + this.inputSet = inputSet; + this.mask = mask; + } + + @Override + public Iterator iterator() { + return new UnmodifiableIterator() { + final ImmutableList elements = inputSet.keySet().asList(); + int remainingSetBits = mask; + + @Override + public boolean hasNext() { + return remainingSetBits != 0; + } + + @Override + public E next() { + int index = Integer.numberOfTrailingZeros(remainingSetBits); + if (index == 32) { + throw new NoSuchElementException(); + } + remainingSetBits &= ~(1 << index); + return elements.get(index); + } + }; + } + + @Override + public int size() { + return Integer.bitCount(mask); + } + + @Override + public boolean contains(Object o) { + Integer index = inputSet.get(o); + return index != null && (mask & (1 << index)) != 0; + } + } + + private static final class PowerSet extends AbstractSet> { + final ImmutableMap inputSet; + + PowerSet(Set input) { + checkArgument( + input.size() <= 30, "Too many elements to create power set: %s > 30", input.size()); + this.inputSet = Maps.indexMap(input); + } + + @Override + public int size() { + return 1 << inputSet.size(); + } + + @Override + public boolean isEmpty() { + return false; + } + + @Override + public Iterator> iterator() { + return new AbstractIndexedListIterator>(size()) { + @Override + protected Set get(final int setBits) { + return new SubSet(inputSet, setBits); + } + }; + } + + @Override + public boolean contains(Object obj) { + if (obj instanceof Set) { + Set set = (Set) obj; + return inputSet.keySet().containsAll(set); + } + return false; + } + + @Override + public boolean equals(Object obj) { + if (obj instanceof PowerSet) { + PowerSet that = (PowerSet) obj; + return inputSet.equals(that.inputSet); + } + return super.equals(obj); + } + + @Override + public int hashCode() { + /* + * The sum of the sums of the hash codes in each subset is just the sum of + * each input element's hash code times the number of sets that element + * appears in. Each element appears in exactly half of the 2^n sets, so: + */ + return inputSet.keySet().hashCode() << (inputSet.size() - 1); + } + + @Override + public String toString() { + return "powerSet(" + inputSet + ")"; + } + } + + /** + * Returns the set of all subsets of {@code set} of size {@code size}. For example, {@code + * combinations(ImmutableSet.of(1, 2, 3), 2)} returns the set {@code {{1, 2}, {1, 3}, {2, 3}}}. + * + *

Elements appear in these subsets in the same iteration order as they appeared in the input + * set. The order in which these subsets appear in the outer set is undefined. + * + *

The returned set and its constituent sets use {@code equals} to decide whether two elements + * are identical, even if the input set uses a different concept of equivalence. + * + *

Performance notes: the memory usage of the returned set is only {@code O(n)}. When + * the result set is constructed, the input set is merely copied. Only as the result set is + * iterated are the individual subsets created. Each of these subsets occupies an additional O(n) + * memory but only for as long as the user retains a reference to it. That is, the set returned by + * {@code combinations} does not retain the individual subsets. + * + * @param set the set of elements to take combinations of + * @param size the number of elements per combination + * @return the set of all combinations of {@code size} elements from {@code set} + * @throws IllegalArgumentException if {@code size} is not between 0 and {@code set.size()} + * inclusive + * @throws NullPointerException if {@code set} is or contains {@code null} + * @since 23.0 + */ + @Beta + public static Set> combinations(Set set, final int size) { + final ImmutableMap index = Maps.indexMap(set); + checkNonnegative(size, "size"); + checkArgument(size <= index.size(), "size (%s) must be <= set.size() (%s)", size, index.size()); + if (size == 0) { + return ImmutableSet.>of(ImmutableSet.of()); + } else if (size == index.size()) { + return ImmutableSet.>of(index.keySet()); + } + return new AbstractSet>() { + @Override + public boolean contains(Object o) { + if (o instanceof Set) { + Set s = (Set) o; + return s.size() == size && index.keySet().containsAll(s); + } + return false; + } + + @Override + public Iterator> iterator() { + return new AbstractIterator>() { + final BitSet bits = new BitSet(index.size()); + + @Override + protected Set computeNext() { + if (bits.isEmpty()) { + bits.set(0, size); + } else { + int firstSetBit = bits.nextSetBit(0); + int bitToFlip = bits.nextClearBit(firstSetBit); + + if (bitToFlip == index.size()) { + return endOfData(); + } + + /* + * The current set in sorted order looks like + * {firstSetBit, firstSetBit + 1, ..., bitToFlip - 1, ...} + * where it does *not* contain bitToFlip. + * + * The next combination is + * + * {0, 1, ..., bitToFlip - firstSetBit - 2, bitToFlip, ...} + * + * This is lexicographically next if you look at the combinations in descending order + * e.g. {2, 1, 0}, {3, 1, 0}, {3, 2, 0}, {3, 2, 1}, {4, 1, 0}... + */ + + bits.set(0, bitToFlip - firstSetBit - 1); + bits.clear(bitToFlip - firstSetBit - 1, bitToFlip); + bits.set(bitToFlip); + } + final BitSet copy = (BitSet) bits.clone(); + return new AbstractSet() { + @Override + public boolean contains(Object o) { + Integer i = index.get(o); + return i != null && copy.get(i); + } + + @Override + public Iterator iterator() { + return new AbstractIterator() { + int i = -1; + + @Override + protected E computeNext() { + i = copy.nextSetBit(i + 1); + if (i == -1) { + return endOfData(); + } + return index.keySet().asList().get(i); + } + }; + } + + @Override + public int size() { + return size; + } + }; + } + }; + } + + @Override + public int size() { + return IntMath.binomial(index.size(), size); + } + + @Override + public String toString() { + return "Sets.combinations(" + index.keySet() + ", " + size + ")"; + } + }; + } + + /** An implementation for {@link Set#hashCode()}. */ + static int hashCodeImpl(Set s) { + int hashCode = 0; + for (Object o : s) { + hashCode += o != null ? o.hashCode() : 0; + + hashCode = ~~hashCode; + // Needed to deal with unusual integer overflow in GWT. + } + return hashCode; + } + + /** An implementation for {@link Set#equals(Object)}. */ + static boolean equalsImpl(Set s, Object object) { + if (s == object) { + return true; + } + if (object instanceof Set) { + Set o = (Set) object; + + try { + return s.size() == o.size() && s.containsAll(o); + } catch (NullPointerException | ClassCastException ignored) { + return false; + } + } + return false; + } + + /** + * Returns an unmodifiable view of the specified navigable set. This method allows modules to + * provide users with "read-only" access to internal navigable sets. Query operations on the + * returned set "read through" to the specified set, and attempts to modify the returned set, + * whether direct or via its collection views, result in an {@code UnsupportedOperationException}. + * + *

The returned navigable set will be serializable if the specified navigable set is + * serializable. + * + * @param set the navigable set for which an unmodifiable view is to be returned + * @return an unmodifiable view of the specified navigable set + * @since 12.0 + */ + public static NavigableSet unmodifiableNavigableSet(NavigableSet set) { + if (set instanceof ImmutableCollection || set instanceof UnmodifiableNavigableSet) { + return set; + } + return new UnmodifiableNavigableSet(set); + } + + static final class UnmodifiableNavigableSet extends ForwardingSortedSet + implements NavigableSet, Serializable { + private final NavigableSet delegate; + private final SortedSet unmodifiableDelegate; + + UnmodifiableNavigableSet(NavigableSet delegate) { + this.delegate = checkNotNull(delegate); + this.unmodifiableDelegate = Collections.unmodifiableSortedSet(delegate); + } + + @Override + protected SortedSet delegate() { + return unmodifiableDelegate; + } + + // default methods not forwarded by ForwardingSortedSet + + @Override + public boolean removeIf(java.util.function.Predicate filter) { + throw new UnsupportedOperationException(); + } + + @Override + public Stream stream() { + return delegate.stream(); + } + + @Override + public Stream parallelStream() { + return delegate.parallelStream(); + } + + @Override + public void forEach(Consumer action) { + delegate.forEach(action); + } + + @Override + public E lower(E e) { + return delegate.lower(e); + } + + @Override + public E floor(E e) { + return delegate.floor(e); + } + + @Override + public E ceiling(E e) { + return delegate.ceiling(e); + } + + @Override + public E higher(E e) { + return delegate.higher(e); + } + + @Override + public E pollFirst() { + throw new UnsupportedOperationException(); + } + + @Override + public E pollLast() { + throw new UnsupportedOperationException(); + } + + private transient UnmodifiableNavigableSet descendingSet; + + @Override + public NavigableSet descendingSet() { + UnmodifiableNavigableSet result = descendingSet; + if (result == null) { + result = descendingSet = new UnmodifiableNavigableSet(delegate.descendingSet()); + result.descendingSet = this; + } + return result; + } + + @Override + public Iterator descendingIterator() { + return Iterators.unmodifiableIterator(delegate.descendingIterator()); + } + + @Override + public NavigableSet subSet( + E fromElement, boolean fromInclusive, E toElement, boolean toInclusive) { + return unmodifiableNavigableSet( + delegate.subSet(fromElement, fromInclusive, toElement, toInclusive)); + } + + @Override + public NavigableSet headSet(E toElement, boolean inclusive) { + return unmodifiableNavigableSet(delegate.headSet(toElement, inclusive)); + } + + @Override + public NavigableSet tailSet(E fromElement, boolean inclusive) { + return unmodifiableNavigableSet(delegate.tailSet(fromElement, inclusive)); + } + + private static final long serialVersionUID = 0; + } + + /** + * Returns a synchronized (thread-safe) navigable set backed by the specified navigable set. In + * order to guarantee serial access, it is critical that all access to the backing + * navigable set is accomplished through the returned navigable set (or its views). + * + *

It is imperative that the user manually synchronize on the returned sorted set when + * iterating over it or any of its {@code descendingSet}, {@code subSet}, {@code headSet}, or + * {@code tailSet} views. + * + *

{@code
+   * NavigableSet set = synchronizedNavigableSet(new TreeSet());
+   *  ...
+   * synchronized (set) {
+   *   // Must be in the synchronized block
+   *   Iterator it = set.iterator();
+   *   while (it.hasNext()) {
+   *     foo(it.next());
+   *   }
+   * }
+   * }
+ * + *

or: + * + *

{@code
+   * NavigableSet set = synchronizedNavigableSet(new TreeSet());
+   * NavigableSet set2 = set.descendingSet().headSet(foo);
+   *  ...
+   * synchronized (set) { // Note: set, not set2!!!
+   *   // Must be in the synchronized block
+   *   Iterator it = set2.descendingIterator();
+   *   while (it.hasNext())
+   *     foo(it.next());
+   *   }
+   * }
+   * }
+ * + *

Failure to follow this advice may result in non-deterministic behavior. + * + *

The returned navigable set will be serializable if the specified navigable set is + * serializable. + * + * @param navigableSet the navigable set to be "wrapped" in a synchronized navigable set. + * @return a synchronized view of the specified navigable set. + * @since 13.0 + */ + @GwtIncompatible // NavigableSet + public static NavigableSet synchronizedNavigableSet(NavigableSet navigableSet) { + return Synchronized.navigableSet(navigableSet); + } + + /** Remove each element in an iterable from a set. */ + static boolean removeAllImpl(Set set, Iterator iterator) { + boolean changed = false; + while (iterator.hasNext()) { + changed |= set.remove(iterator.next()); + } + return changed; + } + + static boolean removeAllImpl(Set set, Collection collection) { + checkNotNull(collection); // for GWT + if (collection instanceof Multiset) { + collection = ((Multiset) collection).elementSet(); + } + /* + * AbstractSet.removeAll(List) has quadratic behavior if the list size + * is just more than the set's size. We augment the test by + * assuming that sets have fast contains() performance, and other + * collections don't. See + * http://code.google.com/p/guava-libraries/issues/detail?id=1013 + */ + if (collection instanceof Set && collection.size() > set.size()) { + return Iterators.removeAll(set.iterator(), collection); + } else { + return removeAllImpl(set, collection.iterator()); + } + } + + @GwtIncompatible // NavigableSet + static class DescendingSet extends ForwardingNavigableSet { + private final NavigableSet forward; + + DescendingSet(NavigableSet forward) { + this.forward = forward; + } + + @Override + protected NavigableSet delegate() { + return forward; + } + + @Override + public E lower(E e) { + return forward.higher(e); + } + + @Override + public E floor(E e) { + return forward.ceiling(e); + } + + @Override + public E ceiling(E e) { + return forward.floor(e); + } + + @Override + public E higher(E e) { + return forward.lower(e); + } + + @Override + public E pollFirst() { + return forward.pollLast(); + } + + @Override + public E pollLast() { + return forward.pollFirst(); + } + + @Override + public NavigableSet descendingSet() { + return forward; + } + + @Override + public Iterator descendingIterator() { + return forward.iterator(); + } + + @Override + public NavigableSet subSet( + E fromElement, boolean fromInclusive, E toElement, boolean toInclusive) { + return forward.subSet(toElement, toInclusive, fromElement, fromInclusive).descendingSet(); + } + + @Override + public SortedSet subSet(E fromElement, E toElement) { + return standardSubSet(fromElement, toElement); + } + + @Override + public NavigableSet headSet(E toElement, boolean inclusive) { + return forward.tailSet(toElement, inclusive).descendingSet(); + } + + @Override + public SortedSet headSet(E toElement) { + return standardHeadSet(toElement); + } + + @Override + public NavigableSet tailSet(E fromElement, boolean inclusive) { + return forward.headSet(fromElement, inclusive).descendingSet(); + } + + @Override + public SortedSet tailSet(E fromElement) { + return standardTailSet(fromElement); + } + + @SuppressWarnings("unchecked") + @Override + public Comparator comparator() { + Comparator forwardComparator = forward.comparator(); + if (forwardComparator == null) { + return (Comparator) Ordering.natural().reverse(); + } else { + return reverse(forwardComparator); + } + } + + // If we inline this, we get a javac error. + private static Ordering reverse(Comparator forward) { + return Ordering.from(forward).reverse(); + } + + @Override + public E first() { + return forward.last(); + } + + @Override + public E last() { + return forward.first(); + } + + @Override + public Iterator iterator() { + return forward.descendingIterator(); + } + + @Override + public Object[] toArray() { + return standardToArray(); + } + + @Override + public T[] toArray(T[] array) { + return standardToArray(array); + } + + @Override + public String toString() { + return standardToString(); + } + } + + /** + * Returns a view of the portion of {@code set} whose elements are contained by {@code range}. + * + *

This method delegates to the appropriate methods of {@link NavigableSet} (namely {@link + * NavigableSet#subSet(Object, boolean, Object, boolean) subSet()}, {@link + * NavigableSet#tailSet(Object, boolean) tailSet()}, and {@link NavigableSet#headSet(Object, + * boolean) headSet()}) to actually construct the view. Consult these methods for a full + * description of the returned view's behavior. + * + *

Warning: {@code Range}s always represent a range of values using the values' natural + * ordering. {@code NavigableSet} on the other hand can specify a custom ordering via a {@link + * Comparator}, which can violate the natural ordering. Using this method (or in general using + * {@code Range}) with unnaturally-ordered sets can lead to unexpected and undefined behavior. + * + * @since 20.0 + */ + @Beta + @GwtIncompatible // NavigableSet + public static > NavigableSet subSet( + NavigableSet set, Range range) { + if (set.comparator() != null + && set.comparator() != Ordering.natural() + && range.hasLowerBound() + && range.hasUpperBound()) { + checkArgument( + set.comparator().compare(range.lowerEndpoint(), range.upperEndpoint()) <= 0, + "set is using a custom comparator which is inconsistent with the natural ordering."); + } + if (range.hasLowerBound() && range.hasUpperBound()) { + return set.subSet( + range.lowerEndpoint(), + range.lowerBoundType() == BoundType.CLOSED, + range.upperEndpoint(), + range.upperBoundType() == BoundType.CLOSED); + } else if (range.hasLowerBound()) { + return set.tailSet(range.lowerEndpoint(), range.lowerBoundType() == BoundType.CLOSED); + } else if (range.hasUpperBound()) { + return set.headSet(range.upperEndpoint(), range.upperBoundType() == BoundType.CLOSED); + } + return checkNotNull(set); + } +} diff --git a/src/main/java/com/google/common/collect/SingletonImmutableBiMap.java b/src/main/java/com/google/common/collect/SingletonImmutableBiMap.java new file mode 100644 index 0000000..d605ba8 --- /dev/null +++ b/src/main/java/com/google/common/collect/SingletonImmutableBiMap.java @@ -0,0 +1,105 @@ +/* + * Copyright (C) 2008 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.collect.CollectPreconditions.checkEntryNotNull; + +import com.google.common.annotations.GwtCompatible; + + +import java.util.function.BiConsumer; + + +/** + * Implementation of {@link ImmutableMap} with exactly one entry. + * + * @author Jesse Wilson + * @author Kevin Bourrillion + */ +@GwtCompatible(serializable = true, emulated = true) +@SuppressWarnings("serial") // uses writeReplace(), not default serialization +final class SingletonImmutableBiMap extends ImmutableBiMap { + + final transient K singleKey; + final transient V singleValue; + + SingletonImmutableBiMap(K singleKey, V singleValue) { + checkEntryNotNull(singleKey, singleValue); + this.singleKey = singleKey; + this.singleValue = singleValue; + } + + private SingletonImmutableBiMap(K singleKey, V singleValue, ImmutableBiMap inverse) { + this.singleKey = singleKey; + this.singleValue = singleValue; + this.inverse = inverse; + } + + @Override + public V get(Object key) { + return singleKey.equals(key) ? singleValue : null; + } + + @Override + public int size() { + return 1; + } + + @Override + public void forEach(BiConsumer action) { + checkNotNull(action).accept(singleKey, singleValue); + } + + @Override + public boolean containsKey(Object key) { + return singleKey.equals(key); + } + + @Override + public boolean containsValue(Object value) { + return singleValue.equals(value); + } + + @Override + boolean isPartialView() { + return false; + } + + @Override + ImmutableSet> createEntrySet() { + return ImmutableSet.of(Maps.immutableEntry(singleKey, singleValue)); + } + + @Override + ImmutableSet createKeySet() { + return ImmutableSet.of(singleKey); + } + + transient ImmutableBiMap inverse; + + @Override + public ImmutableBiMap inverse() { + // racy single-check idiom + ImmutableBiMap result = inverse; + if (result == null) { + return inverse = new SingletonImmutableBiMap<>(singleValue, singleKey, this); + } else { + return result; + } + } +} diff --git a/src/main/java/com/google/common/collect/SingletonImmutableList.java b/src/main/java/com/google/common/collect/SingletonImmutableList.java new file mode 100644 index 0000000..eec0daa --- /dev/null +++ b/src/main/java/com/google/common/collect/SingletonImmutableList.java @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2009 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.annotations.GwtCompatible; +import com.google.common.base.Preconditions; +import java.util.Collections; +import java.util.Spliterator; + +/** + * Implementation of {@link ImmutableList} with exactly one element. + * + * @author Hayward Chan + */ +@GwtCompatible(serializable = true, emulated = true) +@SuppressWarnings("serial") // uses writeReplace(), not default serialization +final class SingletonImmutableList extends ImmutableList { + + final transient E element; + + SingletonImmutableList(E element) { + this.element = checkNotNull(element); + } + + @Override + public E get(int index) { + Preconditions.checkElementIndex(index, 1); + return element; + } + + @Override + public UnmodifiableIterator iterator() { + return Iterators.singletonIterator(element); + } + + @Override + public Spliterator spliterator() { + return Collections.singleton(element).spliterator(); + } + + @Override + public int size() { + return 1; + } + + @Override + public ImmutableList subList(int fromIndex, int toIndex) { + Preconditions.checkPositionIndexes(fromIndex, toIndex, 1); + return (fromIndex == toIndex) ? ImmutableList.of() : this; + } + + @Override + public String toString() { + return '[' + element.toString() + ']'; + } + + @Override + boolean isPartialView() { + return false; + } +} diff --git a/src/main/java/com/google/common/collect/SingletonImmutableSet.java b/src/main/java/com/google/common/collect/SingletonImmutableSet.java new file mode 100644 index 0000000..3e6a50a --- /dev/null +++ b/src/main/java/com/google/common/collect/SingletonImmutableSet.java @@ -0,0 +1,103 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import com.google.common.annotations.GwtCompatible; +import com.google.common.base.Preconditions; + + +/** + * Implementation of {@link ImmutableSet} with exactly one element. + * + * @author Kevin Bourrillion + * @author Nick Kralevich + */ +@GwtCompatible(serializable = true, emulated = true) +@SuppressWarnings("serial") // uses writeReplace(), not default serialization +final class SingletonImmutableSet extends ImmutableSet { + + final transient E element; + // This is transient because it will be recalculated on the first + // call to hashCode(). + // + // A race condition is avoided since threads will either see that the value + // is zero and recalculate it themselves, or two threads will see it at + // the same time, and both recalculate it. If the cachedHashCode is 0, + // it will always be recalculated, unfortunately. + private transient int cachedHashCode; + + SingletonImmutableSet(E element) { + this.element = Preconditions.checkNotNull(element); + } + + SingletonImmutableSet(E element, int hashCode) { + // Guaranteed to be non-null by the presence of the pre-computed hash code. + this.element = element; + cachedHashCode = hashCode; + } + + @Override + public int size() { + return 1; + } + + @Override + public boolean contains(Object target) { + return element.equals(target); + } + + @Override + public UnmodifiableIterator iterator() { + return Iterators.singletonIterator(element); + } + + @Override + ImmutableList createAsList() { + return ImmutableList.of(element); + } + + @Override + boolean isPartialView() { + return false; + } + + @Override + int copyIntoArray(Object[] dst, int offset) { + dst[offset] = element; + return offset + 1; + } + + @Override + public final int hashCode() { + // Racy single-check. + int code = cachedHashCode; + if (code == 0) { + cachedHashCode = code = element.hashCode(); + } + return code; + } + + @Override + boolean isHashCodeFast() { + return cachedHashCode != 0; + } + + @Override + public String toString() { + return '[' + element.toString() + ']'; + } +} diff --git a/src/main/java/com/google/common/collect/SingletonImmutableTable.java b/src/main/java/com/google/common/collect/SingletonImmutableTable.java new file mode 100644 index 0000000..58a182c --- /dev/null +++ b/src/main/java/com/google/common/collect/SingletonImmutableTable.java @@ -0,0 +1,82 @@ +/* + * Copyright (C) 2009 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.annotations.GwtCompatible; +import java.util.Map; + +/** + * An implementation of {@link ImmutableTable} that holds a single cell. + * + * @author Gregory Kick + */ +@GwtCompatible +class SingletonImmutableTable extends ImmutableTable { + final R singleRowKey; + final C singleColumnKey; + final V singleValue; + + SingletonImmutableTable(R rowKey, C columnKey, V value) { + this.singleRowKey = checkNotNull(rowKey); + this.singleColumnKey = checkNotNull(columnKey); + this.singleValue = checkNotNull(value); + } + + SingletonImmutableTable(Cell cell) { + this(cell.getRowKey(), cell.getColumnKey(), cell.getValue()); + } + + @Override + public ImmutableMap column(C columnKey) { + checkNotNull(columnKey); + return containsColumn(columnKey) + ? ImmutableMap.of(singleRowKey, singleValue) + : ImmutableMap.of(); + } + + @Override + public ImmutableMap> columnMap() { + return ImmutableMap.of(singleColumnKey, (Map) ImmutableMap.of(singleRowKey, singleValue)); + } + + @Override + public ImmutableMap> rowMap() { + return ImmutableMap.of(singleRowKey, (Map) ImmutableMap.of(singleColumnKey, singleValue)); + } + + @Override + public int size() { + return 1; + } + + @Override + ImmutableSet> createCellSet() { + return ImmutableSet.of(cellOf(singleRowKey, singleColumnKey, singleValue)); + } + + @Override + ImmutableCollection createValues() { + return ImmutableSet.of(singleValue); + } + + @Override + SerializedForm createSerializedForm() { + return SerializedForm.create(this, new int[] {0}, new int[] {0}); + } +} diff --git a/src/main/java/com/google/common/collect/SortedIterable.java b/src/main/java/com/google/common/collect/SortedIterable.java new file mode 100644 index 0000000..d46e8af --- /dev/null +++ b/src/main/java/com/google/common/collect/SortedIterable.java @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2011 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the + * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import com.google.common.annotations.GwtCompatible; +import java.util.Comparator; +import java.util.Iterator; + +/** + * An {@code Iterable} whose elements are sorted relative to a {@code Comparator}, typically + * provided at creation time. + * + * @author Louis Wasserman + */ +@GwtCompatible +interface SortedIterable extends Iterable { + /** + * Returns the {@code Comparator} by which the elements of this iterable are ordered, or {@code + * Ordering.natural()} if the elements are ordered by their natural ordering. + */ + Comparator comparator(); + + /** + * Returns an iterator over elements of type {@code T}. The elements are returned in nondecreasing + * order according to the associated {@link #comparator}. + */ + @Override + Iterator iterator(); +} diff --git a/src/main/java/com/google/common/collect/SortedIterables.java b/src/main/java/com/google/common/collect/SortedIterables.java new file mode 100644 index 0000000..2c0aa7c --- /dev/null +++ b/src/main/java/com/google/common/collect/SortedIterables.java @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2011 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the + * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.annotations.GwtCompatible; +import java.util.Comparator; +import java.util.SortedSet; + +/** + * Utilities for dealing with sorted collections of all types. + * + * @author Louis Wasserman + */ +@GwtCompatible +final class SortedIterables { + private SortedIterables() {} + + /** + * Returns {@code true} if {@code elements} is a sorted collection using an ordering equivalent to + * {@code comparator}. + */ + public static boolean hasSameComparator(Comparator comparator, Iterable elements) { + checkNotNull(comparator); + checkNotNull(elements); + Comparator comparator2; + if (elements instanceof SortedSet) { + comparator2 = comparator((SortedSet) elements); + } else if (elements instanceof SortedIterable) { + comparator2 = ((SortedIterable) elements).comparator(); + } else { + return false; + } + return comparator.equals(comparator2); + } + + @SuppressWarnings("unchecked") + // if sortedSet.comparator() is null, the set must be naturally ordered + public static Comparator comparator(SortedSet sortedSet) { + Comparator result = sortedSet.comparator(); + if (result == null) { + result = (Comparator) Ordering.natural(); + } + return result; + } +} diff --git a/src/main/java/com/google/common/collect/SortedLists.java b/src/main/java/com/google/common/collect/SortedLists.java new file mode 100644 index 0000000..286489b --- /dev/null +++ b/src/main/java/com/google/common/collect/SortedLists.java @@ -0,0 +1,280 @@ +/* + * Copyright (C) 2010 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.collect; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.annotations.Beta; +import com.google.common.annotations.GwtCompatible; +import com.google.common.base.Function; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; +import java.util.RandomAccess; + + +/** + * Static methods pertaining to sorted {@link List} instances. + * + *

In this documentation, the terms greatest, greater, least, and + * lesser are considered to refer to the comparator on the elements, and the terms + * first and last are considered to refer to the elements' ordering in a list. + * + * @author Louis Wasserman + */ +@GwtCompatible +@Beta final class SortedLists { + private SortedLists() {} + + /** + * A specification for which index to return if the list contains at least one element that + * compares as equal to the key. + */ enum KeyPresentBehavior { + /** + * Return the index of any list element that compares as equal to the key. No guarantees are + * made as to which index is returned, if more than one element compares as equal to the key. + */ + ANY_PRESENT { + @Override + int resultIndex( + Comparator comparator, E key, List list, int foundIndex) { + return foundIndex; + } + }, + /** Return the index of the last list element that compares as equal to the key. */ + LAST_PRESENT { + @Override + int resultIndex( + Comparator comparator, E key, List list, int foundIndex) { + // Of course, we have to use binary search to find the precise + // breakpoint... + int lower = foundIndex; + int upper = list.size() - 1; + // Everything between lower and upper inclusive compares at >= 0. + while (lower < upper) { + int middle = (lower + upper + 1) >>> 1; + int c = comparator.compare(list.get(middle), key); + if (c > 0) { + upper = middle - 1; + } else { // c == 0 + lower = middle; + } + } + return lower; + } + }, + /** Return the index of the first list element that compares as equal to the key. */ + FIRST_PRESENT { + @Override + int resultIndex( + Comparator comparator, E key, List list, int foundIndex) { + // Of course, we have to use binary search to find the precise + // breakpoint... + int lower = 0; + int upper = foundIndex; + // Of course, we have to use binary search to find the precise breakpoint... + // Everything between lower and upper inclusive compares at <= 0. + while (lower < upper) { + int middle = (lower + upper) >>> 1; + int c = comparator.compare(list.get(middle), key); + if (c < 0) { + lower = middle + 1; + } else { // c == 0 + upper = middle; + } + } + return lower; + } + }, + /** + * Return the index of the first list element that compares as greater than the key, or {@code + * list.size()} if there is no such element. + */ + FIRST_AFTER { + @Override + public int resultIndex( + Comparator comparator, E key, List list, int foundIndex) { + return LAST_PRESENT.resultIndex(comparator, key, list, foundIndex) + 1; + } + }, + /** + * Return the index of the last list element that compares as less than the key, or {@code -1} + * if there is no such element. + */ + LAST_BEFORE { + @Override + public int resultIndex( + Comparator comparator, E key, List list, int foundIndex) { + return FIRST_PRESENT.resultIndex(comparator, key, list, foundIndex) - 1; + } + }; + + abstract int resultIndex( + Comparator comparator, E key, List list, int foundIndex); + } + + /** + * A specification for which index to return if the list contains no elements that compare as + * equal to the key. + */ enum KeyAbsentBehavior { + /** + * Return the index of the next lower element in the list, or {@code -1} if there is no such + * element. + */ + NEXT_LOWER { + @Override + int resultIndex(int higherIndex) { + return higherIndex - 1; + } + }, + /** + * Return the index of the next higher element in the list, or {@code list.size()} if there is + * no such element. + */ + NEXT_HIGHER { + @Override + public int resultIndex(int higherIndex) { + return higherIndex; + } + }, + /** + * Return {@code ~insertionIndex}, where {@code insertionIndex} is defined as the point at which + * the key would be inserted into the list: the index of the next higher element in the list, or + * {@code list.size()} if there is no such element. + * + *

Note that the return value will be {@code >= 0} if and only if there is an element of the + * list that compares as equal to the key. + * + *

This is equivalent to the behavior of {@link java.util.Collections#binarySearch(List, + * Object)} when the key isn't present, since {@code ~insertionIndex} is equal to {@code -1 - + * insertionIndex}. + */ + INVERTED_INSERTION_INDEX { + @Override + public int resultIndex(int higherIndex) { + return ~higherIndex; + } + }; + + abstract int resultIndex(int higherIndex); + } + + /** + * Searches the specified naturally ordered list for the specified object using the binary search + * algorithm. + * + *

Equivalent to {@link #binarySearch(List, Function, Object, Comparator, KeyPresentBehavior, + * KeyAbsentBehavior)} using {@link Ordering#natural}. + */ + public static int binarySearch( + List list, + E e, + KeyPresentBehavior presentBehavior, + KeyAbsentBehavior absentBehavior) { + checkNotNull(e); + return binarySearch(list, e, Ordering.natural(), presentBehavior, absentBehavior); + } + + /** + * Binary searches the list for the specified key, using the specified key function. + * + *

Equivalent to {@link #binarySearch(List, Function, Object, Comparator, KeyPresentBehavior, + * KeyAbsentBehavior)} using {@link Ordering#natural}. + */ + public static int binarySearch( + List list, + Function keyFunction, + K key, + KeyPresentBehavior presentBehavior, + KeyAbsentBehavior absentBehavior) { + return binarySearch( + list, keyFunction, key, Ordering.natural(), presentBehavior, absentBehavior); + } + + /** + * Binary searches the list for the specified key, using the specified key function. + * + *

Equivalent to {@link #binarySearch(List, Object, Comparator, KeyPresentBehavior, + * KeyAbsentBehavior)} using {@link Lists#transform(List, Function) Lists.transform(list, + * keyFunction)}. + */ + public static int binarySearch( + List list, + Function keyFunction, + K key, + Comparator keyComparator, + KeyPresentBehavior presentBehavior, + KeyAbsentBehavior absentBehavior) { + return binarySearch( + Lists.transform(list, keyFunction), key, keyComparator, presentBehavior, absentBehavior); + } + + /** + * Searches the specified list for the specified object using the binary search algorithm. The + * list must be sorted into ascending order according to the specified comparator (as by the + * {@link Collections#sort(List, Comparator) Collections.sort(List, Comparator)} method), prior to + * making this call. If it is not sorted, the results are undefined. + * + *

If there are elements in the list which compare as equal to the key, the choice of {@link + * KeyPresentBehavior} decides which index is returned. If no elements compare as equal to the + * key, the choice of {@link KeyAbsentBehavior} decides which index is returned. + * + *

This method runs in log(n) time on random-access lists, which offer near-constant-time + * access to each list element. + * + * @param list the list to be searched. + * @param key the value to be searched for. + * @param comparator the comparator by which the list is ordered. + * @param presentBehavior the specification for what to do if at least one element of the list + * compares as equal to the key. + * @param absentBehavior the specification for what to do if no elements of the list compare as + * equal to the key. + * @return the index determined by the {@code KeyPresentBehavior}, if the key is in the list; + * otherwise the index determined by the {@code KeyAbsentBehavior}. + */ + public static int binarySearch( + List list, + E key, + Comparator comparator, + KeyPresentBehavior presentBehavior, + KeyAbsentBehavior absentBehavior) { + checkNotNull(comparator); + checkNotNull(list); + checkNotNull(presentBehavior); + checkNotNull(absentBehavior); + if (!(list instanceof RandomAccess)) { + list = Lists.newArrayList(list); + } + // TODO(lowasser): benchmark when it's best to do a linear search + + int lower = 0; + int upper = list.size() - 1; + + while (lower <= upper) { + int middle = (lower + upper) >>> 1; + int c = comparator.compare(key, list.get(middle)); + if (c < 0) { + upper = middle - 1; + } else if (c > 0) { + lower = middle + 1; + } else { + return lower + + presentBehavior.resultIndex( + comparator, key, list.subList(lower, upper + 1), middle - lower); + } + } + return absentBehavior.resultIndex(lower); + } +} diff --git a/src/main/java/com/google/common/collect/SortedMapDifference.java b/src/main/java/com/google/common/collect/SortedMapDifference.java new file mode 100644 index 0000000..4715e93 --- /dev/null +++ b/src/main/java/com/google/common/collect/SortedMapDifference.java @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2010 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import com.google.common.annotations.GwtCompatible; +import java.util.SortedMap; + +/** + * An object representing the differences between two sorted maps. + * + * @author Louis Wasserman + * @since 8.0 + */ +@GwtCompatible +public interface SortedMapDifference extends MapDifference { + + @Override + SortedMap entriesOnlyOnLeft(); + + @Override + SortedMap entriesOnlyOnRight(); + + @Override + SortedMap entriesInCommon(); + + @Override + SortedMap> entriesDiffering(); +} diff --git a/src/main/java/com/google/common/collect/SortedMultiset.java b/src/main/java/com/google/common/collect/SortedMultiset.java new file mode 100644 index 0000000..849ea50 --- /dev/null +++ b/src/main/java/com/google/common/collect/SortedMultiset.java @@ -0,0 +1,145 @@ +/* + * Copyright (C) 2011 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.collect; + +import com.google.common.annotations.GwtCompatible; +import java.util.Collection; +import java.util.Comparator; +import java.util.Iterator; +import java.util.NavigableSet; +import java.util.Set; + +/** + * A {@link Multiset} which maintains the ordering of its elements, according to either their + * natural order or an explicit {@link Comparator}. This order is reflected when iterating over the + * sorted multiset, either directly, or through its {@code elementSet} or {@code entrySet} views. In + * all cases, this implementation uses {@link Comparable#compareTo} or {@link Comparator#compare} + * instead of {@link Object#equals} to determine equivalence of instances. + * + *

Warning: The comparison must be consistent with equals as explained by the + * {@link Comparable} class specification. Otherwise, the resulting multiset will violate the {@link + * Collection} contract, which it is specified in terms of {@link Object#equals}. + * + *

See the Guava User Guide article on {@code + * Multiset}. + * + * @author Louis Wasserman + * @since 11.0 + */ +@GwtCompatible(emulated = true) +public interface SortedMultiset extends SortedMultisetBridge, SortedIterable { + /** + * Returns the comparator that orders this multiset, or {@link Ordering#natural()} if the natural + * ordering of the elements is used. + */ + @Override + Comparator comparator(); + + /** + * Returns the entry of the first element in this multiset, or {@code null} if this multiset is + * empty. + */ + Entry firstEntry(); + + /** + * Returns the entry of the last element in this multiset, or {@code null} if this multiset is + * empty. + */ + Entry lastEntry(); + + /** + * Returns and removes the entry associated with the lowest element in this multiset, or returns + * {@code null} if this multiset is empty. + */ + Entry pollFirstEntry(); + + /** + * Returns and removes the entry associated with the greatest element in this multiset, or returns + * {@code null} if this multiset is empty. + */ + Entry pollLastEntry(); + + /** + * Returns a {@link NavigableSet} view of the distinct elements in this multiset. + * + * @since 14.0 (present with return type {@code SortedSet} since 11.0) + */ + @Override + NavigableSet elementSet(); + + /** + * {@inheritDoc} + * + *

The {@code entrySet}'s iterator returns entries in ascending element order according to the + * this multiset's comparator. + */ + @Override + Set> entrySet(); + + /** + * {@inheritDoc} + * + *

The iterator returns the elements in ascending order according to this multiset's + * comparator. + */ + @Override + Iterator iterator(); + + /** + * Returns a descending view of this multiset. Modifications made to either map will be reflected + * in the other. + */ + SortedMultiset descendingMultiset(); + + /** + * Returns a view of this multiset restricted to the elements less than {@code upperBound}, + * optionally including {@code upperBound} itself. The returned multiset is a view of this + * multiset, so changes to one will be reflected in the other. The returned multiset supports all + * operations that this multiset supports. + * + *

The returned multiset will throw an {@link IllegalArgumentException} on attempts to add + * elements outside its range. + */ + SortedMultiset headMultiset(E upperBound, BoundType boundType); + + /** + * Returns a view of this multiset restricted to the range between {@code lowerBound} and {@code + * upperBound}. The returned multiset is a view of this multiset, so changes to one will be + * reflected in the other. The returned multiset supports all operations that this multiset + * supports. + * + *

The returned multiset will throw an {@link IllegalArgumentException} on attempts to add + * elements outside its range. + * + *

This method is equivalent to {@code tailMultiset(lowerBound, + * lowerBoundType).headMultiset(upperBound, upperBoundType)}. + */ + SortedMultiset subMultiset( + E lowerBound, BoundType lowerBoundType, E upperBound, BoundType upperBoundType); + + /** + * Returns a view of this multiset restricted to the elements greater than {@code lowerBound}, + * optionally including {@code lowerBound} itself. The returned multiset is a view of this + * multiset, so changes to one will be reflected in the other. The returned multiset supports all + * operations that this multiset supports. + * + *

The returned multiset will throw an {@link IllegalArgumentException} on attempts to add + * elements outside its range. + */ + SortedMultiset tailMultiset(E lowerBound, BoundType boundType); +} diff --git a/src/main/java/com/google/common/collect/SortedMultisetBridge.java b/src/main/java/com/google/common/collect/SortedMultisetBridge.java new file mode 100644 index 0000000..064cb75 --- /dev/null +++ b/src/main/java/com/google/common/collect/SortedMultisetBridge.java @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2012 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import com.google.common.annotations.GwtIncompatible; +import java.util.SortedSet; + +/** + * Superinterface of {@link SortedMultiset} to introduce a bridge method for {@code elementSet()}, + * to ensure binary compatibility with older Guava versions that specified {@code elementSet()} to + * return {@code SortedSet}. + * + * @author Louis Wasserman + */ +@GwtIncompatible +interface SortedMultisetBridge extends Multiset { + @Override + SortedSet elementSet(); +} diff --git a/src/main/java/com/google/common/collect/SortedMultisets.java b/src/main/java/com/google/common/collect/SortedMultisets.java new file mode 100644 index 0000000..23ccced --- /dev/null +++ b/src/main/java/com/google/common/collect/SortedMultisets.java @@ -0,0 +1,171 @@ +/* + * Copyright (C) 2011 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.collect; + +import static com.google.common.collect.BoundType.CLOSED; +import static com.google.common.collect.BoundType.OPEN; + +import com.google.common.annotations.GwtCompatible; +import com.google.common.annotations.GwtIncompatible; +import com.google.common.collect.Multiset.Entry; + +import java.util.Comparator; +import java.util.Iterator; +import java.util.NavigableSet; +import java.util.NoSuchElementException; +import java.util.SortedSet; + + +/** + * Provides static utility methods for creating and working with {@link SortedMultiset} instances. + * + * @author Louis Wasserman + */ +@GwtCompatible(emulated = true) +final class SortedMultisets { + private SortedMultisets() {} + + /** A skeleton implementation for {@link SortedMultiset#elementSet}. */ + static class ElementSet extends Multisets.ElementSet implements SortedSet { + private final SortedMultiset multiset; + + ElementSet(SortedMultiset multiset) { + this.multiset = multiset; + } + + @Override + final SortedMultiset multiset() { + return multiset; + } + + @Override + public Iterator iterator() { + return Multisets.elementIterator(multiset().entrySet().iterator()); + } + + @Override + public Comparator comparator() { + return multiset().comparator(); + } + + @Override + public SortedSet subSet(E fromElement, E toElement) { + return multiset().subMultiset(fromElement, CLOSED, toElement, OPEN).elementSet(); + } + + @Override + public SortedSet headSet(E toElement) { + return multiset().headMultiset(toElement, OPEN).elementSet(); + } + + @Override + public SortedSet tailSet(E fromElement) { + return multiset().tailMultiset(fromElement, CLOSED).elementSet(); + } + + @Override + public E first() { + return getElementOrThrow(multiset().firstEntry()); + } + + @Override + public E last() { + return getElementOrThrow(multiset().lastEntry()); + } + } + + /** A skeleton navigable implementation for {@link SortedMultiset#elementSet}. */ + @GwtIncompatible // Navigable + static class NavigableElementSet extends ElementSet implements NavigableSet { + NavigableElementSet(SortedMultiset multiset) { + super(multiset); + } + + @Override + public E lower(E e) { + return getElementOrNull(multiset().headMultiset(e, OPEN).lastEntry()); + } + + @Override + public E floor(E e) { + return getElementOrNull(multiset().headMultiset(e, CLOSED).lastEntry()); + } + + @Override + public E ceiling(E e) { + return getElementOrNull(multiset().tailMultiset(e, CLOSED).firstEntry()); + } + + @Override + public E higher(E e) { + return getElementOrNull(multiset().tailMultiset(e, OPEN).firstEntry()); + } + + @Override + public NavigableSet descendingSet() { + return new NavigableElementSet(multiset().descendingMultiset()); + } + + @Override + public Iterator descendingIterator() { + return descendingSet().iterator(); + } + + @Override + public E pollFirst() { + return getElementOrNull(multiset().pollFirstEntry()); + } + + @Override + public E pollLast() { + return getElementOrNull(multiset().pollLastEntry()); + } + + @Override + public NavigableSet subSet( + E fromElement, boolean fromInclusive, E toElement, boolean toInclusive) { + return new NavigableElementSet( + multiset() + .subMultiset( + fromElement, BoundType.forBoolean(fromInclusive), + toElement, BoundType.forBoolean(toInclusive))); + } + + @Override + public NavigableSet headSet(E toElement, boolean inclusive) { + return new NavigableElementSet( + multiset().headMultiset(toElement, BoundType.forBoolean(inclusive))); + } + + @Override + public NavigableSet tailSet(E fromElement, boolean inclusive) { + return new NavigableElementSet( + multiset().tailMultiset(fromElement, BoundType.forBoolean(inclusive))); + } + } + + private static E getElementOrThrow(Entry entry) { + if (entry == null) { + throw new NoSuchElementException(); + } + return entry.getElement(); + } + + private static E getElementOrNull(Entry entry) { + return (entry == null) ? null : entry.getElement(); + } +} diff --git a/src/main/java/com/google/common/collect/SortedSetMultimap.java b/src/main/java/com/google/common/collect/SortedSetMultimap.java new file mode 100644 index 0000000..7128c1f --- /dev/null +++ b/src/main/java/com/google/common/collect/SortedSetMultimap.java @@ -0,0 +1,113 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import com.google.common.annotations.GwtCompatible; + +import java.util.Collection; +import java.util.Comparator; +import java.util.Map; +import java.util.Set; +import java.util.SortedMap; +import java.util.SortedSet; + + +/** + * A {@code SetMultimap} whose set of values for a given key are kept sorted; that is, they comprise + * a {@link SortedSet}. It cannot hold duplicate key-value pairs; adding a key-value pair that's + * already in the multimap has no effect. This interface does not specify the ordering of the + * multimap's keys. See the {@link Multimap} documentation for information common to all multimaps. + * + *

The {@link #get}, {@link #removeAll}, and {@link #replaceValues} methods each return a {@link + * SortedSet} of values, while {@link Multimap#entries()} returns a {@link Set} of map entries. + * Though the method signature doesn't say so explicitly, the map returned by {@link #asMap} has + * {@code SortedSet} values. + * + *

See the Guava User Guide article on {@code + * Multimap}. + * + * @author Jared Levy + * @since 2.0 + */ +@GwtCompatible +public interface SortedSetMultimap extends SetMultimap { + // Following Javadoc copied from Multimap. + + /** + * Returns a collection view of all values associated with a key. If no mappings in the multimap + * have the provided key, an empty collection is returned. + * + *

Changes to the returned collection will update the underlying multimap, and vice versa. + * + *

Because a {@code SortedSetMultimap} has unique sorted values for a given key, this method + * returns a {@link SortedSet}, instead of the {@link java.util.Collection} specified in the + * {@link Multimap} interface. + */ + @Override + SortedSet get(K key); + + /** + * Removes all values associated with a given key. + * + *

Because a {@code SortedSetMultimap} has unique sorted values for a given key, this method + * returns a {@link SortedSet}, instead of the {@link java.util.Collection} specified in the + * {@link Multimap} interface. + */ + + @Override + SortedSet removeAll(Object key); + + /** + * Stores a collection of values with the same key, replacing any existing values for that key. + * + *

Because a {@code SortedSetMultimap} has unique sorted values for a given key, this method + * returns a {@link SortedSet}, instead of the {@link java.util.Collection} specified in the + * {@link Multimap} interface. + * + *

Any duplicates in {@code values} will be stored in the multimap once. + */ + + @Override + SortedSet replaceValues(K key, Iterable values); + + /** + * Returns a map view that associates each key with the corresponding values in the multimap. + * Changes to the returned map, such as element removal, will update the underlying multimap. The + * map does not support {@code setValue()} on its entries, {@code put}, or {@code putAll}. + * + *

When passed a key that is present in the map, {@code asMap().get(Object)} has the same + * behavior as {@link #get}, returning a live collection. When passed a key that is not present, + * however, {@code asMap().get(Object)} returns {@code null} instead of an empty collection. + * + *

Note: The returned map's values are guaranteed to be of type {@link SortedSet}. To + * obtain this map with the more specific generic type {@code Map>}, call {@link + * Multimaps#asMap(SortedSetMultimap)} instead. However, the returned map itself is + * not necessarily a {@link SortedMap}: A {@code SortedSetMultimap} must expose the values + * for a given key in sorted order, but it need not expose the keys in sorted order. + * Individual {@code SortedSetMultimap} implementations, like those built with {@link + * MultimapBuilder#treeKeys()}, may make additional guarantees. + */ + @Override + Map> asMap(); + + /** + * Returns the comparator that orders the multimap values, with {@code null} indicating that + * natural ordering is used. + */ + Comparator valueComparator(); +} diff --git a/src/main/java/com/google/common/collect/SparseImmutableTable.java b/src/main/java/com/google/common/collect/SparseImmutableTable.java new file mode 100644 index 0000000..e1c94f0 --- /dev/null +++ b/src/main/java/com/google/common/collect/SparseImmutableTable.java @@ -0,0 +1,134 @@ +/* + * Copyright (C) 2009 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.collect; + +import com.google.common.annotations.GwtCompatible; + +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Map.Entry; + +/** A {@code RegularImmutableTable} optimized for sparse data. */ +@GwtCompatible +final class SparseImmutableTable extends RegularImmutableTable { + static final ImmutableTable EMPTY = + new SparseImmutableTable<>( + ImmutableList.>of(), ImmutableSet.of(), ImmutableSet.of()); + + private final ImmutableMap> rowMap; + private final ImmutableMap> columnMap; + + // For each cell in iteration order, the index of that cell's row key in the row key list. + @SuppressWarnings("Immutable") // We don't modify this after construction. + private final int[] cellRowIndices; + + // For each cell in iteration order, the index of that cell's column key in the list of column + // keys present in that row. + @SuppressWarnings("Immutable") // We don't modify this after construction. + private final int[] cellColumnInRowIndices; + + SparseImmutableTable( + ImmutableList> cellList, + ImmutableSet rowSpace, + ImmutableSet columnSpace) { + Map rowIndex = Maps.indexMap(rowSpace); + Map> rows = Maps.newLinkedHashMap(); + for (R row : rowSpace) { + rows.put(row, new LinkedHashMap()); + } + Map> columns = Maps.newLinkedHashMap(); + for (C col : columnSpace) { + columns.put(col, new LinkedHashMap()); + } + int[] cellRowIndices = new int[cellList.size()]; + int[] cellColumnInRowIndices = new int[cellList.size()]; + for (int i = 0; i < cellList.size(); i++) { + Cell cell = cellList.get(i); + R rowKey = cell.getRowKey(); + C columnKey = cell.getColumnKey(); + V value = cell.getValue(); + + cellRowIndices[i] = rowIndex.get(rowKey); + Map thisRow = rows.get(rowKey); + cellColumnInRowIndices[i] = thisRow.size(); + V oldValue = thisRow.put(columnKey, value); + checkNoDuplicate(rowKey, columnKey, oldValue, value); + columns.get(columnKey).put(rowKey, value); + } + this.cellRowIndices = cellRowIndices; + this.cellColumnInRowIndices = cellColumnInRowIndices; + ImmutableMap.Builder> rowBuilder = + new ImmutableMap.Builder<>(rows.size()); + for (Entry> row : rows.entrySet()) { + rowBuilder.put(row.getKey(), ImmutableMap.copyOf(row.getValue())); + } + this.rowMap = rowBuilder.build(); + + ImmutableMap.Builder> columnBuilder = + new ImmutableMap.Builder<>(columns.size()); + for (Entry> col : columns.entrySet()) { + columnBuilder.put(col.getKey(), ImmutableMap.copyOf(col.getValue())); + } + this.columnMap = columnBuilder.build(); + } + + @Override + public ImmutableMap> columnMap() { + // Casts without copying. + ImmutableMap> columnMap = this.columnMap; + return ImmutableMap.>copyOf(columnMap); + } + + @Override + public ImmutableMap> rowMap() { + // Casts without copying. + ImmutableMap> rowMap = this.rowMap; + return ImmutableMap.>copyOf(rowMap); + } + + @Override + public int size() { + return cellRowIndices.length; + } + + @Override + Cell getCell(int index) { + int rowIndex = cellRowIndices[index]; + Entry> rowEntry = rowMap.entrySet().asList().get(rowIndex); + ImmutableMap row = rowEntry.getValue(); + int columnIndex = cellColumnInRowIndices[index]; + Entry colEntry = row.entrySet().asList().get(columnIndex); + return cellOf(rowEntry.getKey(), colEntry.getKey(), colEntry.getValue()); + } + + @Override + V getValue(int index) { + int rowIndex = cellRowIndices[index]; + ImmutableMap row = rowMap.values().asList().get(rowIndex); + int columnIndex = cellColumnInRowIndices[index]; + return row.values().asList().get(columnIndex); + } + + @Override + SerializedForm createSerializedForm() { + Map columnKeyToIndex = Maps.indexMap(columnKeySet()); + int[] cellColumnIndices = new int[cellSet().size()]; + int i = 0; + for (Cell cell : cellSet()) { + cellColumnIndices[i++] = columnKeyToIndex.get(cell.getColumnKey()); + } + return SerializedForm.create(this, cellRowIndices, cellColumnIndices); + } +} diff --git a/src/main/java/com/google/common/collect/StandardRowSortedTable.java b/src/main/java/com/google/common/collect/StandardRowSortedTable.java new file mode 100644 index 0000000..84ce1d5 --- /dev/null +++ b/src/main/java/com/google/common/collect/StandardRowSortedTable.java @@ -0,0 +1,143 @@ +/* + * Copyright (C) 2008 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.annotations.GwtCompatible; +import com.google.common.base.Supplier; + +import java.util.Comparator; +import java.util.Map; +import java.util.Set; +import java.util.SortedMap; +import java.util.SortedSet; + +/** + * Implementation of {@code Table} whose iteration ordering across row keys is sorted by their + * natural ordering or by a supplied comparator. Note that iterations across the columns keys for a + * single row key may or may not be ordered, depending on the implementation. When rows and columns + * are both sorted, it's easier to use the {@link TreeBasedTable} subclass. + * + *

The {@link #rowKeySet} method returns a {@link SortedSet} and the {@link #rowMap} method + * returns a {@link SortedMap}, instead of the {@link Set} and {@link Map} specified by the {@link + * Table} interface. + * + *

Null keys and values are not supported. + * + *

See the {@link StandardTable} superclass for more information about the behavior of this + * class. + * + * @author Jared Levy + */ +@GwtCompatible +class StandardRowSortedTable extends StandardTable + implements RowSortedTable { + /* + * TODO(jlevy): Consider adding headTable, tailTable, and subTable methods, + * which return a Table view with rows keys in a given range. Create a + * RowSortedTable subinterface with the revised methods? + */ + + StandardRowSortedTable( + SortedMap> backingMap, Supplier> factory) { + super(backingMap, factory); + } + + private SortedMap> sortedBackingMap() { + return (SortedMap>) backingMap; + } + + /** + * {@inheritDoc} + * + *

This method returns a {@link SortedSet}, instead of the {@code Set} specified in the {@link + * Table} interface. + */ + @Override + public SortedSet rowKeySet() { + return (SortedSet) rowMap().keySet(); + } + + /** + * {@inheritDoc} + * + *

This method returns a {@link SortedMap}, instead of the {@code Map} specified in the {@link + * Table} interface. + */ + @Override + public SortedMap> rowMap() { + return (SortedMap>) super.rowMap(); + } + + @Override + SortedMap> createRowMap() { + return new RowSortedMap(); + } + + + private class RowSortedMap extends RowMap implements SortedMap> { + @Override + public SortedSet keySet() { + return (SortedSet) super.keySet(); + } + + @Override + SortedSet createKeySet() { + return new Maps.SortedKeySet<>(this); + } + + @Override + public Comparator comparator() { + return sortedBackingMap().comparator(); + } + + @Override + public R firstKey() { + return sortedBackingMap().firstKey(); + } + + @Override + public R lastKey() { + return sortedBackingMap().lastKey(); + } + + @Override + public SortedMap> headMap(R toKey) { + checkNotNull(toKey); + return new StandardRowSortedTable(sortedBackingMap().headMap(toKey), factory) + .rowMap(); + } + + @Override + public SortedMap> subMap(R fromKey, R toKey) { + checkNotNull(fromKey); + checkNotNull(toKey); + return new StandardRowSortedTable(sortedBackingMap().subMap(fromKey, toKey), factory) + .rowMap(); + } + + @Override + public SortedMap> tailMap(R fromKey) { + checkNotNull(fromKey); + return new StandardRowSortedTable(sortedBackingMap().tailMap(fromKey), factory) + .rowMap(); + } + } + + private static final long serialVersionUID = 0; +} diff --git a/src/main/java/com/google/common/collect/StandardTable.java b/src/main/java/com/google/common/collect/StandardTable.java new file mode 100644 index 0000000..ad1b58c --- /dev/null +++ b/src/main/java/com/google/common/collect/StandardTable.java @@ -0,0 +1,990 @@ +/* + * Copyright (C) 2008 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.base.Predicates.alwaysTrue; +import static com.google.common.base.Predicates.equalTo; +import static com.google.common.base.Predicates.in; +import static com.google.common.base.Predicates.not; +import static com.google.common.collect.Maps.safeContainsKey; +import static com.google.common.collect.Maps.safeGet; + +import com.google.common.annotations.GwtCompatible; +import com.google.common.base.Function; +import com.google.common.base.Predicate; +import com.google.common.base.Supplier; +import com.google.common.collect.Maps.IteratorBasedAbstractMap; +import com.google.common.collect.Maps.ViewCachingAbstractMap; +import com.google.common.collect.Sets.ImprovedAbstractSet; + + +import java.io.Serializable; +import java.util.Collection; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; +import java.util.Spliterator; +import java.util.Spliterators; + + + +/** + * {@link Table} implementation backed by a map that associates row keys with column key / value + * secondary maps. This class provides rapid access to records by the row key alone or by both keys, + * but not by just the column key. + * + *

The views returned by {@link #column}, {@link #columnKeySet()}, and {@link #columnMap()} have + * iterators that don't support {@code remove()}. Otherwise, all optional operations are supported. + * Null row keys, columns keys, and values are not supported. + * + *

Lookups by row key are often faster than lookups by column key, because the data is stored in + * a {@code Map>}. A method call like {@code column(columnKey).get(rowKey)} still runs + * quickly, since the row key is provided. However, {@code column(columnKey).size()} takes longer, + * since an iteration across all row keys occurs. + * + *

Note that this implementation is not synchronized. If multiple threads access this table + * concurrently and one of the threads modifies the table, it must be synchronized externally. + * + * @author Jared Levy + */ +@GwtCompatible +class StandardTable extends AbstractTable implements Serializable { + @GwtTransient final Map> backingMap; + @GwtTransient final Supplier> factory; + + StandardTable(Map> backingMap, Supplier> factory) { + this.backingMap = backingMap; + this.factory = factory; + } + + // Accessors + + @Override + public boolean contains(Object rowKey, Object columnKey) { + return rowKey != null && columnKey != null && super.contains(rowKey, columnKey); + } + + @Override + public boolean containsColumn(Object columnKey) { + if (columnKey == null) { + return false; + } + for (Map map : backingMap.values()) { + if (safeContainsKey(map, columnKey)) { + return true; + } + } + return false; + } + + @Override + public boolean containsRow(Object rowKey) { + return rowKey != null && safeContainsKey(backingMap, rowKey); + } + + @Override + public boolean containsValue(Object value) { + return value != null && super.containsValue(value); + } + + @Override + public V get(Object rowKey, Object columnKey) { + return (rowKey == null || columnKey == null) ? null : super.get(rowKey, columnKey); + } + + @Override + public boolean isEmpty() { + return backingMap.isEmpty(); + } + + @Override + public int size() { + int size = 0; + for (Map map : backingMap.values()) { + size += map.size(); + } + return size; + } + + // Mutators + + @Override + public void clear() { + backingMap.clear(); + } + + private Map getOrCreate(R rowKey) { + Map map = backingMap.get(rowKey); + if (map == null) { + map = factory.get(); + backingMap.put(rowKey, map); + } + return map; + } + + + @Override + public V put(R rowKey, C columnKey, V value) { + checkNotNull(rowKey); + checkNotNull(columnKey); + checkNotNull(value); + return getOrCreate(rowKey).put(columnKey, value); + } + + + @Override + public V remove(Object rowKey, Object columnKey) { + if ((rowKey == null) || (columnKey == null)) { + return null; + } + Map map = safeGet(backingMap, rowKey); + if (map == null) { + return null; + } + V value = map.remove(columnKey); + if (map.isEmpty()) { + backingMap.remove(rowKey); + } + return value; + } + + + private Map removeColumn(Object column) { + Map output = new LinkedHashMap<>(); + Iterator>> iterator = backingMap.entrySet().iterator(); + while (iterator.hasNext()) { + Entry> entry = iterator.next(); + V value = entry.getValue().remove(column); + if (value != null) { + output.put(entry.getKey(), value); + if (entry.getValue().isEmpty()) { + iterator.remove(); + } + } + } + return output; + } + + private boolean containsMapping(Object rowKey, Object columnKey, Object value) { + return value != null && value.equals(get(rowKey, columnKey)); + } + + /** Remove a row key / column key / value mapping, if present. */ + private boolean removeMapping(Object rowKey, Object columnKey, Object value) { + if (containsMapping(rowKey, columnKey, value)) { + remove(rowKey, columnKey); + return true; + } + return false; + } + + // Views + + /** + * Abstract set whose {@code isEmpty()} returns whether the table is empty and whose {@code + * clear()} clears all table mappings. + */ + + private abstract class TableSet extends ImprovedAbstractSet { + @Override + public boolean isEmpty() { + return backingMap.isEmpty(); + } + + @Override + public void clear() { + backingMap.clear(); + } + } + + /** + * {@inheritDoc} + * + *

The set's iterator traverses the mappings for the first row, the mappings for the second + * row, and so on. + * + *

Each cell is an immutable snapshot of a row key / column key / value mapping, taken at the + * time the cell is returned by a method call to the set or its iterator. + */ + @Override + public Set> cellSet() { + return super.cellSet(); + } + + @Override + Iterator> cellIterator() { + return new CellIterator(); + } + + private class CellIterator implements Iterator> { + final Iterator>> rowIterator = backingMap.entrySet().iterator(); + Entry> rowEntry; + Iterator> columnIterator = Iterators.emptyModifiableIterator(); + + @Override + public boolean hasNext() { + return rowIterator.hasNext() || columnIterator.hasNext(); + } + + @Override + public Cell next() { + if (!columnIterator.hasNext()) { + rowEntry = rowIterator.next(); + columnIterator = rowEntry.getValue().entrySet().iterator(); + } + Entry columnEntry = columnIterator.next(); + return Tables.immutableCell(rowEntry.getKey(), columnEntry.getKey(), columnEntry.getValue()); + } + + @Override + public void remove() { + columnIterator.remove(); + if (rowEntry.getValue().isEmpty()) { + rowIterator.remove(); + rowEntry = null; + } + } + } + + @Override + Spliterator> cellSpliterator() { + return CollectSpliterators.flatMap( + backingMap.entrySet().spliterator(), + (Entry> rowEntry) -> + CollectSpliterators.map( + rowEntry.getValue().entrySet().spliterator(), + (Entry columnEntry) -> + Tables.immutableCell( + rowEntry.getKey(), columnEntry.getKey(), columnEntry.getValue())), + Spliterator.DISTINCT | Spliterator.SIZED, + size()); + } + + @Override + public Map row(R rowKey) { + return new Row(rowKey); + } + + class Row extends IteratorBasedAbstractMap { + final R rowKey; + + Row(R rowKey) { + this.rowKey = checkNotNull(rowKey); + } + + Map backingRowMap; + + Map backingRowMap() { + return (backingRowMap == null || (backingRowMap.isEmpty() && backingMap.containsKey(rowKey))) + ? backingRowMap = computeBackingRowMap() + : backingRowMap; + } + + Map computeBackingRowMap() { + return backingMap.get(rowKey); + } + + // Call this every time we perform a removal. + void maintainEmptyInvariant() { + if (backingRowMap() != null && backingRowMap.isEmpty()) { + backingMap.remove(rowKey); + backingRowMap = null; + } + } + + @Override + public boolean containsKey(Object key) { + Map backingRowMap = backingRowMap(); + return (key != null && backingRowMap != null) && Maps.safeContainsKey(backingRowMap, key); + } + + @Override + public V get(Object key) { + Map backingRowMap = backingRowMap(); + return (key != null && backingRowMap != null) ? Maps.safeGet(backingRowMap, key) : null; + } + + @Override + public V put(C key, V value) { + checkNotNull(key); + checkNotNull(value); + if (backingRowMap != null && !backingRowMap.isEmpty()) { + return backingRowMap.put(key, value); + } + return StandardTable.this.put(rowKey, key, value); + } + + @Override + public V remove(Object key) { + Map backingRowMap = backingRowMap(); + if (backingRowMap == null) { + return null; + } + V result = Maps.safeRemove(backingRowMap, key); + maintainEmptyInvariant(); + return result; + } + + @Override + public void clear() { + Map backingRowMap = backingRowMap(); + if (backingRowMap != null) { + backingRowMap.clear(); + } + maintainEmptyInvariant(); + } + + @Override + public int size() { + Map map = backingRowMap(); + return (map == null) ? 0 : map.size(); + } + + @Override + Iterator> entryIterator() { + final Map map = backingRowMap(); + if (map == null) { + return Iterators.emptyModifiableIterator(); + } + final Iterator> iterator = map.entrySet().iterator(); + return new Iterator>() { + @Override + public boolean hasNext() { + return iterator.hasNext(); + } + + @Override + public Entry next() { + return wrapEntry(iterator.next()); + } + + @Override + public void remove() { + iterator.remove(); + maintainEmptyInvariant(); + } + }; + } + + @Override + Spliterator> entrySpliterator() { + Map map = backingRowMap(); + if (map == null) { + return Spliterators.emptySpliterator(); + } + return CollectSpliterators.map(map.entrySet().spliterator(), this::wrapEntry); + } + + Entry wrapEntry(final Entry entry) { + return new ForwardingMapEntry() { + @Override + protected Entry delegate() { + return entry; + } + + @Override + public V setValue(V value) { + return super.setValue(checkNotNull(value)); + } + + @Override + public boolean equals(Object object) { + // TODO(lowasser): identify why this affects GWT tests + return standardEquals(object); + } + }; + } + } + + /** + * {@inheritDoc} + * + *

The returned map's views have iterators that don't support {@code remove()}. + */ + @Override + public Map column(C columnKey) { + return new Column(columnKey); + } + + private class Column extends ViewCachingAbstractMap { + final C columnKey; + + Column(C columnKey) { + this.columnKey = checkNotNull(columnKey); + } + + @Override + public V put(R key, V value) { + return StandardTable.this.put(key, columnKey, value); + } + + @Override + public V get(Object key) { + return StandardTable.this.get(key, columnKey); + } + + @Override + public boolean containsKey(Object key) { + return StandardTable.this.contains(key, columnKey); + } + + @Override + public V remove(Object key) { + return StandardTable.this.remove(key, columnKey); + } + + /** Removes all {@code Column} mappings whose row key and value satisfy the given predicate. */ + + boolean removeFromColumnIf(Predicate> predicate) { + boolean changed = false; + Iterator>> iterator = backingMap.entrySet().iterator(); + while (iterator.hasNext()) { + Entry> entry = iterator.next(); + Map map = entry.getValue(); + V value = map.get(columnKey); + if (value != null && predicate.apply(Maps.immutableEntry(entry.getKey(), value))) { + map.remove(columnKey); + changed = true; + if (map.isEmpty()) { + iterator.remove(); + } + } + } + return changed; + } + + @Override + Set> createEntrySet() { + return new EntrySet(); + } + + + private class EntrySet extends ImprovedAbstractSet> { + @Override + public Iterator> iterator() { + return new EntrySetIterator(); + } + + @Override + public int size() { + int size = 0; + for (Map map : backingMap.values()) { + if (map.containsKey(columnKey)) { + size++; + } + } + return size; + } + + @Override + public boolean isEmpty() { + return !containsColumn(columnKey); + } + + @Override + public void clear() { + removeFromColumnIf(alwaysTrue()); + } + + @Override + public boolean contains(Object o) { + if (o instanceof Entry) { + Entry entry = (Entry) o; + return containsMapping(entry.getKey(), columnKey, entry.getValue()); + } + return false; + } + + @Override + public boolean remove(Object obj) { + if (obj instanceof Entry) { + Entry entry = (Entry) obj; + return removeMapping(entry.getKey(), columnKey, entry.getValue()); + } + return false; + } + + @Override + public boolean retainAll(Collection c) { + return removeFromColumnIf(not(in(c))); + } + } + + private class EntrySetIterator extends AbstractIterator> { + final Iterator>> iterator = backingMap.entrySet().iterator(); + + @Override + protected Entry computeNext() { + while (iterator.hasNext()) { + final Entry> entry = iterator.next(); + if (entry.getValue().containsKey(columnKey)) { + + class EntryImpl extends AbstractMapEntry { + @Override + public R getKey() { + return entry.getKey(); + } + + @Override + public V getValue() { + return entry.getValue().get(columnKey); + } + + @Override + public V setValue(V value) { + return entry.getValue().put(columnKey, checkNotNull(value)); + } + } + return new EntryImpl(); + } + } + return endOfData(); + } + } + + @Override + Set createKeySet() { + return new KeySet(); + } + + + private class KeySet extends Maps.KeySet { + KeySet() { + super(Column.this); + } + + @Override + public boolean contains(Object obj) { + return StandardTable.this.contains(obj, columnKey); + } + + @Override + public boolean remove(Object obj) { + return StandardTable.this.remove(obj, columnKey) != null; + } + + @Override + public boolean retainAll(final Collection c) { + return removeFromColumnIf(Maps.keyPredicateOnEntries(not(in(c)))); + } + } + + @Override + Collection createValues() { + return new Values(); + } + + + private class Values extends Maps.Values { + Values() { + super(Column.this); + } + + @Override + public boolean remove(Object obj) { + return obj != null && removeFromColumnIf(Maps.valuePredicateOnEntries(equalTo(obj))); + } + + @Override + public boolean removeAll(final Collection c) { + return removeFromColumnIf(Maps.valuePredicateOnEntries(in(c))); + } + + @Override + public boolean retainAll(final Collection c) { + return removeFromColumnIf(Maps.valuePredicateOnEntries(not(in(c)))); + } + } + } + + @Override + public Set rowKeySet() { + return rowMap().keySet(); + } + + private transient Set columnKeySet; + + /** + * {@inheritDoc} + * + *

The returned set has an iterator that does not support {@code remove()}. + * + *

The set's iterator traverses the columns of the first row, the columns of the second row, + * etc., skipping any columns that have appeared previously. + */ + @Override + public Set columnKeySet() { + Set result = columnKeySet; + return (result == null) ? columnKeySet = new ColumnKeySet() : result; + } + + + private class ColumnKeySet extends TableSet { + @Override + public Iterator iterator() { + return createColumnKeyIterator(); + } + + @Override + public int size() { + return Iterators.size(iterator()); + } + + @Override + public boolean remove(Object obj) { + if (obj == null) { + return false; + } + boolean changed = false; + Iterator> iterator = backingMap.values().iterator(); + while (iterator.hasNext()) { + Map map = iterator.next(); + if (map.keySet().remove(obj)) { + changed = true; + if (map.isEmpty()) { + iterator.remove(); + } + } + } + return changed; + } + + @Override + public boolean removeAll(Collection c) { + checkNotNull(c); + boolean changed = false; + Iterator> iterator = backingMap.values().iterator(); + while (iterator.hasNext()) { + Map map = iterator.next(); + // map.keySet().removeAll(c) can throw a NPE when map is a TreeMap with + // natural ordering and c contains a null. + if (Iterators.removeAll(map.keySet().iterator(), c)) { + changed = true; + if (map.isEmpty()) { + iterator.remove(); + } + } + } + return changed; + } + + @Override + public boolean retainAll(Collection c) { + checkNotNull(c); + boolean changed = false; + Iterator> iterator = backingMap.values().iterator(); + while (iterator.hasNext()) { + Map map = iterator.next(); + if (map.keySet().retainAll(c)) { + changed = true; + if (map.isEmpty()) { + iterator.remove(); + } + } + } + return changed; + } + + @Override + public boolean contains(Object obj) { + return containsColumn(obj); + } + } + + /** Creates an iterator that returns each column value with duplicates omitted. */ + Iterator createColumnKeyIterator() { + return new ColumnKeyIterator(); + } + + private class ColumnKeyIterator extends AbstractIterator { + // Use the same map type to support TreeMaps with comparators that aren't + // consistent with equals(). + final Map seen = factory.get(); + final Iterator> mapIterator = backingMap.values().iterator(); + Iterator> entryIterator = Iterators.emptyIterator(); + + @Override + protected C computeNext() { + while (true) { + if (entryIterator.hasNext()) { + Entry entry = entryIterator.next(); + if (!seen.containsKey(entry.getKey())) { + seen.put(entry.getKey(), entry.getValue()); + return entry.getKey(); + } + } else if (mapIterator.hasNext()) { + entryIterator = mapIterator.next().entrySet().iterator(); + } else { + return endOfData(); + } + } + } + } + + /** + * {@inheritDoc} + * + *

The collection's iterator traverses the values for the first row, the values for the second + * row, and so on. + */ + @Override + public Collection values() { + return super.values(); + } + + private transient Map> rowMap; + + @Override + public Map> rowMap() { + Map> result = rowMap; + return (result == null) ? rowMap = createRowMap() : result; + } + + Map> createRowMap() { + return new RowMap(); + } + + + class RowMap extends ViewCachingAbstractMap> { + @Override + public boolean containsKey(Object key) { + return containsRow(key); + } + + // performing cast only when key is in backing map and has the correct type + @SuppressWarnings("unchecked") + @Override + public Map get(Object key) { + return containsRow(key) ? row((R) key) : null; + } + + @Override + public Map remove(Object key) { + return (key == null) ? null : backingMap.remove(key); + } + + @Override + protected Set>> createEntrySet() { + return new EntrySet(); + } + + + class EntrySet extends TableSet>> { + @Override + public Iterator>> iterator() { + return Maps.asMapEntryIterator( + backingMap.keySet(), + new Function>() { + @Override + public Map apply(R rowKey) { + return row(rowKey); + } + }); + } + + @Override + public int size() { + return backingMap.size(); + } + + @Override + public boolean contains(Object obj) { + if (obj instanceof Entry) { + Entry entry = (Entry) obj; + return entry.getKey() != null + && entry.getValue() instanceof Map + && Collections2.safeContains(backingMap.entrySet(), entry); + } + return false; + } + + @Override + public boolean remove(Object obj) { + if (obj instanceof Entry) { + Entry entry = (Entry) obj; + return entry.getKey() != null + && entry.getValue() instanceof Map + && backingMap.entrySet().remove(entry); + } + return false; + } + } + } + + private transient ColumnMap columnMap; + + @Override + public Map> columnMap() { + ColumnMap result = columnMap; + return (result == null) ? columnMap = new ColumnMap() : result; + } + + + private class ColumnMap extends ViewCachingAbstractMap> { + // The cast to C occurs only when the key is in the map, implying that it + // has the correct type. + @SuppressWarnings("unchecked") + @Override + public Map get(Object key) { + return containsColumn(key) ? column((C) key) : null; + } + + @Override + public boolean containsKey(Object key) { + return containsColumn(key); + } + + @Override + public Map remove(Object key) { + return containsColumn(key) ? removeColumn(key) : null; + } + + @Override + public Set>> createEntrySet() { + return new ColumnMapEntrySet(); + } + + @Override + public Set keySet() { + return columnKeySet(); + } + + @Override + Collection> createValues() { + return new ColumnMapValues(); + } + + + class ColumnMapEntrySet extends TableSet>> { + @Override + public Iterator>> iterator() { + return Maps.asMapEntryIterator( + columnKeySet(), + new Function>() { + @Override + public Map apply(C columnKey) { + return column(columnKey); + } + }); + } + + @Override + public int size() { + return columnKeySet().size(); + } + + @Override + public boolean contains(Object obj) { + if (obj instanceof Entry) { + Entry entry = (Entry) obj; + if (containsColumn(entry.getKey())) { + // The cast to C occurs only when the key is in the map, implying + // that it has the correct type. + @SuppressWarnings("unchecked") + C columnKey = (C) entry.getKey(); + return get(columnKey).equals(entry.getValue()); + } + } + return false; + } + + @Override + public boolean remove(Object obj) { + if (contains(obj)) { + Entry entry = (Entry) obj; + removeColumn(entry.getKey()); + return true; + } + return false; + } + + @Override + public boolean removeAll(Collection c) { + /* + * We can't inherit the normal implementation (which calls + * Sets.removeAllImpl(Set, *Collection*) because, under some + * circumstances, it attempts to call columnKeySet().iterator().remove, + * which is unsupported. + */ + checkNotNull(c); + return Sets.removeAllImpl(this, c.iterator()); + } + + @Override + public boolean retainAll(Collection c) { + checkNotNull(c); + boolean changed = false; + for (C columnKey : Lists.newArrayList(columnKeySet().iterator())) { + if (!c.contains(Maps.immutableEntry(columnKey, column(columnKey)))) { + removeColumn(columnKey); + changed = true; + } + } + return changed; + } + } + + + private class ColumnMapValues extends Maps.Values> { + ColumnMapValues() { + super(ColumnMap.this); + } + + @Override + public boolean remove(Object obj) { + for (Entry> entry : ColumnMap.this.entrySet()) { + if (entry.getValue().equals(obj)) { + removeColumn(entry.getKey()); + return true; + } + } + return false; + } + + @Override + public boolean removeAll(Collection c) { + checkNotNull(c); + boolean changed = false; + for (C columnKey : Lists.newArrayList(columnKeySet().iterator())) { + if (c.contains(column(columnKey))) { + removeColumn(columnKey); + changed = true; + } + } + return changed; + } + + @Override + public boolean retainAll(Collection c) { + checkNotNull(c); + boolean changed = false; + for (C columnKey : Lists.newArrayList(columnKeySet().iterator())) { + if (!c.contains(column(columnKey))) { + removeColumn(columnKey); + changed = true; + } + } + return changed; + } + } + } + + private static final long serialVersionUID = 0; +} diff --git a/src/main/java/com/google/common/collect/Streams.java b/src/main/java/com/google/common/collect/Streams.java new file mode 100644 index 0000000..d232301 --- /dev/null +++ b/src/main/java/com/google/common/collect/Streams.java @@ -0,0 +1,941 @@ +/* + * Copyright (C) 2015 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you + * may not use this file except in compliance with the License. You may + * obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + * implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package com.google.common.collect; + +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.base.Preconditions.checkState; + +import com.google.common.annotations.Beta; +import com.google.common.annotations.GwtCompatible; +import com.google.common.math.LongMath; +import java.util.ArrayDeque; +import java.util.Collection; +import java.util.Deque; +import java.util.Iterator; +import java.util.OptionalDouble; +import java.util.OptionalInt; +import java.util.OptionalLong; +import java.util.PrimitiveIterator; +import java.util.Spliterator; +import java.util.Spliterators; +import java.util.Spliterators.AbstractSpliterator; +import java.util.function.BiConsumer; +import java.util.function.BiFunction; +import java.util.function.Consumer; +import java.util.function.DoubleConsumer; +import java.util.function.IntConsumer; +import java.util.function.LongConsumer; +import java.util.stream.BaseStream; +import java.util.stream.DoubleStream; +import java.util.stream.IntStream; +import java.util.stream.LongStream; +import java.util.stream.Stream; +import java.util.stream.StreamSupport; + + +/** + * Static utility methods related to {@code Stream} instances. + * + * @since 21.0 + */ +@GwtCompatible +public final class Streams { + /** + * Returns a sequential {@link Stream} of the contents of {@code iterable}, delegating to {@link + * Collection#stream} if possible. + */ + public static Stream stream(Iterable iterable) { + return (iterable instanceof Collection) + ? ((Collection) iterable).stream() + : StreamSupport.stream(iterable.spliterator(), false); + } + + /** + * Returns {@link Collection#stream}. + * + * @deprecated There is no reason to use this; just invoke {@code collection.stream()} directly. + */ + @Beta + @Deprecated + public static Stream stream(Collection collection) { + return collection.stream(); + } + + /** + * Returns a sequential {@link Stream} of the remaining contents of {@code iterator}. Do not use + * {@code iterator} directly after passing it to this method. + */ + @Beta + public static Stream stream(Iterator iterator) { + return StreamSupport.stream(Spliterators.spliteratorUnknownSize(iterator, 0), false); + } + + /** + * If a value is present in {@code optional}, returns a stream containing only that element, + * otherwise returns an empty stream. + */ + @Beta + public static Stream stream(com.google.common.base.Optional optional) { + return optional.isPresent() ? Stream.of(optional.get()) : Stream.empty(); + } + + /** + * If a value is present in {@code optional}, returns a stream containing only that element, + * otherwise returns an empty stream. + * + *

Java 9 users: use {@code optional.stream()} instead. + */ + @Beta + public static Stream stream(java.util.Optional optional) { + return optional.isPresent() ? Stream.of(optional.get()) : Stream.empty(); + } + + /** + * If a value is present in {@code optional}, returns a stream containing only that element, + * otherwise returns an empty stream. + * + *

Java 9 users: use {@code optional.stream()} instead. + */ + @Beta + public static IntStream stream(OptionalInt optional) { + return optional.isPresent() ? IntStream.of(optional.getAsInt()) : IntStream.empty(); + } + + /** + * If a value is present in {@code optional}, returns a stream containing only that element, + * otherwise returns an empty stream. + * + *

Java 9 users: use {@code optional.stream()} instead. + */ + @Beta + public static LongStream stream(OptionalLong optional) { + return optional.isPresent() ? LongStream.of(optional.getAsLong()) : LongStream.empty(); + } + + /** + * If a value is present in {@code optional}, returns a stream containing only that element, + * otherwise returns an empty stream. + * + *

Java 9 users: use {@code optional.stream()} instead. + */ + @Beta + public static DoubleStream stream(OptionalDouble optional) { + return optional.isPresent() ? DoubleStream.of(optional.getAsDouble()) : DoubleStream.empty(); + } + + private static void closeAll(BaseStream[] toClose) { + for (BaseStream stream : toClose) { + // TODO(b/80534298): Catch exceptions, rethrowing later with extras as suppressed exceptions. + stream.close(); + } + } + + /** + * Returns a {@link Stream} containing the elements of the first stream, followed by the elements + * of the second stream, and so on. + * + *

This is equivalent to {@code Stream.of(streams).flatMap(stream -> stream)}, but the returned + * stream may perform better. + * + * @see Stream#concat(Stream, Stream) + */ + @SafeVarargs + public static Stream concat(Stream... streams) { + // TODO(lowasser): consider an implementation that can support SUBSIZED + boolean isParallel = false; + int characteristics = Spliterator.ORDERED | Spliterator.SIZED | Spliterator.NONNULL; + long estimatedSize = 0L; + ImmutableList.Builder> splitrsBuilder = + new ImmutableList.Builder<>(streams.length); + for (Stream stream : streams) { + isParallel |= stream.isParallel(); + Spliterator splitr = stream.spliterator(); + splitrsBuilder.add(splitr); + characteristics &= splitr.characteristics(); + estimatedSize = LongMath.saturatedAdd(estimatedSize, splitr.estimateSize()); + } + return StreamSupport.stream( + CollectSpliterators.flatMap( + splitrsBuilder.build().spliterator(), + splitr -> (Spliterator) splitr, + characteristics, + estimatedSize), + isParallel) + .onClose(() -> closeAll(streams)); + } + + /** + * Returns an {@link IntStream} containing the elements of the first stream, followed by the + * elements of the second stream, and so on. + * + *

This is equivalent to {@code Stream.of(streams).flatMapToInt(stream -> stream)}, but the + * returned stream may perform better. + * + * @see IntStream#concat(IntStream, IntStream) + */ + public static IntStream concat(IntStream... streams) { + boolean isParallel = false; + int characteristics = Spliterator.ORDERED | Spliterator.SIZED | Spliterator.NONNULL; + long estimatedSize = 0L; + ImmutableList.Builder splitrsBuilder = + new ImmutableList.Builder<>(streams.length); + for (IntStream stream : streams) { + isParallel |= stream.isParallel(); + Spliterator.OfInt splitr = stream.spliterator(); + splitrsBuilder.add(splitr); + characteristics &= splitr.characteristics(); + estimatedSize = LongMath.saturatedAdd(estimatedSize, splitr.estimateSize()); + } + return StreamSupport.intStream( + CollectSpliterators.flatMapToInt( + splitrsBuilder.build().spliterator(), + splitr -> splitr, + characteristics, + estimatedSize), + isParallel) + .onClose(() -> closeAll(streams)); + } + + /** + * Returns a {@link LongStream} containing the elements of the first stream, followed by the + * elements of the second stream, and so on. + * + *

This is equivalent to {@code Stream.of(streams).flatMapToLong(stream -> stream)}, but the + * returned stream may perform better. + * + * @see LongStream#concat(LongStream, LongStream) + */ + public static LongStream concat(LongStream... streams) { + boolean isParallel = false; + int characteristics = Spliterator.ORDERED | Spliterator.SIZED | Spliterator.NONNULL; + long estimatedSize = 0L; + ImmutableList.Builder splitrsBuilder = + new ImmutableList.Builder<>(streams.length); + for (LongStream stream : streams) { + isParallel |= stream.isParallel(); + Spliterator.OfLong splitr = stream.spliterator(); + splitrsBuilder.add(splitr); + characteristics &= splitr.characteristics(); + estimatedSize = LongMath.saturatedAdd(estimatedSize, splitr.estimateSize()); + } + return StreamSupport.longStream( + CollectSpliterators.flatMapToLong( + splitrsBuilder.build().spliterator(), + splitr -> splitr, + characteristics, + estimatedSize), + isParallel) + .onClose(() -> closeAll(streams)); + } + + /** + * Returns a {@link DoubleStream} containing the elements of the first stream, followed by the + * elements of the second stream, and so on. + * + *

This is equivalent to {@code Stream.of(streams).flatMapToDouble(stream -> stream)}, but the + * returned stream may perform better. + * + * @see DoubleStream#concat(DoubleStream, DoubleStream) + */ + public static DoubleStream concat(DoubleStream... streams) { + boolean isParallel = false; + int characteristics = Spliterator.ORDERED | Spliterator.SIZED | Spliterator.NONNULL; + long estimatedSize = 0L; + ImmutableList.Builder splitrsBuilder = + new ImmutableList.Builder<>(streams.length); + for (DoubleStream stream : streams) { + isParallel |= stream.isParallel(); + Spliterator.OfDouble splitr = stream.spliterator(); + splitrsBuilder.add(splitr); + characteristics &= splitr.characteristics(); + estimatedSize = LongMath.saturatedAdd(estimatedSize, splitr.estimateSize()); + } + return StreamSupport.doubleStream( + CollectSpliterators.flatMapToDouble( + splitrsBuilder.build().spliterator(), + splitr -> splitr, + characteristics, + estimatedSize), + isParallel) + .onClose(() -> closeAll(streams)); + } + + /** + * Returns a stream in which each element is the result of passing the corresponding element of + * each of {@code streamA} and {@code streamB} to {@code function}. + * + *

For example: + * + *

{@code
+   * Streams.zip(
+   *   Stream.of("foo1", "foo2", "foo3"),
+   *   Stream.of("bar1", "bar2"),
+   *   (arg1, arg2) -> arg1 + ":" + arg2)
+   * }
+ * + *

will return {@code Stream.of("foo1:bar1", "foo2:bar2")}. + * + *

The resulting stream will only be as long as the shorter of the two input streams; if one + * stream is longer, its extra elements will be ignored. + * + *

Note that if you are calling {@link Stream#forEach} on the resulting stream, you might want + * to consider using {@link #forEachPair} instead of this method. + * + *

Performance note: The resulting stream is not efficiently splittable. + * This may harm parallel performance. + */ + @Beta + public static Stream zip( + Stream streamA, Stream streamB, BiFunction function) { + checkNotNull(streamA); + checkNotNull(streamB); + checkNotNull(function); + boolean isParallel = streamA.isParallel() || streamB.isParallel(); // same as Stream.concat + Spliterator splitrA = streamA.spliterator(); + Spliterator splitrB = streamB.spliterator(); + int characteristics = + splitrA.characteristics() + & splitrB.characteristics() + & (Spliterator.SIZED | Spliterator.ORDERED); + Iterator itrA = Spliterators.iterator(splitrA); + Iterator itrB = Spliterators.iterator(splitrB); + return StreamSupport.stream( + new AbstractSpliterator( + Math.min(splitrA.estimateSize(), splitrB.estimateSize()), characteristics) { + @Override + public boolean tryAdvance(Consumer action) { + if (itrA.hasNext() && itrB.hasNext()) { + action.accept(function.apply(itrA.next(), itrB.next())); + return true; + } + return false; + } + }, + isParallel) + .onClose(streamA::close) + .onClose(streamB::close); + } + + /** + * Invokes {@code consumer} once for each pair of corresponding elements in {@code streamA} + * and {@code streamB}. If one stream is longer than the other, the extra elements are silently + * ignored. Elements passed to the consumer are guaranteed to come from the same position in their + * respective source streams. For example: + * + *

{@code
+   * Streams.forEachPair(
+   *   Stream.of("foo1", "foo2", "foo3"),
+   *   Stream.of("bar1", "bar2"),
+   *   (arg1, arg2) -> System.out.println(arg1 + ":" + arg2)
+   * }
+ * + *

will print: + * + *

{@code
+   * foo1:bar1
+   * foo2:bar2
+   * }
+ * + *

Warning: If either supplied stream is a parallel stream, the same correspondence + * between elements will be made, but the order in which those pairs of elements are passed to the + * consumer is not defined. + * + *

Note that many usages of this method can be replaced with simpler calls to {@link #zip}. + * This method behaves equivalently to {@linkplain #zip zipping} the stream elements into + * temporary pair objects and then using {@link Stream#forEach} on that stream. + * + * @since 22.0 + */ + @Beta + public static void forEachPair( + Stream streamA, Stream streamB, BiConsumer consumer) { + checkNotNull(consumer); + + if (streamA.isParallel() || streamB.isParallel()) { + zip(streamA, streamB, TemporaryPair::new).forEach(pair -> consumer.accept(pair.a, pair.b)); + } else { + Iterator iterA = streamA.iterator(); + Iterator iterB = streamB.iterator(); + while (iterA.hasNext() && iterB.hasNext()) { + consumer.accept(iterA.next(), iterB.next()); + } + } + } + + // Use this carefully - it doesn't implement value semantics + private static class TemporaryPair { + final A a; + final B b; + + TemporaryPair(A a, B b) { + this.a = a; + this.b = b; + } + } + + /** + * Returns a stream consisting of the results of applying the given function to the elements of + * {@code stream} and their indices in the stream. For example, + * + *

{@code
+   * mapWithIndex(
+   *     Stream.of("a", "b", "c"),
+   *     (str, index) -> str + ":" + index)
+   * }
+ * + *

would return {@code Stream.of("a:0", "b:1", "c:2")}. + * + *

The resulting stream is efficiently splittable + * if and only if {@code stream} was efficiently splittable and its underlying spliterator + * reported {@link Spliterator#SUBSIZED}. This is generally the case if the underlying stream + * comes from a data structure supporting efficient indexed random access, typically an array or + * list. + * + *

The order of the resulting stream is defined if and only if the order of the original stream + * was defined. + */ + @Beta + public static Stream mapWithIndex( + Stream stream, FunctionWithIndex function) { + checkNotNull(stream); + checkNotNull(function); + boolean isParallel = stream.isParallel(); + Spliterator fromSpliterator = stream.spliterator(); + + if (!fromSpliterator.hasCharacteristics(Spliterator.SUBSIZED)) { + Iterator fromIterator = Spliterators.iterator(fromSpliterator); + return StreamSupport.stream( + new AbstractSpliterator( + fromSpliterator.estimateSize(), + fromSpliterator.characteristics() & (Spliterator.ORDERED | Spliterator.SIZED)) { + long index = 0; + + @Override + public boolean tryAdvance(Consumer action) { + if (fromIterator.hasNext()) { + action.accept(function.apply(fromIterator.next(), index++)); + return true; + } + return false; + } + }, + isParallel) + .onClose(stream::close); + } + class Splitr extends MapWithIndexSpliterator, R, Splitr> implements Consumer { + T holder; + + Splitr(Spliterator splitr, long index) { + super(splitr, index); + } + + @Override + public void accept(T t) { + this.holder = t; + } + + @Override + public boolean tryAdvance(Consumer action) { + if (fromSpliterator.tryAdvance(this)) { + try { + action.accept(function.apply(holder, index++)); + return true; + } finally { + holder = null; + } + } + return false; + } + + @Override + Splitr createSplit(Spliterator from, long i) { + return new Splitr(from, i); + } + } + return StreamSupport.stream(new Splitr(fromSpliterator, 0), isParallel).onClose(stream::close); + } + + /** + * Returns a stream consisting of the results of applying the given function to the elements of + * {@code stream} and their indexes in the stream. For example, + * + *

{@code
+   * mapWithIndex(
+   *     IntStream.of(0, 1, 2),
+   *     (i, index) -> i + ":" + index)
+   * }
+ * + *

...would return {@code Stream.of("0:0", "1:1", "2:2")}. + * + *

The resulting stream is efficiently splittable + * if and only if {@code stream} was efficiently splittable and its underlying spliterator + * reported {@link Spliterator#SUBSIZED}. This is generally the case if the underlying stream + * comes from a data structure supporting efficient indexed random access, typically an array or + * list. + * + *

The order of the resulting stream is defined if and only if the order of the original stream + * was defined. + */ + @Beta + public static Stream mapWithIndex(IntStream stream, IntFunctionWithIndex function) { + checkNotNull(stream); + checkNotNull(function); + boolean isParallel = stream.isParallel(); + Spliterator.OfInt fromSpliterator = stream.spliterator(); + + if (!fromSpliterator.hasCharacteristics(Spliterator.SUBSIZED)) { + PrimitiveIterator.OfInt fromIterator = Spliterators.iterator(fromSpliterator); + return StreamSupport.stream( + new AbstractSpliterator( + fromSpliterator.estimateSize(), + fromSpliterator.characteristics() & (Spliterator.ORDERED | Spliterator.SIZED)) { + long index = 0; + + @Override + public boolean tryAdvance(Consumer action) { + if (fromIterator.hasNext()) { + action.accept(function.apply(fromIterator.nextInt(), index++)); + return true; + } + return false; + } + }, + isParallel) + .onClose(stream::close); + } + class Splitr extends MapWithIndexSpliterator + implements IntConsumer, Spliterator { + int holder; + + Splitr(Spliterator.OfInt splitr, long index) { + super(splitr, index); + } + + @Override + public void accept(int t) { + this.holder = t; + } + + @Override + public boolean tryAdvance(Consumer action) { + if (fromSpliterator.tryAdvance(this)) { + action.accept(function.apply(holder, index++)); + return true; + } + return false; + } + + @Override + Splitr createSplit(Spliterator.OfInt from, long i) { + return new Splitr(from, i); + } + } + return StreamSupport.stream(new Splitr(fromSpliterator, 0), isParallel).onClose(stream::close); + } + + /** + * Returns a stream consisting of the results of applying the given function to the elements of + * {@code stream} and their indexes in the stream. For example, + * + *

{@code
+   * mapWithIndex(
+   *     LongStream.of(0, 1, 2),
+   *     (i, index) -> i + ":" + index)
+   * }
+ * + *

...would return {@code Stream.of("0:0", "1:1", "2:2")}. + * + *

The resulting stream is efficiently splittable + * if and only if {@code stream} was efficiently splittable and its underlying spliterator + * reported {@link Spliterator#SUBSIZED}. This is generally the case if the underlying stream + * comes from a data structure supporting efficient indexed random access, typically an array or + * list. + * + *

The order of the resulting stream is defined if and only if the order of the original stream + * was defined. + */ + @Beta + public static Stream mapWithIndex(LongStream stream, LongFunctionWithIndex function) { + checkNotNull(stream); + checkNotNull(function); + boolean isParallel = stream.isParallel(); + Spliterator.OfLong fromSpliterator = stream.spliterator(); + + if (!fromSpliterator.hasCharacteristics(Spliterator.SUBSIZED)) { + PrimitiveIterator.OfLong fromIterator = Spliterators.iterator(fromSpliterator); + return StreamSupport.stream( + new AbstractSpliterator( + fromSpliterator.estimateSize(), + fromSpliterator.characteristics() & (Spliterator.ORDERED | Spliterator.SIZED)) { + long index = 0; + + @Override + public boolean tryAdvance(Consumer action) { + if (fromIterator.hasNext()) { + action.accept(function.apply(fromIterator.nextLong(), index++)); + return true; + } + return false; + } + }, + isParallel) + .onClose(stream::close); + } + class Splitr extends MapWithIndexSpliterator + implements LongConsumer, Spliterator { + long holder; + + Splitr(Spliterator.OfLong splitr, long index) { + super(splitr, index); + } + + @Override + public void accept(long t) { + this.holder = t; + } + + @Override + public boolean tryAdvance(Consumer action) { + if (fromSpliterator.tryAdvance(this)) { + action.accept(function.apply(holder, index++)); + return true; + } + return false; + } + + @Override + Splitr createSplit(Spliterator.OfLong from, long i) { + return new Splitr(from, i); + } + } + return StreamSupport.stream(new Splitr(fromSpliterator, 0), isParallel).onClose(stream::close); + } + + /** + * Returns a stream consisting of the results of applying the given function to the elements of + * {@code stream} and their indexes in the stream. For example, + * + *

{@code
+   * mapWithIndex(
+   *     DoubleStream.of(0, 1, 2),
+   *     (x, index) -> x + ":" + index)
+   * }
+ * + *

...would return {@code Stream.of("0.0:0", "1.0:1", "2.0:2")}. + * + *

The resulting stream is efficiently splittable + * if and only if {@code stream} was efficiently splittable and its underlying spliterator + * reported {@link Spliterator#SUBSIZED}. This is generally the case if the underlying stream + * comes from a data structure supporting efficient indexed random access, typically an array or + * list. + * + *

The order of the resulting stream is defined if and only if the order of the original stream + * was defined. + */ + @Beta + public static Stream mapWithIndex( + DoubleStream stream, DoubleFunctionWithIndex function) { + checkNotNull(stream); + checkNotNull(function); + boolean isParallel = stream.isParallel(); + Spliterator.OfDouble fromSpliterator = stream.spliterator(); + + if (!fromSpliterator.hasCharacteristics(Spliterator.SUBSIZED)) { + PrimitiveIterator.OfDouble fromIterator = Spliterators.iterator(fromSpliterator); + return StreamSupport.stream( + new AbstractSpliterator( + fromSpliterator.estimateSize(), + fromSpliterator.characteristics() & (Spliterator.ORDERED | Spliterator.SIZED)) { + long index = 0; + + @Override + public boolean tryAdvance(Consumer action) { + if (fromIterator.hasNext()) { + action.accept(function.apply(fromIterator.nextDouble(), index++)); + return true; + } + return false; + } + }, + isParallel) + .onClose(stream::close); + } + class Splitr extends MapWithIndexSpliterator + implements DoubleConsumer, Spliterator { + double holder; + + Splitr(Spliterator.OfDouble splitr, long index) { + super(splitr, index); + } + + @Override + public void accept(double t) { + this.holder = t; + } + + @Override + public boolean tryAdvance(Consumer action) { + if (fromSpliterator.tryAdvance(this)) { + action.accept(function.apply(holder, index++)); + return true; + } + return false; + } + + @Override + Splitr createSplit(Spliterator.OfDouble from, long i) { + return new Splitr(from, i); + } + } + return StreamSupport.stream(new Splitr(fromSpliterator, 0), isParallel).onClose(stream::close); + } + + /** + * An analogue of {@link java.util.function.Function} also accepting an index. + * + *

This interface is only intended for use by callers of {@link #mapWithIndex(Stream, + * FunctionWithIndex)}. + * + * @since 21.0 + */ + @Beta + public interface FunctionWithIndex { + /** Applies this function to the given argument and its index within a stream. */ + R apply(T from, long index); + } + + private abstract static class MapWithIndexSpliterator< + F extends Spliterator, R, S extends MapWithIndexSpliterator> + implements Spliterator { + final F fromSpliterator; + long index; + + MapWithIndexSpliterator(F fromSpliterator, long index) { + this.fromSpliterator = fromSpliterator; + this.index = index; + } + + abstract S createSplit(F from, long i); + + @Override + public S trySplit() { + @SuppressWarnings("unchecked") + F split = (F) fromSpliterator.trySplit(); + if (split == null) { + return null; + } + S result = createSplit(split, index); + this.index += split.getExactSizeIfKnown(); + return result; + } + + @Override + public long estimateSize() { + return fromSpliterator.estimateSize(); + } + + @Override + public int characteristics() { + return fromSpliterator.characteristics() + & (Spliterator.ORDERED | Spliterator.SIZED | Spliterator.SUBSIZED); + } + } + + /** + * An analogue of {@link java.util.function.IntFunction} also accepting an index. + * + *

This interface is only intended for use by callers of {@link #mapWithIndex(IntStream, + * IntFunctionWithIndex)}. + * + * @since 21.0 + */ + @Beta + public interface IntFunctionWithIndex { + /** Applies this function to the given argument and its index within a stream. */ + R apply(int from, long index); + } + + /** + * An analogue of {@link java.util.function.LongFunction} also accepting an index. + * + *

This interface is only intended for use by callers of {@link #mapWithIndex(LongStream, + * LongFunctionWithIndex)}. + * + * @since 21.0 + */ + @Beta + public interface LongFunctionWithIndex { + /** Applies this function to the given argument and its index within a stream. */ + R apply(long from, long index); + } + + /** + * An analogue of {@link java.util.function.DoubleFunction} also accepting an index. + * + *

This interface is only intended for use by callers of {@link #mapWithIndex(DoubleStream, + * DoubleFunctionWithIndex)}. + * + * @since 21.0 + */ + @Beta + public interface DoubleFunctionWithIndex { + /** Applies this function to the given argument and its index within a stream. */ + R apply(double from, long index); + } + + /** + * Returns the last element of the specified stream, or {@link java.util.Optional#empty} if the + * stream is empty. + * + *

Equivalent to {@code stream.reduce((a, b) -> b)}, but may perform significantly better. This + * method's runtime will be between O(log n) and O(n), performing better on efficiently splittable + * streams. + * + *

If the stream has nondeterministic order, this has equivalent semantics to {@link + * Stream#findAny} (which you might as well use). + * + * @see Stream#findFirst() + * @throws NullPointerException if the last element of the stream is null + */ + @Beta + public static java.util.Optional findLast(Stream stream) { + class OptionalState { + boolean set = false; + T value = null; + + void set(T value) { + this.set = true; + this.value = value; + } + + T get() { + checkState(set); + return value; + } + } + OptionalState state = new OptionalState(); + + Deque> splits = new ArrayDeque<>(); + splits.addLast(stream.spliterator()); + + while (!splits.isEmpty()) { + Spliterator spliterator = splits.removeLast(); + + if (spliterator.getExactSizeIfKnown() == 0) { + continue; // drop this split + } + + // Many spliterators will have trySplits that are SUBSIZED even if they are not themselves + // SUBSIZED. + if (spliterator.hasCharacteristics(Spliterator.SUBSIZED)) { + // we can drill down to exactly the smallest nonempty spliterator + while (true) { + Spliterator prefix = spliterator.trySplit(); + if (prefix == null || prefix.getExactSizeIfKnown() == 0) { + break; + } else if (spliterator.getExactSizeIfKnown() == 0) { + spliterator = prefix; + break; + } + } + + // spliterator is known to be nonempty now + spliterator.forEachRemaining(state::set); + return java.util.Optional.of(state.get()); + } + + Spliterator prefix = spliterator.trySplit(); + if (prefix == null || prefix.getExactSizeIfKnown() == 0) { + // we can't split this any further + spliterator.forEachRemaining(state::set); + if (state.set) { + return java.util.Optional.of(state.get()); + } + // fall back to the last split + continue; + } + splits.addLast(prefix); + splits.addLast(spliterator); + } + return java.util.Optional.empty(); + } + + /** + * Returns the last element of the specified stream, or {@link OptionalInt#empty} if the stream is + * empty. + * + *

Equivalent to {@code stream.reduce((a, b) -> b)}, but may perform significantly better. This + * method's runtime will be between O(log n) and O(n), performing better on efficiently splittable + * streams. + * + * @see IntStream#findFirst() + * @throws NullPointerException if the last element of the stream is null + */ + @Beta + public static OptionalInt findLast(IntStream stream) { + // findLast(Stream) does some allocation, so we might as well box some more + java.util.Optional boxedLast = findLast(stream.boxed()); + return boxedLast.isPresent() ? OptionalInt.of(boxedLast.get()) : OptionalInt.empty(); + } + + /** + * Returns the last element of the specified stream, or {@link OptionalLong#empty} if the stream + * is empty. + * + *

Equivalent to {@code stream.reduce((a, b) -> b)}, but may perform significantly better. This + * method's runtime will be between O(log n) and O(n), performing better on efficiently splittable + * streams. + * + * @see LongStream#findFirst() + * @throws NullPointerException if the last element of the stream is null + */ + @Beta + public static OptionalLong findLast(LongStream stream) { + // findLast(Stream) does some allocation, so we might as well box some more + java.util.Optional boxedLast = findLast(stream.boxed()); + return boxedLast.isPresent() ? OptionalLong.of(boxedLast.get()) : OptionalLong.empty(); + } + + /** + * Returns the last element of the specified stream, or {@link OptionalDouble#empty} if the stream + * is empty. + * + *

Equivalent to {@code stream.reduce((a, b) -> b)}, but may perform significantly better. This + * method's runtime will be between O(log n) and O(n), performing better on efficiently splittable + * streams. + * + * @see DoubleStream#findFirst() + * @throws NullPointerException if the last element of the stream is null + */ + @Beta + public static OptionalDouble findLast(DoubleStream stream) { + // findLast(Stream) does some allocation, so we might as well box some more + java.util.Optional boxedLast = findLast(stream.boxed()); + return boxedLast.isPresent() ? OptionalDouble.of(boxedLast.get()) : OptionalDouble.empty(); + } + + private Streams() {} +} diff --git a/src/main/java/com/google/common/collect/Synchronized.java b/src/main/java/com/google/common/collect/Synchronized.java new file mode 100644 index 0000000..49c7966 --- /dev/null +++ b/src/main/java/com/google/common/collect/Synchronized.java @@ -0,0 +1,2162 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.annotations.GwtCompatible; +import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.VisibleForTesting; + +import java.io.IOException; +import java.io.ObjectOutputStream; +import java.io.Serializable; +import java.util.Collection; +import java.util.Comparator; +import java.util.Deque; +import java.util.Iterator; +import java.util.List; +import java.util.ListIterator; +import java.util.Map; +import java.util.Map.Entry; +import java.util.NavigableMap; +import java.util.NavigableSet; +import java.util.Queue; +import java.util.RandomAccess; +import java.util.Set; +import java.util.SortedMap; +import java.util.SortedSet; +import java.util.Spliterator; +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.UnaryOperator; +import java.util.stream.Stream; + + + +/** + * Synchronized collection views. The returned synchronized collection views are serializable if the + * backing collection and the mutex are serializable. + * + *

If {@code null} is passed as the {@code mutex} parameter to any of this class's top-level + * methods or inner class constructors, the created object uses itself as the synchronization mutex. + * + *

This class should be used by other collection classes only. + * + * @author Mike Bostock + * @author Jared Levy + */ +@GwtCompatible(emulated = true) +final class Synchronized { + private Synchronized() {} + + static class SynchronizedObject implements Serializable { + final Object delegate; + final Object mutex; + + SynchronizedObject(Object delegate, Object mutex) { + this.delegate = checkNotNull(delegate); + this.mutex = (mutex == null) ? this : mutex; + } + + Object delegate() { + return delegate; + } + + // No equals and hashCode; see ForwardingObject for details. + + @Override + public String toString() { + synchronized (mutex) { + return delegate.toString(); + } + } + + // Serialization invokes writeObject only when it's private. + // The SynchronizedObject subclasses don't need a writeObject method since + // they don't contain any non-transient member variables, while the + // following writeObject() handles the SynchronizedObject members. + + @GwtIncompatible // java.io.ObjectOutputStream + private void writeObject(ObjectOutputStream stream) throws IOException { + synchronized (mutex) { + stream.defaultWriteObject(); + } + } + + @GwtIncompatible // not needed in emulated source + private static final long serialVersionUID = 0; + } + + private static Collection collection(Collection collection, Object mutex) { + return new SynchronizedCollection(collection, mutex); + } + + @VisibleForTesting + static class SynchronizedCollection extends SynchronizedObject implements Collection { + private SynchronizedCollection(Collection delegate, Object mutex) { + super(delegate, mutex); + } + + @SuppressWarnings("unchecked") + @Override + Collection delegate() { + return (Collection) super.delegate(); + } + + @Override + public boolean add(E e) { + synchronized (mutex) { + return delegate().add(e); + } + } + + @Override + public boolean addAll(Collection c) { + synchronized (mutex) { + return delegate().addAll(c); + } + } + + @Override + public void clear() { + synchronized (mutex) { + delegate().clear(); + } + } + + @Override + public boolean contains(Object o) { + synchronized (mutex) { + return delegate().contains(o); + } + } + + @Override + public boolean containsAll(Collection c) { + synchronized (mutex) { + return delegate().containsAll(c); + } + } + + @Override + public boolean isEmpty() { + synchronized (mutex) { + return delegate().isEmpty(); + } + } + + @Override + public Iterator iterator() { + return delegate().iterator(); // manually synchronized + } + + @Override + public Spliterator spliterator() { + synchronized (mutex) { + return delegate().spliterator(); + } + } + + @Override + public Stream stream() { + synchronized (mutex) { + return delegate().stream(); + } + } + + @Override + public Stream parallelStream() { + synchronized (mutex) { + return delegate().parallelStream(); + } + } + + @Override + public void forEach(Consumer action) { + synchronized (mutex) { + delegate().forEach(action); + } + } + + @Override + public boolean remove(Object o) { + synchronized (mutex) { + return delegate().remove(o); + } + } + + @Override + public boolean removeAll(Collection c) { + synchronized (mutex) { + return delegate().removeAll(c); + } + } + + @Override + public boolean retainAll(Collection c) { + synchronized (mutex) { + return delegate().retainAll(c); + } + } + + @Override + public boolean removeIf(Predicate filter) { + synchronized (mutex) { + return delegate().removeIf(filter); + } + } + + @Override + public int size() { + synchronized (mutex) { + return delegate().size(); + } + } + + @Override + public Object[] toArray() { + synchronized (mutex) { + return delegate().toArray(); + } + } + + @Override + public T[] toArray(T[] a) { + synchronized (mutex) { + return delegate().toArray(a); + } + } + + private static final long serialVersionUID = 0; + } + + @VisibleForTesting + static Set set(Set set, Object mutex) { + return new SynchronizedSet(set, mutex); + } + + static class SynchronizedSet extends SynchronizedCollection implements Set { + + SynchronizedSet(Set delegate, Object mutex) { + super(delegate, mutex); + } + + @Override + Set delegate() { + return (Set) super.delegate(); + } + + @Override + public boolean equals(Object o) { + if (o == this) { + return true; + } + synchronized (mutex) { + return delegate().equals(o); + } + } + + @Override + public int hashCode() { + synchronized (mutex) { + return delegate().hashCode(); + } + } + + private static final long serialVersionUID = 0; + } + + private static SortedSet sortedSet(SortedSet set, Object mutex) { + return new SynchronizedSortedSet(set, mutex); + } + + static class SynchronizedSortedSet extends SynchronizedSet implements SortedSet { + SynchronizedSortedSet(SortedSet delegate, Object mutex) { + super(delegate, mutex); + } + + @Override + SortedSet delegate() { + return (SortedSet) super.delegate(); + } + + @Override + public Comparator comparator() { + synchronized (mutex) { + return delegate().comparator(); + } + } + + @Override + public SortedSet subSet(E fromElement, E toElement) { + synchronized (mutex) { + return sortedSet(delegate().subSet(fromElement, toElement), mutex); + } + } + + @Override + public SortedSet headSet(E toElement) { + synchronized (mutex) { + return sortedSet(delegate().headSet(toElement), mutex); + } + } + + @Override + public SortedSet tailSet(E fromElement) { + synchronized (mutex) { + return sortedSet(delegate().tailSet(fromElement), mutex); + } + } + + @Override + public E first() { + synchronized (mutex) { + return delegate().first(); + } + } + + @Override + public E last() { + synchronized (mutex) { + return delegate().last(); + } + } + + private static final long serialVersionUID = 0; + } + + private static List list(List list, Object mutex) { + return (list instanceof RandomAccess) + ? new SynchronizedRandomAccessList(list, mutex) + : new SynchronizedList(list, mutex); + } + + private static class SynchronizedList extends SynchronizedCollection implements List { + SynchronizedList(List delegate, Object mutex) { + super(delegate, mutex); + } + + @Override + List delegate() { + return (List) super.delegate(); + } + + @Override + public void add(int index, E element) { + synchronized (mutex) { + delegate().add(index, element); + } + } + + @Override + public boolean addAll(int index, Collection c) { + synchronized (mutex) { + return delegate().addAll(index, c); + } + } + + @Override + public E get(int index) { + synchronized (mutex) { + return delegate().get(index); + } + } + + @Override + public int indexOf(Object o) { + synchronized (mutex) { + return delegate().indexOf(o); + } + } + + @Override + public int lastIndexOf(Object o) { + synchronized (mutex) { + return delegate().lastIndexOf(o); + } + } + + @Override + public ListIterator listIterator() { + return delegate().listIterator(); // manually synchronized + } + + @Override + public ListIterator listIterator(int index) { + return delegate().listIterator(index); // manually synchronized + } + + @Override + public E remove(int index) { + synchronized (mutex) { + return delegate().remove(index); + } + } + + @Override + public E set(int index, E element) { + synchronized (mutex) { + return delegate().set(index, element); + } + } + + @Override + public void replaceAll(UnaryOperator operator) { + synchronized (mutex) { + delegate().replaceAll(operator); + } + } + + @Override + public void sort(Comparator c) { + synchronized (mutex) { + delegate().sort(c); + } + } + + @Override + public List subList(int fromIndex, int toIndex) { + synchronized (mutex) { + return list(delegate().subList(fromIndex, toIndex), mutex); + } + } + + @Override + public boolean equals(Object o) { + if (o == this) { + return true; + } + synchronized (mutex) { + return delegate().equals(o); + } + } + + @Override + public int hashCode() { + synchronized (mutex) { + return delegate().hashCode(); + } + } + + private static final long serialVersionUID = 0; + } + + private static class SynchronizedRandomAccessList extends SynchronizedList + implements RandomAccess { + SynchronizedRandomAccessList(List list, Object mutex) { + super(list, mutex); + } + + private static final long serialVersionUID = 0; + } + + static Multiset multiset(Multiset multiset, Object mutex) { + if (multiset instanceof SynchronizedMultiset || multiset instanceof ImmutableMultiset) { + return multiset; + } + return new SynchronizedMultiset(multiset, mutex); + } + + private static class SynchronizedMultiset extends SynchronizedCollection + implements Multiset { + transient Set elementSet; + transient Set> entrySet; + + SynchronizedMultiset(Multiset delegate, Object mutex) { + super(delegate, mutex); + } + + @Override + Multiset delegate() { + return (Multiset) super.delegate(); + } + + @Override + public int count(Object o) { + synchronized (mutex) { + return delegate().count(o); + } + } + + @Override + public int add(E e, int n) { + synchronized (mutex) { + return delegate().add(e, n); + } + } + + @Override + public int remove(Object o, int n) { + synchronized (mutex) { + return delegate().remove(o, n); + } + } + + @Override + public int setCount(E element, int count) { + synchronized (mutex) { + return delegate().setCount(element, count); + } + } + + @Override + public boolean setCount(E element, int oldCount, int newCount) { + synchronized (mutex) { + return delegate().setCount(element, oldCount, newCount); + } + } + + @Override + public Set elementSet() { + synchronized (mutex) { + if (elementSet == null) { + elementSet = typePreservingSet(delegate().elementSet(), mutex); + } + return elementSet; + } + } + + @Override + public Set> entrySet() { + synchronized (mutex) { + if (entrySet == null) { + entrySet = typePreservingSet(delegate().entrySet(), mutex); + } + return entrySet; + } + } + + @Override + public boolean equals(Object o) { + if (o == this) { + return true; + } + synchronized (mutex) { + return delegate().equals(o); + } + } + + @Override + public int hashCode() { + synchronized (mutex) { + return delegate().hashCode(); + } + } + + private static final long serialVersionUID = 0; + } + + static Multimap multimap(Multimap multimap, Object mutex) { + if (multimap instanceof SynchronizedMultimap || multimap instanceof BaseImmutableMultimap) { + return multimap; + } + return new SynchronizedMultimap<>(multimap, mutex); + } + + private static class SynchronizedMultimap extends SynchronizedObject + implements Multimap { + transient Set keySet; + transient Collection valuesCollection; + transient Collection> entries; + transient Map> asMap; + transient Multiset keys; + + @SuppressWarnings("unchecked") + @Override + Multimap delegate() { + return (Multimap) super.delegate(); + } + + SynchronizedMultimap(Multimap delegate, Object mutex) { + super(delegate, mutex); + } + + @Override + public int size() { + synchronized (mutex) { + return delegate().size(); + } + } + + @Override + public boolean isEmpty() { + synchronized (mutex) { + return delegate().isEmpty(); + } + } + + @Override + public boolean containsKey(Object key) { + synchronized (mutex) { + return delegate().containsKey(key); + } + } + + @Override + public boolean containsValue(Object value) { + synchronized (mutex) { + return delegate().containsValue(value); + } + } + + @Override + public boolean containsEntry(Object key, Object value) { + synchronized (mutex) { + return delegate().containsEntry(key, value); + } + } + + @Override + public Collection get(K key) { + synchronized (mutex) { + return typePreservingCollection(delegate().get(key), mutex); + } + } + + @Override + public boolean put(K key, V value) { + synchronized (mutex) { + return delegate().put(key, value); + } + } + + @Override + public boolean putAll(K key, Iterable values) { + synchronized (mutex) { + return delegate().putAll(key, values); + } + } + + @Override + public boolean putAll(Multimap multimap) { + synchronized (mutex) { + return delegate().putAll(multimap); + } + } + + @Override + public Collection replaceValues(K key, Iterable values) { + synchronized (mutex) { + return delegate().replaceValues(key, values); // copy not synchronized + } + } + + @Override + public boolean remove(Object key, Object value) { + synchronized (mutex) { + return delegate().remove(key, value); + } + } + + @Override + public Collection removeAll(Object key) { + synchronized (mutex) { + return delegate().removeAll(key); // copy not synchronized + } + } + + @Override + public void clear() { + synchronized (mutex) { + delegate().clear(); + } + } + + @Override + public Set keySet() { + synchronized (mutex) { + if (keySet == null) { + keySet = typePreservingSet(delegate().keySet(), mutex); + } + return keySet; + } + } + + @Override + public Collection values() { + synchronized (mutex) { + if (valuesCollection == null) { + valuesCollection = collection(delegate().values(), mutex); + } + return valuesCollection; + } + } + + @Override + public Collection> entries() { + synchronized (mutex) { + if (entries == null) { + entries = typePreservingCollection(delegate().entries(), mutex); + } + return entries; + } + } + + @Override + public void forEach(BiConsumer action) { + synchronized (mutex) { + delegate().forEach(action); + } + } + + @Override + public Map> asMap() { + synchronized (mutex) { + if (asMap == null) { + asMap = new SynchronizedAsMap<>(delegate().asMap(), mutex); + } + return asMap; + } + } + + @Override + public Multiset keys() { + synchronized (mutex) { + if (keys == null) { + keys = multiset(delegate().keys(), mutex); + } + return keys; + } + } + + @Override + public boolean equals(Object o) { + if (o == this) { + return true; + } + synchronized (mutex) { + return delegate().equals(o); + } + } + + @Override + public int hashCode() { + synchronized (mutex) { + return delegate().hashCode(); + } + } + + private static final long serialVersionUID = 0; + } + + static ListMultimap listMultimap( + ListMultimap multimap, Object mutex) { + if (multimap instanceof SynchronizedListMultimap || multimap instanceof BaseImmutableMultimap) { + return multimap; + } + return new SynchronizedListMultimap<>(multimap, mutex); + } + + private static class SynchronizedListMultimap extends SynchronizedMultimap + implements ListMultimap { + SynchronizedListMultimap(ListMultimap delegate, Object mutex) { + super(delegate, mutex); + } + + @Override + ListMultimap delegate() { + return (ListMultimap) super.delegate(); + } + + @Override + public List get(K key) { + synchronized (mutex) { + return list(delegate().get(key), mutex); + } + } + + @Override + public List removeAll(Object key) { + synchronized (mutex) { + return delegate().removeAll(key); // copy not synchronized + } + } + + @Override + public List replaceValues(K key, Iterable values) { + synchronized (mutex) { + return delegate().replaceValues(key, values); // copy not synchronized + } + } + + private static final long serialVersionUID = 0; + } + + static SetMultimap setMultimap(SetMultimap multimap, Object mutex) { + if (multimap instanceof SynchronizedSetMultimap || multimap instanceof BaseImmutableMultimap) { + return multimap; + } + return new SynchronizedSetMultimap<>(multimap, mutex); + } + + private static class SynchronizedSetMultimap extends SynchronizedMultimap + implements SetMultimap { + transient Set> entrySet; + + SynchronizedSetMultimap(SetMultimap delegate, Object mutex) { + super(delegate, mutex); + } + + @Override + SetMultimap delegate() { + return (SetMultimap) super.delegate(); + } + + @Override + public Set get(K key) { + synchronized (mutex) { + return set(delegate().get(key), mutex); + } + } + + @Override + public Set removeAll(Object key) { + synchronized (mutex) { + return delegate().removeAll(key); // copy not synchronized + } + } + + @Override + public Set replaceValues(K key, Iterable values) { + synchronized (mutex) { + return delegate().replaceValues(key, values); // copy not synchronized + } + } + + @Override + public Set> entries() { + synchronized (mutex) { + if (entrySet == null) { + entrySet = set(delegate().entries(), mutex); + } + return entrySet; + } + } + + private static final long serialVersionUID = 0; + } + + static SortedSetMultimap sortedSetMultimap( + SortedSetMultimap multimap, Object mutex) { + if (multimap instanceof SynchronizedSortedSetMultimap) { + return multimap; + } + return new SynchronizedSortedSetMultimap<>(multimap, mutex); + } + + private static class SynchronizedSortedSetMultimap extends SynchronizedSetMultimap + implements SortedSetMultimap { + SynchronizedSortedSetMultimap(SortedSetMultimap delegate, Object mutex) { + super(delegate, mutex); + } + + @Override + SortedSetMultimap delegate() { + return (SortedSetMultimap) super.delegate(); + } + + @Override + public SortedSet get(K key) { + synchronized (mutex) { + return sortedSet(delegate().get(key), mutex); + } + } + + @Override + public SortedSet removeAll(Object key) { + synchronized (mutex) { + return delegate().removeAll(key); // copy not synchronized + } + } + + @Override + public SortedSet replaceValues(K key, Iterable values) { + synchronized (mutex) { + return delegate().replaceValues(key, values); // copy not synchronized + } + } + + @Override + public Comparator valueComparator() { + synchronized (mutex) { + return delegate().valueComparator(); + } + } + + private static final long serialVersionUID = 0; + } + + private static Collection typePreservingCollection( + Collection collection, Object mutex) { + if (collection instanceof SortedSet) { + return sortedSet((SortedSet) collection, mutex); + } + if (collection instanceof Set) { + return set((Set) collection, mutex); + } + if (collection instanceof List) { + return list((List) collection, mutex); + } + return collection(collection, mutex); + } + + private static Set typePreservingSet(Set set, Object mutex) { + if (set instanceof SortedSet) { + return sortedSet((SortedSet) set, mutex); + } else { + return set(set, mutex); + } + } + + private static class SynchronizedAsMapEntries + extends SynchronizedSet>> { + SynchronizedAsMapEntries(Set>> delegate, Object mutex) { + super(delegate, mutex); + } + + @Override + public Iterator>> iterator() { + // Must be manually synchronized. + return new TransformedIterator>, Entry>>( + super.iterator()) { + @Override + Entry> transform(final Entry> entry) { + return new ForwardingMapEntry>() { + @Override + protected Entry> delegate() { + return entry; + } + + @Override + public Collection getValue() { + return typePreservingCollection(entry.getValue(), mutex); + } + }; + } + }; + } + + // See Collections.CheckedMap.CheckedEntrySet for details on attacks. + + @Override + public Object[] toArray() { + synchronized (mutex) { + return ObjectArrays.toArrayImpl(delegate()); + } + } + + @Override + public T[] toArray(T[] array) { + synchronized (mutex) { + return ObjectArrays.toArrayImpl(delegate(), array); + } + } + + @Override + public boolean contains(Object o) { + synchronized (mutex) { + return Maps.containsEntryImpl(delegate(), o); + } + } + + @Override + public boolean containsAll(Collection c) { + synchronized (mutex) { + return Collections2.containsAllImpl(delegate(), c); + } + } + + @Override + public boolean equals(Object o) { + if (o == this) { + return true; + } + synchronized (mutex) { + return Sets.equalsImpl(delegate(), o); + } + } + + @Override + public boolean remove(Object o) { + synchronized (mutex) { + return Maps.removeEntryImpl(delegate(), o); + } + } + + @Override + public boolean removeAll(Collection c) { + synchronized (mutex) { + return Iterators.removeAll(delegate().iterator(), c); + } + } + + @Override + public boolean retainAll(Collection c) { + synchronized (mutex) { + return Iterators.retainAll(delegate().iterator(), c); + } + } + + private static final long serialVersionUID = 0; + } + + @VisibleForTesting + static Map map(Map map, Object mutex) { + return new SynchronizedMap<>(map, mutex); + } + + private static class SynchronizedMap extends SynchronizedObject implements Map { + transient Set keySet; + transient Collection values; + transient Set> entrySet; + + SynchronizedMap(Map delegate, Object mutex) { + super(delegate, mutex); + } + + @SuppressWarnings("unchecked") + @Override + Map delegate() { + return (Map) super.delegate(); + } + + @Override + public void clear() { + synchronized (mutex) { + delegate().clear(); + } + } + + @Override + public boolean containsKey(Object key) { + synchronized (mutex) { + return delegate().containsKey(key); + } + } + + @Override + public boolean containsValue(Object value) { + synchronized (mutex) { + return delegate().containsValue(value); + } + } + + @Override + public Set> entrySet() { + synchronized (mutex) { + if (entrySet == null) { + entrySet = set(delegate().entrySet(), mutex); + } + return entrySet; + } + } + + @Override + public void forEach(BiConsumer action) { + synchronized (mutex) { + delegate().forEach(action); + } + } + + @Override + public V get(Object key) { + synchronized (mutex) { + return delegate().get(key); + } + } + + @Override + public V getOrDefault(Object key, V defaultValue) { + synchronized (mutex) { + return delegate().getOrDefault(key, defaultValue); + } + } + + @Override + public boolean isEmpty() { + synchronized (mutex) { + return delegate().isEmpty(); + } + } + + @Override + public Set keySet() { + synchronized (mutex) { + if (keySet == null) { + keySet = set(delegate().keySet(), mutex); + } + return keySet; + } + } + + @Override + public V put(K key, V value) { + synchronized (mutex) { + return delegate().put(key, value); + } + } + + @Override + public V putIfAbsent(K key, V value) { + synchronized (mutex) { + return delegate().putIfAbsent(key, value); + } + } + + @Override + public boolean replace(K key, V oldValue, V newValue) { + synchronized (mutex) { + return delegate().replace(key, oldValue, newValue); + } + } + + @Override + public V replace(K key, V value) { + synchronized (mutex) { + return delegate().replace(key, value); + } + } + + @Override + public V computeIfAbsent(K key, Function mappingFunction) { + synchronized (mutex) { + return delegate().computeIfAbsent(key, mappingFunction); + } + } + + @Override + public V computeIfPresent( + K key, BiFunction remappingFunction) { + synchronized (mutex) { + return delegate().computeIfPresent(key, remappingFunction); + } + } + + @Override + public V compute(K key, BiFunction remappingFunction) { + synchronized (mutex) { + return delegate().compute(key, remappingFunction); + } + } + + @Override + public V merge( + K key, V value, BiFunction remappingFunction) { + synchronized (mutex) { + return delegate().merge(key, value, remappingFunction); + } + } + + @Override + public void putAll(Map map) { + synchronized (mutex) { + delegate().putAll(map); + } + } + + @Override + public void replaceAll(BiFunction function) { + synchronized (mutex) { + delegate().replaceAll(function); + } + } + + @Override + public V remove(Object key) { + synchronized (mutex) { + return delegate().remove(key); + } + } + + @Override + public boolean remove(Object key, Object value) { + synchronized (mutex) { + return delegate().remove(key, value); + } + } + + @Override + public int size() { + synchronized (mutex) { + return delegate().size(); + } + } + + @Override + public Collection values() { + synchronized (mutex) { + if (values == null) { + values = collection(delegate().values(), mutex); + } + return values; + } + } + + @Override + public boolean equals(Object o) { + if (o == this) { + return true; + } + synchronized (mutex) { + return delegate().equals(o); + } + } + + @Override + public int hashCode() { + synchronized (mutex) { + return delegate().hashCode(); + } + } + + private static final long serialVersionUID = 0; + } + + static SortedMap sortedMap(SortedMap sortedMap, Object mutex) { + return new SynchronizedSortedMap<>(sortedMap, mutex); + } + + static class SynchronizedSortedMap extends SynchronizedMap + implements SortedMap { + + SynchronizedSortedMap(SortedMap delegate, Object mutex) { + super(delegate, mutex); + } + + @Override + SortedMap delegate() { + return (SortedMap) super.delegate(); + } + + @Override + public Comparator comparator() { + synchronized (mutex) { + return delegate().comparator(); + } + } + + @Override + public K firstKey() { + synchronized (mutex) { + return delegate().firstKey(); + } + } + + @Override + public SortedMap headMap(K toKey) { + synchronized (mutex) { + return sortedMap(delegate().headMap(toKey), mutex); + } + } + + @Override + public K lastKey() { + synchronized (mutex) { + return delegate().lastKey(); + } + } + + @Override + public SortedMap subMap(K fromKey, K toKey) { + synchronized (mutex) { + return sortedMap(delegate().subMap(fromKey, toKey), mutex); + } + } + + @Override + public SortedMap tailMap(K fromKey) { + synchronized (mutex) { + return sortedMap(delegate().tailMap(fromKey), mutex); + } + } + + private static final long serialVersionUID = 0; + } + + static BiMap biMap(BiMap bimap, Object mutex) { + if (bimap instanceof SynchronizedBiMap || bimap instanceof ImmutableBiMap) { + return bimap; + } + return new SynchronizedBiMap<>(bimap, mutex, null); + } + + @VisibleForTesting + static class SynchronizedBiMap extends SynchronizedMap + implements BiMap, Serializable { + private transient Set valueSet; + private transient BiMap inverse; + + private SynchronizedBiMap( + BiMap delegate, Object mutex, BiMap inverse) { + super(delegate, mutex); + this.inverse = inverse; + } + + @Override + BiMap delegate() { + return (BiMap) super.delegate(); + } + + @Override + public Set values() { + synchronized (mutex) { + if (valueSet == null) { + valueSet = set(delegate().values(), mutex); + } + return valueSet; + } + } + + @Override + public V forcePut(K key, V value) { + synchronized (mutex) { + return delegate().forcePut(key, value); + } + } + + @Override + public BiMap inverse() { + synchronized (mutex) { + if (inverse == null) { + inverse = new SynchronizedBiMap<>(delegate().inverse(), mutex, this); + } + return inverse; + } + } + + private static final long serialVersionUID = 0; + } + + private static class SynchronizedAsMap extends SynchronizedMap> { + transient Set>> asMapEntrySet; + transient Collection> asMapValues; + + SynchronizedAsMap(Map> delegate, Object mutex) { + super(delegate, mutex); + } + + @Override + public Collection get(Object key) { + synchronized (mutex) { + Collection collection = super.get(key); + return (collection == null) ? null : typePreservingCollection(collection, mutex); + } + } + + @Override + public Set>> entrySet() { + synchronized (mutex) { + if (asMapEntrySet == null) { + asMapEntrySet = new SynchronizedAsMapEntries<>(delegate().entrySet(), mutex); + } + return asMapEntrySet; + } + } + + @Override + public Collection> values() { + synchronized (mutex) { + if (asMapValues == null) { + asMapValues = new SynchronizedAsMapValues(delegate().values(), mutex); + } + return asMapValues; + } + } + + @Override + public boolean containsValue(Object o) { + // values() and its contains() method are both synchronized. + return values().contains(o); + } + + private static final long serialVersionUID = 0; + } + + private static class SynchronizedAsMapValues extends SynchronizedCollection> { + SynchronizedAsMapValues(Collection> delegate, Object mutex) { + super(delegate, mutex); + } + + @Override + public Iterator> iterator() { + // Must be manually synchronized. + return new TransformedIterator, Collection>(super.iterator()) { + @Override + Collection transform(Collection from) { + return typePreservingCollection(from, mutex); + } + }; + } + + private static final long serialVersionUID = 0; + } + + @GwtIncompatible // NavigableSet + @VisibleForTesting + static class SynchronizedNavigableSet extends SynchronizedSortedSet + implements NavigableSet { + SynchronizedNavigableSet(NavigableSet delegate, Object mutex) { + super(delegate, mutex); + } + + @Override + NavigableSet delegate() { + return (NavigableSet) super.delegate(); + } + + @Override + public E ceiling(E e) { + synchronized (mutex) { + return delegate().ceiling(e); + } + } + + @Override + public Iterator descendingIterator() { + return delegate().descendingIterator(); // manually synchronized + } + + transient NavigableSet descendingSet; + + @Override + public NavigableSet descendingSet() { + synchronized (mutex) { + if (descendingSet == null) { + NavigableSet dS = Synchronized.navigableSet(delegate().descendingSet(), mutex); + descendingSet = dS; + return dS; + } + return descendingSet; + } + } + + @Override + public E floor(E e) { + synchronized (mutex) { + return delegate().floor(e); + } + } + + @Override + public NavigableSet headSet(E toElement, boolean inclusive) { + synchronized (mutex) { + return Synchronized.navigableSet(delegate().headSet(toElement, inclusive), mutex); + } + } + + @Override + public SortedSet headSet(E toElement) { + return headSet(toElement, false); + } + + @Override + public E higher(E e) { + synchronized (mutex) { + return delegate().higher(e); + } + } + + @Override + public E lower(E e) { + synchronized (mutex) { + return delegate().lower(e); + } + } + + @Override + public E pollFirst() { + synchronized (mutex) { + return delegate().pollFirst(); + } + } + + @Override + public E pollLast() { + synchronized (mutex) { + return delegate().pollLast(); + } + } + + @Override + public NavigableSet subSet( + E fromElement, boolean fromInclusive, E toElement, boolean toInclusive) { + synchronized (mutex) { + return Synchronized.navigableSet( + delegate().subSet(fromElement, fromInclusive, toElement, toInclusive), mutex); + } + } + + @Override + public SortedSet subSet(E fromElement, E toElement) { + return subSet(fromElement, true, toElement, false); + } + + @Override + public NavigableSet tailSet(E fromElement, boolean inclusive) { + synchronized (mutex) { + return Synchronized.navigableSet(delegate().tailSet(fromElement, inclusive), mutex); + } + } + + @Override + public SortedSet tailSet(E fromElement) { + return tailSet(fromElement, true); + } + + private static final long serialVersionUID = 0; + } + + @GwtIncompatible // NavigableSet + static NavigableSet navigableSet(NavigableSet navigableSet, Object mutex) { + return new SynchronizedNavigableSet(navigableSet, mutex); + } + + @GwtIncompatible // NavigableSet + static NavigableSet navigableSet(NavigableSet navigableSet) { + return navigableSet(navigableSet, null); + } + + @GwtIncompatible // NavigableMap + static NavigableMap navigableMap(NavigableMap navigableMap) { + return navigableMap(navigableMap, null); + } + + @GwtIncompatible // NavigableMap + static NavigableMap navigableMap( + NavigableMap navigableMap, Object mutex) { + return new SynchronizedNavigableMap<>(navigableMap, mutex); + } + + @GwtIncompatible // NavigableMap + @VisibleForTesting + static class SynchronizedNavigableMap extends SynchronizedSortedMap + implements NavigableMap { + + SynchronizedNavigableMap(NavigableMap delegate, Object mutex) { + super(delegate, mutex); + } + + @Override + NavigableMap delegate() { + return (NavigableMap) super.delegate(); + } + + @Override + public Entry ceilingEntry(K key) { + synchronized (mutex) { + return nullableSynchronizedEntry(delegate().ceilingEntry(key), mutex); + } + } + + @Override + public K ceilingKey(K key) { + synchronized (mutex) { + return delegate().ceilingKey(key); + } + } + + transient NavigableSet descendingKeySet; + + @Override + public NavigableSet descendingKeySet() { + synchronized (mutex) { + if (descendingKeySet == null) { + return descendingKeySet = Synchronized.navigableSet(delegate().descendingKeySet(), mutex); + } + return descendingKeySet; + } + } + + transient NavigableMap descendingMap; + + @Override + public NavigableMap descendingMap() { + synchronized (mutex) { + if (descendingMap == null) { + return descendingMap = navigableMap(delegate().descendingMap(), mutex); + } + return descendingMap; + } + } + + @Override + public Entry firstEntry() { + synchronized (mutex) { + return nullableSynchronizedEntry(delegate().firstEntry(), mutex); + } + } + + @Override + public Entry floorEntry(K key) { + synchronized (mutex) { + return nullableSynchronizedEntry(delegate().floorEntry(key), mutex); + } + } + + @Override + public K floorKey(K key) { + synchronized (mutex) { + return delegate().floorKey(key); + } + } + + @Override + public NavigableMap headMap(K toKey, boolean inclusive) { + synchronized (mutex) { + return navigableMap(delegate().headMap(toKey, inclusive), mutex); + } + } + + @Override + public SortedMap headMap(K toKey) { + return headMap(toKey, false); + } + + @Override + public Entry higherEntry(K key) { + synchronized (mutex) { + return nullableSynchronizedEntry(delegate().higherEntry(key), mutex); + } + } + + @Override + public K higherKey(K key) { + synchronized (mutex) { + return delegate().higherKey(key); + } + } + + @Override + public Entry lastEntry() { + synchronized (mutex) { + return nullableSynchronizedEntry(delegate().lastEntry(), mutex); + } + } + + @Override + public Entry lowerEntry(K key) { + synchronized (mutex) { + return nullableSynchronizedEntry(delegate().lowerEntry(key), mutex); + } + } + + @Override + public K lowerKey(K key) { + synchronized (mutex) { + return delegate().lowerKey(key); + } + } + + @Override + public Set keySet() { + return navigableKeySet(); + } + + transient NavigableSet navigableKeySet; + + @Override + public NavigableSet navigableKeySet() { + synchronized (mutex) { + if (navigableKeySet == null) { + return navigableKeySet = Synchronized.navigableSet(delegate().navigableKeySet(), mutex); + } + return navigableKeySet; + } + } + + @Override + public Entry pollFirstEntry() { + synchronized (mutex) { + return nullableSynchronizedEntry(delegate().pollFirstEntry(), mutex); + } + } + + @Override + public Entry pollLastEntry() { + synchronized (mutex) { + return nullableSynchronizedEntry(delegate().pollLastEntry(), mutex); + } + } + + @Override + public NavigableMap subMap( + K fromKey, boolean fromInclusive, K toKey, boolean toInclusive) { + synchronized (mutex) { + return navigableMap(delegate().subMap(fromKey, fromInclusive, toKey, toInclusive), mutex); + } + } + + @Override + public SortedMap subMap(K fromKey, K toKey) { + return subMap(fromKey, true, toKey, false); + } + + @Override + public NavigableMap tailMap(K fromKey, boolean inclusive) { + synchronized (mutex) { + return navigableMap(delegate().tailMap(fromKey, inclusive), mutex); + } + } + + @Override + public SortedMap tailMap(K fromKey) { + return tailMap(fromKey, true); + } + + private static final long serialVersionUID = 0; + } + + @GwtIncompatible // works but is needed only for NavigableMap + private static Entry nullableSynchronizedEntry( + Entry entry, Object mutex) { + if (entry == null) { + return null; + } + return new SynchronizedEntry<>(entry, mutex); + } + + @GwtIncompatible // works but is needed only for NavigableMap + private static class SynchronizedEntry extends SynchronizedObject implements Entry { + + SynchronizedEntry(Entry delegate, Object mutex) { + super(delegate, mutex); + } + + @SuppressWarnings("unchecked") // guaranteed by the constructor + @Override + Entry delegate() { + return (Entry) super.delegate(); + } + + @Override + public boolean equals(Object obj) { + synchronized (mutex) { + return delegate().equals(obj); + } + } + + @Override + public int hashCode() { + synchronized (mutex) { + return delegate().hashCode(); + } + } + + @Override + public K getKey() { + synchronized (mutex) { + return delegate().getKey(); + } + } + + @Override + public V getValue() { + synchronized (mutex) { + return delegate().getValue(); + } + } + + @Override + public V setValue(V value) { + synchronized (mutex) { + return delegate().setValue(value); + } + } + + private static final long serialVersionUID = 0; + } + + static Queue queue(Queue queue, Object mutex) { + return (queue instanceof SynchronizedQueue) ? queue : new SynchronizedQueue(queue, mutex); + } + + private static class SynchronizedQueue extends SynchronizedCollection implements Queue { + + SynchronizedQueue(Queue delegate, Object mutex) { + super(delegate, mutex); + } + + @Override + Queue delegate() { + return (Queue) super.delegate(); + } + + @Override + public E element() { + synchronized (mutex) { + return delegate().element(); + } + } + + @Override + public boolean offer(E e) { + synchronized (mutex) { + return delegate().offer(e); + } + } + + @Override + public E peek() { + synchronized (mutex) { + return delegate().peek(); + } + } + + @Override + public E poll() { + synchronized (mutex) { + return delegate().poll(); + } + } + + @Override + public E remove() { + synchronized (mutex) { + return delegate().remove(); + } + } + + private static final long serialVersionUID = 0; + } + + static Deque deque(Deque deque, Object mutex) { + return new SynchronizedDeque(deque, mutex); + } + + private static final class SynchronizedDeque extends SynchronizedQueue implements Deque { + + SynchronizedDeque(Deque delegate, Object mutex) { + super(delegate, mutex); + } + + @Override + Deque delegate() { + return (Deque) super.delegate(); + } + + @Override + public void addFirst(E e) { + synchronized (mutex) { + delegate().addFirst(e); + } + } + + @Override + public void addLast(E e) { + synchronized (mutex) { + delegate().addLast(e); + } + } + + @Override + public boolean offerFirst(E e) { + synchronized (mutex) { + return delegate().offerFirst(e); + } + } + + @Override + public boolean offerLast(E e) { + synchronized (mutex) { + return delegate().offerLast(e); + } + } + + @Override + public E removeFirst() { + synchronized (mutex) { + return delegate().removeFirst(); + } + } + + @Override + public E removeLast() { + synchronized (mutex) { + return delegate().removeLast(); + } + } + + @Override + public E pollFirst() { + synchronized (mutex) { + return delegate().pollFirst(); + } + } + + @Override + public E pollLast() { + synchronized (mutex) { + return delegate().pollLast(); + } + } + + @Override + public E getFirst() { + synchronized (mutex) { + return delegate().getFirst(); + } + } + + @Override + public E getLast() { + synchronized (mutex) { + return delegate().getLast(); + } + } + + @Override + public E peekFirst() { + synchronized (mutex) { + return delegate().peekFirst(); + } + } + + @Override + public E peekLast() { + synchronized (mutex) { + return delegate().peekLast(); + } + } + + @Override + public boolean removeFirstOccurrence(Object o) { + synchronized (mutex) { + return delegate().removeFirstOccurrence(o); + } + } + + @Override + public boolean removeLastOccurrence(Object o) { + synchronized (mutex) { + return delegate().removeLastOccurrence(o); + } + } + + @Override + public void push(E e) { + synchronized (mutex) { + delegate().push(e); + } + } + + @Override + public E pop() { + synchronized (mutex) { + return delegate().pop(); + } + } + + @Override + public Iterator descendingIterator() { + synchronized (mutex) { + return delegate().descendingIterator(); + } + } + + private static final long serialVersionUID = 0; + } + + static Table table(Table table, Object mutex) { + return new SynchronizedTable<>(table, mutex); + } + + private static final class SynchronizedTable extends SynchronizedObject + implements Table { + + SynchronizedTable(Table delegate, Object mutex) { + super(delegate, mutex); + } + + @SuppressWarnings("unchecked") + @Override + Table delegate() { + return (Table) super.delegate(); + } + + @Override + public boolean contains(Object rowKey, Object columnKey) { + synchronized (mutex) { + return delegate().contains(rowKey, columnKey); + } + } + + @Override + public boolean containsRow(Object rowKey) { + synchronized (mutex) { + return delegate().containsRow(rowKey); + } + } + + @Override + public boolean containsColumn(Object columnKey) { + synchronized (mutex) { + return delegate().containsColumn(columnKey); + } + } + + @Override + public boolean containsValue(Object value) { + synchronized (mutex) { + return delegate().containsValue(value); + } + } + + @Override + public V get(Object rowKey, Object columnKey) { + synchronized (mutex) { + return delegate().get(rowKey, columnKey); + } + } + + @Override + public boolean isEmpty() { + synchronized (mutex) { + return delegate().isEmpty(); + } + } + + @Override + public int size() { + synchronized (mutex) { + return delegate().size(); + } + } + + @Override + public void clear() { + synchronized (mutex) { + delegate().clear(); + } + } + + @Override + public V put(R rowKey, C columnKey, V value) { + synchronized (mutex) { + return delegate().put(rowKey, columnKey, value); + } + } + + @Override + public void putAll(Table table) { + synchronized (mutex) { + delegate().putAll(table); + } + } + + @Override + public V remove(Object rowKey, Object columnKey) { + synchronized (mutex) { + return delegate().remove(rowKey, columnKey); + } + } + + @Override + public Map row(R rowKey) { + synchronized (mutex) { + return map(delegate().row(rowKey), mutex); + } + } + + @Override + public Map column(C columnKey) { + synchronized (mutex) { + return map(delegate().column(columnKey), mutex); + } + } + + @Override + public Set> cellSet() { + synchronized (mutex) { + return set(delegate().cellSet(), mutex); + } + } + + @Override + public Set rowKeySet() { + synchronized (mutex) { + return set(delegate().rowKeySet(), mutex); + } + } + + @Override + public Set columnKeySet() { + synchronized (mutex) { + return set(delegate().columnKeySet(), mutex); + } + } + + @Override + public Collection values() { + synchronized (mutex) { + return collection(delegate().values(), mutex); + } + } + + @Override + public Map> rowMap() { + synchronized (mutex) { + return map( + Maps.transformValues( + delegate().rowMap(), + new com.google.common.base.Function, Map>() { + @Override + public Map apply(Map t) { + return map(t, mutex); + } + }), + mutex); + } + } + + @Override + public Map> columnMap() { + synchronized (mutex) { + return map( + Maps.transformValues( + delegate().columnMap(), + new com.google.common.base.Function, Map>() { + @Override + public Map apply(Map t) { + return map(t, mutex); + } + }), + mutex); + } + } + + @Override + public int hashCode() { + synchronized (mutex) { + return delegate().hashCode(); + } + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + synchronized (mutex) { + return delegate().equals(obj); + } + } + } +} diff --git a/src/main/java/com/google/common/collect/Table.java b/src/main/java/com/google/common/collect/Table.java new file mode 100644 index 0000000..2dd0820 --- /dev/null +++ b/src/main/java/com/google/common/collect/Table.java @@ -0,0 +1,276 @@ +/* + * Copyright (C) 2008 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import com.google.common.annotations.GwtCompatible; +import com.google.common.base.Objects; + + +import java.util.Collection; +import java.util.Map; +import java.util.Set; + + +/** + * A collection that associates an ordered pair of keys, called a row key and a column key, with a + * single value. A table may be sparse, with only a small fraction of row key / column key pairs + * possessing a corresponding value. + * + *

The mappings corresponding to a given row key may be viewed as a {@link Map} whose keys are + * the columns. The reverse is also available, associating a column with a row key / value map. Note + * that, in some implementations, data access by column key may have fewer supported operations or + * worse performance than data access by row key. + * + *

The methods returning collections or maps always return views of the underlying table. + * Updating the table can change the contents of those collections, and updating the collections + * will change the table. + * + *

All methods that modify the table are optional, and the views returned by the table may or may + * not be modifiable. When modification isn't supported, those methods will throw an {@link + * UnsupportedOperationException}. + * + *

See the Guava User Guide article on {@code Table}. + * + * @author Jared Levy + * @param the type of the table row keys + * @param the type of the table column keys + * @param the type of the mapped values + * @since 7.0 + */ +@GwtCompatible +public interface Table { + // TODO(jlevy): Consider adding methods similar to ConcurrentMap methods. + + // Accessors + + /** + * Returns {@code true} if the table contains a mapping with the specified row and column keys. + * + * @param rowKey key of row to search for + * @param columnKey key of column to search for + */ + boolean contains(Object rowKey, Object columnKey); + + /** + * Returns {@code true} if the table contains a mapping with the specified row key. + * + * @param rowKey key of row to search for + */ + boolean containsRow(Object rowKey); + + /** + * Returns {@code true} if the table contains a mapping with the specified column. + * + * @param columnKey key of column to search for + */ + boolean containsColumn(Object columnKey); + + /** + * Returns {@code true} if the table contains a mapping with the specified value. + * + * @param value value to search for + */ + boolean containsValue(Object value); + + /** + * Returns the value corresponding to the given row and column keys, or {@code null} if no such + * mapping exists. + * + * @param rowKey key of row to search for + * @param columnKey key of column to search for + */ + V get(Object rowKey, Object columnKey); + + /** Returns {@code true} if the table contains no mappings. */ + boolean isEmpty(); + + /** Returns the number of row key / column key / value mappings in the table. */ + int size(); + + /** + * Compares the specified object with this table for equality. Two tables are equal when their + * cell views, as returned by {@link #cellSet}, are equal. + */ + @Override + boolean equals(Object obj); + + /** + * Returns the hash code for this table. The hash code of a table is defined as the hash code of + * its cell view, as returned by {@link #cellSet}. + */ + @Override + int hashCode(); + + // Mutators + + /** Removes all mappings from the table. */ + void clear(); + + /** + * Associates the specified value with the specified keys. If the table already contained a + * mapping for those keys, the old value is replaced with the specified value. + * + * @param rowKey row key that the value should be associated with + * @param columnKey column key that the value should be associated with + * @param value value to be associated with the specified keys + * @return the value previously associated with the keys, or {@code null} if no mapping existed + * for the keys + */ + + + V put(R rowKey, C columnKey, V value); + + /** + * Copies all mappings from the specified table to this table. The effect is equivalent to calling + * {@link #put} with each row key / column key / value mapping in {@code table}. + * + * @param table the table to add to this table + */ + void putAll(Table table); + + /** + * Removes the mapping, if any, associated with the given keys. + * + * @param rowKey row key of mapping to be removed + * @param columnKey column key of mapping to be removed + * @return the value previously associated with the keys, or {@code null} if no such value existed + */ + + + V remove(Object rowKey, Object columnKey); + + // Views + + /** + * Returns a view of all mappings that have the given row key. For each row key / column key / + * value mapping in the table with that row key, the returned map associates the column key with + * the value. If no mappings in the table have the provided row key, an empty map is returned. + * + *

Changes to the returned map will update the underlying table, and vice versa. + * + * @param rowKey key of row to search for in the table + * @return the corresponding map from column keys to values + */ + Map row(R rowKey); + + /** + * Returns a view of all mappings that have the given column key. For each row key / column key / + * value mapping in the table with that column key, the returned map associates the row key with + * the value. If no mappings in the table have the provided column key, an empty map is returned. + * + *

Changes to the returned map will update the underlying table, and vice versa. + * + * @param columnKey key of column to search for in the table + * @return the corresponding map from row keys to values + */ + Map column(C columnKey); + + /** + * Returns a set of all row key / column key / value triplets. Changes to the returned set will + * update the underlying table, and vice versa. The cell set does not support the {@code add} or + * {@code addAll} methods. + * + * @return set of table cells consisting of row key / column key / value triplets + */ + Set> cellSet(); + + /** + * Returns a set of row keys that have one or more values in the table. Changes to the set will + * update the underlying table, and vice versa. + * + * @return set of row keys + */ + Set rowKeySet(); + + /** + * Returns a set of column keys that have one or more values in the table. Changes to the set will + * update the underlying table, and vice versa. + * + * @return set of column keys + */ + Set columnKeySet(); + + /** + * Returns a collection of all values, which may contain duplicates. Changes to the returned + * collection will update the underlying table, and vice versa. + * + * @return collection of values + */ + Collection values(); + + /** + * Returns a view that associates each row key with the corresponding map from column keys to + * values. Changes to the returned map will update this table. The returned map does not support + * {@code put()} or {@code putAll()}, or {@code setValue()} on its entries. + * + *

In contrast, the maps returned by {@code rowMap().get()} have the same behavior as those + * returned by {@link #row}. Those maps may support {@code setValue()}, {@code put()}, and {@code + * putAll()}. + * + * @return a map view from each row key to a secondary map from column keys to values + */ + Map> rowMap(); + + /** + * Returns a view that associates each column key with the corresponding map from row keys to + * values. Changes to the returned map will update this table. The returned map does not support + * {@code put()} or {@code putAll()}, or {@code setValue()} on its entries. + * + *

In contrast, the maps returned by {@code columnMap().get()} have the same behavior as those + * returned by {@link #column}. Those maps may support {@code setValue()}, {@code put()}, and + * {@code putAll()}. + * + * @return a map view from each column key to a secondary map from row keys to values + */ + Map> columnMap(); + + /** + * Row key / column key / value triplet corresponding to a mapping in a table. + * + * @since 7.0 + */ + interface Cell { + /** Returns the row key of this cell. */ + + R getRowKey(); + + /** Returns the column key of this cell. */ + + C getColumnKey(); + + /** Returns the value of this cell. */ + + V getValue(); + + /** + * Compares the specified object with this cell for equality. Two cells are equal when they have + * equal row keys, column keys, and values. + */ + @Override + boolean equals(Object obj); + + /** + * Returns the hash code of this cell. + * + *

The hash code of a table cell is equal to {@link Objects#hashCode}{@code (e.getRowKey(), + * e.getColumnKey(), e.getValue())}. + */ + @Override + int hashCode(); + } +} diff --git a/src/main/java/com/google/common/collect/Tables.java b/src/main/java/com/google/common/collect/Tables.java new file mode 100644 index 0000000..0cc25ee --- /dev/null +++ b/src/main/java/com/google/common/collect/Tables.java @@ -0,0 +1,736 @@ +/* + * Copyright (C) 2008 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.annotations.Beta; +import com.google.common.annotations.GwtCompatible; +import com.google.common.base.Function; +import com.google.common.base.Objects; +import com.google.common.base.Supplier; +import com.google.common.collect.Table.Cell; +import java.io.Serializable; +import java.util.Collection; +import java.util.Collections; +import java.util.Iterator; +import java.util.Map; +import java.util.Set; +import java.util.SortedMap; +import java.util.SortedSet; +import java.util.Spliterator; +import java.util.function.BinaryOperator; +import java.util.stream.Collector; + + +/** + * Provides static methods that involve a {@code Table}. + * + *

See the Guava User Guide article on {@code Tables}. + * + * @author Jared Levy + * @author Louis Wasserman + * @since 7.0 + */ +@GwtCompatible +public final class Tables { + private Tables() {} + + /** + * Returns a {@link Collector} that accumulates elements into a {@code Table} created using the + * specified supplier, whose cells are generated by applying the provided mapping functions to the + * input elements. Cells are inserted into the generated {@code Table} in encounter order. + * + *

If multiple input elements map to the same row and column, an {@code IllegalStateException} + * is thrown when the collection operation is performed. + * + * @since 21.0 + */ + @Beta + public static > Collector toTable( + java.util.function.Function rowFunction, + java.util.function.Function columnFunction, + java.util.function.Function valueFunction, + java.util.function.Supplier tableSupplier) { + return toTable( + rowFunction, + columnFunction, + valueFunction, + (v1, v2) -> { + throw new IllegalStateException("Conflicting values " + v1 + " and " + v2); + }, + tableSupplier); + } + + /** + * Returns a {@link Collector} that accumulates elements into a {@code Table} created using the + * specified supplier, whose cells are generated by applying the provided mapping functions to the + * input elements. Cells are inserted into the generated {@code Table} in encounter order. + * + *

If multiple input elements map to the same row and column, the specified merging function is + * used to combine the values. Like {@link + * java.util.stream.Collectors#toMap(java.util.function.Function, java.util.function.Function, + * BinaryOperator, java.util.function.Supplier)}, this Collector throws a {@code + * NullPointerException} on null values returned from {@code valueFunction}, and treats nulls + * returned from {@code mergeFunction} as removals of that row/column pair. + * + * @since 21.0 + */ + public static > Collector toTable( + java.util.function.Function rowFunction, + java.util.function.Function columnFunction, + java.util.function.Function valueFunction, + BinaryOperator mergeFunction, + java.util.function.Supplier tableSupplier) { + checkNotNull(rowFunction); + checkNotNull(columnFunction); + checkNotNull(valueFunction); + checkNotNull(mergeFunction); + checkNotNull(tableSupplier); + return Collector.of( + tableSupplier, + (table, input) -> + merge( + table, + rowFunction.apply(input), + columnFunction.apply(input), + valueFunction.apply(input), + mergeFunction), + (table1, table2) -> { + for (Table.Cell cell2 : table2.cellSet()) { + merge(table1, cell2.getRowKey(), cell2.getColumnKey(), cell2.getValue(), mergeFunction); + } + return table1; + }); + } + + private static void merge( + Table table, R row, C column, V value, BinaryOperator mergeFunction) { + checkNotNull(value); + V oldValue = table.get(row, column); + if (oldValue == null) { + table.put(row, column, value); + } else { + V newValue = mergeFunction.apply(oldValue, value); + if (newValue == null) { + table.remove(row, column); + } else { + table.put(row, column, newValue); + } + } + } + + /** + * Returns an immutable cell with the specified row key, column key, and value. + * + *

The returned cell is serializable. + * + * @param rowKey the row key to be associated with the returned cell + * @param columnKey the column key to be associated with the returned cell + * @param value the value to be associated with the returned cell + */ + public static Cell immutableCell( + R rowKey, C columnKey, V value) { + return new ImmutableCell<>(rowKey, columnKey, value); + } + + static final class ImmutableCell extends AbstractCell implements Serializable { + private final R rowKey; + private final C columnKey; + private final V value; + + ImmutableCell(R rowKey, C columnKey, V value) { + this.rowKey = rowKey; + this.columnKey = columnKey; + this.value = value; + } + + @Override + public R getRowKey() { + return rowKey; + } + + @Override + public C getColumnKey() { + return columnKey; + } + + @Override + public V getValue() { + return value; + } + + private static final long serialVersionUID = 0; + } + + abstract static class AbstractCell implements Cell { + // needed for serialization + AbstractCell() {} + + @Override + public boolean equals(Object obj) { + if (obj == this) { + return true; + } + if (obj instanceof Cell) { + Cell other = (Cell) obj; + return Objects.equal(getRowKey(), other.getRowKey()) + && Objects.equal(getColumnKey(), other.getColumnKey()) + && Objects.equal(getValue(), other.getValue()); + } + return false; + } + + @Override + public int hashCode() { + return Objects.hashCode(getRowKey(), getColumnKey(), getValue()); + } + + @Override + public String toString() { + return "(" + getRowKey() + "," + getColumnKey() + ")=" + getValue(); + } + } + + /** + * Creates a transposed view of a given table that flips its row and column keys. In other words, + * calling {@code get(columnKey, rowKey)} on the generated table always returns the same value as + * calling {@code get(rowKey, columnKey)} on the original table. Updating the original table + * changes the contents of the transposed table and vice versa. + * + *

The returned table supports update operations as long as the input table supports the + * analogous operation with swapped rows and columns. For example, in a {@link HashBasedTable} + * instance, {@code rowKeySet().iterator()} supports {@code remove()} but {@code + * columnKeySet().iterator()} doesn't. With a transposed {@link HashBasedTable}, it's the other + * way around. + */ + public static Table transpose(Table table) { + return (table instanceof TransposeTable) + ? ((TransposeTable) table).original + : new TransposeTable(table); + } + + private static class TransposeTable extends AbstractTable { + final Table original; + + TransposeTable(Table original) { + this.original = checkNotNull(original); + } + + @Override + public void clear() { + original.clear(); + } + + @Override + public Map column(R columnKey) { + return original.row(columnKey); + } + + @Override + public Set columnKeySet() { + return original.rowKeySet(); + } + + @Override + public Map> columnMap() { + return original.rowMap(); + } + + @Override + public boolean contains(Object rowKey, Object columnKey) { + return original.contains(columnKey, rowKey); + } + + @Override + public boolean containsColumn(Object columnKey) { + return original.containsRow(columnKey); + } + + @Override + public boolean containsRow(Object rowKey) { + return original.containsColumn(rowKey); + } + + @Override + public boolean containsValue(Object value) { + return original.containsValue(value); + } + + @Override + public V get(Object rowKey, Object columnKey) { + return original.get(columnKey, rowKey); + } + + @Override + public V put(C rowKey, R columnKey, V value) { + return original.put(columnKey, rowKey, value); + } + + @Override + public void putAll(Table table) { + original.putAll(transpose(table)); + } + + @Override + public V remove(Object rowKey, Object columnKey) { + return original.remove(columnKey, rowKey); + } + + @Override + public Map row(C rowKey) { + return original.column(rowKey); + } + + @Override + public Set rowKeySet() { + return original.columnKeySet(); + } + + @Override + public Map> rowMap() { + return original.columnMap(); + } + + @Override + public int size() { + return original.size(); + } + + @Override + public Collection values() { + return original.values(); + } + + // Will cast TRANSPOSE_CELL to a type that always succeeds + private static final Function, Cell> TRANSPOSE_CELL = + new Function, Cell>() { + @Override + public Cell apply(Cell cell) { + return immutableCell(cell.getColumnKey(), cell.getRowKey(), cell.getValue()); + } + }; + + @SuppressWarnings("unchecked") + @Override + Iterator> cellIterator() { + return Iterators.transform(original.cellSet().iterator(), (Function) TRANSPOSE_CELL); + } + + @SuppressWarnings("unchecked") + @Override + Spliterator> cellSpliterator() { + return CollectSpliterators.map(original.cellSet().spliterator(), (Function) TRANSPOSE_CELL); + } + } + + /** + * Creates a table that uses the specified backing map and factory. It can generate a table based + * on arbitrary {@link Map} classes. + * + *

The {@code factory}-generated and {@code backingMap} classes determine the table iteration + * order. However, the table's {@code row()} method returns instances of a different class than + * {@code factory.get()} does. + * + *

Call this method only when the simpler factory methods in classes like {@link + * HashBasedTable} and {@link TreeBasedTable} won't suffice. + * + *

The views returned by the {@code Table} methods {@link Table#column}, {@link + * Table#columnKeySet}, and {@link Table#columnMap} have iterators that don't support {@code + * remove()}. Otherwise, all optional operations are supported. Null row keys, columns keys, and + * values are not supported. + * + *

Lookups by row key are often faster than lookups by column key, because the data is stored + * in a {@code Map>}. A method call like {@code column(columnKey).get(rowKey)} still + * runs quickly, since the row key is provided. However, {@code column(columnKey).size()} takes + * longer, since an iteration across all row keys occurs. + * + *

Note that this implementation is not synchronized. If multiple threads access this table + * concurrently and one of the threads modifies the table, it must be synchronized externally. + * + *

The table is serializable if {@code backingMap}, {@code factory}, the maps generated by + * {@code factory}, and the table contents are all serializable. + * + *

Note: the table assumes complete ownership over of {@code backingMap} and the maps returned + * by {@code factory}. Those objects should not be manually updated and they should not use soft, + * weak, or phantom references. + * + * @param backingMap place to store the mapping from each row key to its corresponding column key + * / value map + * @param factory supplier of new, empty maps that will each hold all column key / value mappings + * for a given row key + * @throws IllegalArgumentException if {@code backingMap} is not empty + * @since 10.0 + */ + @Beta + public static Table newCustomTable( + Map> backingMap, Supplier> factory) { + checkArgument(backingMap.isEmpty()); + checkNotNull(factory); + // TODO(jlevy): Wrap factory to validate that the supplied maps are empty? + return new StandardTable<>(backingMap, factory); + } + + /** + * Returns a view of a table where each value is transformed by a function. All other properties + * of the table, such as iteration order, are left intact. + * + *

Changes in the underlying table are reflected in this view. Conversely, this view supports + * removal operations, and these are reflected in the underlying table. + * + *

It's acceptable for the underlying table to contain null keys, and even null values provided + * that the function is capable of accepting null input. The transformed table might contain null + * values, if the function sometimes gives a null result. + * + *

The returned table is not thread-safe or serializable, even if the underlying table is. + * + *

The function is applied lazily, invoked when needed. This is necessary for the returned + * table to be a view, but it means that the function will be applied many times for bulk + * operations like {@link Table#containsValue} and {@code Table.toString()}. For this to perform + * well, {@code function} should be fast. To avoid lazy evaluation when the returned table doesn't + * need to be a view, copy the returned table into a new table of your choosing. + * + * @since 10.0 + */ + @Beta + public static Table transformValues( + Table fromTable, Function function) { + return new TransformedTable<>(fromTable, function); + } + + private static class TransformedTable extends AbstractTable { + final Table fromTable; + final Function function; + + TransformedTable(Table fromTable, Function function) { + this.fromTable = checkNotNull(fromTable); + this.function = checkNotNull(function); + } + + @Override + public boolean contains(Object rowKey, Object columnKey) { + return fromTable.contains(rowKey, columnKey); + } + + @Override + public V2 get(Object rowKey, Object columnKey) { + // The function is passed a null input only when the table contains a null + // value. + return contains(rowKey, columnKey) ? function.apply(fromTable.get(rowKey, columnKey)) : null; + } + + @Override + public int size() { + return fromTable.size(); + } + + @Override + public void clear() { + fromTable.clear(); + } + + @Override + public V2 put(R rowKey, C columnKey, V2 value) { + throw new UnsupportedOperationException(); + } + + @Override + public void putAll(Table table) { + throw new UnsupportedOperationException(); + } + + @Override + public V2 remove(Object rowKey, Object columnKey) { + return contains(rowKey, columnKey) + ? function.apply(fromTable.remove(rowKey, columnKey)) + : null; + } + + @Override + public Map row(R rowKey) { + return Maps.transformValues(fromTable.row(rowKey), function); + } + + @Override + public Map column(C columnKey) { + return Maps.transformValues(fromTable.column(columnKey), function); + } + + Function, Cell> cellFunction() { + return new Function, Cell>() { + @Override + public Cell apply(Cell cell) { + return immutableCell( + cell.getRowKey(), cell.getColumnKey(), function.apply(cell.getValue())); + } + }; + } + + @Override + Iterator> cellIterator() { + return Iterators.transform(fromTable.cellSet().iterator(), cellFunction()); + } + + @Override + Spliterator> cellSpliterator() { + return CollectSpliterators.map(fromTable.cellSet().spliterator(), cellFunction()); + } + + @Override + public Set rowKeySet() { + return fromTable.rowKeySet(); + } + + @Override + public Set columnKeySet() { + return fromTable.columnKeySet(); + } + + @Override + Collection createValues() { + return Collections2.transform(fromTable.values(), function); + } + + @Override + public Map> rowMap() { + Function, Map> rowFunction = + new Function, Map>() { + @Override + public Map apply(Map row) { + return Maps.transformValues(row, function); + } + }; + return Maps.transformValues(fromTable.rowMap(), rowFunction); + } + + @Override + public Map> columnMap() { + Function, Map> columnFunction = + new Function, Map>() { + @Override + public Map apply(Map column) { + return Maps.transformValues(column, function); + } + }; + return Maps.transformValues(fromTable.columnMap(), columnFunction); + } + } + + /** + * Returns an unmodifiable view of the specified table. This method allows modules to provide + * users with "read-only" access to internal tables. Query operations on the returned table "read + * through" to the specified table, and attempts to modify the returned table, whether direct or + * via its collection views, result in an {@code UnsupportedOperationException}. + * + *

The returned table will be serializable if the specified table is serializable. + * + *

Consider using an {@link ImmutableTable}, which is guaranteed never to change. + * + * @since 11.0 + */ + public static Table unmodifiableTable( + Table table) { + return new UnmodifiableTable<>(table); + } + + private static class UnmodifiableTable extends ForwardingTable + implements Serializable { + final Table delegate; + + UnmodifiableTable(Table delegate) { + this.delegate = checkNotNull(delegate); + } + + @SuppressWarnings("unchecked") // safe, covariant cast + @Override + protected Table delegate() { + return (Table) delegate; + } + + @Override + public Set> cellSet() { + return Collections.unmodifiableSet(super.cellSet()); + } + + @Override + public void clear() { + throw new UnsupportedOperationException(); + } + + @Override + public Map column(C columnKey) { + return Collections.unmodifiableMap(super.column(columnKey)); + } + + @Override + public Set columnKeySet() { + return Collections.unmodifiableSet(super.columnKeySet()); + } + + @Override + public Map> columnMap() { + Function, Map> wrapper = unmodifiableWrapper(); + return Collections.unmodifiableMap(Maps.transformValues(super.columnMap(), wrapper)); + } + + @Override + public V put(R rowKey, C columnKey, V value) { + throw new UnsupportedOperationException(); + } + + @Override + public void putAll(Table table) { + throw new UnsupportedOperationException(); + } + + @Override + public V remove(Object rowKey, Object columnKey) { + throw new UnsupportedOperationException(); + } + + @Override + public Map row(R rowKey) { + return Collections.unmodifiableMap(super.row(rowKey)); + } + + @Override + public Set rowKeySet() { + return Collections.unmodifiableSet(super.rowKeySet()); + } + + @Override + public Map> rowMap() { + Function, Map> wrapper = unmodifiableWrapper(); + return Collections.unmodifiableMap(Maps.transformValues(super.rowMap(), wrapper)); + } + + @Override + public Collection values() { + return Collections.unmodifiableCollection(super.values()); + } + + private static final long serialVersionUID = 0; + } + + /** + * Returns an unmodifiable view of the specified row-sorted table. This method allows modules to + * provide users with "read-only" access to internal tables. Query operations on the returned + * table "read through" to the specified table, and attempts to modify the returned table, whether + * direct or via its collection views, result in an {@code UnsupportedOperationException}. + * + *

The returned table will be serializable if the specified table is serializable. + * + * @param table the row-sorted table for which an unmodifiable view is to be returned + * @return an unmodifiable view of the specified table + * @since 11.0 + */ + @Beta + public static RowSortedTable unmodifiableRowSortedTable( + RowSortedTable table) { + /* + * It's not ? extends R, because it's technically not covariant in R. Specifically, + * table.rowMap().comparator() could return a comparator that only works for the ? extends R. + * Collections.unmodifiableSortedMap makes the same distinction. + */ + return new UnmodifiableRowSortedMap<>(table); + } + + static final class UnmodifiableRowSortedMap extends UnmodifiableTable + implements RowSortedTable { + + public UnmodifiableRowSortedMap(RowSortedTable delegate) { + super(delegate); + } + + @Override + protected RowSortedTable delegate() { + return (RowSortedTable) super.delegate(); + } + + @Override + public SortedMap> rowMap() { + Function, Map> wrapper = unmodifiableWrapper(); + return Collections.unmodifiableSortedMap(Maps.transformValues(delegate().rowMap(), wrapper)); + } + + @Override + public SortedSet rowKeySet() { + return Collections.unmodifiableSortedSet(delegate().rowKeySet()); + } + + private static final long serialVersionUID = 0; + } + + @SuppressWarnings("unchecked") + private static Function, Map> unmodifiableWrapper() { + return (Function) UNMODIFIABLE_WRAPPER; + } + + private static final Function, ? extends Map> UNMODIFIABLE_WRAPPER = + new Function, Map>() { + @Override + public Map apply(Map input) { + return Collections.unmodifiableMap(input); + } + }; + + /** + * Returns a synchronized (thread-safe) table backed by the specified table. In order to guarantee + * serial access, it is critical that all access to the backing table is accomplished + * through the returned table. + * + *

It is imperative that the user manually synchronize on the returned table when accessing any + * of its collection views: + * + *

{@code
+   * Table table = Tables.synchronizedTable(HashBasedTable.create());
+   * ...
+   * Map row = table.row(rowKey);  // Needn't be in synchronized block
+   * ...
+   * synchronized (table) {  // Synchronizing on table, not row!
+   *   Iterator> i = row.entrySet().iterator(); // Must be in synchronized block
+   *   while (i.hasNext()) {
+   *     foo(i.next());
+   *   }
+   * }
+   * }
+ * + *

Failure to follow this advice may result in non-deterministic behavior. + * + *

The returned table will be serializable if the specified table is serializable. + * + * @param table the table to be wrapped in a synchronized view + * @return a synchronized view of the specified table + * @since 22.0 + */ + public static Table synchronizedTable(Table table) { + return Synchronized.table(table, null); + } + + static boolean equalsImpl(Table table, Object obj) { + if (obj == table) { + return true; + } else if (obj instanceof Table) { + Table that = (Table) obj; + return table.cellSet().equals(that.cellSet()); + } else { + return false; + } + } +} diff --git a/src/main/java/com/google/common/collect/TopKSelector.java b/src/main/java/com/google/common/collect/TopKSelector.java new file mode 100644 index 0000000..ebdd174 --- /dev/null +++ b/src/main/java/com/google/common/collect/TopKSelector.java @@ -0,0 +1,271 @@ +/* + * Copyright (C) 2014 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.annotations.GwtCompatible; +import com.google.common.math.IntMath; +import java.math.RoundingMode; +import java.util.Arrays; +import java.util.Collections; +import java.util.Comparator; +import java.util.Iterator; +import java.util.List; +import java.util.stream.Stream; + + +/** + * An accumulator that selects the "top" {@code k} elements added to it, relative to a provided + * comparator. "Top" can mean the greatest or the lowest elements, specified in the factory used to + * create the {@code TopKSelector} instance. + * + *

If your input data is available as a {@link Stream}, prefer passing {@link + * Comparators#least(int)} to {@link Stream#collect(java.util.stream.Collector)}. If it is available + * as an {@link Iterable} or {@link Iterator}, prefer {@link Ordering#leastOf(Iterable, int)}. + * + *

This uses the same efficient implementation as {@link Ordering#leastOf(Iterable, int)}, + * offering expected O(n + k log k) performance (worst case O(n log k)) for n calls to {@link + * #offer} and a call to {@link #topK}, with O(k) memory. In comparison, quickselect has the same + * asymptotics but requires O(n) memory, and a {@code PriorityQueue} implementation takes O(n log + * k). In benchmarks, this implementation performs at least as well as either implementation, and + * degrades more gracefully for worst-case input. + * + *

The implementation does not necessarily use a stable sorting algorithm; when multiple + * equivalent elements are added to it, it is undefined which will come first in the output. + * + * @author Louis Wasserman + */ +@GwtCompatible final class TopKSelector { + + /** + * Returns a {@code TopKSelector} that collects the lowest {@code k} elements added to it, + * relative to the natural ordering of the elements, and returns them via {@link #topK} in + * ascending order. + * + * @throws IllegalArgumentException if {@code k < 0} + */ + public static > TopKSelector least(int k) { + return least(k, Ordering.natural()); + } + + /** + * Returns a {@code TopKSelector} that collects the lowest {@code k} elements added to it, + * relative to the specified comparator, and returns them via {@link #topK} in ascending order. + * + * @throws IllegalArgumentException if {@code k < 0} + */ + public static TopKSelector least(int k, Comparator comparator) { + return new TopKSelector(comparator, k); + } + + /** + * Returns a {@code TopKSelector} that collects the greatest {@code k} elements added to it, + * relative to the natural ordering of the elements, and returns them via {@link #topK} in + * descending order. + * + * @throws IllegalArgumentException if {@code k < 0} + */ + public static > TopKSelector greatest(int k) { + return greatest(k, Ordering.natural()); + } + + /** + * Returns a {@code TopKSelector} that collects the greatest {@code k} elements added to it, + * relative to the specified comparator, and returns them via {@link #topK} in descending order. + * + * @throws IllegalArgumentException if {@code k < 0} + */ + public static TopKSelector greatest(int k, Comparator comparator) { + return new TopKSelector(Ordering.from(comparator).reverse(), k); + } + + private final int k; + private final Comparator comparator; + + /* + * We are currently considering the elements in buffer in the range [0, bufferSize) as candidates + * for the top k elements. Whenever the buffer is filled, we quickselect the top k elements to the + * range [0, k) and ignore the remaining elements. + */ + private final T[] buffer; + private int bufferSize; + + /** + * The largest of the lowest k elements we've seen so far relative to this comparator. If + * bufferSize ≥ k, then we can ignore any elements greater than this value. + */ + private T threshold; + + private TopKSelector(Comparator comparator, int k) { + this.comparator = checkNotNull(comparator, "comparator"); + this.k = k; + checkArgument(k >= 0, "k must be nonnegative, was %s", k); + this.buffer = (T[]) new Object[k * 2]; + this.bufferSize = 0; + this.threshold = null; + } + + /** + * Adds {@code elem} as a candidate for the top {@code k} elements. This operation takes amortized + * O(1) time. + */ + public void offer(T elem) { + if (k == 0) { + return; + } else if (bufferSize == 0) { + buffer[0] = elem; + threshold = elem; + bufferSize = 1; + } else if (bufferSize < k) { + buffer[bufferSize++] = elem; + if (comparator.compare(elem, threshold) > 0) { + threshold = elem; + } + } else if (comparator.compare(elem, threshold) < 0) { + // Otherwise, we can ignore elem; we've seen k better elements. + buffer[bufferSize++] = elem; + if (bufferSize == 2 * k) { + trim(); + } + } + } + + /** + * Quickselects the top k elements from the 2k elements in the buffer. O(k) expected time, O(k log + * k) worst case. + */ + private void trim() { + int left = 0; + int right = 2 * k - 1; + + int minThresholdPosition = 0; + // The leftmost position at which the greatest of the k lower elements + // -- the new value of threshold -- might be found. + + int iterations = 0; + int maxIterations = IntMath.log2(right - left, RoundingMode.CEILING) * 3; + while (left < right) { + int pivotIndex = (left + right + 1) >>> 1; + + int pivotNewIndex = partition(left, right, pivotIndex); + + if (pivotNewIndex > k) { + right = pivotNewIndex - 1; + } else if (pivotNewIndex < k) { + left = Math.max(pivotNewIndex, left + 1); + minThresholdPosition = pivotNewIndex; + } else { + break; + } + iterations++; + if (iterations >= maxIterations) { + // We've already taken O(k log k), let's make sure we don't take longer than O(k log k). + Arrays.sort(buffer, left, right, comparator); + break; + } + } + bufferSize = k; + + threshold = buffer[minThresholdPosition]; + for (int i = minThresholdPosition + 1; i < k; i++) { + if (comparator.compare(buffer[i], threshold) > 0) { + threshold = buffer[i]; + } + } + } + + /** + * Partitions the contents of buffer in the range [left, right] around the pivot element + * previously stored in buffer[pivotValue]. Returns the new index of the pivot element, + * pivotNewIndex, so that everything in [left, pivotNewIndex] is ≤ pivotValue and everything in + * (pivotNewIndex, right] is greater than pivotValue. + */ + private int partition(int left, int right, int pivotIndex) { + T pivotValue = buffer[pivotIndex]; + buffer[pivotIndex] = buffer[right]; + + int pivotNewIndex = left; + for (int i = left; i < right; i++) { + if (comparator.compare(buffer[i], pivotValue) < 0) { + swap(pivotNewIndex, i); + pivotNewIndex++; + } + } + buffer[right] = buffer[pivotNewIndex]; + buffer[pivotNewIndex] = pivotValue; + return pivotNewIndex; + } + + private void swap(int i, int j) { + T tmp = buffer[i]; + buffer[i] = buffer[j]; + buffer[j] = tmp; + } + + TopKSelector combine(TopKSelector other) { + for (int i = 0; i < other.bufferSize; i++) { + this.offer(other.buffer[i]); + } + return this; + } + + /** + * Adds each member of {@code elements} as a candidate for the top {@code k} elements. This + * operation takes amortized linear time in the length of {@code elements}. + * + *

If all input data to this {@code TopKSelector} is in a single {@code Iterable}, prefer + * {@link Ordering#leastOf(Iterable, int)}, which provides a simpler API for that use case. + */ + public void offerAll(Iterable elements) { + offerAll(elements.iterator()); + } + + /** + * Adds each member of {@code elements} as a candidate for the top {@code k} elements. This + * operation takes amortized linear time in the length of {@code elements}. The iterator is + * consumed after this operation completes. + * + *

If all input data to this {@code TopKSelector} is in a single {@code Iterator}, prefer + * {@link Ordering#leastOf(Iterator, int)}, which provides a simpler API for that use case. + */ + public void offerAll(Iterator elements) { + while (elements.hasNext()) { + offer(elements.next()); + } + } + + /** + * Returns the top {@code k} elements offered to this {@code TopKSelector}, or all elements if + * fewer than {@code k} have been offered, in the order specified by the factory used to create + * this {@code TopKSelector}. + * + *

The returned list is an unmodifiable copy and will not be affected by further changes to + * this {@code TopKSelector}. This method returns in O(k log k) time. + */ + public List topK() { + Arrays.sort(buffer, 0, bufferSize, comparator); + if (bufferSize > k) { + Arrays.fill(buffer, k, buffer.length, null); + bufferSize = k; + threshold = buffer[k - 1]; + } + // we have to support null elements, so no ImmutableList for us + return Collections.unmodifiableList(Arrays.asList(Arrays.copyOf(buffer, bufferSize))); + } +} diff --git a/src/main/java/com/google/common/collect/TransformedIterator.java b/src/main/java/com/google/common/collect/TransformedIterator.java new file mode 100644 index 0000000..b7214b8 --- /dev/null +++ b/src/main/java/com/google/common/collect/TransformedIterator.java @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2012 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.annotations.GwtCompatible; +import java.util.Iterator; + +/** + * An iterator that transforms a backing iterator; for internal use. This avoids the object overhead + * of constructing a {@link com.google.common.base.Function Function} for internal methods. + * + * @author Louis Wasserman + */ +@GwtCompatible +abstract class TransformedIterator implements Iterator { + final Iterator backingIterator; + + TransformedIterator(Iterator backingIterator) { + this.backingIterator = checkNotNull(backingIterator); + } + + abstract T transform(F from); + + @Override + public final boolean hasNext() { + return backingIterator.hasNext(); + } + + @Override + public final T next() { + return transform(backingIterator.next()); + } + + @Override + public final void remove() { + backingIterator.remove(); + } +} diff --git a/src/main/java/com/google/common/collect/TransformedListIterator.java b/src/main/java/com/google/common/collect/TransformedListIterator.java new file mode 100644 index 0000000..ac2eea1 --- /dev/null +++ b/src/main/java/com/google/common/collect/TransformedListIterator.java @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2012 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import com.google.common.annotations.GwtCompatible; +import com.google.common.base.Function; +import java.util.ListIterator; + +/** + * An iterator that transforms a backing list iterator; for internal use. This avoids the object + * overhead of constructing a {@link Function} for internal methods. + * + * @author Louis Wasserman + */ +@GwtCompatible +abstract class TransformedListIterator extends TransformedIterator + implements ListIterator { + TransformedListIterator(ListIterator backingIterator) { + super(backingIterator); + } + + private ListIterator backingIterator() { + return Iterators.cast(backingIterator); + } + + @Override + public final boolean hasPrevious() { + return backingIterator().hasPrevious(); + } + + @Override + public final T previous() { + return transform(backingIterator().previous()); + } + + @Override + public final int nextIndex() { + return backingIterator().nextIndex(); + } + + @Override + public final int previousIndex() { + return backingIterator().previousIndex(); + } + + @Override + public void set(T element) { + throw new UnsupportedOperationException(); + } + + @Override + public void add(T element) { + throw new UnsupportedOperationException(); + } +} diff --git a/src/main/java/com/google/common/collect/TreeBasedTable.java b/src/main/java/com/google/common/collect/TreeBasedTable.java new file mode 100644 index 0000000..96df2a2 --- /dev/null +++ b/src/main/java/com/google/common/collect/TreeBasedTable.java @@ -0,0 +1,352 @@ +/* + * Copyright (C) 2008 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.annotations.GwtCompatible; +import com.google.common.base.Function; +import com.google.common.base.Supplier; +import java.io.Serializable; +import java.util.Comparator; +import java.util.Iterator; +import java.util.Map; +import java.util.NoSuchElementException; +import java.util.Set; +import java.util.SortedMap; +import java.util.SortedSet; +import java.util.TreeMap; + + +/** + * Implementation of {@code Table} whose row keys and column keys are ordered by their natural + * ordering or by supplied comparators. When constructing a {@code TreeBasedTable}, you may provide + * comparators for the row keys and the column keys, or you may use natural ordering for both. + * + *

The {@link #rowKeySet} method returns a {@link SortedSet} and the {@link #rowMap} method + * returns a {@link SortedMap}, instead of the {@link Set} and {@link Map} specified by the {@link + * Table} interface. + * + *

The views returned by {@link #column}, {@link #columnKeySet()}, and {@link #columnMap()} have + * iterators that don't support {@code remove()}. Otherwise, all optional operations are supported. + * Null row keys, columns keys, and values are not supported. + * + *

Lookups by row key are often faster than lookups by column key, because the data is stored in + * a {@code Map>}. A method call like {@code column(columnKey).get(rowKey)} still runs + * quickly, since the row key is provided. However, {@code column(columnKey).size()} takes longer, + * since an iteration across all row keys occurs. + * + *

Because a {@code TreeBasedTable} has unique sorted values for a given row, both {@code + * row(rowKey)} and {@code rowMap().get(rowKey)} are {@link SortedMap} instances, instead of the + * {@link Map} specified in the {@link Table} interface. + * + *

Note that this implementation is not synchronized. If multiple threads access this table + * concurrently and one of the threads modifies the table, it must be synchronized externally. + * + *

See the Guava User Guide article on {@code Table}. + * + * @author Jared Levy + * @author Louis Wasserman + * @since 7.0 + */ +@GwtCompatible(serializable = true) +public class TreeBasedTable extends StandardRowSortedTable { + private final Comparator columnComparator; + + private static class Factory implements Supplier>, Serializable { + final Comparator comparator; + + Factory(Comparator comparator) { + this.comparator = comparator; + } + + @Override + public TreeMap get() { + return new TreeMap<>(comparator); + } + + private static final long serialVersionUID = 0; + } + + /** + * Creates an empty {@code TreeBasedTable} that uses the natural orderings of both row and column + * keys. + * + *

The method signature specifies {@code R extends Comparable} with a raw {@link Comparable}, + * instead of {@code R extends Comparable}, and the same for {@code C}. That's + * necessary to support classes defined without generics. + */ + public static TreeBasedTable create() { + return new TreeBasedTable<>(Ordering.natural(), Ordering.natural()); + } + + /** + * Creates an empty {@code TreeBasedTable} that is ordered by the specified comparators. + * + * @param rowComparator the comparator that orders the row keys + * @param columnComparator the comparator that orders the column keys + */ + public static TreeBasedTable create( + Comparator rowComparator, Comparator columnComparator) { + checkNotNull(rowComparator); + checkNotNull(columnComparator); + return new TreeBasedTable<>(rowComparator, columnComparator); + } + + /** + * Creates a {@code TreeBasedTable} with the same mappings and sort order as the specified {@code + * TreeBasedTable}. + */ + public static TreeBasedTable create(TreeBasedTable table) { + TreeBasedTable result = + new TreeBasedTable<>(table.rowComparator(), table.columnComparator()); + result.putAll(table); + return result; + } + + TreeBasedTable(Comparator rowComparator, Comparator columnComparator) { + super(new TreeMap>(rowComparator), new Factory(columnComparator)); + this.columnComparator = columnComparator; + } + + // TODO(jlevy): Move to StandardRowSortedTable? + + /** + * Returns the comparator that orders the rows. With natural ordering, {@link Ordering#natural()} + * is returned. + * + * @deprecated Use {@code table.rowKeySet().comparator()} instead. + */ + @Deprecated + public Comparator rowComparator() { + return rowKeySet().comparator(); + } + + /** + * Returns the comparator that orders the columns. With natural ordering, {@link + * Ordering#natural()} is returned. + * + * @deprecated Store the {@link Comparator} alongside the {@link Table}. Or, if you know that the + * {@link Table} contains at least one value, you can retrieve the {@link Comparator} with: + * {@code ((SortedMap) table.rowMap().values().iterator().next()).comparator();}. + */ + @Deprecated + public Comparator columnComparator() { + return columnComparator; + } + + // TODO(lowasser): make column return a SortedMap + + /** + * {@inheritDoc} + * + *

Because a {@code TreeBasedTable} has unique sorted values for a given row, this method + * returns a {@link SortedMap}, instead of the {@link Map} specified in the {@link Table} + * interface. + * + * @since 10.0 (mostly + * source-compatible since 7.0) + */ + @Override + public SortedMap row(R rowKey) { + return new TreeRow(rowKey); + } + + private class TreeRow extends Row implements SortedMap { + final C lowerBound; + final C upperBound; + + TreeRow(R rowKey) { + this(rowKey, null, null); + } + + TreeRow(R rowKey, C lowerBound, C upperBound) { + super(rowKey); + this.lowerBound = lowerBound; + this.upperBound = upperBound; + checkArgument( + lowerBound == null || upperBound == null || compare(lowerBound, upperBound) <= 0); + } + + @Override + public SortedSet keySet() { + return new Maps.SortedKeySet<>(this); + } + + @Override + public Comparator comparator() { + return columnComparator(); + } + + int compare(Object a, Object b) { + // pretend we can compare anything + @SuppressWarnings("unchecked") + Comparator cmp = (Comparator) comparator(); + return cmp.compare(a, b); + } + + boolean rangeContains(Object o) { + return o != null + && (lowerBound == null || compare(lowerBound, o) <= 0) + && (upperBound == null || compare(upperBound, o) > 0); + } + + @Override + public SortedMap subMap(C fromKey, C toKey) { + checkArgument(rangeContains(checkNotNull(fromKey)) && rangeContains(checkNotNull(toKey))); + return new TreeRow(rowKey, fromKey, toKey); + } + + @Override + public SortedMap headMap(C toKey) { + checkArgument(rangeContains(checkNotNull(toKey))); + return new TreeRow(rowKey, lowerBound, toKey); + } + + @Override + public SortedMap tailMap(C fromKey) { + checkArgument(rangeContains(checkNotNull(fromKey))); + return new TreeRow(rowKey, fromKey, upperBound); + } + + @Override + public C firstKey() { + SortedMap backing = backingRowMap(); + if (backing == null) { + throw new NoSuchElementException(); + } + return backingRowMap().firstKey(); + } + + @Override + public C lastKey() { + SortedMap backing = backingRowMap(); + if (backing == null) { + throw new NoSuchElementException(); + } + return backingRowMap().lastKey(); + } + + transient SortedMap wholeRow; + + /* + * If the row was previously empty, we check if there's a new row here every + * time we're queried. + */ + SortedMap wholeRow() { + if (wholeRow == null || (wholeRow.isEmpty() && backingMap.containsKey(rowKey))) { + wholeRow = (SortedMap) backingMap.get(rowKey); + } + return wholeRow; + } + + @Override + SortedMap backingRowMap() { + return (SortedMap) super.backingRowMap(); + } + + @Override + SortedMap computeBackingRowMap() { + SortedMap map = wholeRow(); + if (map != null) { + if (lowerBound != null) { + map = map.tailMap(lowerBound); + } + if (upperBound != null) { + map = map.headMap(upperBound); + } + return map; + } + return null; + } + + @Override + void maintainEmptyInvariant() { + if (wholeRow() != null && wholeRow.isEmpty()) { + backingMap.remove(rowKey); + wholeRow = null; + backingRowMap = null; + } + } + + @Override + public boolean containsKey(Object key) { + return rangeContains(key) && super.containsKey(key); + } + + @Override + public V put(C key, V value) { + checkArgument(rangeContains(checkNotNull(key))); + return super.put(key, value); + } + } + + // rowKeySet() and rowMap() are defined here so they appear in the Javadoc. + + @Override + public SortedSet rowKeySet() { + return super.rowKeySet(); + } + + @Override + public SortedMap> rowMap() { + return super.rowMap(); + } + + /** Overridden column iterator to return columns values in globally sorted order. */ + @Override + Iterator createColumnKeyIterator() { + final Comparator comparator = columnComparator(); + + final Iterator merged = + Iterators.mergeSorted( + Iterables.transform( + backingMap.values(), + new Function, Iterator>() { + @Override + public Iterator apply(Map input) { + return input.keySet().iterator(); + } + }), + comparator); + + return new AbstractIterator() { + C lastValue; + + @Override + protected C computeNext() { + while (merged.hasNext()) { + C next = merged.next(); + boolean duplicate = lastValue != null && comparator.compare(next, lastValue) == 0; + + // Keep looping till we find a non-duplicate value. + if (!duplicate) { + lastValue = next; + return lastValue; + } + } + + lastValue = null; // clear reference to unused data + return endOfData(); + } + }; + } + + private static final long serialVersionUID = 0; +} diff --git a/src/main/java/com/google/common/collect/TreeMultimap.java b/src/main/java/com/google/common/collect/TreeMultimap.java new file mode 100644 index 0000000..a35a176 --- /dev/null +++ b/src/main/java/com/google/common/collect/TreeMultimap.java @@ -0,0 +1,222 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.annotations.GwtCompatible; +import com.google.common.annotations.GwtIncompatible; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.util.Collection; +import java.util.Comparator; +import java.util.Map; +import java.util.NavigableMap; +import java.util.NavigableSet; +import java.util.SortedSet; +import java.util.TreeMap; +import java.util.TreeSet; + + +/** + * Implementation of {@code Multimap} whose keys and values are ordered by their natural ordering or + * by supplied comparators. In all cases, this implementation uses {@link Comparable#compareTo} or + * {@link Comparator#compare} instead of {@link Object#equals} to determine equivalence of + * instances. + * + *

Warning: The comparators or comparables used must be consistent with equals as + * explained by the {@link Comparable} class specification. Otherwise, the resulting multiset will + * violate the general contract of {@link SetMultimap}, which it is specified in terms of {@link + * Object#equals}. + * + *

The collections returned by {@code keySet} and {@code asMap} iterate through the keys + * according to the key comparator ordering or the natural ordering of the keys. Similarly, {@code + * get}, {@code removeAll}, and {@code replaceValues} return collections that iterate through the + * values according to the value comparator ordering or the natural ordering of the values. The + * collections generated by {@code entries}, {@code keys}, and {@code values} iterate across the + * keys according to the above key ordering, and for each key they iterate across the values + * according to the value ordering. + * + *

The multimap does not store duplicate key-value pairs. Adding a new key-value pair equal to an + * existing key-value pair has no effect. + * + *

Null keys and values are permitted (provided, of course, that the respective comparators + * support them). All optional multimap methods are supported, and all returned views are + * modifiable. + * + *

This class is not threadsafe when any concurrent operations update the multimap. Concurrent + * read operations will work correctly. To allow concurrent update operations, wrap your multimap + * with a call to {@link Multimaps#synchronizedSortedSetMultimap}. + * + *

See the Guava User Guide article on {@code + * Multimap}. + * + * @author Jared Levy + * @author Louis Wasserman + * @since 2.0 + */ +@GwtCompatible(serializable = true, emulated = true) +public class TreeMultimap extends AbstractSortedKeySortedSetMultimap { + private transient Comparator keyComparator; + private transient Comparator valueComparator; + + /** + * Creates an empty {@code TreeMultimap} ordered by the natural ordering of its keys and values. + */ + public static TreeMultimap create() { + return new TreeMultimap<>(Ordering.natural(), Ordering.natural()); + } + + /** + * Creates an empty {@code TreeMultimap} instance using explicit comparators. Neither comparator + * may be null; use {@link Ordering#natural()} to specify natural order. + * + * @param keyComparator the comparator that determines the key ordering + * @param valueComparator the comparator that determines the value ordering + */ + public static TreeMultimap create( + Comparator keyComparator, Comparator valueComparator) { + return new TreeMultimap<>(checkNotNull(keyComparator), checkNotNull(valueComparator)); + } + + /** + * Constructs a {@code TreeMultimap}, ordered by the natural ordering of its keys and values, with + * the same mappings as the specified multimap. + * + * @param multimap the multimap whose contents are copied to this multimap + */ + public static TreeMultimap create( + Multimap multimap) { + return new TreeMultimap<>(Ordering.natural(), Ordering.natural(), multimap); + } + + TreeMultimap(Comparator keyComparator, Comparator valueComparator) { + super(new TreeMap>(keyComparator)); + this.keyComparator = keyComparator; + this.valueComparator = valueComparator; + } + + private TreeMultimap( + Comparator keyComparator, + Comparator valueComparator, + Multimap multimap) { + this(keyComparator, valueComparator); + putAll(multimap); + } + + @Override + Map> createAsMap() { + return createMaybeNavigableAsMap(); + } + + /** + * {@inheritDoc} + * + *

Creates an empty {@code TreeSet} for a collection of values for one key. + * + * @return a new {@code TreeSet} containing a collection of values for one key + */ + @Override + SortedSet createCollection() { + return new TreeSet(valueComparator); + } + + @Override + Collection createCollection(K key) { + if (key == null) { + keyComparator().compare(key, key); + } + return super.createCollection(key); + } + + /** + * Returns the comparator that orders the multimap keys. + * + * @deprecated Use {@code ((NavigableSet) multimap.keySet()).comparator()} instead. + */ + @Deprecated + public Comparator keyComparator() { + return keyComparator; + } + + @Override + public Comparator valueComparator() { + return valueComparator; + } + + /** @since 14.0 (present with return type {@code SortedSet} since 2.0) */ + @Override + @GwtIncompatible // NavigableSet + public NavigableSet get(K key) { + return (NavigableSet) super.get(key); + } + + /** + * {@inheritDoc} + * + *

Because a {@code TreeMultimap} has unique sorted keys, this method returns a {@link + * NavigableSet}, instead of the {@link java.util.Set} specified in the {@link Multimap} + * interface. + * + * @since 14.0 (present with return type {@code SortedSet} since 2.0) + */ + @Override + public NavigableSet keySet() { + return (NavigableSet) super.keySet(); + } + + /** + * {@inheritDoc} + * + *

Because a {@code TreeMultimap} has unique sorted keys, this method returns a {@link + * NavigableMap}, instead of the {@link java.util.Map} specified in the {@link Multimap} + * interface. + * + * @since 14.0 (present with return type {@code SortedMap} since 2.0) + */ + @Override + public NavigableMap> asMap() { + return (NavigableMap>) super.asMap(); + } + + /** + * @serialData key comparator, value comparator, number of distinct keys, and then for each + * distinct key: the key, number of values for that key, and key values + */ + @GwtIncompatible // java.io.ObjectOutputStream + private void writeObject(ObjectOutputStream stream) throws IOException { + stream.defaultWriteObject(); + stream.writeObject(keyComparator()); + stream.writeObject(valueComparator()); + Serialization.writeMultimap(this, stream); + } + + @GwtIncompatible // java.io.ObjectInputStream + @SuppressWarnings("unchecked") // reading data stored by writeObject + private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException { + stream.defaultReadObject(); + keyComparator = checkNotNull((Comparator) stream.readObject()); + valueComparator = checkNotNull((Comparator) stream.readObject()); + setMap(new TreeMap>(keyComparator)); + Serialization.populateMultimap(this, stream); + } + + @GwtIncompatible // not needed in emulated source + private static final long serialVersionUID = 0; +} diff --git a/src/main/java/com/google/common/collect/TreeMultiset.java b/src/main/java/com/google/common/collect/TreeMultiset.java new file mode 100644 index 0000000..35b4d82 --- /dev/null +++ b/src/main/java/com/google/common/collect/TreeMultiset.java @@ -0,0 +1,1028 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.base.Preconditions.checkState; +import static com.google.common.collect.CollectPreconditions.checkNonnegative; +import static com.google.common.collect.CollectPreconditions.checkRemove; + +import com.google.common.annotations.GwtCompatible; +import com.google.common.annotations.GwtIncompatible; +import com.google.common.base.MoreObjects; +import com.google.common.primitives.Ints; + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.Serializable; +import java.util.Comparator; +import java.util.ConcurrentModificationException; +import java.util.Iterator; +import java.util.NoSuchElementException; +import java.util.function.ObjIntConsumer; + + +/** + * A multiset which maintains the ordering of its elements, according to either their natural order + * or an explicit {@link Comparator}. In all cases, this implementation uses {@link + * Comparable#compareTo} or {@link Comparator#compare} instead of {@link Object#equals} to determine + * equivalence of instances. + * + *

Warning: The comparison must be consistent with equals as explained by the + * {@link Comparable} class specification. Otherwise, the resulting multiset will violate the {@link + * java.util.Collection} contract, which is specified in terms of {@link Object#equals}. + * + *

See the Guava User Guide article on {@code + * Multiset}. + * + * @author Louis Wasserman + * @author Jared Levy + * @since 2.0 + */ +@GwtCompatible(emulated = true) +public final class TreeMultiset extends AbstractSortedMultiset implements Serializable { + + /** + * Creates a new, empty multiset, sorted according to the elements' natural order. All elements + * inserted into the multiset must implement the {@code Comparable} interface. Furthermore, all + * such elements must be mutually comparable: {@code e1.compareTo(e2)} must not throw a + * {@code ClassCastException} for any elements {@code e1} and {@code e2} in the multiset. If the + * user attempts to add an element to the multiset that violates this constraint (for example, the + * user attempts to add a string element to a set whose elements are integers), the {@code + * add(Object)} call will throw a {@code ClassCastException}. + * + *

The type specification is {@code }, instead of the more specific + * {@code >}, to support classes defined without generics. + */ + public static TreeMultiset create() { + return new TreeMultiset(Ordering.natural()); + } + + /** + * Creates a new, empty multiset, sorted according to the specified comparator. All elements + * inserted into the multiset must be mutually comparable by the specified comparator: + * {@code comparator.compare(e1, e2)} must not throw a {@code ClassCastException} for any elements + * {@code e1} and {@code e2} in the multiset. If the user attempts to add an element to the + * multiset that violates this constraint, the {@code add(Object)} call will throw a {@code + * ClassCastException}. + * + * @param comparator the comparator that will be used to sort this multiset. A null value + * indicates that the elements' natural ordering should be used. + */ + @SuppressWarnings("unchecked") + public static TreeMultiset create(Comparator comparator) { + return (comparator == null) + ? new TreeMultiset((Comparator) Ordering.natural()) + : new TreeMultiset(comparator); + } + + /** + * Creates an empty multiset containing the given initial elements, sorted according to the + * elements' natural order. + * + *

This implementation is highly efficient when {@code elements} is itself a {@link Multiset}. + * + *

The type specification is {@code }, instead of the more specific + * {@code >}, to support classes defined without generics. + */ + public static TreeMultiset create(Iterable elements) { + TreeMultiset multiset = create(); + Iterables.addAll(multiset, elements); + return multiset; + } + + private final transient Reference> rootReference; + private final transient GeneralRange range; + private final transient AvlNode header; + + TreeMultiset(Reference> rootReference, GeneralRange range, AvlNode endLink) { + super(range.comparator()); + this.rootReference = rootReference; + this.range = range; + this.header = endLink; + } + + TreeMultiset(Comparator comparator) { + super(comparator); + this.range = GeneralRange.all(comparator); + this.header = new AvlNode(null, 1); + successor(header, header); + this.rootReference = new Reference<>(); + } + + /** A function which can be summed across a subtree. */ + private enum Aggregate { + SIZE { + @Override + int nodeAggregate(AvlNode node) { + return node.elemCount; + } + + @Override + long treeAggregate(AvlNode root) { + return (root == null) ? 0 : root.totalCount; + } + }, + DISTINCT { + @Override + int nodeAggregate(AvlNode node) { + return 1; + } + + @Override + long treeAggregate(AvlNode root) { + return (root == null) ? 0 : root.distinctElements; + } + }; + + abstract int nodeAggregate(AvlNode node); + + abstract long treeAggregate(AvlNode root); + } + + private long aggregateForEntries(Aggregate aggr) { + AvlNode root = rootReference.get(); + long total = aggr.treeAggregate(root); + if (range.hasLowerBound()) { + total -= aggregateBelowRange(aggr, root); + } + if (range.hasUpperBound()) { + total -= aggregateAboveRange(aggr, root); + } + return total; + } + + private long aggregateBelowRange(Aggregate aggr, AvlNode node) { + if (node == null) { + return 0; + } + int cmp = comparator().compare(range.getLowerEndpoint(), node.elem); + if (cmp < 0) { + return aggregateBelowRange(aggr, node.left); + } else if (cmp == 0) { + switch (range.getLowerBoundType()) { + case OPEN: + return aggr.nodeAggregate(node) + aggr.treeAggregate(node.left); + case CLOSED: + return aggr.treeAggregate(node.left); + default: + throw new AssertionError(); + } + } else { + return aggr.treeAggregate(node.left) + + aggr.nodeAggregate(node) + + aggregateBelowRange(aggr, node.right); + } + } + + private long aggregateAboveRange(Aggregate aggr, AvlNode node) { + if (node == null) { + return 0; + } + int cmp = comparator().compare(range.getUpperEndpoint(), node.elem); + if (cmp > 0) { + return aggregateAboveRange(aggr, node.right); + } else if (cmp == 0) { + switch (range.getUpperBoundType()) { + case OPEN: + return aggr.nodeAggregate(node) + aggr.treeAggregate(node.right); + case CLOSED: + return aggr.treeAggregate(node.right); + default: + throw new AssertionError(); + } + } else { + return aggr.treeAggregate(node.right) + + aggr.nodeAggregate(node) + + aggregateAboveRange(aggr, node.left); + } + } + + @Override + public int size() { + return Ints.saturatedCast(aggregateForEntries(Aggregate.SIZE)); + } + + @Override + int distinctElements() { + return Ints.saturatedCast(aggregateForEntries(Aggregate.DISTINCT)); + } + + static int distinctElements(AvlNode node) { + return (node == null) ? 0 : node.distinctElements; + } + + @Override + public int count(Object element) { + try { + @SuppressWarnings("unchecked") + E e = (E) element; + AvlNode root = rootReference.get(); + if (!range.contains(e) || root == null) { + return 0; + } + return root.count(comparator(), e); + } catch (ClassCastException | NullPointerException e) { + return 0; + } + } + + + @Override + public int add(E element, int occurrences) { + checkNonnegative(occurrences, "occurrences"); + if (occurrences == 0) { + return count(element); + } + checkArgument(range.contains(element)); + AvlNode root = rootReference.get(); + if (root == null) { + comparator().compare(element, element); + AvlNode newRoot = new AvlNode(element, occurrences); + successor(header, newRoot, header); + rootReference.checkAndSet(root, newRoot); + return 0; + } + int[] result = new int[1]; // used as a mutable int reference to hold result + AvlNode newRoot = root.add(comparator(), element, occurrences, result); + rootReference.checkAndSet(root, newRoot); + return result[0]; + } + + + @Override + public int remove(Object element, int occurrences) { + checkNonnegative(occurrences, "occurrences"); + if (occurrences == 0) { + return count(element); + } + AvlNode root = rootReference.get(); + int[] result = new int[1]; // used as a mutable int reference to hold result + AvlNode newRoot; + try { + @SuppressWarnings("unchecked") + E e = (E) element; + if (!range.contains(e) || root == null) { + return 0; + } + newRoot = root.remove(comparator(), e, occurrences, result); + } catch (ClassCastException | NullPointerException e) { + return 0; + } + rootReference.checkAndSet(root, newRoot); + return result[0]; + } + + + @Override + public int setCount(E element, int count) { + checkNonnegative(count, "count"); + if (!range.contains(element)) { + checkArgument(count == 0); + return 0; + } + + AvlNode root = rootReference.get(); + if (root == null) { + if (count > 0) { + add(element, count); + } + return 0; + } + int[] result = new int[1]; // used as a mutable int reference to hold result + AvlNode newRoot = root.setCount(comparator(), element, count, result); + rootReference.checkAndSet(root, newRoot); + return result[0]; + } + + + @Override + public boolean setCount(E element, int oldCount, int newCount) { + checkNonnegative(newCount, "newCount"); + checkNonnegative(oldCount, "oldCount"); + checkArgument(range.contains(element)); + + AvlNode root = rootReference.get(); + if (root == null) { + if (oldCount == 0) { + if (newCount > 0) { + add(element, newCount); + } + return true; + } else { + return false; + } + } + int[] result = new int[1]; // used as a mutable int reference to hold result + AvlNode newRoot = root.setCount(comparator(), element, oldCount, newCount, result); + rootReference.checkAndSet(root, newRoot); + return result[0] == oldCount; + } + + @Override + public void clear() { + if (!range.hasLowerBound() && !range.hasUpperBound()) { + // We can do this in O(n) rather than removing one by one, which could force rebalancing. + for (AvlNode current = header.succ; current != header; ) { + AvlNode next = current.succ; + + current.elemCount = 0; + // Also clear these fields so that one deleted Entry doesn't retain all elements. + current.left = null; + current.right = null; + current.pred = null; + current.succ = null; + + current = next; + } + successor(header, header); + rootReference.clear(); + } else { + // TODO(cpovirk): Perhaps we can optimize in this case, too? + Iterators.clear(entryIterator()); + } + } + + private Entry wrapEntry(final AvlNode baseEntry) { + return new Multisets.AbstractEntry() { + @Override + public E getElement() { + return baseEntry.getElement(); + } + + @Override + public int getCount() { + int result = baseEntry.getCount(); + if (result == 0) { + return count(getElement()); + } else { + return result; + } + } + }; + } + + /** Returns the first node in the tree that is in range. */ + private AvlNode firstNode() { + AvlNode root = rootReference.get(); + if (root == null) { + return null; + } + AvlNode node; + if (range.hasLowerBound()) { + E endpoint = range.getLowerEndpoint(); + node = rootReference.get().ceiling(comparator(), endpoint); + if (node == null) { + return null; + } + if (range.getLowerBoundType() == BoundType.OPEN + && comparator().compare(endpoint, node.getElement()) == 0) { + node = node.succ; + } + } else { + node = header.succ; + } + return (node == header || !range.contains(node.getElement())) ? null : node; + } + + private AvlNode lastNode() { + AvlNode root = rootReference.get(); + if (root == null) { + return null; + } + AvlNode node; + if (range.hasUpperBound()) { + E endpoint = range.getUpperEndpoint(); + node = rootReference.get().floor(comparator(), endpoint); + if (node == null) { + return null; + } + if (range.getUpperBoundType() == BoundType.OPEN + && comparator().compare(endpoint, node.getElement()) == 0) { + node = node.pred; + } + } else { + node = header.pred; + } + return (node == header || !range.contains(node.getElement())) ? null : node; + } + + @Override + Iterator elementIterator() { + return Multisets.elementIterator(entryIterator()); + } + + @Override + Iterator> entryIterator() { + return new Iterator>() { + AvlNode current = firstNode(); + Entry prevEntry; + + @Override + public boolean hasNext() { + if (current == null) { + return false; + } else if (range.tooHigh(current.getElement())) { + current = null; + return false; + } else { + return true; + } + } + + @Override + public Entry next() { + if (!hasNext()) { + throw new NoSuchElementException(); + } + Entry result = wrapEntry(current); + prevEntry = result; + if (current.succ == header) { + current = null; + } else { + current = current.succ; + } + return result; + } + + @Override + public void remove() { + checkRemove(prevEntry != null); + setCount(prevEntry.getElement(), 0); + prevEntry = null; + } + }; + } + + @Override + Iterator> descendingEntryIterator() { + return new Iterator>() { + AvlNode current = lastNode(); + Entry prevEntry = null; + + @Override + public boolean hasNext() { + if (current == null) { + return false; + } else if (range.tooLow(current.getElement())) { + current = null; + return false; + } else { + return true; + } + } + + @Override + public Entry next() { + if (!hasNext()) { + throw new NoSuchElementException(); + } + Entry result = wrapEntry(current); + prevEntry = result; + if (current.pred == header) { + current = null; + } else { + current = current.pred; + } + return result; + } + + @Override + public void remove() { + checkRemove(prevEntry != null); + setCount(prevEntry.getElement(), 0); + prevEntry = null; + } + }; + } + + @Override + public void forEachEntry(ObjIntConsumer action) { + checkNotNull(action); + for (AvlNode node = firstNode(); + node != header && node != null && !range.tooHigh(node.getElement()); + node = node.succ) { + action.accept(node.getElement(), node.getCount()); + } + } + + @Override + public Iterator iterator() { + return Multisets.iteratorImpl(this); + } + + @Override + public SortedMultiset headMultiset(E upperBound, BoundType boundType) { + return new TreeMultiset( + rootReference, + range.intersect(GeneralRange.upTo(comparator(), upperBound, boundType)), + header); + } + + @Override + public SortedMultiset tailMultiset(E lowerBound, BoundType boundType) { + return new TreeMultiset( + rootReference, + range.intersect(GeneralRange.downTo(comparator(), lowerBound, boundType)), + header); + } + + private static final class Reference { + private T value; + + public T get() { + return value; + } + + public void checkAndSet(T expected, T newValue) { + if (value != expected) { + throw new ConcurrentModificationException(); + } + value = newValue; + } + + void clear() { + value = null; + } + } + + private static final class AvlNode { + private final E elem; + + // elemCount is 0 iff this node has been deleted. + private int elemCount; + + private int distinctElements; + private long totalCount; + private int height; + private AvlNode left; + private AvlNode right; + private AvlNode pred; + private AvlNode succ; + + AvlNode(E elem, int elemCount) { + checkArgument(elemCount > 0); + this.elem = elem; + this.elemCount = elemCount; + this.totalCount = elemCount; + this.distinctElements = 1; + this.height = 1; + this.left = null; + this.right = null; + } + + public int count(Comparator comparator, E e) { + int cmp = comparator.compare(e, elem); + if (cmp < 0) { + return (left == null) ? 0 : left.count(comparator, e); + } else if (cmp > 0) { + return (right == null) ? 0 : right.count(comparator, e); + } else { + return elemCount; + } + } + + private AvlNode addRightChild(E e, int count) { + right = new AvlNode(e, count); + successor(this, right, succ); + height = Math.max(2, height); + distinctElements++; + totalCount += count; + return this; + } + + private AvlNode addLeftChild(E e, int count) { + left = new AvlNode(e, count); + successor(pred, left, this); + height = Math.max(2, height); + distinctElements++; + totalCount += count; + return this; + } + + AvlNode add(Comparator comparator, E e, int count, int[] result) { + /* + * It speeds things up considerably to unconditionally add count to totalCount here, + * but that destroys failure atomicity in the case of count overflow. =( + */ + int cmp = comparator.compare(e, elem); + if (cmp < 0) { + AvlNode initLeft = left; + if (initLeft == null) { + result[0] = 0; + return addLeftChild(e, count); + } + int initHeight = initLeft.height; + + left = initLeft.add(comparator, e, count, result); + if (result[0] == 0) { + distinctElements++; + } + this.totalCount += count; + return (left.height == initHeight) ? this : rebalance(); + } else if (cmp > 0) { + AvlNode initRight = right; + if (initRight == null) { + result[0] = 0; + return addRightChild(e, count); + } + int initHeight = initRight.height; + + right = initRight.add(comparator, e, count, result); + if (result[0] == 0) { + distinctElements++; + } + this.totalCount += count; + return (right.height == initHeight) ? this : rebalance(); + } + + // adding count to me! No rebalance possible. + result[0] = elemCount; + long resultCount = (long) elemCount + count; + checkArgument(resultCount <= Integer.MAX_VALUE); + this.elemCount += count; + this.totalCount += count; + return this; + } + + AvlNode remove(Comparator comparator, E e, int count, int[] result) { + int cmp = comparator.compare(e, elem); + if (cmp < 0) { + AvlNode initLeft = left; + if (initLeft == null) { + result[0] = 0; + return this; + } + + left = initLeft.remove(comparator, e, count, result); + + if (result[0] > 0) { + if (count >= result[0]) { + this.distinctElements--; + this.totalCount -= result[0]; + } else { + this.totalCount -= count; + } + } + return (result[0] == 0) ? this : rebalance(); + } else if (cmp > 0) { + AvlNode initRight = right; + if (initRight == null) { + result[0] = 0; + return this; + } + + right = initRight.remove(comparator, e, count, result); + + if (result[0] > 0) { + if (count >= result[0]) { + this.distinctElements--; + this.totalCount -= result[0]; + } else { + this.totalCount -= count; + } + } + return rebalance(); + } + + // removing count from me! + result[0] = elemCount; + if (count >= elemCount) { + return deleteMe(); + } else { + this.elemCount -= count; + this.totalCount -= count; + return this; + } + } + + AvlNode setCount(Comparator comparator, E e, int count, int[] result) { + int cmp = comparator.compare(e, elem); + if (cmp < 0) { + AvlNode initLeft = left; + if (initLeft == null) { + result[0] = 0; + return (count > 0) ? addLeftChild(e, count) : this; + } + + left = initLeft.setCount(comparator, e, count, result); + + if (count == 0 && result[0] != 0) { + this.distinctElements--; + } else if (count > 0 && result[0] == 0) { + this.distinctElements++; + } + + this.totalCount += count - result[0]; + return rebalance(); + } else if (cmp > 0) { + AvlNode initRight = right; + if (initRight == null) { + result[0] = 0; + return (count > 0) ? addRightChild(e, count) : this; + } + + right = initRight.setCount(comparator, e, count, result); + + if (count == 0 && result[0] != 0) { + this.distinctElements--; + } else if (count > 0 && result[0] == 0) { + this.distinctElements++; + } + + this.totalCount += count - result[0]; + return rebalance(); + } + + // setting my count + result[0] = elemCount; + if (count == 0) { + return deleteMe(); + } + this.totalCount += count - elemCount; + this.elemCount = count; + return this; + } + + AvlNode setCount( + Comparator comparator, + E e, + int expectedCount, + int newCount, + int[] result) { + int cmp = comparator.compare(e, elem); + if (cmp < 0) { + AvlNode initLeft = left; + if (initLeft == null) { + result[0] = 0; + if (expectedCount == 0 && newCount > 0) { + return addLeftChild(e, newCount); + } + return this; + } + + left = initLeft.setCount(comparator, e, expectedCount, newCount, result); + + if (result[0] == expectedCount) { + if (newCount == 0 && result[0] != 0) { + this.distinctElements--; + } else if (newCount > 0 && result[0] == 0) { + this.distinctElements++; + } + this.totalCount += newCount - result[0]; + } + return rebalance(); + } else if (cmp > 0) { + AvlNode initRight = right; + if (initRight == null) { + result[0] = 0; + if (expectedCount == 0 && newCount > 0) { + return addRightChild(e, newCount); + } + return this; + } + + right = initRight.setCount(comparator, e, expectedCount, newCount, result); + + if (result[0] == expectedCount) { + if (newCount == 0 && result[0] != 0) { + this.distinctElements--; + } else if (newCount > 0 && result[0] == 0) { + this.distinctElements++; + } + this.totalCount += newCount - result[0]; + } + return rebalance(); + } + + // setting my count + result[0] = elemCount; + if (expectedCount == elemCount) { + if (newCount == 0) { + return deleteMe(); + } + this.totalCount += newCount - elemCount; + this.elemCount = newCount; + } + return this; + } + + private AvlNode deleteMe() { + int oldElemCount = this.elemCount; + this.elemCount = 0; + successor(pred, succ); + if (left == null) { + return right; + } else if (right == null) { + return left; + } else if (left.height >= right.height) { + AvlNode newTop = pred; + // newTop is the maximum node in my left subtree + newTop.left = left.removeMax(newTop); + newTop.right = right; + newTop.distinctElements = distinctElements - 1; + newTop.totalCount = totalCount - oldElemCount; + return newTop.rebalance(); + } else { + AvlNode newTop = succ; + newTop.right = right.removeMin(newTop); + newTop.left = left; + newTop.distinctElements = distinctElements - 1; + newTop.totalCount = totalCount - oldElemCount; + return newTop.rebalance(); + } + } + + // Removes the minimum node from this subtree to be reused elsewhere + private AvlNode removeMin(AvlNode node) { + if (left == null) { + return right; + } else { + left = left.removeMin(node); + distinctElements--; + totalCount -= node.elemCount; + return rebalance(); + } + } + + // Removes the maximum node from this subtree to be reused elsewhere + private AvlNode removeMax(AvlNode node) { + if (right == null) { + return left; + } else { + right = right.removeMax(node); + distinctElements--; + totalCount -= node.elemCount; + return rebalance(); + } + } + + private void recomputeMultiset() { + this.distinctElements = + 1 + TreeMultiset.distinctElements(left) + TreeMultiset.distinctElements(right); + this.totalCount = elemCount + totalCount(left) + totalCount(right); + } + + private void recomputeHeight() { + this.height = 1 + Math.max(height(left), height(right)); + } + + private void recompute() { + recomputeMultiset(); + recomputeHeight(); + } + + private AvlNode rebalance() { + switch (balanceFactor()) { + case -2: + if (right.balanceFactor() > 0) { + right = right.rotateRight(); + } + return rotateLeft(); + case 2: + if (left.balanceFactor() < 0) { + left = left.rotateLeft(); + } + return rotateRight(); + default: + recomputeHeight(); + return this; + } + } + + private int balanceFactor() { + return height(left) - height(right); + } + + private AvlNode rotateLeft() { + checkState(right != null); + AvlNode newTop = right; + this.right = newTop.left; + newTop.left = this; + newTop.totalCount = this.totalCount; + newTop.distinctElements = this.distinctElements; + this.recompute(); + newTop.recomputeHeight(); + return newTop; + } + + private AvlNode rotateRight() { + checkState(left != null); + AvlNode newTop = left; + this.left = newTop.right; + newTop.right = this; + newTop.totalCount = this.totalCount; + newTop.distinctElements = this.distinctElements; + this.recompute(); + newTop.recomputeHeight(); + return newTop; + } + + private static long totalCount(AvlNode node) { + return (node == null) ? 0 : node.totalCount; + } + + private static int height(AvlNode node) { + return (node == null) ? 0 : node.height; + } + + private AvlNode ceiling(Comparator comparator, E e) { + int cmp = comparator.compare(e, elem); + if (cmp < 0) { + return (left == null) ? this : MoreObjects.firstNonNull(left.ceiling(comparator, e), this); + } else if (cmp == 0) { + return this; + } else { + return (right == null) ? null : right.ceiling(comparator, e); + } + } + + private AvlNode floor(Comparator comparator, E e) { + int cmp = comparator.compare(e, elem); + if (cmp > 0) { + return (right == null) ? this : MoreObjects.firstNonNull(right.floor(comparator, e), this); + } else if (cmp == 0) { + return this; + } else { + return (left == null) ? null : left.floor(comparator, e); + } + } + + E getElement() { + return elem; + } + + int getCount() { + return elemCount; + } + + @Override + public String toString() { + return Multisets.immutableEntry(getElement(), getCount()).toString(); + } + } + + private static void successor(AvlNode a, AvlNode b) { + a.succ = b; + b.pred = a; + } + + private static void successor(AvlNode a, AvlNode b, AvlNode c) { + successor(a, b); + successor(b, c); + } + + /* + * TODO(jlevy): Decide whether entrySet() should return entries with an equals() method that + * calls the comparator to compare the two keys. If that change is made, + * AbstractMultiset.equals() can simply check whether two multisets have equal entry sets. + */ + + /** + * @serialData the comparator, the number of distinct elements, the first element, its count, the + * second element, its count, and so on + */ + @GwtIncompatible // java.io.ObjectOutputStream + private void writeObject(ObjectOutputStream stream) throws IOException { + stream.defaultWriteObject(); + stream.writeObject(elementSet().comparator()); + Serialization.writeMultiset(this, stream); + } + + @GwtIncompatible // java.io.ObjectInputStream + private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException { + stream.defaultReadObject(); + @SuppressWarnings("unchecked") + // reading data stored by writeObject + Comparator comparator = (Comparator) stream.readObject(); + Serialization.getFieldSetter(AbstractSortedMultiset.class, "comparator").set(this, comparator); + Serialization.getFieldSetter(TreeMultiset.class, "range") + .set(this, GeneralRange.all(comparator)); + Serialization.getFieldSetter(TreeMultiset.class, "rootReference") + .set(this, new Reference>()); + AvlNode header = new AvlNode(null, 1); + Serialization.getFieldSetter(TreeMultiset.class, "header").set(this, header); + successor(header, header); + Serialization.populateMultiset(this, stream); + } + + @GwtIncompatible // not needed in emulated source + private static final long serialVersionUID = 1; +} diff --git a/src/main/java/com/google/common/collect/TreeRangeMap.java b/src/main/java/com/google/common/collect/TreeRangeMap.java new file mode 100644 index 0000000..a4a7745 --- /dev/null +++ b/src/main/java/com/google/common/collect/TreeRangeMap.java @@ -0,0 +1,803 @@ +/* + * Copyright (C) 2012 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.base.Predicates.compose; +import static com.google.common.base.Predicates.in; +import static com.google.common.base.Predicates.not; + +import com.google.common.annotations.Beta; +import com.google.common.annotations.GwtIncompatible; +import com.google.common.base.MoreObjects; +import com.google.common.base.Predicate; +import com.google.common.collect.Maps.IteratorBasedAbstractMap; +import java.util.AbstractMap; +import java.util.Collection; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.NavigableMap; +import java.util.NoSuchElementException; +import java.util.Set; +import java.util.function.BiFunction; + + +/** + * An implementation of {@code RangeMap} based on a {@code TreeMap}, supporting all optional + * operations. + * + *

Like all {@code RangeMap} implementations, this supports neither null keys nor null values. + * + * @author Louis Wasserman + * @since 14.0 + */ +@Beta +@GwtIncompatible // NavigableMap +public final class TreeRangeMap implements RangeMap { + + private final NavigableMap, RangeMapEntry> entriesByLowerBound; + + public static TreeRangeMap create() { + return new TreeRangeMap<>(); + } + + private TreeRangeMap() { + this.entriesByLowerBound = Maps.newTreeMap(); + } + + private static final class RangeMapEntry + extends AbstractMapEntry, V> { + private final Range range; + private final V value; + + RangeMapEntry(Cut lowerBound, Cut upperBound, V value) { + this(Range.create(lowerBound, upperBound), value); + } + + RangeMapEntry(Range range, V value) { + this.range = range; + this.value = value; + } + + @Override + public Range getKey() { + return range; + } + + @Override + public V getValue() { + return value; + } + + public boolean contains(K value) { + return range.contains(value); + } + + Cut getLowerBound() { + return range.lowerBound; + } + + Cut getUpperBound() { + return range.upperBound; + } + } + + @Override + public V get(K key) { + Entry, V> entry = getEntry(key); + return (entry == null) ? null : entry.getValue(); + } + + @Override + public Entry, V> getEntry(K key) { + Entry, RangeMapEntry> mapEntry = + entriesByLowerBound.floorEntry(Cut.belowValue(key)); + if (mapEntry != null && mapEntry.getValue().contains(key)) { + return mapEntry.getValue(); + } else { + return null; + } + } + + @Override + public void put(Range range, V value) { + // don't short-circuit if the range is empty - it may be between two ranges we can coalesce. + if (!range.isEmpty()) { + checkNotNull(value); + remove(range); + entriesByLowerBound.put(range.lowerBound, new RangeMapEntry(range, value)); + } + } + + @Override + public void putCoalescing(Range range, V value) { + if (entriesByLowerBound.isEmpty()) { + put(range, value); + return; + } + + Range coalescedRange = coalescedRange(range, checkNotNull(value)); + put(coalescedRange, value); + } + + /** Computes the coalesced range for the given range+value - does not mutate the map. */ + private Range coalescedRange(Range range, V value) { + Range coalescedRange = range; + Entry, RangeMapEntry> lowerEntry = + entriesByLowerBound.lowerEntry(range.lowerBound); + coalescedRange = coalesce(coalescedRange, value, lowerEntry); + + Entry, RangeMapEntry> higherEntry = + entriesByLowerBound.floorEntry(range.upperBound); + coalescedRange = coalesce(coalescedRange, value, higherEntry); + + return coalescedRange; + } + + /** Returns the range that spans the given range and entry, if the entry can be coalesced. */ + private static Range coalesce( + Range range, V value, Entry, RangeMapEntry> entry) { + if (entry != null + && entry.getValue().getKey().isConnected(range) + && entry.getValue().getValue().equals(value)) { + return range.span(entry.getValue().getKey()); + } + return range; + } + + @Override + public void putAll(RangeMap rangeMap) { + for (Entry, V> entry : rangeMap.asMapOfRanges().entrySet()) { + put(entry.getKey(), entry.getValue()); + } + } + + @Override + public void clear() { + entriesByLowerBound.clear(); + } + + @Override + public Range span() { + Entry, RangeMapEntry> firstEntry = entriesByLowerBound.firstEntry(); + Entry, RangeMapEntry> lastEntry = entriesByLowerBound.lastEntry(); + if (firstEntry == null) { + throw new NoSuchElementException(); + } + return Range.create( + firstEntry.getValue().getKey().lowerBound, lastEntry.getValue().getKey().upperBound); + } + + private void putRangeMapEntry(Cut lowerBound, Cut upperBound, V value) { + entriesByLowerBound.put(lowerBound, new RangeMapEntry(lowerBound, upperBound, value)); + } + + @Override + public void remove(Range rangeToRemove) { + if (rangeToRemove.isEmpty()) { + return; + } + + /* + * The comments for this method will use [ ] to indicate the bounds of rangeToRemove and ( ) to + * indicate the bounds of ranges in the range map. + */ + Entry, RangeMapEntry> mapEntryBelowToTruncate = + entriesByLowerBound.lowerEntry(rangeToRemove.lowerBound); + if (mapEntryBelowToTruncate != null) { + // we know ( [ + RangeMapEntry rangeMapEntry = mapEntryBelowToTruncate.getValue(); + if (rangeMapEntry.getUpperBound().compareTo(rangeToRemove.lowerBound) > 0) { + // we know ( [ ) + if (rangeMapEntry.getUpperBound().compareTo(rangeToRemove.upperBound) > 0) { + // we know ( [ ] ), so insert the range ] ) back into the map -- + // it's being split apart + putRangeMapEntry( + rangeToRemove.upperBound, + rangeMapEntry.getUpperBound(), + mapEntryBelowToTruncate.getValue().getValue()); + } + // overwrite mapEntryToTruncateBelow with a truncated range + putRangeMapEntry( + rangeMapEntry.getLowerBound(), + rangeToRemove.lowerBound, + mapEntryBelowToTruncate.getValue().getValue()); + } + } + + Entry, RangeMapEntry> mapEntryAboveToTruncate = + entriesByLowerBound.lowerEntry(rangeToRemove.upperBound); + if (mapEntryAboveToTruncate != null) { + // we know ( ] + RangeMapEntry rangeMapEntry = mapEntryAboveToTruncate.getValue(); + if (rangeMapEntry.getUpperBound().compareTo(rangeToRemove.upperBound) > 0) { + // we know ( ] ), and since we dealt with truncating below already, + // we know [ ( ] ) + putRangeMapEntry( + rangeToRemove.upperBound, + rangeMapEntry.getUpperBound(), + mapEntryAboveToTruncate.getValue().getValue()); + } + } + entriesByLowerBound.subMap(rangeToRemove.lowerBound, rangeToRemove.upperBound).clear(); + } + + private void split(Cut cut) { + /* + * The comments for this method will use | to indicate the cut point and ( ) to indicate the + * bounds of ranges in the range map. + */ + Entry, RangeMapEntry> mapEntryToSplit = entriesByLowerBound.lowerEntry(cut); + if (mapEntryToSplit == null) { + return; + } + // we know ( | + RangeMapEntry rangeMapEntry = mapEntryToSplit.getValue(); + if (rangeMapEntry.getUpperBound().compareTo(cut) <= 0) { + return; + } + // we know ( | ) + putRangeMapEntry(rangeMapEntry.getLowerBound(), cut, rangeMapEntry.getValue()); + putRangeMapEntry(cut, rangeMapEntry.getUpperBound(), rangeMapEntry.getValue()); + } + + @Override + public void merge( + Range range, + V value, + BiFunction remappingFunction) { + checkNotNull(range); + checkNotNull(remappingFunction); + + if (range.isEmpty()) { + return; + } + split(range.lowerBound); + split(range.upperBound); + + // Due to the splitting of any entries spanning the range bounds, we know that any entry with a + // lower bound in the merge range is entirely contained by the merge range. + Set, RangeMapEntry>> entriesInMergeRange = + entriesByLowerBound.subMap(range.lowerBound, range.upperBound).entrySet(); + + // Create entries mapping any unmapped ranges in the merge range to the specified value. + ImmutableMap.Builder, RangeMapEntry> gaps = ImmutableMap.builder(); + if (value != null) { + final Iterator, RangeMapEntry>> backingItr = + entriesInMergeRange.iterator(); + Cut lowerBound = range.lowerBound; + while (backingItr.hasNext()) { + RangeMapEntry entry = backingItr.next().getValue(); + Cut upperBound = entry.getLowerBound(); + if (!lowerBound.equals(upperBound)) { + gaps.put(lowerBound, new RangeMapEntry(lowerBound, upperBound, value)); + } + lowerBound = entry.getUpperBound(); + } + if (!lowerBound.equals(range.upperBound)) { + gaps.put(lowerBound, new RangeMapEntry(lowerBound, range.upperBound, value)); + } + } + + // Remap all existing entries in the merge range. + final Iterator, RangeMapEntry>> backingItr = entriesInMergeRange.iterator(); + while (backingItr.hasNext()) { + Entry, RangeMapEntry> entry = backingItr.next(); + V newValue = remappingFunction.apply(entry.getValue().getValue(), value); + if (newValue == null) { + backingItr.remove(); + } else { + entry.setValue( + new RangeMapEntry( + entry.getValue().getLowerBound(), entry.getValue().getUpperBound(), newValue)); + } + } + + entriesByLowerBound.putAll(gaps.build()); + } + + @Override + public Map, V> asMapOfRanges() { + return new AsMapOfRanges(entriesByLowerBound.values()); + } + + @Override + public Map, V> asDescendingMapOfRanges() { + return new AsMapOfRanges(entriesByLowerBound.descendingMap().values()); + } + + private final class AsMapOfRanges extends IteratorBasedAbstractMap, V> { + + final Iterable, V>> entryIterable; + + @SuppressWarnings("unchecked") // it's safe to upcast iterables + AsMapOfRanges(Iterable> entryIterable) { + this.entryIterable = (Iterable) entryIterable; + } + + @Override + public boolean containsKey(Object key) { + return get(key) != null; + } + + @Override + public V get(Object key) { + if (key instanceof Range) { + Range range = (Range) key; + RangeMapEntry rangeMapEntry = entriesByLowerBound.get(range.lowerBound); + if (rangeMapEntry != null && rangeMapEntry.getKey().equals(range)) { + return rangeMapEntry.getValue(); + } + } + return null; + } + + @Override + public int size() { + return entriesByLowerBound.size(); + } + + @Override + Iterator, V>> entryIterator() { + return entryIterable.iterator(); + } + } + + @Override + public RangeMap subRangeMap(Range subRange) { + if (subRange.equals(Range.all())) { + return this; + } else { + return new SubRangeMap(subRange); + } + } + + @SuppressWarnings("unchecked") + private RangeMap emptySubRangeMap() { + return EMPTY_SUB_RANGE_MAP; + } + + private static final RangeMap EMPTY_SUB_RANGE_MAP = + new RangeMap() { + @Override + public Object get(Comparable key) { + return null; + } + + @Override + public Entry getEntry(Comparable key) { + return null; + } + + @Override + public Range span() { + throw new NoSuchElementException(); + } + + @Override + public void put(Range range, Object value) { + checkNotNull(range); + throw new IllegalArgumentException( + "Cannot insert range " + range + " into an empty subRangeMap"); + } + + @Override + public void putCoalescing(Range range, Object value) { + checkNotNull(range); + throw new IllegalArgumentException( + "Cannot insert range " + range + " into an empty subRangeMap"); + } + + @Override + public void putAll(RangeMap rangeMap) { + if (!rangeMap.asMapOfRanges().isEmpty()) { + throw new IllegalArgumentException( + "Cannot putAll(nonEmptyRangeMap) into an empty subRangeMap"); + } + } + + @Override + public void clear() {} + + @Override + public void remove(Range range) { + checkNotNull(range); + } + + @Override + @SuppressWarnings("rawtypes") // necessary for static EMPTY_SUB_RANGE_MAP instance + public void merge(Range range, Object value, BiFunction remappingFunction) { + checkNotNull(range); + throw new IllegalArgumentException( + "Cannot merge range " + range + " into an empty subRangeMap"); + } + + @Override + public Map asMapOfRanges() { + return Collections.emptyMap(); + } + + @Override + public Map asDescendingMapOfRanges() { + return Collections.emptyMap(); + } + + @Override + public RangeMap subRangeMap(Range range) { + checkNotNull(range); + return this; + } + }; + + private class SubRangeMap implements RangeMap { + + private final Range subRange; + + SubRangeMap(Range subRange) { + this.subRange = subRange; + } + + @Override + public V get(K key) { + return subRange.contains(key) ? TreeRangeMap.this.get(key) : null; + } + + @Override + public Entry, V> getEntry(K key) { + if (subRange.contains(key)) { + Entry, V> entry = TreeRangeMap.this.getEntry(key); + if (entry != null) { + return Maps.immutableEntry(entry.getKey().intersection(subRange), entry.getValue()); + } + } + return null; + } + + @Override + public Range span() { + Cut lowerBound; + Entry, RangeMapEntry> lowerEntry = + entriesByLowerBound.floorEntry(subRange.lowerBound); + if (lowerEntry != null + && lowerEntry.getValue().getUpperBound().compareTo(subRange.lowerBound) > 0) { + lowerBound = subRange.lowerBound; + } else { + lowerBound = entriesByLowerBound.ceilingKey(subRange.lowerBound); + if (lowerBound == null || lowerBound.compareTo(subRange.upperBound) >= 0) { + throw new NoSuchElementException(); + } + } + + Cut upperBound; + Entry, RangeMapEntry> upperEntry = + entriesByLowerBound.lowerEntry(subRange.upperBound); + if (upperEntry == null) { + throw new NoSuchElementException(); + } else if (upperEntry.getValue().getUpperBound().compareTo(subRange.upperBound) >= 0) { + upperBound = subRange.upperBound; + } else { + upperBound = upperEntry.getValue().getUpperBound(); + } + return Range.create(lowerBound, upperBound); + } + + @Override + public void put(Range range, V value) { + checkArgument( + subRange.encloses(range), "Cannot put range %s into a subRangeMap(%s)", range, subRange); + TreeRangeMap.this.put(range, value); + } + + @Override + public void putCoalescing(Range range, V value) { + if (entriesByLowerBound.isEmpty() || range.isEmpty() || !subRange.encloses(range)) { + put(range, value); + return; + } + + Range coalescedRange = coalescedRange(range, checkNotNull(value)); + // only coalesce ranges within the subRange + put(coalescedRange.intersection(subRange), value); + } + + @Override + public void putAll(RangeMap rangeMap) { + if (rangeMap.asMapOfRanges().isEmpty()) { + return; + } + Range span = rangeMap.span(); + checkArgument( + subRange.encloses(span), + "Cannot putAll rangeMap with span %s into a subRangeMap(%s)", + span, + subRange); + TreeRangeMap.this.putAll(rangeMap); + } + + @Override + public void clear() { + TreeRangeMap.this.remove(subRange); + } + + @Override + public void remove(Range range) { + if (range.isConnected(subRange)) { + TreeRangeMap.this.remove(range.intersection(subRange)); + } + } + + @Override + public void merge( + Range range, + V value, + BiFunction remappingFunction) { + checkArgument( + subRange.encloses(range), + "Cannot merge range %s into a subRangeMap(%s)", + range, + subRange); + TreeRangeMap.this.merge(range, value, remappingFunction); + } + + @Override + public RangeMap subRangeMap(Range range) { + if (!range.isConnected(subRange)) { + return emptySubRangeMap(); + } else { + return TreeRangeMap.this.subRangeMap(range.intersection(subRange)); + } + } + + @Override + public Map, V> asMapOfRanges() { + return new SubRangeMapAsMap(); + } + + @Override + public Map, V> asDescendingMapOfRanges() { + return new SubRangeMapAsMap() { + + @Override + Iterator, V>> entryIterator() { + if (subRange.isEmpty()) { + return Iterators.emptyIterator(); + } + final Iterator> backingItr = + entriesByLowerBound + .headMap(subRange.upperBound, false) + .descendingMap() + .values() + .iterator(); + return new AbstractIterator, V>>() { + + @Override + protected Entry, V> computeNext() { + if (backingItr.hasNext()) { + RangeMapEntry entry = backingItr.next(); + if (entry.getUpperBound().compareTo(subRange.lowerBound) <= 0) { + return endOfData(); + } + return Maps.immutableEntry(entry.getKey().intersection(subRange), entry.getValue()); + } + return endOfData(); + } + }; + } + }; + } + + @Override + public boolean equals(Object o) { + if (o instanceof RangeMap) { + RangeMap rangeMap = (RangeMap) o; + return asMapOfRanges().equals(rangeMap.asMapOfRanges()); + } + return false; + } + + @Override + public int hashCode() { + return asMapOfRanges().hashCode(); + } + + @Override + public String toString() { + return asMapOfRanges().toString(); + } + + class SubRangeMapAsMap extends AbstractMap, V> { + + @Override + public boolean containsKey(Object key) { + return get(key) != null; + } + + @Override + public V get(Object key) { + try { + if (key instanceof Range) { + @SuppressWarnings("unchecked") // we catch ClassCastExceptions + Range r = (Range) key; + if (!subRange.encloses(r) || r.isEmpty()) { + return null; + } + RangeMapEntry candidate = null; + if (r.lowerBound.compareTo(subRange.lowerBound) == 0) { + // r could be truncated on the left + Entry, RangeMapEntry> entry = + entriesByLowerBound.floorEntry(r.lowerBound); + if (entry != null) { + candidate = entry.getValue(); + } + } else { + candidate = entriesByLowerBound.get(r.lowerBound); + } + + if (candidate != null + && candidate.getKey().isConnected(subRange) + && candidate.getKey().intersection(subRange).equals(r)) { + return candidate.getValue(); + } + } + } catch (ClassCastException e) { + return null; + } + return null; + } + + @Override + public V remove(Object key) { + V value = get(key); + if (value != null) { + @SuppressWarnings("unchecked") // it's definitely in the map, so safe + Range range = (Range) key; + TreeRangeMap.this.remove(range); + return value; + } + return null; + } + + @Override + public void clear() { + SubRangeMap.this.clear(); + } + + private boolean removeEntryIf(Predicate, V>> predicate) { + List> toRemove = Lists.newArrayList(); + for (Entry, V> entry : entrySet()) { + if (predicate.apply(entry)) { + toRemove.add(entry.getKey()); + } + } + for (Range range : toRemove) { + TreeRangeMap.this.remove(range); + } + return !toRemove.isEmpty(); + } + + @Override + public Set> keySet() { + return new Maps.KeySet, V>(SubRangeMapAsMap.this) { + @Override + public boolean remove(Object o) { + return SubRangeMapAsMap.this.remove(o) != null; + } + + @Override + public boolean retainAll(Collection c) { + return removeEntryIf(compose(not(in(c)), Maps.>keyFunction())); + } + }; + } + + @Override + public Set, V>> entrySet() { + return new Maps.EntrySet, V>() { + @Override + Map, V> map() { + return SubRangeMapAsMap.this; + } + + @Override + public Iterator, V>> iterator() { + return entryIterator(); + } + + @Override + public boolean retainAll(Collection c) { + return removeEntryIf(not(in(c))); + } + + @Override + public int size() { + return Iterators.size(iterator()); + } + + @Override + public boolean isEmpty() { + return !iterator().hasNext(); + } + }; + } + + Iterator, V>> entryIterator() { + if (subRange.isEmpty()) { + return Iterators.emptyIterator(); + } + Cut cutToStart = + MoreObjects.firstNonNull( + entriesByLowerBound.floorKey(subRange.lowerBound), subRange.lowerBound); + final Iterator> backingItr = + entriesByLowerBound.tailMap(cutToStart, true).values().iterator(); + return new AbstractIterator, V>>() { + + @Override + protected Entry, V> computeNext() { + while (backingItr.hasNext()) { + RangeMapEntry entry = backingItr.next(); + if (entry.getLowerBound().compareTo(subRange.upperBound) >= 0) { + return endOfData(); + } else if (entry.getUpperBound().compareTo(subRange.lowerBound) > 0) { + // this might not be true e.g. at the start of the iteration + return Maps.immutableEntry(entry.getKey().intersection(subRange), entry.getValue()); + } + } + return endOfData(); + } + }; + } + + @Override + public Collection values() { + return new Maps.Values, V>(this) { + @Override + public boolean removeAll(Collection c) { + return removeEntryIf(compose(in(c), Maps.valueFunction())); + } + + @Override + public boolean retainAll(Collection c) { + return removeEntryIf(compose(not(in(c)), Maps.valueFunction())); + } + }; + } + } + } + + @Override + public boolean equals(Object o) { + if (o instanceof RangeMap) { + RangeMap rangeMap = (RangeMap) o; + return asMapOfRanges().equals(rangeMap.asMapOfRanges()); + } + return false; + } + + @Override + public int hashCode() { + return asMapOfRanges().hashCode(); + } + + @Override + public String toString() { + return entriesByLowerBound.values().toString(); + } +} diff --git a/src/main/java/com/google/common/collect/TreeRangeSet.java b/src/main/java/com/google/common/collect/TreeRangeSet.java new file mode 100644 index 0000000..a0afe2a --- /dev/null +++ b/src/main/java/com/google/common/collect/TreeRangeSet.java @@ -0,0 +1,926 @@ +/* + * Copyright (C) 2011 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.collect; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.annotations.Beta; +import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.MoreObjects; +import java.io.Serializable; +import java.util.Collection; +import java.util.Comparator; +import java.util.Iterator; +import java.util.Map.Entry; +import java.util.NavigableMap; +import java.util.NoSuchElementException; +import java.util.Set; +import java.util.TreeMap; + + + +/** + * An implementation of {@link RangeSet} backed by a {@link TreeMap}. + * + * @author Louis Wasserman + * @since 14.0 + */ +@Beta +@GwtIncompatible // uses NavigableMap +public class TreeRangeSet> extends AbstractRangeSet + implements Serializable { + + @VisibleForTesting final NavigableMap, Range> rangesByLowerBound; + + /** Creates an empty {@code TreeRangeSet} instance. */ + public static > TreeRangeSet create() { + return new TreeRangeSet(new TreeMap, Range>()); + } + + /** Returns a {@code TreeRangeSet} initialized with the ranges in the specified range set. */ + public static > TreeRangeSet create(RangeSet rangeSet) { + TreeRangeSet result = create(); + result.addAll(rangeSet); + return result; + } + + /** + * Returns a {@code TreeRangeSet} representing the union of the specified ranges. + * + *

This is the smallest {@code RangeSet} which encloses each of the specified ranges. An + * element will be contained in this {@code RangeSet} if and only if it is contained in at least + * one {@code Range} in {@code ranges}. + * + * @since 21.0 + */ + public static > TreeRangeSet create(Iterable> ranges) { + TreeRangeSet result = create(); + result.addAll(ranges); + return result; + } + + private TreeRangeSet(NavigableMap, Range> rangesByLowerCut) { + this.rangesByLowerBound = rangesByLowerCut; + } + + private transient Set> asRanges; + private transient Set> asDescendingSetOfRanges; + + @Override + public Set> asRanges() { + Set> result = asRanges; + return (result == null) ? asRanges = new AsRanges(rangesByLowerBound.values()) : result; + } + + @Override + public Set> asDescendingSetOfRanges() { + Set> result = asDescendingSetOfRanges; + return (result == null) + ? asDescendingSetOfRanges = new AsRanges(rangesByLowerBound.descendingMap().values()) + : result; + } + + final class AsRanges extends ForwardingCollection> implements Set> { + + final Collection> delegate; + + AsRanges(Collection> delegate) { + this.delegate = delegate; + } + + @Override + protected Collection> delegate() { + return delegate; + } + + @Override + public int hashCode() { + return Sets.hashCodeImpl(this); + } + + @Override + public boolean equals(Object o) { + return Sets.equalsImpl(this, o); + } + } + + @Override + public Range rangeContaining(C value) { + checkNotNull(value); + Entry, Range> floorEntry = rangesByLowerBound.floorEntry(Cut.belowValue(value)); + if (floorEntry != null && floorEntry.getValue().contains(value)) { + return floorEntry.getValue(); + } else { + // TODO(kevinb): revisit this design choice + return null; + } + } + + @Override + public boolean intersects(Range range) { + checkNotNull(range); + Entry, Range> ceilingEntry = rangesByLowerBound.ceilingEntry(range.lowerBound); + if (ceilingEntry != null + && ceilingEntry.getValue().isConnected(range) + && !ceilingEntry.getValue().intersection(range).isEmpty()) { + return true; + } + Entry, Range> priorEntry = rangesByLowerBound.lowerEntry(range.lowerBound); + return priorEntry != null + && priorEntry.getValue().isConnected(range) + && !priorEntry.getValue().intersection(range).isEmpty(); + } + + @Override + public boolean encloses(Range range) { + checkNotNull(range); + Entry, Range> floorEntry = rangesByLowerBound.floorEntry(range.lowerBound); + return floorEntry != null && floorEntry.getValue().encloses(range); + } + + private Range rangeEnclosing(Range range) { + checkNotNull(range); + Entry, Range> floorEntry = rangesByLowerBound.floorEntry(range.lowerBound); + return (floorEntry != null && floorEntry.getValue().encloses(range)) + ? floorEntry.getValue() + : null; + } + + @Override + public Range span() { + Entry, Range> firstEntry = rangesByLowerBound.firstEntry(); + Entry, Range> lastEntry = rangesByLowerBound.lastEntry(); + if (firstEntry == null) { + throw new NoSuchElementException(); + } + return Range.create(firstEntry.getValue().lowerBound, lastEntry.getValue().upperBound); + } + + @Override + public void add(Range rangeToAdd) { + checkNotNull(rangeToAdd); + + if (rangeToAdd.isEmpty()) { + return; + } + + // We will use { } to illustrate ranges currently in the range set, and < > + // to illustrate rangeToAdd. + Cut lbToAdd = rangeToAdd.lowerBound; + Cut ubToAdd = rangeToAdd.upperBound; + + Entry, Range> entryBelowLB = rangesByLowerBound.lowerEntry(lbToAdd); + if (entryBelowLB != null) { + // { < + Range rangeBelowLB = entryBelowLB.getValue(); + if (rangeBelowLB.upperBound.compareTo(lbToAdd) >= 0) { + // { < }, and we will need to coalesce + if (rangeBelowLB.upperBound.compareTo(ubToAdd) >= 0) { + // { < > } + ubToAdd = rangeBelowLB.upperBound; + /* + * TODO(cpovirk): can we just "return;" here? Or, can we remove this if() entirely? If + * not, add tests to demonstrate the problem with each approach + */ + } + lbToAdd = rangeBelowLB.lowerBound; + } + } + + Entry, Range> entryBelowUB = rangesByLowerBound.floorEntry(ubToAdd); + if (entryBelowUB != null) { + // { > + Range rangeBelowUB = entryBelowUB.getValue(); + if (rangeBelowUB.upperBound.compareTo(ubToAdd) >= 0) { + // { > }, and we need to coalesce + ubToAdd = rangeBelowUB.upperBound; + } + } + + // Remove ranges which are strictly enclosed. + rangesByLowerBound.subMap(lbToAdd, ubToAdd).clear(); + + replaceRangeWithSameLowerBound(Range.create(lbToAdd, ubToAdd)); + } + + @Override + public void remove(Range rangeToRemove) { + checkNotNull(rangeToRemove); + + if (rangeToRemove.isEmpty()) { + return; + } + + // We will use { } to illustrate ranges currently in the range set, and < > + // to illustrate rangeToRemove. + + Entry, Range> entryBelowLB = rangesByLowerBound.lowerEntry(rangeToRemove.lowerBound); + if (entryBelowLB != null) { + // { < + Range rangeBelowLB = entryBelowLB.getValue(); + if (rangeBelowLB.upperBound.compareTo(rangeToRemove.lowerBound) >= 0) { + // { < }, and we will need to subdivide + if (rangeToRemove.hasUpperBound() + && rangeBelowLB.upperBound.compareTo(rangeToRemove.upperBound) >= 0) { + // { < > } + replaceRangeWithSameLowerBound( + Range.create(rangeToRemove.upperBound, rangeBelowLB.upperBound)); + } + replaceRangeWithSameLowerBound( + Range.create(rangeBelowLB.lowerBound, rangeToRemove.lowerBound)); + } + } + + Entry, Range> entryBelowUB = rangesByLowerBound.floorEntry(rangeToRemove.upperBound); + if (entryBelowUB != null) { + // { > + Range rangeBelowUB = entryBelowUB.getValue(); + if (rangeToRemove.hasUpperBound() + && rangeBelowUB.upperBound.compareTo(rangeToRemove.upperBound) >= 0) { + // { > } + replaceRangeWithSameLowerBound( + Range.create(rangeToRemove.upperBound, rangeBelowUB.upperBound)); + } + } + + rangesByLowerBound.subMap(rangeToRemove.lowerBound, rangeToRemove.upperBound).clear(); + } + + private void replaceRangeWithSameLowerBound(Range range) { + if (range.isEmpty()) { + rangesByLowerBound.remove(range.lowerBound); + } else { + rangesByLowerBound.put(range.lowerBound, range); + } + } + + private transient RangeSet complement; + + @Override + public RangeSet complement() { + RangeSet result = complement; + return (result == null) ? complement = new Complement() : result; + } + + @VisibleForTesting + static final class RangesByUpperBound> + extends AbstractNavigableMap, Range> { + private final NavigableMap, Range> rangesByLowerBound; + + /** + * upperBoundWindow represents the headMap/subMap/tailMap view of the entire "ranges by upper + * bound" map; it's a constraint on the *keys*, and does not affect the values. + */ + private final Range> upperBoundWindow; + + RangesByUpperBound(NavigableMap, Range> rangesByLowerBound) { + this.rangesByLowerBound = rangesByLowerBound; + this.upperBoundWindow = Range.all(); + } + + private RangesByUpperBound( + NavigableMap, Range> rangesByLowerBound, Range> upperBoundWindow) { + this.rangesByLowerBound = rangesByLowerBound; + this.upperBoundWindow = upperBoundWindow; + } + + private NavigableMap, Range> subMap(Range> window) { + if (window.isConnected(upperBoundWindow)) { + return new RangesByUpperBound(rangesByLowerBound, window.intersection(upperBoundWindow)); + } else { + return ImmutableSortedMap.of(); + } + } + + @Override + public NavigableMap, Range> subMap( + Cut fromKey, boolean fromInclusive, Cut toKey, boolean toInclusive) { + return subMap( + Range.range( + fromKey, BoundType.forBoolean(fromInclusive), + toKey, BoundType.forBoolean(toInclusive))); + } + + @Override + public NavigableMap, Range> headMap(Cut toKey, boolean inclusive) { + return subMap(Range.upTo(toKey, BoundType.forBoolean(inclusive))); + } + + @Override + public NavigableMap, Range> tailMap(Cut fromKey, boolean inclusive) { + return subMap(Range.downTo(fromKey, BoundType.forBoolean(inclusive))); + } + + @Override + public Comparator> comparator() { + return Ordering.>natural(); + } + + @Override + public boolean containsKey(Object key) { + return get(key) != null; + } + + @Override + public Range get(Object key) { + if (key instanceof Cut) { + try { + @SuppressWarnings("unchecked") // we catch CCEs + Cut cut = (Cut) key; + if (!upperBoundWindow.contains(cut)) { + return null; + } + Entry, Range> candidate = rangesByLowerBound.lowerEntry(cut); + if (candidate != null && candidate.getValue().upperBound.equals(cut)) { + return candidate.getValue(); + } + } catch (ClassCastException e) { + return null; + } + } + return null; + } + + @Override + Iterator, Range>> entryIterator() { + /* + * We want to start the iteration at the first range where the upper bound is in + * upperBoundWindow. + */ + final Iterator> backingItr; + if (!upperBoundWindow.hasLowerBound()) { + backingItr = rangesByLowerBound.values().iterator(); + } else { + Entry, Range> lowerEntry = + rangesByLowerBound.lowerEntry(upperBoundWindow.lowerEndpoint()); + if (lowerEntry == null) { + backingItr = rangesByLowerBound.values().iterator(); + } else if (upperBoundWindow.lowerBound.isLessThan(lowerEntry.getValue().upperBound)) { + backingItr = rangesByLowerBound.tailMap(lowerEntry.getKey(), true).values().iterator(); + } else { + backingItr = + rangesByLowerBound + .tailMap(upperBoundWindow.lowerEndpoint(), true) + .values() + .iterator(); + } + } + return new AbstractIterator, Range>>() { + @Override + protected Entry, Range> computeNext() { + if (!backingItr.hasNext()) { + return endOfData(); + } + Range range = backingItr.next(); + if (upperBoundWindow.upperBound.isLessThan(range.upperBound)) { + return endOfData(); + } else { + return Maps.immutableEntry(range.upperBound, range); + } + } + }; + } + + @Override + Iterator, Range>> descendingEntryIterator() { + Collection> candidates; + if (upperBoundWindow.hasUpperBound()) { + candidates = + rangesByLowerBound + .headMap(upperBoundWindow.upperEndpoint(), false) + .descendingMap() + .values(); + } else { + candidates = rangesByLowerBound.descendingMap().values(); + } + final PeekingIterator> backingItr = Iterators.peekingIterator(candidates.iterator()); + if (backingItr.hasNext() + && upperBoundWindow.upperBound.isLessThan(backingItr.peek().upperBound)) { + backingItr.next(); + } + return new AbstractIterator, Range>>() { + @Override + protected Entry, Range> computeNext() { + if (!backingItr.hasNext()) { + return endOfData(); + } + Range range = backingItr.next(); + return upperBoundWindow.lowerBound.isLessThan(range.upperBound) + ? Maps.immutableEntry(range.upperBound, range) + : endOfData(); + } + }; + } + + @Override + public int size() { + if (upperBoundWindow.equals(Range.all())) { + return rangesByLowerBound.size(); + } + return Iterators.size(entryIterator()); + } + + @Override + public boolean isEmpty() { + return upperBoundWindow.equals(Range.all()) + ? rangesByLowerBound.isEmpty() + : !entryIterator().hasNext(); + } + } + + private static final class ComplementRangesByLowerBound> + extends AbstractNavigableMap, Range> { + private final NavigableMap, Range> positiveRangesByLowerBound; + private final NavigableMap, Range> positiveRangesByUpperBound; + + /** + * complementLowerBoundWindow represents the headMap/subMap/tailMap view of the entire + * "complement ranges by lower bound" map; it's a constraint on the *keys*, and does not affect + * the values. + */ + private final Range> complementLowerBoundWindow; + + ComplementRangesByLowerBound(NavigableMap, Range> positiveRangesByLowerBound) { + this(positiveRangesByLowerBound, Range.>all()); + } + + private ComplementRangesByLowerBound( + NavigableMap, Range> positiveRangesByLowerBound, Range> window) { + this.positiveRangesByLowerBound = positiveRangesByLowerBound; + this.positiveRangesByUpperBound = new RangesByUpperBound(positiveRangesByLowerBound); + this.complementLowerBoundWindow = window; + } + + private NavigableMap, Range> subMap(Range> subWindow) { + if (!complementLowerBoundWindow.isConnected(subWindow)) { + return ImmutableSortedMap.of(); + } else { + subWindow = subWindow.intersection(complementLowerBoundWindow); + return new ComplementRangesByLowerBound(positiveRangesByLowerBound, subWindow); + } + } + + @Override + public NavigableMap, Range> subMap( + Cut fromKey, boolean fromInclusive, Cut toKey, boolean toInclusive) { + return subMap( + Range.range( + fromKey, BoundType.forBoolean(fromInclusive), + toKey, BoundType.forBoolean(toInclusive))); + } + + @Override + public NavigableMap, Range> headMap(Cut toKey, boolean inclusive) { + return subMap(Range.upTo(toKey, BoundType.forBoolean(inclusive))); + } + + @Override + public NavigableMap, Range> tailMap(Cut fromKey, boolean inclusive) { + return subMap(Range.downTo(fromKey, BoundType.forBoolean(inclusive))); + } + + @Override + public Comparator> comparator() { + return Ordering.>natural(); + } + + @Override + Iterator, Range>> entryIterator() { + /* + * firstComplementRangeLowerBound is the first complement range lower bound inside + * complementLowerBoundWindow. Complement range lower bounds are either positive range upper + * bounds, or Cut.belowAll(). + * + * positiveItr starts at the first positive range with lower bound greater than + * firstComplementRangeLowerBound. (Positive range lower bounds correspond to complement range + * upper bounds.) + */ + Collection> positiveRanges; + if (complementLowerBoundWindow.hasLowerBound()) { + positiveRanges = + positiveRangesByUpperBound + .tailMap( + complementLowerBoundWindow.lowerEndpoint(), + complementLowerBoundWindow.lowerBoundType() == BoundType.CLOSED) + .values(); + } else { + positiveRanges = positiveRangesByUpperBound.values(); + } + final PeekingIterator> positiveItr = + Iterators.peekingIterator(positiveRanges.iterator()); + final Cut firstComplementRangeLowerBound; + if (complementLowerBoundWindow.contains(Cut.belowAll()) + && (!positiveItr.hasNext() || positiveItr.peek().lowerBound != Cut.belowAll())) { + firstComplementRangeLowerBound = Cut.belowAll(); + } else if (positiveItr.hasNext()) { + firstComplementRangeLowerBound = positiveItr.next().upperBound; + } else { + return Iterators.emptyIterator(); + } + return new AbstractIterator, Range>>() { + Cut nextComplementRangeLowerBound = firstComplementRangeLowerBound; + + @Override + protected Entry, Range> computeNext() { + if (complementLowerBoundWindow.upperBound.isLessThan(nextComplementRangeLowerBound) + || nextComplementRangeLowerBound == Cut.aboveAll()) { + return endOfData(); + } + Range negativeRange; + if (positiveItr.hasNext()) { + Range positiveRange = positiveItr.next(); + negativeRange = Range.create(nextComplementRangeLowerBound, positiveRange.lowerBound); + nextComplementRangeLowerBound = positiveRange.upperBound; + } else { + negativeRange = Range.create(nextComplementRangeLowerBound, Cut.aboveAll()); + nextComplementRangeLowerBound = Cut.aboveAll(); + } + return Maps.immutableEntry(negativeRange.lowerBound, negativeRange); + } + }; + } + + @Override + Iterator, Range>> descendingEntryIterator() { + /* + * firstComplementRangeUpperBound is the upper bound of the last complement range with lower + * bound inside complementLowerBoundWindow. + * + * positiveItr starts at the first positive range with upper bound less than + * firstComplementRangeUpperBound. (Positive range upper bounds correspond to complement range + * lower bounds.) + */ + Cut startingPoint = + complementLowerBoundWindow.hasUpperBound() + ? complementLowerBoundWindow.upperEndpoint() + : Cut.aboveAll(); + boolean inclusive = + complementLowerBoundWindow.hasUpperBound() + && complementLowerBoundWindow.upperBoundType() == BoundType.CLOSED; + final PeekingIterator> positiveItr = + Iterators.peekingIterator( + positiveRangesByUpperBound + .headMap(startingPoint, inclusive) + .descendingMap() + .values() + .iterator()); + Cut cut; + if (positiveItr.hasNext()) { + cut = + (positiveItr.peek().upperBound == Cut.aboveAll()) + ? positiveItr.next().lowerBound + : positiveRangesByLowerBound.higherKey(positiveItr.peek().upperBound); + } else if (!complementLowerBoundWindow.contains(Cut.belowAll()) + || positiveRangesByLowerBound.containsKey(Cut.belowAll())) { + return Iterators.emptyIterator(); + } else { + cut = positiveRangesByLowerBound.higherKey(Cut.belowAll()); + } + final Cut firstComplementRangeUpperBound = + MoreObjects.firstNonNull(cut, Cut.aboveAll()); + return new AbstractIterator, Range>>() { + Cut nextComplementRangeUpperBound = firstComplementRangeUpperBound; + + @Override + protected Entry, Range> computeNext() { + if (nextComplementRangeUpperBound == Cut.belowAll()) { + return endOfData(); + } else if (positiveItr.hasNext()) { + Range positiveRange = positiveItr.next(); + Range negativeRange = + Range.create(positiveRange.upperBound, nextComplementRangeUpperBound); + nextComplementRangeUpperBound = positiveRange.lowerBound; + if (complementLowerBoundWindow.lowerBound.isLessThan(negativeRange.lowerBound)) { + return Maps.immutableEntry(negativeRange.lowerBound, negativeRange); + } + } else if (complementLowerBoundWindow.lowerBound.isLessThan(Cut.belowAll())) { + Range negativeRange = Range.create(Cut.belowAll(), nextComplementRangeUpperBound); + nextComplementRangeUpperBound = Cut.belowAll(); + return Maps.immutableEntry(Cut.belowAll(), negativeRange); + } + return endOfData(); + } + }; + } + + @Override + public int size() { + return Iterators.size(entryIterator()); + } + + @Override + public Range get(Object key) { + if (key instanceof Cut) { + try { + @SuppressWarnings("unchecked") + Cut cut = (Cut) key; + // tailMap respects the current window + Entry, Range> firstEntry = tailMap(cut, true).firstEntry(); + if (firstEntry != null && firstEntry.getKey().equals(cut)) { + return firstEntry.getValue(); + } + } catch (ClassCastException e) { + return null; + } + } + return null; + } + + @Override + public boolean containsKey(Object key) { + return get(key) != null; + } + } + + private final class Complement extends TreeRangeSet { + Complement() { + super(new ComplementRangesByLowerBound(TreeRangeSet.this.rangesByLowerBound)); + } + + @Override + public void add(Range rangeToAdd) { + TreeRangeSet.this.remove(rangeToAdd); + } + + @Override + public void remove(Range rangeToRemove) { + TreeRangeSet.this.add(rangeToRemove); + } + + @Override + public boolean contains(C value) { + return !TreeRangeSet.this.contains(value); + } + + @Override + public RangeSet complement() { + return TreeRangeSet.this; + } + } + + private static final class SubRangeSetRangesByLowerBound> + extends AbstractNavigableMap, Range> { + /** + * lowerBoundWindow is the headMap/subMap/tailMap view; it only restricts the keys, and does not + * affect the values. + */ + private final Range> lowerBoundWindow; + + /** + * restriction is the subRangeSet view; ranges are truncated to their intersection with + * restriction. + */ + private final Range restriction; + + private final NavigableMap, Range> rangesByLowerBound; + private final NavigableMap, Range> rangesByUpperBound; + + private SubRangeSetRangesByLowerBound( + Range> lowerBoundWindow, + Range restriction, + NavigableMap, Range> rangesByLowerBound) { + this.lowerBoundWindow = checkNotNull(lowerBoundWindow); + this.restriction = checkNotNull(restriction); + this.rangesByLowerBound = checkNotNull(rangesByLowerBound); + this.rangesByUpperBound = new RangesByUpperBound(rangesByLowerBound); + } + + private NavigableMap, Range> subMap(Range> window) { + if (!window.isConnected(lowerBoundWindow)) { + return ImmutableSortedMap.of(); + } else { + return new SubRangeSetRangesByLowerBound( + lowerBoundWindow.intersection(window), restriction, rangesByLowerBound); + } + } + + @Override + public NavigableMap, Range> subMap( + Cut fromKey, boolean fromInclusive, Cut toKey, boolean toInclusive) { + return subMap( + Range.range( + fromKey, + BoundType.forBoolean(fromInclusive), + toKey, + BoundType.forBoolean(toInclusive))); + } + + @Override + public NavigableMap, Range> headMap(Cut toKey, boolean inclusive) { + return subMap(Range.upTo(toKey, BoundType.forBoolean(inclusive))); + } + + @Override + public NavigableMap, Range> tailMap(Cut fromKey, boolean inclusive) { + return subMap(Range.downTo(fromKey, BoundType.forBoolean(inclusive))); + } + + @Override + public Comparator> comparator() { + return Ordering.>natural(); + } + + @Override + public boolean containsKey(Object key) { + return get(key) != null; + } + + @Override + public Range get(Object key) { + if (key instanceof Cut) { + try { + @SuppressWarnings("unchecked") // we catch CCE's + Cut cut = (Cut) key; + if (!lowerBoundWindow.contains(cut) + || cut.compareTo(restriction.lowerBound) < 0 + || cut.compareTo(restriction.upperBound) >= 0) { + return null; + } else if (cut.equals(restriction.lowerBound)) { + // it might be present, truncated on the left + Range candidate = Maps.valueOrNull(rangesByLowerBound.floorEntry(cut)); + if (candidate != null && candidate.upperBound.compareTo(restriction.lowerBound) > 0) { + return candidate.intersection(restriction); + } + } else { + Range result = rangesByLowerBound.get(cut); + if (result != null) { + return result.intersection(restriction); + } + } + } catch (ClassCastException e) { + return null; + } + } + return null; + } + + @Override + Iterator, Range>> entryIterator() { + if (restriction.isEmpty()) { + return Iterators.emptyIterator(); + } + final Iterator> completeRangeItr; + if (lowerBoundWindow.upperBound.isLessThan(restriction.lowerBound)) { + return Iterators.emptyIterator(); + } else if (lowerBoundWindow.lowerBound.isLessThan(restriction.lowerBound)) { + // starts at the first range with upper bound strictly greater than restriction.lowerBound + completeRangeItr = + rangesByUpperBound.tailMap(restriction.lowerBound, false).values().iterator(); + } else { + // starts at the first range with lower bound above lowerBoundWindow.lowerBound + completeRangeItr = + rangesByLowerBound + .tailMap( + lowerBoundWindow.lowerBound.endpoint(), + lowerBoundWindow.lowerBoundType() == BoundType.CLOSED) + .values() + .iterator(); + } + final Cut> upperBoundOnLowerBounds = + Ordering.natural() + .min(lowerBoundWindow.upperBound, Cut.belowValue(restriction.upperBound)); + return new AbstractIterator, Range>>() { + @Override + protected Entry, Range> computeNext() { + if (!completeRangeItr.hasNext()) { + return endOfData(); + } + Range nextRange = completeRangeItr.next(); + if (upperBoundOnLowerBounds.isLessThan(nextRange.lowerBound)) { + return endOfData(); + } else { + nextRange = nextRange.intersection(restriction); + return Maps.immutableEntry(nextRange.lowerBound, nextRange); + } + } + }; + } + + @Override + Iterator, Range>> descendingEntryIterator() { + if (restriction.isEmpty()) { + return Iterators.emptyIterator(); + } + Cut> upperBoundOnLowerBounds = + Ordering.natural() + .min(lowerBoundWindow.upperBound, Cut.belowValue(restriction.upperBound)); + final Iterator> completeRangeItr = + rangesByLowerBound + .headMap( + upperBoundOnLowerBounds.endpoint(), + upperBoundOnLowerBounds.typeAsUpperBound() == BoundType.CLOSED) + .descendingMap() + .values() + .iterator(); + return new AbstractIterator, Range>>() { + @Override + protected Entry, Range> computeNext() { + if (!completeRangeItr.hasNext()) { + return endOfData(); + } + Range nextRange = completeRangeItr.next(); + if (restriction.lowerBound.compareTo(nextRange.upperBound) >= 0) { + return endOfData(); + } + nextRange = nextRange.intersection(restriction); + if (lowerBoundWindow.contains(nextRange.lowerBound)) { + return Maps.immutableEntry(nextRange.lowerBound, nextRange); + } else { + return endOfData(); + } + } + }; + } + + @Override + public int size() { + return Iterators.size(entryIterator()); + } + } + + @Override + public RangeSet subRangeSet(Range view) { + return view.equals(Range.all()) ? this : new SubRangeSet(view); + } + + private final class SubRangeSet extends TreeRangeSet { + private final Range restriction; + + SubRangeSet(Range restriction) { + super( + new SubRangeSetRangesByLowerBound( + Range.>all(), restriction, TreeRangeSet.this.rangesByLowerBound)); + this.restriction = restriction; + } + + @Override + public boolean encloses(Range range) { + if (!restriction.isEmpty() && restriction.encloses(range)) { + Range enclosing = TreeRangeSet.this.rangeEnclosing(range); + return enclosing != null && !enclosing.intersection(restriction).isEmpty(); + } + return false; + } + + @Override + public Range rangeContaining(C value) { + if (!restriction.contains(value)) { + return null; + } + Range result = TreeRangeSet.this.rangeContaining(value); + return (result == null) ? null : result.intersection(restriction); + } + + @Override + public void add(Range rangeToAdd) { + checkArgument( + restriction.encloses(rangeToAdd), + "Cannot add range %s to subRangeSet(%s)", + rangeToAdd, + restriction); + super.add(rangeToAdd); + } + + @Override + public void remove(Range rangeToRemove) { + if (rangeToRemove.isConnected(restriction)) { + TreeRangeSet.this.remove(rangeToRemove.intersection(restriction)); + } + } + + @Override + public boolean contains(C value) { + return restriction.contains(value) && TreeRangeSet.this.contains(value); + } + + @Override + public void clear() { + TreeRangeSet.this.remove(restriction); + } + + @Override + public RangeSet subRangeSet(Range view) { + if (view.encloses(restriction)) { + return this; + } else if (view.isConnected(restriction)) { + return new SubRangeSet(restriction.intersection(view)); + } else { + return ImmutableRangeSet.of(); + } + } + } +} diff --git a/src/main/java/com/google/common/collect/TreeTraverser.java b/src/main/java/com/google/common/collect/TreeTraverser.java new file mode 100644 index 0000000..9109252 --- /dev/null +++ b/src/main/java/com/google/common/collect/TreeTraverser.java @@ -0,0 +1,291 @@ +/* + * Copyright (C) 2012 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.annotations.Beta; +import com.google.common.annotations.GwtCompatible; +import com.google.common.base.Function; +import java.util.ArrayDeque; +import java.util.Deque; +import java.util.Iterator; +import java.util.Queue; +import java.util.function.Consumer; + +/** + * Views elements of a type {@code T} as nodes in a tree, and provides methods to traverse the trees + * induced by this traverser. + * + *

For example, the tree + * + *

{@code
+ *        h
+ *      / | \
+ *     /  e  \
+ *    d       g
+ *   /|\      |
+ *  / | \     f
+ * a  b  c
+ * }
+ * + *

can be iterated over in preorder (hdabcegf), postorder (abcdefgh), or breadth-first order + * (hdegabcf). + * + *

Null nodes are strictly forbidden. + * + *

For Java 8 users: Because this is an abstract class, not an interface, you can't use a + * lambda expression to extend it: + * + *

{@code
+ * // won't work
+ * TreeTraverser traverser = node -> node.getChildNodes();
+ * }
+ * + * Instead, you can pass a lambda expression to the {@code using} factory method: + * + *
{@code
+ * TreeTraverser traverser = TreeTraverser.using(node -> node.getChildNodes());
+ * }
+ * + * @author Louis Wasserman + * @since 15.0 + * @deprecated Use {@link com.google.common.graph.Traverser} instead. All instance methods have + * their equivalent on the result of {@code Traverser.forTree(tree)} where {@code tree} + * implements {@code SuccessorsFunction}, which has a similar API as {@link #children} or can be + * the same lambda function as passed into {@link #using(Function)}. + *

This class is scheduled to be removed in October 2019. + */ +// TODO(b/68134636): Remove by 2019-10 +@Deprecated +@Beta +@GwtCompatible +public abstract class TreeTraverser { + + /** + * Returns a tree traverser that uses the given function to navigate from a node to its children. + * This is useful if the function instance already exists, or so that you can supply a lambda + * expressions. If those circumstances don't apply, you probably don't need to use this; subclass + * {@code TreeTraverser} and implement its {@link #children} method directly. + * + * @since 20.0 + * @deprecated Use {@link com.google.common.graph.Traverser#forTree} instead. If you are using a + * lambda, these methods have exactly the same signature. + */ + @Deprecated + public static TreeTraverser using( + final Function> nodeToChildrenFunction) { + checkNotNull(nodeToChildrenFunction); + return new TreeTraverser() { + @Override + public Iterable children(T root) { + return nodeToChildrenFunction.apply(root); + } + }; + } + + /** Returns the children of the specified node. Must not contain null. */ + public abstract Iterable children(T root); + + /** + * Returns an unmodifiable iterable over the nodes in a tree structure, using pre-order traversal. + * That is, each node's subtrees are traversed after the node itself is returned. + * + *

No guarantees are made about the behavior of the traversal when nodes change while iteration + * is in progress or when the iterators generated by {@link #children} are advanced. + * + * @deprecated Use {@link com.google.common.graph.Traverser#depthFirstPreOrder} instead, which has + * the same behavior. + */ + @Deprecated + public final FluentIterable preOrderTraversal(final T root) { + checkNotNull(root); + return new FluentIterable() { + @Override + public UnmodifiableIterator iterator() { + return preOrderIterator(root); + } + + @Override + public void forEach(Consumer action) { + checkNotNull(action); + new Consumer() { + @Override + public void accept(T t) { + action.accept(t); + children(t).forEach(this); + } + }.accept(root); + } + }; + } + + UnmodifiableIterator preOrderIterator(T root) { + return new PreOrderIterator(root); + } + + private final class PreOrderIterator extends UnmodifiableIterator { + private final Deque> stack; + + PreOrderIterator(T root) { + this.stack = new ArrayDeque<>(); + stack.addLast(Iterators.singletonIterator(checkNotNull(root))); + } + + @Override + public boolean hasNext() { + return !stack.isEmpty(); + } + + @Override + public T next() { + Iterator itr = stack.getLast(); // throws NSEE if empty + T result = checkNotNull(itr.next()); + if (!itr.hasNext()) { + stack.removeLast(); + } + Iterator childItr = children(result).iterator(); + if (childItr.hasNext()) { + stack.addLast(childItr); + } + return result; + } + } + + /** + * Returns an unmodifiable iterable over the nodes in a tree structure, using post-order + * traversal. That is, each node's subtrees are traversed before the node itself is returned. + * + *

No guarantees are made about the behavior of the traversal when nodes change while iteration + * is in progress or when the iterators generated by {@link #children} are advanced. + * + * @deprecated Use {@link com.google.common.graph.Traverser#depthFirstPostOrder} instead, which + * has the same behavior. + */ + @Deprecated + public final FluentIterable postOrderTraversal(final T root) { + checkNotNull(root); + return new FluentIterable() { + @Override + public UnmodifiableIterator iterator() { + return postOrderIterator(root); + } + + @Override + public void forEach(Consumer action) { + checkNotNull(action); + new Consumer() { + @Override + public void accept(T t) { + children(t).forEach(this); + action.accept(t); + } + }.accept(root); + } + }; + } + + UnmodifiableIterator postOrderIterator(T root) { + return new PostOrderIterator(root); + } + + private static final class PostOrderNode { + final T root; + final Iterator childIterator; + + PostOrderNode(T root, Iterator childIterator) { + this.root = checkNotNull(root); + this.childIterator = checkNotNull(childIterator); + } + } + + private final class PostOrderIterator extends AbstractIterator { + private final ArrayDeque> stack; + + PostOrderIterator(T root) { + this.stack = new ArrayDeque<>(); + stack.addLast(expand(root)); + } + + @Override + protected T computeNext() { + while (!stack.isEmpty()) { + PostOrderNode top = stack.getLast(); + if (top.childIterator.hasNext()) { + T child = top.childIterator.next(); + stack.addLast(expand(child)); + } else { + stack.removeLast(); + return top.root; + } + } + return endOfData(); + } + + private PostOrderNode expand(T t) { + return new PostOrderNode(t, children(t).iterator()); + } + } + + /** + * Returns an unmodifiable iterable over the nodes in a tree structure, using breadth-first + * traversal. That is, all the nodes of depth 0 are returned, then depth 1, then 2, and so on. + * + *

No guarantees are made about the behavior of the traversal when nodes change while iteration + * is in progress or when the iterators generated by {@link #children} are advanced. + * + * @deprecated Use {@link com.google.common.graph.Traverser#breadthFirst} instead, which has the + * same behavior. + */ + @Deprecated + public final FluentIterable breadthFirstTraversal(final T root) { + checkNotNull(root); + return new FluentIterable() { + @Override + public UnmodifiableIterator iterator() { + return new BreadthFirstIterator(root); + } + }; + } + + private final class BreadthFirstIterator extends UnmodifiableIterator + implements PeekingIterator { + private final Queue queue; + + BreadthFirstIterator(T root) { + this.queue = new ArrayDeque(); + queue.add(root); + } + + @Override + public boolean hasNext() { + return !queue.isEmpty(); + } + + @Override + public T peek() { + return queue.element(); + } + + @Override + public T next() { + T result = queue.remove(); + Iterables.addAll(queue, children(result)); + return result; + } + } +} diff --git a/src/main/java/com/google/common/collect/UnmodifiableIterator.java b/src/main/java/com/google/common/collect/UnmodifiableIterator.java new file mode 100644 index 0000000..f0f76b2 --- /dev/null +++ b/src/main/java/com/google/common/collect/UnmodifiableIterator.java @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2008 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import com.google.common.annotations.GwtCompatible; +import java.util.Iterator; + +/** + * An iterator that does not support {@link #remove}. + * + *

{@code UnmodifiableIterator} is used primarily in conjunction with implementations of {@link + * ImmutableCollection}, such as {@link ImmutableList}. You can, however, convert an existing + * iterator to an {@code UnmodifiableIterator} using {@link Iterators#unmodifiableIterator}. + * + * @author Jared Levy + * @since 2.0 + */ +@GwtCompatible +public abstract class UnmodifiableIterator implements Iterator { + /** Constructor for use by subclasses. */ + protected UnmodifiableIterator() {} + + /** + * Guaranteed to throw an exception and leave the underlying data unmodified. + * + * @throws UnsupportedOperationException always + * @deprecated Unsupported operation. + */ + @Deprecated + @Override + public final void remove() { + throw new UnsupportedOperationException(); + } +} diff --git a/src/main/java/com/google/common/collect/UnmodifiableListIterator.java b/src/main/java/com/google/common/collect/UnmodifiableListIterator.java new file mode 100644 index 0000000..ec4219c --- /dev/null +++ b/src/main/java/com/google/common/collect/UnmodifiableListIterator.java @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2010 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import com.google.common.annotations.GwtCompatible; +import java.util.ListIterator; + +/** + * A list iterator that does not support {@link #remove}, {@link #add}, or {@link #set}. + * + * @since 7.0 + * @author Louis Wasserman + */ +@GwtCompatible +public abstract class UnmodifiableListIterator extends UnmodifiableIterator + implements ListIterator { + /** Constructor for use by subclasses. */ + protected UnmodifiableListIterator() {} + + /** + * Guaranteed to throw an exception and leave the underlying data unmodified. + * + * @throws UnsupportedOperationException always + * @deprecated Unsupported operation. + */ + @Deprecated + @Override + public final void add(E e) { + throw new UnsupportedOperationException(); + } + + /** + * Guaranteed to throw an exception and leave the underlying data unmodified. + * + * @throws UnsupportedOperationException always + * @deprecated Unsupported operation. + */ + @Deprecated + @Override + public final void set(E e) { + throw new UnsupportedOperationException(); + } +} diff --git a/src/main/java/com/google/common/collect/UnmodifiableSortedMultiset.java b/src/main/java/com/google/common/collect/UnmodifiableSortedMultiset.java new file mode 100644 index 0000000..1a921eb --- /dev/null +++ b/src/main/java/com/google/common/collect/UnmodifiableSortedMultiset.java @@ -0,0 +1,110 @@ +/* + * Copyright (C) 2012 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import com.google.common.annotations.GwtCompatible; +import com.google.common.collect.Multisets.UnmodifiableMultiset; +import java.util.Comparator; +import java.util.NavigableSet; + + +/** + * Implementation of {@link Multisets#unmodifiableSortedMultiset(SortedMultiset)}, split out into + * its own file so it can be GWT emulated (to deal with the differing elementSet() types in GWT and + * non-GWT). + * + * @author Louis Wasserman + */ +@GwtCompatible(emulated = true) +final class UnmodifiableSortedMultiset extends UnmodifiableMultiset + implements SortedMultiset { + UnmodifiableSortedMultiset(SortedMultiset delegate) { + super(delegate); + } + + @Override + protected SortedMultiset delegate() { + return (SortedMultiset) super.delegate(); + } + + @Override + public Comparator comparator() { + return delegate().comparator(); + } + + @Override + NavigableSet createElementSet() { + return Sets.unmodifiableNavigableSet(delegate().elementSet()); + } + + @Override + public NavigableSet elementSet() { + return (NavigableSet) super.elementSet(); + } + + private transient UnmodifiableSortedMultiset descendingMultiset; + + @Override + public SortedMultiset descendingMultiset() { + UnmodifiableSortedMultiset result = descendingMultiset; + if (result == null) { + result = new UnmodifiableSortedMultiset(delegate().descendingMultiset()); + result.descendingMultiset = this; + return descendingMultiset = result; + } + return result; + } + + @Override + public Entry firstEntry() { + return delegate().firstEntry(); + } + + @Override + public Entry lastEntry() { + return delegate().lastEntry(); + } + + @Override + public Entry pollFirstEntry() { + throw new UnsupportedOperationException(); + } + + @Override + public Entry pollLastEntry() { + throw new UnsupportedOperationException(); + } + + @Override + public SortedMultiset headMultiset(E upperBound, BoundType boundType) { + return Multisets.unmodifiableSortedMultiset(delegate().headMultiset(upperBound, boundType)); + } + + @Override + public SortedMultiset subMultiset( + E lowerBound, BoundType lowerBoundType, E upperBound, BoundType upperBoundType) { + return Multisets.unmodifiableSortedMultiset( + delegate().subMultiset(lowerBound, lowerBoundType, upperBound, upperBoundType)); + } + + @Override + public SortedMultiset tailMultiset(E lowerBound, BoundType boundType) { + return Multisets.unmodifiableSortedMultiset(delegate().tailMultiset(lowerBound, boundType)); + } + + private static final long serialVersionUID = 0; +} diff --git a/src/main/java/com/google/common/collect/UsingToStringOrdering.java b/src/main/java/com/google/common/collect/UsingToStringOrdering.java new file mode 100644 index 0000000..3167946 --- /dev/null +++ b/src/main/java/com/google/common/collect/UsingToStringOrdering.java @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.collect; + +import com.google.common.annotations.GwtCompatible; +import java.io.Serializable; + +/** An ordering that uses the natural order of the string representation of the values. */ +@GwtCompatible(serializable = true) +final class UsingToStringOrdering extends Ordering implements Serializable { + static final UsingToStringOrdering INSTANCE = new UsingToStringOrdering(); + + @Override + public int compare(Object left, Object right) { + return left.toString().compareTo(right.toString()); + } + + // preserve singleton-ness, so equals() and hashCode() work correctly + private Object readResolve() { + return INSTANCE; + } + + @Override + public String toString() { + return "Ordering.usingToString()"; + } + + private UsingToStringOrdering() {} + + private static final long serialVersionUID = 0; +} diff --git a/src/main/java/com/google/common/escape/ArrayBasedCharEscaper.java b/src/main/java/com/google/common/escape/ArrayBasedCharEscaper.java new file mode 100644 index 0000000..91e48e6 --- /dev/null +++ b/src/main/java/com/google/common/escape/ArrayBasedCharEscaper.java @@ -0,0 +1,149 @@ +/* + * Copyright (C) 2009 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.escape; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.annotations.Beta; +import com.google.common.annotations.GwtCompatible; +import java.util.Map; + +/** + * A {@link CharEscaper} that uses an array to quickly look up replacement characters for a given + * {@code char} value. An additional safe range is provided that determines whether {@code char} + * values without specific replacements are to be considered safe and left unescaped or should be + * escaped in a general way. + * + *

A good example of usage of this class is for Java source code escaping where the replacement + * array contains information about special ASCII characters such as {@code \\t} and {@code \\n} + * while {@link #escapeUnsafe} is overridden to handle general escaping of the form {@code \\uxxxx}. + * + *

The size of the data structure used by {@link ArrayBasedCharEscaper} is proportional to the + * highest valued character that requires escaping. For example a replacement map containing the + * single character '{@code \}{@code u1000}' will require approximately 16K of memory. If you need + * to create multiple escaper instances that have the same character replacement mapping consider + * using {@link ArrayBasedEscaperMap}. + * + * @author Sven Mawson + * @author David Beaumont + * @since 15.0 + */ +@Beta +@GwtCompatible +public abstract class ArrayBasedCharEscaper extends CharEscaper { + // The replacement array (see ArrayBasedEscaperMap). + private final char[][] replacements; + // The number of elements in the replacement array. + private final int replacementsLength; + // The first character in the safe range. + private final char safeMin; + // The last character in the safe range. + private final char safeMax; + + /** + * Creates a new ArrayBasedCharEscaper instance with the given replacement map and specified safe + * range. If {@code safeMax < safeMin} then no characters are considered safe. + * + *

If a character has no mapped replacement then it is checked against the safe range. If it + * lies outside that, then {@link #escapeUnsafe} is called, otherwise no escaping is performed. + * + * @param replacementMap a map of characters to their escaped representations + * @param safeMin the lowest character value in the safe range + * @param safeMax the highest character value in the safe range + */ + protected ArrayBasedCharEscaper( + Map replacementMap, char safeMin, char safeMax) { + + this(ArrayBasedEscaperMap.create(replacementMap), safeMin, safeMax); + } + + /** + * Creates a new ArrayBasedCharEscaper instance with the given replacement map and specified safe + * range. If {@code safeMax < safeMin} then no characters are considered safe. This initializer is + * useful when explicit instances of ArrayBasedEscaperMap are used to allow the sharing of large + * replacement mappings. + * + *

If a character has no mapped replacement then it is checked against the safe range. If it + * lies outside that, then {@link #escapeUnsafe} is called, otherwise no escaping is performed. + * + * @param escaperMap the mapping of characters to be escaped + * @param safeMin the lowest character value in the safe range + * @param safeMax the highest character value in the safe range + */ + protected ArrayBasedCharEscaper(ArrayBasedEscaperMap escaperMap, char safeMin, char safeMax) { + + checkNotNull(escaperMap); // GWT specific check (do not optimize) + this.replacements = escaperMap.getReplacementArray(); + this.replacementsLength = replacements.length; + if (safeMax < safeMin) { + // If the safe range is empty, set the range limits to opposite extremes + // to ensure the first test of either value will (almost certainly) fail. + safeMax = Character.MIN_VALUE; + safeMin = Character.MAX_VALUE; + } + this.safeMin = safeMin; + this.safeMax = safeMax; + } + + /* + * This is overridden to improve performance. Rough benchmarking shows that this almost doubles + * the speed when processing strings that do not require any escaping. + */ + @Override + public final String escape(String s) { + checkNotNull(s); // GWT specific check (do not optimize). + for (int i = 0; i < s.length(); i++) { + char c = s.charAt(i); + if ((c < replacementsLength && replacements[c] != null) || c > safeMax || c < safeMin) { + return escapeSlow(s, i); + } + } + return s; + } + + /** + * Escapes a single character using the replacement array and safe range values. If the given + * character does not have an explicit replacement and lies outside the safe range then {@link + * #escapeUnsafe} is called. + */ + @Override + protected final char[] escape(char c) { + if (c < replacementsLength) { + char[] chars = replacements[c]; + if (chars != null) { + return chars; + } + } + if (c >= safeMin && c <= safeMax) { + return null; + } + return escapeUnsafe(c); + } + + /** + * Escapes a {@code char} value that has no direct explicit value in the replacement array and + * lies outside the stated safe range. Subclasses should override this method to provide + * generalized escaping for characters. + * + *

Note that arrays returned by this method must not be modified once they have been returned. + * However it is acceptable to return the same array multiple times (even for different input + * characters). + * + * @param c the character to escape + * @return the replacement characters, or {@code null} if no escaping was required + */ + // TODO(dbeaumont,cpovirk): Rename this something better once refactoring done + protected abstract char[] escapeUnsafe(char c); +} diff --git a/src/main/java/com/google/common/escape/ArrayBasedEscaperMap.java b/src/main/java/com/google/common/escape/ArrayBasedEscaperMap.java new file mode 100644 index 0000000..400c3b1 --- /dev/null +++ b/src/main/java/com/google/common/escape/ArrayBasedEscaperMap.java @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2009 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.escape; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.annotations.Beta; +import com.google.common.annotations.GwtCompatible; +import com.google.common.annotations.VisibleForTesting; +import java.util.Collections; +import java.util.Map; + +/** + * An implementation-specific parameter class suitable for initializing {@link + * ArrayBasedCharEscaper} or {@link ArrayBasedUnicodeEscaper} instances. This class should be used + * when more than one escaper is created using the same character replacement mapping to allow the + * underlying (implementation specific) data structures to be shared. + * + *

The size of the data structure used by ArrayBasedCharEscaper and ArrayBasedUnicodeEscaper is + * proportional to the highest valued character that has a replacement. For example a replacement + * map containing the single character '{@literal \}u1000' will require approximately 16K of memory. + * As such sharing this data structure between escaper instances is the primary goal of this class. + * + * @author David Beaumont + * @since 15.0 + */ +@Beta +@GwtCompatible +public final class ArrayBasedEscaperMap { + /** + * Returns a new ArrayBasedEscaperMap for creating ArrayBasedCharEscaper or + * ArrayBasedUnicodeEscaper instances. + * + * @param replacements a map of characters to their escaped representations + */ + public static ArrayBasedEscaperMap create(Map replacements) { + return new ArrayBasedEscaperMap(createReplacementArray(replacements)); + } + + // The underlying replacement array we can share between multiple escaper + // instances. + private final char[][] replacementArray; + + private ArrayBasedEscaperMap(char[][] replacementArray) { + this.replacementArray = replacementArray; + } + + // Returns the non-null array of replacements for fast lookup. + char[][] getReplacementArray() { + return replacementArray; + } + + // Creates a replacement array from the given map. The returned array is a + // linear lookup table of replacement character sequences indexed by the + // original character value. + @VisibleForTesting + static char[][] createReplacementArray(Map map) { + checkNotNull(map); // GWT specific check (do not optimize) + if (map.isEmpty()) { + return EMPTY_REPLACEMENT_ARRAY; + } + char max = Collections.max(map.keySet()); + char[][] replacements = new char[max + 1][]; + for (char c : map.keySet()) { + replacements[c] = map.get(c).toCharArray(); + } + return replacements; + } + + // Immutable empty array for when there are no replacements. + private static final char[][] EMPTY_REPLACEMENT_ARRAY = new char[0][0]; +} diff --git a/src/main/java/com/google/common/escape/ArrayBasedUnicodeEscaper.java b/src/main/java/com/google/common/escape/ArrayBasedUnicodeEscaper.java new file mode 100644 index 0000000..75a9ec6 --- /dev/null +++ b/src/main/java/com/google/common/escape/ArrayBasedUnicodeEscaper.java @@ -0,0 +1,203 @@ +/* + * Copyright (C) 2009 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.escape; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.annotations.Beta; +import com.google.common.annotations.GwtCompatible; +import java.util.Map; + + +/** + * A {@link UnicodeEscaper} that uses an array to quickly look up replacement characters for a given + * code point. An additional safe range is provided that determines whether code points without + * specific replacements are to be considered safe and left unescaped or should be escaped in a + * general way. + * + *

A good example of usage of this class is for HTML escaping where the replacement array + * contains information about the named HTML entities such as {@code &} and {@code "} while + * {@link #escapeUnsafe} is overridden to handle general escaping of the form {@code &#NNNNN;}. + * + *

The size of the data structure used by {@link ArrayBasedUnicodeEscaper} is proportional to the + * highest valued code point that requires escaping. For example a replacement map containing the + * single character '{@code \}{@code u1000}' will require approximately 16K of memory. If you need + * to create multiple escaper instances that have the same character replacement mapping consider + * using {@link ArrayBasedEscaperMap}. + * + * @author David Beaumont + * @since 15.0 + */ +@Beta +@GwtCompatible +public abstract class ArrayBasedUnicodeEscaper extends UnicodeEscaper { + // The replacement array (see ArrayBasedEscaperMap). + private final char[][] replacements; + // The number of elements in the replacement array. + private final int replacementsLength; + // The first code point in the safe range. + private final int safeMin; + // The last code point in the safe range. + private final int safeMax; + + // Cropped values used in the fast path range checks. + private final char safeMinChar; + private final char safeMaxChar; + + /** + * Creates a new ArrayBasedUnicodeEscaper instance with the given replacement map and specified + * safe range. If {@code safeMax < safeMin} then no code points are considered safe. + * + *

If a code point has no mapped replacement then it is checked against the safe range. If it + * lies outside that, then {@link #escapeUnsafe} is called, otherwise no escaping is performed. + * + * @param replacementMap a map of characters to their escaped representations + * @param safeMin the lowest character value in the safe range + * @param safeMax the highest character value in the safe range + * @param unsafeReplacement the default replacement for unsafe characters or null if no default + * replacement is required + */ + protected ArrayBasedUnicodeEscaper( + Map replacementMap, + int safeMin, + int safeMax, + String unsafeReplacement) { + this(ArrayBasedEscaperMap.create(replacementMap), safeMin, safeMax, unsafeReplacement); + } + + /** + * Creates a new ArrayBasedUnicodeEscaper instance with the given replacement map and specified + * safe range. If {@code safeMax < safeMin} then no code points are considered safe. This + * initializer is useful when explicit instances of ArrayBasedEscaperMap are used to allow the + * sharing of large replacement mappings. + * + *

If a code point has no mapped replacement then it is checked against the safe range. If it + * lies outside that, then {@link #escapeUnsafe} is called, otherwise no escaping is performed. + * + * @param escaperMap the map of replacements + * @param safeMin the lowest character value in the safe range + * @param safeMax the highest character value in the safe range + * @param unsafeReplacement the default replacement for unsafe characters or null if no default + * replacement is required + */ + protected ArrayBasedUnicodeEscaper( + ArrayBasedEscaperMap escaperMap, + int safeMin, + int safeMax, + String unsafeReplacement) { + checkNotNull(escaperMap); // GWT specific check (do not optimize) + this.replacements = escaperMap.getReplacementArray(); + this.replacementsLength = replacements.length; + if (safeMax < safeMin) { + // If the safe range is empty, set the range limits to opposite extremes + // to ensure the first test of either value will fail. + safeMax = -1; + safeMin = Integer.MAX_VALUE; + } + this.safeMin = safeMin; + this.safeMax = safeMax; + + // This is a bit of a hack but lets us do quicker per-character checks in + // the fast path code. The safe min/max values are very unlikely to extend + // into the range of surrogate characters, but if they do we must not test + // any values in that range. To see why, consider the case where: + // safeMin <= {hi,lo} <= safeMax + // where {hi,lo} are characters forming a surrogate pair such that: + // codePointOf(hi, lo) > safeMax + // which would result in the surrogate pair being (wrongly) considered safe. + // If we clip the safe range used during the per-character tests so it is + // below the values of characters in surrogate pairs, this cannot occur. + // This approach does mean that we break out of the fast path code in cases + // where we don't strictly need to, but this situation will almost never + // occur in practice. + if (safeMin >= Character.MIN_HIGH_SURROGATE) { + // The safe range is empty or the all safe code points lie in or above the + // surrogate range. Either way the character range is empty. + this.safeMinChar = Character.MAX_VALUE; + this.safeMaxChar = 0; + } else { + // The safe range is non empty and contains values below the surrogate + // range but may extend above it. We may need to clip the maximum value. + this.safeMinChar = (char) safeMin; + this.safeMaxChar = (char) Math.min(safeMax, Character.MIN_HIGH_SURROGATE - 1); + } + } + + /* + * This is overridden to improve performance. Rough benchmarking shows that this almost doubles + * the speed when processing strings that do not require any escaping. + */ + @Override + public final String escape(String s) { + checkNotNull(s); // GWT specific check (do not optimize) + for (int i = 0; i < s.length(); i++) { + char c = s.charAt(i); + if ((c < replacementsLength && replacements[c] != null) + || c > safeMaxChar + || c < safeMinChar) { + return escapeSlow(s, i); + } + } + return s; + } + + /** + * Escapes a single Unicode code point using the replacement array and safe range values. If the + * given character does not have an explicit replacement and lies outside the safe range then + * {@link #escapeUnsafe} is called. + */ + @Override + protected final char[] escape(int cp) { + if (cp < replacementsLength) { + char[] chars = replacements[cp]; + if (chars != null) { + return chars; + } + } + if (cp >= safeMin && cp <= safeMax) { + return null; + } + return escapeUnsafe(cp); + } + + /* Overridden for performance. */ + @Override + protected final int nextEscapeIndex(CharSequence csq, int index, int end) { + while (index < end) { + char c = csq.charAt(index); + if ((c < replacementsLength && replacements[c] != null) + || c > safeMaxChar + || c < safeMinChar) { + break; + } + index++; + } + return index; + } + + /** + * Escapes a code point that has no direct explicit value in the replacement array and lies + * outside the stated safe range. Subclasses should override this method to provide generalized + * escaping for code points if required. + * + *

Note that arrays returned by this method must not be modified once they have been returned. + * However it is acceptable to return the same array multiple times (even for different input + * characters). + * + * @param cp the Unicode code point to escape + * @return the replacement characters, or {@code null} if no escaping was required + */ + protected abstract char[] escapeUnsafe(int cp); +} diff --git a/src/main/java/com/google/common/escape/CharEscaper.java b/src/main/java/com/google/common/escape/CharEscaper.java new file mode 100644 index 0000000..b8ffee3 --- /dev/null +++ b/src/main/java/com/google/common/escape/CharEscaper.java @@ -0,0 +1,175 @@ +/* + * Copyright (C) 2006 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.escape; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.annotations.Beta; +import com.google.common.annotations.GwtCompatible; + +/** + * An object that converts literal text into a format safe for inclusion in a particular context + * (such as an XML document). Typically (but not always), the inverse process of "unescaping" the + * text is performed automatically by the relevant parser. + * + *

For example, an XML escaper would convert the literal string {@code "Foo"} into {@code + * "Foo<Bar>"} to prevent {@code ""} from being confused with an XML tag. When the + * resulting XML document is parsed, the parser API will return this text as the original literal + * string {@code "Foo"}. + * + *

A {@code CharEscaper} instance is required to be stateless, and safe when used concurrently by + * multiple threads. + * + *

Popular escapers are defined as constants in classes like {@link + * com.google.common.html.HtmlEscapers} and {@link com.google.common.xml.XmlEscapers}. To create + * your own escapers extend this class and implement the {@link #escape(char)} method. + * + * @author Sven Mawson + * @since 15.0 + */ +@Beta +@GwtCompatible +public abstract class CharEscaper extends Escaper { + /** Constructor for use by subclasses. */ + protected CharEscaper() {} + + /** + * Returns the escaped form of a given literal string. + * + * @param string the literal string to be escaped + * @return the escaped form of {@code string} + * @throws NullPointerException if {@code string} is null + */ + @Override + public String escape(String string) { + checkNotNull(string); // GWT specific check (do not optimize) + // Inlineable fast-path loop which hands off to escapeSlow() only if needed + int length = string.length(); + for (int index = 0; index < length; index++) { + if (escape(string.charAt(index)) != null) { + return escapeSlow(string, index); + } + } + return string; + } + + /** + * Returns the escaped form of the given character, or {@code null} if this character does not + * need to be escaped. If an empty array is returned, this effectively strips the input character + * from the resulting text. + * + *

If the character does not need to be escaped, this method should return {@code null}, rather + * than a one-character array containing the character itself. This enables the escaping algorithm + * to perform more efficiently. + * + *

An escaper is expected to be able to deal with any {@code char} value, so this method should + * not throw any exceptions. + * + * @param c the character to escape if necessary + * @return the replacement characters, or {@code null} if no escaping was needed + */ + protected abstract char[] escape(char c); + + /** + * Returns the escaped form of a given literal string, starting at the given index. This method is + * called by the {@link #escape(String)} method when it discovers that escaping is required. It is + * protected to allow subclasses to override the fastpath escaping function to inline their + * escaping test. See {@link CharEscaperBuilder} for an example usage. + * + * @param s the literal string to be escaped + * @param index the index to start escaping from + * @return the escaped form of {@code string} + * @throws NullPointerException if {@code string} is null + */ + protected final String escapeSlow(String s, int index) { + int slen = s.length(); + + // Get a destination buffer and setup some loop variables. + char[] dest = Platform.charBufferFromThreadLocal(); + int destSize = dest.length; + int destIndex = 0; + int lastEscape = 0; + + // Loop through the rest of the string, replacing when needed into the + // destination buffer, which gets grown as needed as well. + for (; index < slen; index++) { + + // Get a replacement for the current character. + char[] r = escape(s.charAt(index)); + + // If no replacement is needed, just continue. + if (r == null) { + continue; + } + + int rlen = r.length; + int charsSkipped = index - lastEscape; + + // This is the size needed to add the replacement, not the full size + // needed by the string. We only regrow when we absolutely must, and + // when we do grow, grow enough to avoid excessive growing. Grow. + int sizeNeeded = destIndex + charsSkipped + rlen; + if (destSize < sizeNeeded) { + destSize = sizeNeeded + DEST_PAD_MULTIPLIER * (slen - index); + dest = growBuffer(dest, destIndex, destSize); + } + + // If we have skipped any characters, we need to copy them now. + if (charsSkipped > 0) { + s.getChars(lastEscape, index, dest, destIndex); + destIndex += charsSkipped; + } + + // Copy the replacement string into the dest buffer as needed. + if (rlen > 0) { + System.arraycopy(r, 0, dest, destIndex, rlen); + destIndex += rlen; + } + lastEscape = index + 1; + } + + // Copy leftover characters if there are any. + int charsLeft = slen - lastEscape; + if (charsLeft > 0) { + int sizeNeeded = destIndex + charsLeft; + if (destSize < sizeNeeded) { + + // Regrow and copy, expensive! No padding as this is the final copy. + dest = growBuffer(dest, destIndex, sizeNeeded); + } + s.getChars(lastEscape, slen, dest, destIndex); + destIndex = sizeNeeded; + } + return new String(dest, 0, destIndex); + } + + /** + * Helper method to grow the character buffer as needed, this only happens once in a while so it's + * ok if it's in a method call. If the index passed in is 0 then no copying will be done. + */ + private static char[] growBuffer(char[] dest, int index, int size) { + if (size < 0) { // overflow - should be OutOfMemoryError but GWT/j2cl don't support it + throw new AssertionError("Cannot increase internal buffer any further"); + } + char[] copy = new char[size]; + if (index > 0) { + System.arraycopy(dest, 0, copy, 0, index); + } + return copy; + } + + /** The multiplier for padding to use when growing the escape buffer. */ + private static final int DEST_PAD_MULTIPLIER = 2; +} diff --git a/src/main/java/com/google/common/escape/CharEscaperBuilder.java b/src/main/java/com/google/common/escape/CharEscaperBuilder.java new file mode 100644 index 0000000..1b1cceb --- /dev/null +++ b/src/main/java/com/google/common/escape/CharEscaperBuilder.java @@ -0,0 +1,128 @@ +/* + * Copyright (C) 2006 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.escape; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.annotations.Beta; +import com.google.common.annotations.GwtCompatible; + +import java.util.HashMap; +import java.util.Map; +import java.util.Map.Entry; + +/** + * Simple helper class to build a "sparse" array of objects based on the indexes that were added to + * it. The array will be from 0 to the maximum index given. All non-set indexes will contain null + * (so it's not really a sparse array, just a pseudo sparse array). The builder can also return a + * CharEscaper based on the generated array. + * + * @author Sven Mawson + * @since 15.0 + */ +@Beta +@GwtCompatible +public final class CharEscaperBuilder { + /** + * Simple decorator that turns an array of replacement char[]s into a CharEscaper, this results in + * a very fast escape method. + */ + private static class CharArrayDecorator extends CharEscaper { + private final char[][] replacements; + private final int replaceLength; + + CharArrayDecorator(char[][] replacements) { + this.replacements = replacements; + this.replaceLength = replacements.length; + } + + /* + * Overriding escape method to be slightly faster for this decorator. We test the replacements + * array directly, saving a method call. + */ + @Override + public String escape(String s) { + int slen = s.length(); + for (int index = 0; index < slen; index++) { + char c = s.charAt(index); + if (c < replacements.length && replacements[c] != null) { + return escapeSlow(s, index); + } + } + return s; + } + + @Override + protected char[] escape(char c) { + return c < replaceLength ? replacements[c] : null; + } + } + + // Replacement mappings. + private final Map map; + + // The highest index we've seen so far. + private int max = -1; + + /** Construct a new sparse array builder. */ + public CharEscaperBuilder() { + this.map = new HashMap<>(); + } + + /** Add a new mapping from an index to an object to the escaping. */ + + public CharEscaperBuilder addEscape(char c, String r) { + map.put(c, checkNotNull(r)); + if (c > max) { + max = c; + } + return this; + } + + /** Add multiple mappings at once for a particular index. */ + + public CharEscaperBuilder addEscapes(char[] cs, String r) { + checkNotNull(r); + for (char c : cs) { + addEscape(c, r); + } + return this; + } + + /** + * Convert this builder into an array of char[]s where the maximum index is the value of the + * highest character that has been seen. The array will be sparse in the sense that any unseen + * index will default to null. + * + * @return a "sparse" array that holds the replacement mappings. + */ + public char[][] toArray() { + char[][] result = new char[max + 1][]; + for (Entry entry : map.entrySet()) { + result[entry.getKey()] = entry.getValue().toCharArray(); + } + return result; + } + + /** + * Convert this builder into a char escaper which is just a decorator around the underlying array + * of replacement char[]s. + * + * @return an escaper that escapes based on the underlying array. + */ + public Escaper toEscaper() { + return new CharArrayDecorator(toArray()); + } +} diff --git a/src/main/java/com/google/common/escape/Escaper.java b/src/main/java/com/google/common/escape/Escaper.java new file mode 100644 index 0000000..97abc75 --- /dev/null +++ b/src/main/java/com/google/common/escape/Escaper.java @@ -0,0 +1,97 @@ +/* + * Copyright (C) 2008 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.escape; + +import com.google.common.annotations.GwtCompatible; +import com.google.common.base.Function; + +/** + * An object that converts literal text into a format safe for inclusion in a particular context + * (such as an XML document). Typically (but not always), the inverse process of "unescaping" the + * text is performed automatically by the relevant parser. + * + *

For example, an XML escaper would convert the literal string {@code "Foo"} into {@code + * "Foo<Bar>"} to prevent {@code ""} from being confused with an XML tag. When the + * resulting XML document is parsed, the parser API will return this text as the original literal + * string {@code "Foo"}. + * + *

An {@code Escaper} instance is required to be stateless, and safe when used concurrently by + * multiple threads. + * + *

Because, in general, escaping operates on the code points of a string and not on its + * individual {@code char} values, it is not safe to assume that {@code escape(s)} is equivalent to + * {@code escape(s.substring(0, n)) + escape(s.substring(n))} for arbitrary {@code n}. This is + * because of the possibility of splitting a surrogate pair. The only case in which it is safe to + * escape strings and concatenate the results is if you can rule out this possibility, either by + * splitting an existing long string into short strings adaptively around {@linkplain + * Character#isHighSurrogate surrogate} {@linkplain Character#isLowSurrogate pairs}, or by starting + * with short strings already known to be free of unpaired surrogates. + * + *

The two primary implementations of this interface are {@link CharEscaper} and {@link + * UnicodeEscaper}. They are heavily optimized for performance and greatly simplify the task of + * implementing new escapers. It is strongly recommended that when implementing a new escaper you + * extend one of these classes. If you find that you are unable to achieve the desired behavior + * using either of these classes, please contact the Java libraries team for advice. + * + *

Popular escapers are defined as constants in classes like {@link + * com.google.common.html.HtmlEscapers} and {@link com.google.common.xml.XmlEscapers}. To create + * your own escapers, use {@link CharEscaperBuilder}, or extend {@code CharEscaper} or {@code + * UnicodeEscaper}. + * + * @author David Beaumont + * @since 15.0 + */ +@GwtCompatible +public abstract class Escaper { + // TODO(dbeaumont): evaluate custom implementations, considering package private constructor. + /** Constructor for use by subclasses. */ + protected Escaper() {} + + /** + * Returns the escaped form of a given literal string. + * + *

Note that this method may treat input characters differently depending on the specific + * escaper implementation. + * + *

    + *
  • {@link UnicodeEscaper} handles UTF-16 + * correctly, including surrogate character pairs. If the input is badly formed the escaper + * should throw {@link IllegalArgumentException}. + *
  • {@link CharEscaper} handles Java characters independently and does not verify the input + * for well formed characters. A {@code CharEscaper} should not be used in situations where + * input is not guaranteed to be restricted to the Basic Multilingual Plane (BMP). + *
+ * + * @param string the literal string to be escaped + * @return the escaped form of {@code string} + * @throws NullPointerException if {@code string} is null + * @throws IllegalArgumentException if {@code string} contains badly formed UTF-16 or cannot be + * escaped for any other reason + */ + public abstract String escape(String string); + + private final Function asFunction = + new Function() { + @Override + public String apply(String from) { + return escape(from); + } + }; + + /** Returns a {@link Function} that invokes {@link #escape(String)} on this escaper. */ + public final Function asFunction() { + return asFunction; + } +} diff --git a/src/main/java/com/google/common/escape/Escapers.java b/src/main/java/com/google/common/escape/Escapers.java new file mode 100644 index 0000000..e796ca2 --- /dev/null +++ b/src/main/java/com/google/common/escape/Escapers.java @@ -0,0 +1,271 @@ +/* + * Copyright (C) 2009 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.escape; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.annotations.Beta; +import com.google.common.annotations.GwtCompatible; + +import java.util.HashMap; +import java.util.Map; + + +/** + * Static utility methods pertaining to {@link Escaper} instances. + * + * @author Sven Mawson + * @author David Beaumont + * @since 15.0 + */ +@Beta +@GwtCompatible +public final class Escapers { + private Escapers() {} + + /** + * Returns an {@link Escaper} that does no escaping, passing all character data through unchanged. + */ + public static Escaper nullEscaper() { + return NULL_ESCAPER; + } + + // An Escaper that efficiently performs no escaping. + // Extending CharEscaper (instead of Escaper) makes Escapers.compose() easier. + private static final Escaper NULL_ESCAPER = + new CharEscaper() { + @Override + public String escape(String string) { + return checkNotNull(string); + } + + @Override + protected char[] escape(char c) { + // TODO: Fix tests not to call this directly and make it throw an error. + return null; + } + }; + + /** + * Returns a builder for creating simple, fast escapers. A builder instance can be reused and each + * escaper that is created will be a snapshot of the current builder state. Builders are not + * thread safe. + * + *

The initial state of the builder is such that: + * + *

    + *
  • There are no replacement mappings + *
  • {@code safeMin == Character.MIN_VALUE} + *
  • {@code safeMax == Character.MAX_VALUE} + *
  • {@code unsafeReplacement == null} + *
+ * + *

For performance reasons escapers created by this builder are not Unicode aware and will not + * validate the well-formedness of their input. + */ + public static Builder builder() { + return new Builder(); + } + + /** + * A builder for simple, fast escapers. + * + *

Typically an escaper needs to deal with the escaping of high valued characters or code + * points. In these cases it is necessary to extend either {@link ArrayBasedCharEscaper} or {@link + * ArrayBasedUnicodeEscaper} to provide the desired behavior. However this builder is suitable for + * creating escapers that replace a relative small set of characters. + * + * @author David Beaumont + * @since 15.0 + */ + @Beta + public static final class Builder { + private final Map replacementMap = new HashMap<>(); + private char safeMin = Character.MIN_VALUE; + private char safeMax = Character.MAX_VALUE; + private String unsafeReplacement = null; + + // The constructor is exposed via the builder() method above. + private Builder() {} + + /** + * Sets the safe range of characters for the escaper. Characters in this range that have no + * explicit replacement are considered 'safe' and remain unescaped in the output. If {@code + * safeMax < safeMin} then the safe range is empty. + * + * @param safeMin the lowest 'safe' character + * @param safeMax the highest 'safe' character + * @return the builder instance + */ + + public Builder setSafeRange(char safeMin, char safeMax) { + this.safeMin = safeMin; + this.safeMax = safeMax; + return this; + } + + /** + * Sets the replacement string for any characters outside the 'safe' range that have no explicit + * replacement. If {@code unsafeReplacement} is {@code null} then no replacement will occur, if + * it is {@code ""} then the unsafe characters are removed from the output. + * + * @param unsafeReplacement the string to replace unsafe characters + * @return the builder instance + */ + + public Builder setUnsafeReplacement(String unsafeReplacement) { + this.unsafeReplacement = unsafeReplacement; + return this; + } + + /** + * Adds a replacement string for the given input character. The specified character will be + * replaced by the given string whenever it occurs in the input, irrespective of whether it lies + * inside or outside the 'safe' range. + * + * @param c the character to be replaced + * @param replacement the string to replace the given character + * @return the builder instance + * @throws NullPointerException if {@code replacement} is null + */ + + public Builder addEscape(char c, String replacement) { + checkNotNull(replacement); + // This can replace an existing character (the builder is re-usable). + replacementMap.put(c, replacement); + return this; + } + + /** Returns a new escaper based on the current state of the builder. */ + public Escaper build() { + return new ArrayBasedCharEscaper(replacementMap, safeMin, safeMax) { + private final char[] replacementChars = + unsafeReplacement != null ? unsafeReplacement.toCharArray() : null; + + @Override + protected char[] escapeUnsafe(char c) { + return replacementChars; + } + }; + } + } + + /** + * Returns a {@link UnicodeEscaper} equivalent to the given escaper instance. If the escaper is + * already a UnicodeEscaper then it is simply returned, otherwise it is wrapped in a + * UnicodeEscaper. + * + *

When a {@link CharEscaper} escaper is wrapped by this method it acquires extra behavior with + * respect to the well-formedness of Unicode character sequences and will throw {@link + * IllegalArgumentException} when given bad input. + * + * @param escaper the instance to be wrapped + * @return a UnicodeEscaper with the same behavior as the given instance + * @throws NullPointerException if escaper is null + * @throws IllegalArgumentException if escaper is not a UnicodeEscaper or a CharEscaper + */ + static UnicodeEscaper asUnicodeEscaper(Escaper escaper) { + checkNotNull(escaper); + if (escaper instanceof UnicodeEscaper) { + return (UnicodeEscaper) escaper; + } else if (escaper instanceof CharEscaper) { + return wrap((CharEscaper) escaper); + } + // In practice this shouldn't happen because it would be very odd not to + // extend either CharEscaper or UnicodeEscaper for non trivial cases. + throw new IllegalArgumentException( + "Cannot create a UnicodeEscaper from: " + escaper.getClass().getName()); + } + + /** + * Returns a string that would replace the given character in the specified escaper, or {@code + * null} if no replacement should be made. This method is intended for use in tests through the + * {@code EscaperAsserts} class; production users of {@link CharEscaper} should limit themselves + * to its public interface. + * + * @param c the character to escape if necessary + * @return the replacement string, or {@code null} if no escaping was needed + */ + public static String computeReplacement(CharEscaper escaper, char c) { + return stringOrNull(escaper.escape(c)); + } + + /** + * Returns a string that would replace the given character in the specified escaper, or {@code + * null} if no replacement should be made. This method is intended for use in tests through the + * {@code EscaperAsserts} class; production users of {@link UnicodeEscaper} should limit + * themselves to its public interface. + * + * @param cp the Unicode code point to escape if necessary + * @return the replacement string, or {@code null} if no escaping was needed + */ + public static String computeReplacement(UnicodeEscaper escaper, int cp) { + return stringOrNull(escaper.escape(cp)); + } + + private static String stringOrNull(char[] in) { + return (in == null) ? null : new String(in); + } + + /** Private helper to wrap a CharEscaper as a UnicodeEscaper. */ + private static UnicodeEscaper wrap(final CharEscaper escaper) { + return new UnicodeEscaper() { + @Override + protected char[] escape(int cp) { + // If a code point maps to a single character, just escape that. + if (cp < Character.MIN_SUPPLEMENTARY_CODE_POINT) { + return escaper.escape((char) cp); + } + // Convert the code point to a surrogate pair and escape them both. + // Note: This code path is horribly slow and typically allocates 4 new + // char[] each time it is invoked. However this avoids any + // synchronization issues and makes the escaper thread safe. + char[] surrogateChars = new char[2]; + Character.toChars(cp, surrogateChars, 0); + char[] hiChars = escaper.escape(surrogateChars[0]); + char[] loChars = escaper.escape(surrogateChars[1]); + + // If either hiChars or lowChars are non-null, the CharEscaper is trying + // to escape the characters of a surrogate pair separately. This is + // uncommon and applies only to escapers that assume UCS-2 rather than + // UTF-16. See: http://en.wikipedia.org/wiki/UTF-16/UCS-2 + if (hiChars == null && loChars == null) { + // We expect this to be the common code path for most escapers. + return null; + } + // Combine the characters and/or escaped sequences into a single array. + int hiCount = hiChars != null ? hiChars.length : 1; + int loCount = loChars != null ? loChars.length : 1; + char[] output = new char[hiCount + loCount]; + if (hiChars != null) { + // TODO: Is this faster than System.arraycopy() for small arrays? + for (int n = 0; n < hiChars.length; ++n) { + output[n] = hiChars[n]; + } + } else { + output[0] = surrogateChars[0]; + } + if (loChars != null) { + for (int n = 0; n < loChars.length; ++n) { + output[hiCount + n] = loChars[n]; + } + } else { + output[hiCount] = surrogateChars[1]; + } + return output; + } + }; + } +} diff --git a/src/main/java/com/google/common/escape/Platform.java b/src/main/java/com/google/common/escape/Platform.java new file mode 100644 index 0000000..99a7d4f --- /dev/null +++ b/src/main/java/com/google/common/escape/Platform.java @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2009 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.escape; + +import com.google.common.annotations.GwtCompatible; + +/** + * Methods factored out so that they can be emulated differently in GWT. + * + * @author Jesse Wilson + */ +@GwtCompatible(emulated = true) +final class Platform { + private Platform() {} + + /** Returns a thread-local 1024-char array. */ + static char[] charBufferFromThreadLocal() { + return DEST_TL.get(); + } + + /** + * A thread-local destination buffer to keep us from creating new buffers. The starting size is + * 1024 characters. If we grow past this we don't put it back in the threadlocal, we just keep + * going and grow as needed. + */ + private static final ThreadLocal DEST_TL = + new ThreadLocal() { + @Override + protected char[] initialValue() { + return new char[1024]; + } + }; +} diff --git a/src/main/java/com/google/common/escape/UnicodeEscaper.java b/src/main/java/com/google/common/escape/UnicodeEscaper.java new file mode 100644 index 0000000..0642162 --- /dev/null +++ b/src/main/java/com/google/common/escape/UnicodeEscaper.java @@ -0,0 +1,302 @@ +/* + * Copyright (C) 2008 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.escape; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.annotations.Beta; +import com.google.common.annotations.GwtCompatible; + +/** + * An {@link Escaper} that converts literal text into a format safe for inclusion in a particular + * context (such as an XML document). Typically (but not always), the inverse process of + * "unescaping" the text is performed automatically by the relevant parser. + * + *

For example, an XML escaper would convert the literal string {@code "Foo"} into {@code + * "Foo<Bar>"} to prevent {@code ""} from being confused with an XML tag. When the + * resulting XML document is parsed, the parser API will return this text as the original literal + * string {@code "Foo"}. + * + *

Note: This class is similar to {@link CharEscaper} but with one very important + * difference. A CharEscaper can only process Java UTF16 characters in isolation and may not cope + * when it encounters surrogate pairs. This class facilitates the correct escaping of all Unicode + * characters. + * + *

As there are important reasons, including potential security issues, to handle Unicode + * correctly if you are considering implementing a new escaper you should favor using UnicodeEscaper + * wherever possible. + * + *

A {@code UnicodeEscaper} instance is required to be stateless, and safe when used concurrently + * by multiple threads. + * + *

Popular escapers are defined as constants in classes like {@link + * com.google.common.html.HtmlEscapers} and {@link com.google.common.xml.XmlEscapers}. To create + * your own escapers extend this class and implement the {@link #escape(int)} method. + * + * @author David Beaumont + * @since 15.0 + */ +@Beta +@GwtCompatible +public abstract class UnicodeEscaper extends Escaper { + /** The amount of padding (chars) to use when growing the escape buffer. */ + private static final int DEST_PAD = 32; + + /** Constructor for use by subclasses. */ + protected UnicodeEscaper() {} + + /** + * Returns the escaped form of the given Unicode code point, or {@code null} if this code point + * does not need to be escaped. When called as part of an escaping operation, the given code point + * is guaranteed to be in the range {@code 0 <= cp <= Character#MAX_CODE_POINT}. + * + *

If an empty array is returned, this effectively strips the input character from the + * resulting text. + * + *

If the character does not need to be escaped, this method should return {@code null}, rather + * than an array containing the character representation of the code point. This enables the + * escaping algorithm to perform more efficiently. + * + *

If the implementation of this method cannot correctly handle a particular code point then it + * should either throw an appropriate runtime exception or return a suitable replacement + * character. It must never silently discard invalid input as this may constitute a security risk. + * + * @param cp the Unicode code point to escape if necessary + * @return the replacement characters, or {@code null} if no escaping was needed + */ + protected abstract char[] escape(int cp); + + /** + * Returns the escaped form of a given literal string. + * + *

If you are escaping input in arbitrary successive chunks, then it is not generally safe to + * use this method. If an input string ends with an unmatched high surrogate character, then this + * method will throw {@link IllegalArgumentException}. You should ensure your input is valid UTF-16 before calling this method. + * + *

Note: When implementing an escaper it is a good idea to override this method for + * efficiency by inlining the implementation of {@link #nextEscapeIndex(CharSequence, int, int)} + * directly. Doing this for {@link com.google.common.net.PercentEscaper} more than doubled the + * performance for unescaped strings (as measured by {@code CharEscapersBenchmark}). + * + * @param string the literal string to be escaped + * @return the escaped form of {@code string} + * @throws NullPointerException if {@code string} is null + * @throws IllegalArgumentException if invalid surrogate characters are encountered + */ + @Override + public String escape(String string) { + checkNotNull(string); + int end = string.length(); + int index = nextEscapeIndex(string, 0, end); + return index == end ? string : escapeSlow(string, index); + } + + /** + * Scans a sub-sequence of characters from a given {@link CharSequence}, returning the index of + * the next character that requires escaping. + * + *

Note: When implementing an escaper, it is a good idea to override this method for + * efficiency. The base class implementation determines successive Unicode code points and invokes + * {@link #escape(int)} for each of them. If the semantics of your escaper are such that code + * points in the supplementary range are either all escaped or all unescaped, this method can be + * implemented more efficiently using {@link CharSequence#charAt(int)}. + * + *

Note however that if your escaper does not escape characters in the supplementary range, you + * should either continue to validate the correctness of any surrogate characters encountered or + * provide a clear warning to users that your escaper does not validate its input. + * + *

See {@link com.google.common.net.PercentEscaper} for an example. + * + * @param csq a sequence of characters + * @param start the index of the first character to be scanned + * @param end the index immediately after the last character to be scanned + * @throws IllegalArgumentException if the scanned sub-sequence of {@code csq} contains invalid + * surrogate pairs + */ + protected int nextEscapeIndex(CharSequence csq, int start, int end) { + int index = start; + while (index < end) { + int cp = codePointAt(csq, index, end); + if (cp < 0 || escape(cp) != null) { + break; + } + index += Character.isSupplementaryCodePoint(cp) ? 2 : 1; + } + return index; + } + + /** + * Returns the escaped form of a given literal string, starting at the given index. This method is + * called by the {@link #escape(String)} method when it discovers that escaping is required. It is + * protected to allow subclasses to override the fastpath escaping function to inline their + * escaping test. See {@link CharEscaperBuilder} for an example usage. + * + *

This method is not reentrant and may only be invoked by the top level {@link + * #escape(String)} method. + * + * @param s the literal string to be escaped + * @param index the index to start escaping from + * @return the escaped form of {@code string} + * @throws NullPointerException if {@code string} is null + * @throws IllegalArgumentException if invalid surrogate characters are encountered + */ + protected final String escapeSlow(String s, int index) { + int end = s.length(); + + // Get a destination buffer and setup some loop variables. + char[] dest = Platform.charBufferFromThreadLocal(); + int destIndex = 0; + int unescapedChunkStart = 0; + + while (index < end) { + int cp = codePointAt(s, index, end); + if (cp < 0) { + throw new IllegalArgumentException("Trailing high surrogate at end of input"); + } + // It is possible for this to return null because nextEscapeIndex() may + // (for performance reasons) yield some false positives but it must never + // give false negatives. + char[] escaped = escape(cp); + int nextIndex = index + (Character.isSupplementaryCodePoint(cp) ? 2 : 1); + if (escaped != null) { + int charsSkipped = index - unescapedChunkStart; + + // This is the size needed to add the replacement, not the full + // size needed by the string. We only regrow when we absolutely must. + int sizeNeeded = destIndex + charsSkipped + escaped.length; + if (dest.length < sizeNeeded) { + int destLength = sizeNeeded + (end - index) + DEST_PAD; + dest = growBuffer(dest, destIndex, destLength); + } + // If we have skipped any characters, we need to copy them now. + if (charsSkipped > 0) { + s.getChars(unescapedChunkStart, index, dest, destIndex); + destIndex += charsSkipped; + } + if (escaped.length > 0) { + System.arraycopy(escaped, 0, dest, destIndex, escaped.length); + destIndex += escaped.length; + } + // If we dealt with an escaped character, reset the unescaped range. + unescapedChunkStart = nextIndex; + } + index = nextEscapeIndex(s, nextIndex, end); + } + + // Process trailing unescaped characters - no need to account for escaped + // length or padding the allocation. + int charsSkipped = end - unescapedChunkStart; + if (charsSkipped > 0) { + int endIndex = destIndex + charsSkipped; + if (dest.length < endIndex) { + dest = growBuffer(dest, destIndex, endIndex); + } + s.getChars(unescapedChunkStart, end, dest, destIndex); + destIndex = endIndex; + } + return new String(dest, 0, destIndex); + } + + /** + * Returns the Unicode code point of the character at the given index. + * + *

Unlike {@link Character#codePointAt(CharSequence, int)} or {@link String#codePointAt(int)} + * this method will never fail silently when encountering an invalid surrogate pair. + * + *

The behaviour of this method is as follows: + * + *

    + *
  1. If {@code index >= end}, {@link IndexOutOfBoundsException} is thrown. + *
  2. If the character at the specified index is not a surrogate, it is returned. + *
  3. If the first character was a high surrogate value, then an attempt is made to read the + * next character. + *
      + *
    1. If the end of the sequence was reached, the negated value of the trailing high + * surrogate is returned. + *
    2. If the next character was a valid low surrogate, the code point value of the + * high/low surrogate pair is returned. + *
    3. If the next character was not a low surrogate value, then {@link + * IllegalArgumentException} is thrown. + *
    + *
  4. If the first character was a low surrogate value, {@link IllegalArgumentException} is + * thrown. + *
+ * + * @param seq the sequence of characters from which to decode the code point + * @param index the index of the first character to decode + * @param end the index beyond the last valid character to decode + * @return the Unicode code point for the given index or the negated value of the trailing high + * surrogate character at the end of the sequence + */ + protected static int codePointAt(CharSequence seq, int index, int end) { + checkNotNull(seq); + if (index < end) { + char c1 = seq.charAt(index++); + if (c1 < Character.MIN_HIGH_SURROGATE || c1 > Character.MAX_LOW_SURROGATE) { + // Fast path (first test is probably all we need to do) + return c1; + } else if (c1 <= Character.MAX_HIGH_SURROGATE) { + // If the high surrogate was the last character, return its inverse + if (index == end) { + return -c1; + } + // Otherwise look for the low surrogate following it + char c2 = seq.charAt(index); + if (Character.isLowSurrogate(c2)) { + return Character.toCodePoint(c1, c2); + } + throw new IllegalArgumentException( + "Expected low surrogate but got char '" + + c2 + + "' with value " + + (int) c2 + + " at index " + + index + + " in '" + + seq + + "'"); + } else { + throw new IllegalArgumentException( + "Unexpected low surrogate character '" + + c1 + + "' with value " + + (int) c1 + + " at index " + + (index - 1) + + " in '" + + seq + + "'"); + } + } + throw new IndexOutOfBoundsException("Index exceeds specified range"); + } + + /** + * Helper method to grow the character buffer as needed, this only happens once in a while so it's + * ok if it's in a method call. If the index passed in is 0 then no copying will be done. + */ + private static char[] growBuffer(char[] dest, int index, int size) { + if (size < 0) { // overflow - should be OutOfMemoryError but GWT/j2cl don't support it + throw new AssertionError("Cannot increase internal buffer any further"); + } + char[] copy = new char[size]; + if (index > 0) { + System.arraycopy(dest, 0, copy, 0, index); + } + return copy; + } +} diff --git a/src/main/java/com/google/common/eventbus/AllowConcurrentEvents.java b/src/main/java/com/google/common/eventbus/AllowConcurrentEvents.java new file mode 100644 index 0000000..4c749b4 --- /dev/null +++ b/src/main/java/com/google/common/eventbus/AllowConcurrentEvents.java @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.eventbus; + +import com.google.common.annotations.Beta; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Marks an event subscriber method as being thread-safe. This annotation indicates that EventBus + * may invoke the event subscriber simultaneously from multiple threads. + * + *

This does not mark the method, and so should be used in combination with {@link Subscribe}. + * + * @author Cliff Biffle + * @since 10.0 + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.METHOD) +@Beta +public @interface AllowConcurrentEvents {} diff --git a/src/main/java/com/google/common/eventbus/AsyncEventBus.java b/src/main/java/com/google/common/eventbus/AsyncEventBus.java new file mode 100644 index 0000000..8650a8d --- /dev/null +++ b/src/main/java/com/google/common/eventbus/AsyncEventBus.java @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.eventbus; + +import com.google.common.annotations.Beta; +import java.util.concurrent.Executor; + +/** + * An {@link EventBus} that takes the Executor of your choice and uses it to dispatch events, + * allowing dispatch to occur asynchronously. + * + * @author Cliff Biffle + * @since 10.0 + */ +@Beta +public class AsyncEventBus extends EventBus { + + /** + * Creates a new AsyncEventBus that will use {@code executor} to dispatch events. Assigns {@code + * identifier} as the bus's name for logging purposes. + * + * @param identifier short name for the bus, for logging purposes. + * @param executor Executor to use to dispatch events. It is the caller's responsibility to shut + * down the executor after the last event has been posted to this event bus. + */ + public AsyncEventBus(String identifier, Executor executor) { + super(identifier, executor, Dispatcher.legacyAsync(), LoggingHandler.INSTANCE); + } + + /** + * Creates a new AsyncEventBus that will use {@code executor} to dispatch events. + * + * @param executor Executor to use to dispatch events. It is the caller's responsibility to shut + * down the executor after the last event has been posted to this event bus. + * @param subscriberExceptionHandler Handler used to handle exceptions thrown from subscribers. + * See {@link SubscriberExceptionHandler} for more information. + * @since 16.0 + */ + public AsyncEventBus(Executor executor, SubscriberExceptionHandler subscriberExceptionHandler) { + super("default", executor, Dispatcher.legacyAsync(), subscriberExceptionHandler); + } + + /** + * Creates a new AsyncEventBus that will use {@code executor} to dispatch events. + * + * @param executor Executor to use to dispatch events. It is the caller's responsibility to shut + * down the executor after the last event has been posted to this event bus. + */ + public AsyncEventBus(Executor executor) { + super("default", executor, Dispatcher.legacyAsync(), LoggingHandler.INSTANCE); + } +} diff --git a/src/main/java/com/google/common/eventbus/DeadEvent.java b/src/main/java/com/google/common/eventbus/DeadEvent.java new file mode 100644 index 0000000..6dbfee5 --- /dev/null +++ b/src/main/java/com/google/common/eventbus/DeadEvent.java @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.eventbus; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.annotations.Beta; +import com.google.common.base.MoreObjects; + +/** + * Wraps an event that was posted, but which had no subscribers and thus could not be delivered. + * + *

Registering a DeadEvent subscriber is useful for debugging or logging, as it can detect + * misconfigurations in a system's event distribution. + * + * @author Cliff Biffle + * @since 10.0 + */ +@Beta +public class DeadEvent { + + private final Object source; + private final Object event; + + /** + * Creates a new DeadEvent. + * + * @param source object broadcasting the DeadEvent (generally the {@link EventBus}). + * @param event the event that could not be delivered. + */ + public DeadEvent(Object source, Object event) { + this.source = checkNotNull(source); + this.event = checkNotNull(event); + } + + /** + * Returns the object that originated this event (not the object that originated the + * wrapped event). This is generally an {@link EventBus}. + * + * @return the source of this event. + */ + public Object getSource() { + return source; + } + + /** + * Returns the wrapped, 'dead' event, which the system was unable to deliver to any registered + * subscriber. + * + * @return the 'dead' event that could not be delivered. + */ + public Object getEvent() { + return event; + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this).add("source", source).add("event", event).toString(); + } +} diff --git a/src/main/java/com/google/common/eventbus/Dispatcher.java b/src/main/java/com/google/common/eventbus/Dispatcher.java new file mode 100644 index 0000000..11e2de1 --- /dev/null +++ b/src/main/java/com/google/common/eventbus/Dispatcher.java @@ -0,0 +1,190 @@ +/* + * Copyright (C) 2014 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.eventbus; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.collect.Queues; +import java.util.Iterator; +import java.util.Queue; +import java.util.concurrent.ConcurrentLinkedQueue; + +/** + * Handler for dispatching events to subscribers, providing different event ordering guarantees that + * make sense for different situations. + * + *

Note: The dispatcher is orthogonal to the subscriber's {@code Executor}. The dispatcher + * controls the order in which events are dispatched, while the executor controls how (i.e. on which + * thread) the subscriber is actually called when an event is dispatched to it. + * + * @author Colin Decker + */ +abstract class Dispatcher { + + /** + * Returns a dispatcher that queues events that are posted reentrantly on a thread that is already + * dispatching an event, guaranteeing that all events posted on a single thread are dispatched to + * all subscribers in the order they are posted. + * + *

When all subscribers are dispatched to using a direct executor (which dispatches on + * the same thread that posts the event), this yields a breadth-first dispatch order on each + * thread. That is, all subscribers to a single event A will be called before any subscribers to + * any events B and C that are posted to the event bus by the subscribers to A. + */ + static Dispatcher perThreadDispatchQueue() { + return new PerThreadQueuedDispatcher(); + } + + /** + * Returns a dispatcher that queues events that are posted in a single global queue. This behavior + * matches the original behavior of AsyncEventBus exactly, but is otherwise not especially useful. + * For async dispatch, an {@linkplain #immediate() immediate} dispatcher should generally be + * preferable. + */ + static Dispatcher legacyAsync() { + return new LegacyAsyncDispatcher(); + } + + /** + * Returns a dispatcher that dispatches events to subscribers immediately as they're posted + * without using an intermediate queue to change the dispatch order. This is effectively a + * depth-first dispatch order, vs. breadth-first when using a queue. + */ + static Dispatcher immediate() { + return ImmediateDispatcher.INSTANCE; + } + + /** Dispatches the given {@code event} to the given {@code subscribers}. */ + abstract void dispatch(Object event, Iterator subscribers); + + /** Implementation of a {@link #perThreadDispatchQueue()} dispatcher. */ + private static final class PerThreadQueuedDispatcher extends Dispatcher { + + // This dispatcher matches the original dispatch behavior of EventBus. + + /** Per-thread queue of events to dispatch. */ + private final ThreadLocal> queue = + new ThreadLocal>() { + @Override + protected Queue initialValue() { + return Queues.newArrayDeque(); + } + }; + + /** Per-thread dispatch state, used to avoid reentrant event dispatching. */ + private final ThreadLocal dispatching = + new ThreadLocal() { + @Override + protected Boolean initialValue() { + return false; + } + }; + + @Override + void dispatch(Object event, Iterator subscribers) { + checkNotNull(event); + checkNotNull(subscribers); + Queue queueForThread = queue.get(); + queueForThread.offer(new Event(event, subscribers)); + + if (!dispatching.get()) { + dispatching.set(true); + try { + Event nextEvent; + while ((nextEvent = queueForThread.poll()) != null) { + while (nextEvent.subscribers.hasNext()) { + nextEvent.subscribers.next().dispatchEvent(nextEvent.event); + } + } + } finally { + dispatching.remove(); + queue.remove(); + } + } + } + + private static final class Event { + private final Object event; + private final Iterator subscribers; + + private Event(Object event, Iterator subscribers) { + this.event = event; + this.subscribers = subscribers; + } + } + } + + /** Implementation of a {@link #legacyAsync()} dispatcher. */ + private static final class LegacyAsyncDispatcher extends Dispatcher { + + // This dispatcher matches the original dispatch behavior of AsyncEventBus. + // + // We can't really make any guarantees about the overall dispatch order for this dispatcher in + // a multithreaded environment for a couple reasons: + // + // 1. Subscribers to events posted on different threads can be interleaved with each other + // freely. (A event on one thread, B event on another could yield any of + // [a1, a2, a3, b1, b2], [a1, b2, a2, a3, b2], [a1, b2, b3, a2, a3], etc.) + // 2. It's possible for subscribers to actually be dispatched to in a different order than they + // were added to the queue. It's easily possible for one thread to take the head of the + // queue, immediately followed by another thread taking the next element in the queue. That + // second thread can then dispatch to the subscriber it took before the first thread does. + // + // All this makes me really wonder if there's any value in queueing here at all. A dispatcher + // that simply loops through the subscribers and dispatches the event to each would actually + // probably provide a stronger order guarantee, though that order would obviously be different + // in some cases. + + /** Global event queue. */ + private final ConcurrentLinkedQueue queue = + Queues.newConcurrentLinkedQueue(); + + @Override + void dispatch(Object event, Iterator subscribers) { + checkNotNull(event); + while (subscribers.hasNext()) { + queue.add(new EventWithSubscriber(event, subscribers.next())); + } + + EventWithSubscriber e; + while ((e = queue.poll()) != null) { + e.subscriber.dispatchEvent(e.event); + } + } + + private static final class EventWithSubscriber { + private final Object event; + private final Subscriber subscriber; + + private EventWithSubscriber(Object event, Subscriber subscriber) { + this.event = event; + this.subscriber = subscriber; + } + } + } + + /** Implementation of {@link #immediate()}. */ + private static final class ImmediateDispatcher extends Dispatcher { + private static final ImmediateDispatcher INSTANCE = new ImmediateDispatcher(); + + @Override + void dispatch(Object event, Iterator subscribers) { + checkNotNull(event); + while (subscribers.hasNext()) { + subscribers.next().dispatchEvent(event); + } + } + } +} diff --git a/src/main/java/com/google/common/eventbus/EventBus.java b/src/main/java/com/google/common/eventbus/EventBus.java new file mode 100644 index 0000000..e505399 --- /dev/null +++ b/src/main/java/com/google/common/eventbus/EventBus.java @@ -0,0 +1,253 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.eventbus; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.annotations.Beta; +import com.google.common.base.MoreObjects; +import com.google.common.util.concurrent.MoreExecutors; +import java.lang.reflect.Method; +import java.util.Iterator; +import java.util.Locale; +import java.util.concurrent.Executor; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * Dispatches events to listeners, and provides ways for listeners to register themselves. + * + *

The EventBus allows publish-subscribe-style communication between components without requiring + * the components to explicitly register with one another (and thus be aware of each other). It is + * designed exclusively to replace traditional Java in-process event distribution using explicit + * registration. It is not a general-purpose publish-subscribe system, nor is it intended + * for interprocess communication. + * + *

Receiving Events

+ * + *

To receive events, an object should: + * + *

    + *
  1. Expose a public method, known as the event subscriber, which accepts a single + * argument of the type of event desired; + *
  2. Mark it with a {@link Subscribe} annotation; + *
  3. Pass itself to an EventBus instance's {@link #register(Object)} method. + *
+ * + *

Posting Events

+ * + *

To post an event, simply provide the event object to the {@link #post(Object)} method. The + * EventBus instance will determine the type of event and route it to all registered listeners. + * + *

Events are routed based on their type — an event will be delivered to any subscriber for + * any type to which the event is assignable. This includes implemented interfaces, all + * superclasses, and all interfaces implemented by superclasses. + * + *

When {@code post} is called, all registered subscribers for an event are run in sequence, so + * subscribers should be reasonably quick. If an event may trigger an extended process (such as a + * database load), spawn a thread or queue it for later. (For a convenient way to do this, use an + * {@link AsyncEventBus}.) + * + *

Subscriber Methods

+ * + *

Event subscriber methods must accept only one argument: the event. + * + *

Subscribers should not, in general, throw. If they do, the EventBus will catch and log the + * exception. This is rarely the right solution for error handling and should not be relied upon; it + * is intended solely to help find problems during development. + * + *

The EventBus guarantees that it will not call a subscriber method from multiple threads + * simultaneously, unless the method explicitly allows it by bearing the {@link + * AllowConcurrentEvents} annotation. If this annotation is not present, subscriber methods need not + * worry about being reentrant, unless also called from outside the EventBus. + * + *

Dead Events

+ * + *

If an event is posted, but no registered subscribers can accept it, it is considered "dead." + * To give the system a second chance to handle dead events, they are wrapped in an instance of + * {@link DeadEvent} and reposted. + * + *

If a subscriber for a supertype of all events (such as Object) is registered, no event will + * ever be considered dead, and no DeadEvents will be generated. Accordingly, while DeadEvent + * extends {@link Object}, a subscriber registered to receive any Object will never receive a + * DeadEvent. + * + *

This class is safe for concurrent use. + * + *

See the Guava User Guide article on {@code EventBus}. + * + * @author Cliff Biffle + * @since 10.0 + */ +@Beta +public class EventBus { + + private static final Logger logger = Logger.getLogger(EventBus.class.getName()); + + private final String identifier; + private final Executor executor; + private final SubscriberExceptionHandler exceptionHandler; + + private final SubscriberRegistry subscribers = new SubscriberRegistry(this); + private final Dispatcher dispatcher; + + /** Creates a new EventBus named "default". */ + public EventBus() { + this("default"); + } + + /** + * Creates a new EventBus with the given {@code identifier}. + * + * @param identifier a brief name for this bus, for logging purposes. Should be a valid Java + * identifier. + */ + public EventBus(String identifier) { + this( + identifier, + MoreExecutors.directExecutor(), + Dispatcher.perThreadDispatchQueue(), + LoggingHandler.INSTANCE); + } + + /** + * Creates a new EventBus with the given {@link SubscriberExceptionHandler}. + * + * @param exceptionHandler Handler for subscriber exceptions. + * @since 16.0 + */ + public EventBus(SubscriberExceptionHandler exceptionHandler) { + this( + "default", + MoreExecutors.directExecutor(), + Dispatcher.perThreadDispatchQueue(), + exceptionHandler); + } + + EventBus( + String identifier, + Executor executor, + Dispatcher dispatcher, + SubscriberExceptionHandler exceptionHandler) { + this.identifier = checkNotNull(identifier); + this.executor = checkNotNull(executor); + this.dispatcher = checkNotNull(dispatcher); + this.exceptionHandler = checkNotNull(exceptionHandler); + } + + /** + * Returns the identifier for this event bus. + * + * @since 19.0 + */ + public final String identifier() { + return identifier; + } + + /** Returns the default executor this event bus uses for dispatching events to subscribers. */ + final Executor executor() { + return executor; + } + + /** Handles the given exception thrown by a subscriber with the given context. */ + void handleSubscriberException(Throwable e, SubscriberExceptionContext context) { + checkNotNull(e); + checkNotNull(context); + try { + exceptionHandler.handleException(e, context); + } catch (Throwable e2) { + // if the handler threw an exception... well, just log it + logger.log( + Level.SEVERE, + String.format(Locale.ROOT, "Exception %s thrown while handling exception: %s", e2, e), + e2); + } + } + + /** + * Registers all subscriber methods on {@code object} to receive events. + * + * @param object object whose subscriber methods should be registered. + */ + public void register(Object object) { + subscribers.register(object); + } + + /** + * Unregisters all subscriber methods on a registered {@code object}. + * + * @param object object whose subscriber methods should be unregistered. + * @throws IllegalArgumentException if the object was not previously registered. + */ + public void unregister(Object object) { + subscribers.unregister(object); + } + + /** + * Posts an event to all registered subscribers. This method will return successfully after the + * event has been posted to all subscribers, and regardless of any exceptions thrown by + * subscribers. + * + *

If no subscribers have been subscribed for {@code event}'s class, and {@code event} is not + * already a {@link DeadEvent}, it will be wrapped in a DeadEvent and reposted. + * + * @param event event to post. + */ + public void post(Object event) { + Iterator eventSubscribers = subscribers.getSubscribers(event); + if (eventSubscribers.hasNext()) { + dispatcher.dispatch(event, eventSubscribers); + } else if (!(event instanceof DeadEvent)) { + // the event had no subscribers and was not itself a DeadEvent + post(new DeadEvent(this, event)); + } + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this).addValue(identifier).toString(); + } + + /** Simple logging handler for subscriber exceptions. */ + static final class LoggingHandler implements SubscriberExceptionHandler { + static final LoggingHandler INSTANCE = new LoggingHandler(); + + @Override + public void handleException(Throwable exception, SubscriberExceptionContext context) { + Logger logger = logger(context); + if (logger.isLoggable(Level.SEVERE)) { + logger.log(Level.SEVERE, message(context), exception); + } + } + + private static Logger logger(SubscriberExceptionContext context) { + return Logger.getLogger(EventBus.class.getName() + "." + context.getEventBus().identifier()); + } + + private static String message(SubscriberExceptionContext context) { + Method method = context.getSubscriberMethod(); + return "Exception thrown by subscriber method " + + method.getName() + + '(' + + method.getParameterTypes()[0].getName() + + ')' + + " on subscriber " + + context.getSubscriber() + + " when dispatching event: " + + context.getEvent(); + } + } +} diff --git a/src/main/java/com/google/common/eventbus/Subscribe.java b/src/main/java/com/google/common/eventbus/Subscribe.java new file mode 100644 index 0000000..37337e6 --- /dev/null +++ b/src/main/java/com/google/common/eventbus/Subscribe.java @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.eventbus; + +import com.google.common.annotations.Beta; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Marks a method as an event subscriber. + * + *

The type of event will be indicated by the method's first (and only) parameter. If this + * annotation is applied to methods with zero parameters, or more than one parameter, the object + * containing the method will not be able to register for event delivery from the {@link EventBus}. + * + *

Unless also annotated with @{@link AllowConcurrentEvents}, event subscriber methods will be + * invoked serially by each event bus that they are registered with. + * + * @author Cliff Biffle + * @since 10.0 + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.METHOD) +@Beta +public @interface Subscribe {} diff --git a/src/main/java/com/google/common/eventbus/Subscriber.java b/src/main/java/com/google/common/eventbus/Subscriber.java new file mode 100644 index 0000000..61dcf36 --- /dev/null +++ b/src/main/java/com/google/common/eventbus/Subscriber.java @@ -0,0 +1,148 @@ +/* + * Copyright (C) 2014 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.eventbus; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.annotations.VisibleForTesting; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.concurrent.Executor; + + +/** + * A subscriber method on a specific object, plus the executor that should be used for dispatching + * events to it. + * + *

Two subscribers are equivalent when they refer to the same method on the same object (not + * class). This property is used to ensure that no subscriber method is registered more than once. + * + * @author Colin Decker + */ +class Subscriber { + + /** Creates a {@code Subscriber} for {@code method} on {@code listener}. */ + static Subscriber create(EventBus bus, Object listener, Method method) { + return isDeclaredThreadSafe(method) + ? new Subscriber(bus, listener, method) + : new SynchronizedSubscriber(bus, listener, method); + } + + /** The event bus this subscriber belongs to. */ + private EventBus bus; + + /** The object with the subscriber method. */ + @VisibleForTesting final Object target; + + /** Subscriber method. */ + private final Method method; + + /** Executor to use for dispatching events to this subscriber. */ + private final Executor executor; + + private Subscriber(EventBus bus, Object target, Method method) { + this.bus = bus; + this.target = checkNotNull(target); + this.method = method; + method.setAccessible(true); + + this.executor = bus.executor(); + } + + /** Dispatches {@code event} to this subscriber using the proper executor. */ + final void dispatchEvent(final Object event) { + executor.execute( + new Runnable() { + @Override + public void run() { + try { + invokeSubscriberMethod(event); + } catch (InvocationTargetException e) { + bus.handleSubscriberException(e.getCause(), context(event)); + } + } + }); + } + + /** + * Invokes the subscriber method. This method can be overridden to make the invocation + * synchronized. + */ + @VisibleForTesting + void invokeSubscriberMethod(Object event) throws InvocationTargetException { + try { + method.invoke(target, checkNotNull(event)); + } catch (IllegalArgumentException e) { + throw new Error("Method rejected target/argument: " + event, e); + } catch (IllegalAccessException e) { + throw new Error("Method became inaccessible: " + event, e); + } catch (InvocationTargetException e) { + if (e.getCause() instanceof Error) { + throw (Error) e.getCause(); + } + throw e; + } + } + + /** Gets the context for the given event. */ + private SubscriberExceptionContext context(Object event) { + return new SubscriberExceptionContext(bus, event, target, method); + } + + @Override + public final int hashCode() { + return (31 + method.hashCode()) * 31 + System.identityHashCode(target); + } + + @Override + public final boolean equals(Object obj) { + if (obj instanceof Subscriber) { + Subscriber that = (Subscriber) obj; + // Use == so that different equal instances will still receive events. + // We only guard against the case that the same object is registered + // multiple times + return target == that.target && method.equals(that.method); + } + return false; + } + + /** + * Checks whether {@code method} is thread-safe, as indicated by the presence of the {@link + * AllowConcurrentEvents} annotation. + */ + private static boolean isDeclaredThreadSafe(Method method) { + return method.getAnnotation(AllowConcurrentEvents.class) != null; + } + + /** + * Subscriber that synchronizes invocations of a method to ensure that only one thread may enter + * the method at a time. + */ + @VisibleForTesting + static final class SynchronizedSubscriber extends Subscriber { + + private SynchronizedSubscriber(EventBus bus, Object target, Method method) { + super(bus, target, method); + } + + @Override + void invokeSubscriberMethod(Object event) throws InvocationTargetException { + synchronized (this) { + super.invokeSubscriberMethod(event); + } + } + } +} diff --git a/src/main/java/com/google/common/eventbus/SubscriberExceptionContext.java b/src/main/java/com/google/common/eventbus/SubscriberExceptionContext.java new file mode 100644 index 0000000..6ddd86f --- /dev/null +++ b/src/main/java/com/google/common/eventbus/SubscriberExceptionContext.java @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2013 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.eventbus; + +import static com.google.common.base.Preconditions.checkNotNull; + +import java.lang.reflect.Method; + +/** + * Context for an exception thrown by a subscriber. + * + * @since 16.0 + */ +public class SubscriberExceptionContext { + private final EventBus eventBus; + private final Object event; + private final Object subscriber; + private final Method subscriberMethod; + + /** + * @param eventBus The {@link EventBus} that handled the event and the subscriber. Useful for + * broadcasting a a new event based on the error. + * @param event The event object that caused the subscriber to throw. + * @param subscriber The source subscriber context. + * @param subscriberMethod the subscribed method. + */ + SubscriberExceptionContext( + EventBus eventBus, Object event, Object subscriber, Method subscriberMethod) { + this.eventBus = checkNotNull(eventBus); + this.event = checkNotNull(event); + this.subscriber = checkNotNull(subscriber); + this.subscriberMethod = checkNotNull(subscriberMethod); + } + + /** + * @return The {@link EventBus} that handled the event and the subscriber. Useful for broadcasting + * a a new event based on the error. + */ + public EventBus getEventBus() { + return eventBus; + } + + /** @return The event object that caused the subscriber to throw. */ + public Object getEvent() { + return event; + } + + /** @return The object context that the subscriber was called on. */ + public Object getSubscriber() { + return subscriber; + } + + /** @return The subscribed method that threw the exception. */ + public Method getSubscriberMethod() { + return subscriberMethod; + } +} diff --git a/src/main/java/com/google/common/eventbus/SubscriberExceptionHandler.java b/src/main/java/com/google/common/eventbus/SubscriberExceptionHandler.java new file mode 100644 index 0000000..c239ad7 --- /dev/null +++ b/src/main/java/com/google/common/eventbus/SubscriberExceptionHandler.java @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2013 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.eventbus; + +/** + * Handler for exceptions thrown by event subscribers. + * + * @since 16.0 + */ +public interface SubscriberExceptionHandler { + /** Handles exceptions thrown by subscribers. */ + void handleException(Throwable exception, SubscriberExceptionContext context); +} diff --git a/src/main/java/com/google/common/eventbus/SubscriberRegistry.java b/src/main/java/com/google/common/eventbus/SubscriberRegistry.java new file mode 100644 index 0000000..575529e --- /dev/null +++ b/src/main/java/com/google/common/eventbus/SubscriberRegistry.java @@ -0,0 +1,253 @@ +/* + * Copyright (C) 2014 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.eventbus; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.MoreObjects; +import com.google.common.base.Objects; +import com.google.common.base.Throwables; +import com.google.common.cache.CacheBuilder; +import com.google.common.cache.CacheLoader; +import com.google.common.cache.LoadingCache; +import com.google.common.collect.HashMultimap; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Iterators; +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; +import com.google.common.collect.Multimap; +import com.google.common.reflect.TypeToken; +import com.google.common.util.concurrent.UncheckedExecutionException; + +import java.lang.reflect.Method; +import java.util.Arrays; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.CopyOnWriteArraySet; + + +/** + * Registry of subscribers to a single event bus. + * + * @author Colin Decker + */ +final class SubscriberRegistry { + + /** + * All registered subscribers, indexed by event type. + * + *

The {@link CopyOnWriteArraySet} values make it easy and relatively lightweight to get an + * immutable snapshot of all current subscribers to an event without any locking. + */ + private final ConcurrentMap, CopyOnWriteArraySet> subscribers = + Maps.newConcurrentMap(); + + /** The event bus this registry belongs to. */ + private final EventBus bus; + + SubscriberRegistry(EventBus bus) { + this.bus = checkNotNull(bus); + } + + /** Registers all subscriber methods on the given listener object. */ + void register(Object listener) { + Multimap, Subscriber> listenerMethods = findAllSubscribers(listener); + + for (Entry, Collection> entry : listenerMethods.asMap().entrySet()) { + Class eventType = entry.getKey(); + Collection eventMethodsInListener = entry.getValue(); + + CopyOnWriteArraySet eventSubscribers = subscribers.get(eventType); + + if (eventSubscribers == null) { + CopyOnWriteArraySet newSet = new CopyOnWriteArraySet<>(); + eventSubscribers = + MoreObjects.firstNonNull(subscribers.putIfAbsent(eventType, newSet), newSet); + } + + eventSubscribers.addAll(eventMethodsInListener); + } + } + + /** Unregisters all subscribers on the given listener object. */ + void unregister(Object listener) { + Multimap, Subscriber> listenerMethods = findAllSubscribers(listener); + + for (Entry, Collection> entry : listenerMethods.asMap().entrySet()) { + Class eventType = entry.getKey(); + Collection listenerMethodsForType = entry.getValue(); + + CopyOnWriteArraySet currentSubscribers = subscribers.get(eventType); + if (currentSubscribers == null || !currentSubscribers.removeAll(listenerMethodsForType)) { + // if removeAll returns true, all we really know is that at least one subscriber was + // removed... however, barring something very strange we can assume that if at least one + // subscriber was removed, all subscribers on listener for that event type were... after + // all, the definition of subscribers on a particular class is totally static + throw new IllegalArgumentException( + "missing event subscriber for an annotated method. Is " + listener + " registered?"); + } + + // don't try to remove the set if it's empty; that can't be done safely without a lock + // anyway, if the set is empty it'll just be wrapping an array of length 0 + } + } + + @VisibleForTesting + Set getSubscribersForTesting(Class eventType) { + return MoreObjects.firstNonNull(subscribers.get(eventType), ImmutableSet.of()); + } + + /** + * Gets an iterator representing an immutable snapshot of all subscribers to the given event at + * the time this method is called. + */ + Iterator getSubscribers(Object event) { + ImmutableSet> eventTypes = flattenHierarchy(event.getClass()); + + List> subscriberIterators = + Lists.newArrayListWithCapacity(eventTypes.size()); + + for (Class eventType : eventTypes) { + CopyOnWriteArraySet eventSubscribers = subscribers.get(eventType); + if (eventSubscribers != null) { + // eager no-copy snapshot + subscriberIterators.add(eventSubscribers.iterator()); + } + } + + return Iterators.concat(subscriberIterators.iterator()); + } + + /** + * A thread-safe cache that contains the mapping from each class to all methods in that class and + * all super-classes, that are annotated with {@code @Subscribe}. The cache is shared across all + * instances of this class; this greatly improves performance if multiple EventBus instances are + * created and objects of the same class are registered on all of them. + */ + private static final LoadingCache, ImmutableList> subscriberMethodsCache = + CacheBuilder.newBuilder() + .weakKeys() + .build( + new CacheLoader, ImmutableList>() { + @Override + public ImmutableList load(Class concreteClass) throws Exception { + return getAnnotatedMethodsNotCached(concreteClass); + } + }); + + /** + * Returns all subscribers for the given listener grouped by the type of event they subscribe to. + */ + private Multimap, Subscriber> findAllSubscribers(Object listener) { + Multimap, Subscriber> methodsInListener = HashMultimap.create(); + Class clazz = listener.getClass(); + for (Method method : getAnnotatedMethods(clazz)) { + Class[] parameterTypes = method.getParameterTypes(); + Class eventType = parameterTypes[0]; + methodsInListener.put(eventType, Subscriber.create(bus, listener, method)); + } + return methodsInListener; + } + + private static ImmutableList getAnnotatedMethods(Class clazz) { + return subscriberMethodsCache.getUnchecked(clazz); + } + + private static ImmutableList getAnnotatedMethodsNotCached(Class clazz) { + Set> supertypes = TypeToken.of(clazz).getTypes().rawTypes(); + Map identifiers = Maps.newHashMap(); + for (Class supertype : supertypes) { + for (Method method : supertype.getDeclaredMethods()) { + if (method.isAnnotationPresent(Subscribe.class) && !method.isSynthetic()) { + // TODO(cgdecker): Should check for a generic parameter type and error out + Class[] parameterTypes = method.getParameterTypes(); + checkArgument( + parameterTypes.length == 1, + "Method %s has @Subscribe annotation but has %s parameters." + + "Subscriber methods must have exactly 1 parameter.", + method, + parameterTypes.length); + + MethodIdentifier ident = new MethodIdentifier(method); + if (!identifiers.containsKey(ident)) { + identifiers.put(ident, method); + } + } + } + } + return ImmutableList.copyOf(identifiers.values()); + } + + /** Global cache of classes to their flattened hierarchy of supertypes. */ + private static final LoadingCache, ImmutableSet>> flattenHierarchyCache = + CacheBuilder.newBuilder() + .weakKeys() + .build( + new CacheLoader, ImmutableSet>>() { + // > is actually needed to compile + @SuppressWarnings("RedundantTypeArguments") + @Override + public ImmutableSet> load(Class concreteClass) { + return ImmutableSet.>copyOf( + TypeToken.of(concreteClass).getTypes().rawTypes()); + } + }); + + /** + * Flattens a class's type hierarchy into a set of {@code Class} objects including all + * superclasses (transitively) and all interfaces implemented by these superclasses. + */ + @VisibleForTesting + static ImmutableSet> flattenHierarchy(Class concreteClass) { + try { + return flattenHierarchyCache.getUnchecked(concreteClass); + } catch (UncheckedExecutionException e) { + throw Throwables.propagate(e.getCause()); + } + } + + private static final class MethodIdentifier { + + private final String name; + private final List> parameterTypes; + + MethodIdentifier(Method method) { + this.name = method.getName(); + this.parameterTypes = Arrays.asList(method.getParameterTypes()); + } + + @Override + public int hashCode() { + return Objects.hashCode(name, parameterTypes); + } + + @Override + public boolean equals(Object o) { + if (o instanceof MethodIdentifier) { + MethodIdentifier ident = (MethodIdentifier) o; + return name.equals(ident.name) && parameterTypes.equals(ident.parameterTypes); + } + return false; + } + } +} diff --git a/src/main/java/com/google/common/graph/AbstractBaseGraph.java b/src/main/java/com/google/common/graph/AbstractBaseGraph.java new file mode 100644 index 0000000..9f725c4 --- /dev/null +++ b/src/main/java/com/google/common/graph/AbstractBaseGraph.java @@ -0,0 +1,273 @@ +/* + * Copyright (C) 2017 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.graph; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.base.Preconditions.checkState; +import static com.google.common.graph.GraphConstants.ENDPOINTS_MISMATCH; + +import com.google.common.base.Function; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Iterators; +import com.google.common.collect.Sets; +import com.google.common.collect.UnmodifiableIterator; +import com.google.common.math.IntMath; +import com.google.common.primitives.Ints; +import java.util.AbstractSet; +import java.util.Set; + + +/** + * This class provides a skeletal implementation of {@link BaseGraph}. + * + *

The methods implemented in this class should not be overridden unless the subclass admits a + * more efficient implementation. + * + * @author James Sexton + * @param Node parameter type + */ +abstract class AbstractBaseGraph implements BaseGraph { + + /** + * Returns the number of edges in this graph; used to calculate the size of {@link #edges()}. This + * implementation requires O(|N|) time. Classes extending this one may manually keep track of the + * number of edges as the graph is updated, and override this method for better performance. + */ + protected long edgeCount() { + long degreeSum = 0L; + for (N node : nodes()) { + degreeSum += degree(node); + } + // According to the degree sum formula, this is equal to twice the number of edges. + checkState((degreeSum & 1) == 0); + return degreeSum >>> 1; + } + + /** + * An implementation of {@link BaseGraph#edges()} defined in terms of {@link #nodes()} and {@link + * #successors(Object)}. + */ + @Override + public Set> edges() { + return new AbstractSet>() { + @Override + public UnmodifiableIterator> iterator() { + return EndpointPairIterator.of(AbstractBaseGraph.this); + } + + @Override + public int size() { + return Ints.saturatedCast(edgeCount()); + } + + @Override + public boolean remove(Object o) { + throw new UnsupportedOperationException(); + } + + // Mostly safe: We check contains(u) before calling successors(u), so we perform unsafe + // operations only in weird cases like checking for an EndpointPair in a + // Graph. + @SuppressWarnings("unchecked") + @Override + public boolean contains(Object obj) { + if (!(obj instanceof EndpointPair)) { + return false; + } + EndpointPair endpointPair = (EndpointPair) obj; + return isOrderingCompatible(endpointPair) + && nodes().contains(endpointPair.nodeU()) + && successors((N) endpointPair.nodeU()).contains(endpointPair.nodeV()); + } + }; + } + + @Override + public Set> incidentEdges(N node) { + checkNotNull(node); + checkArgument(nodes().contains(node), "Node %s is not an element of this graph.", node); + return IncidentEdgeSet.of(this, node); + } + + @Override + public int degree(N node) { + if (isDirected()) { + return IntMath.saturatedAdd(predecessors(node).size(), successors(node).size()); + } else { + Set neighbors = adjacentNodes(node); + int selfLoopCount = (allowsSelfLoops() && neighbors.contains(node)) ? 1 : 0; + return IntMath.saturatedAdd(neighbors.size(), selfLoopCount); + } + } + + @Override + public int inDegree(N node) { + return isDirected() ? predecessors(node).size() : degree(node); + } + + @Override + public int outDegree(N node) { + return isDirected() ? successors(node).size() : degree(node); + } + + @Override + public boolean hasEdgeConnecting(N nodeU, N nodeV) { + checkNotNull(nodeU); + checkNotNull(nodeV); + return nodes().contains(nodeU) && successors(nodeU).contains(nodeV); + } + + @Override + public boolean hasEdgeConnecting(EndpointPair endpoints) { + checkNotNull(endpoints); + if (!isOrderingCompatible(endpoints)) { + return false; + } + N nodeU = endpoints.nodeU(); + N nodeV = endpoints.nodeV(); + return nodes().contains(nodeU) && successors(nodeU).contains(nodeV); + } + + /** + * Throws {@code IllegalArgumentException} if the ordering of {@code endpoints} is not compatible + * with the directionality of this graph. + */ + protected final void validateEndpoints(EndpointPair endpoints) { + checkNotNull(endpoints); + checkArgument(isOrderingCompatible(endpoints), ENDPOINTS_MISMATCH); + } + + protected final boolean isOrderingCompatible(EndpointPair endpoints) { + return endpoints.isOrdered() || !this.isDirected(); + } + + private abstract static class IncidentEdgeSet extends AbstractSet> { + protected final N node; + protected final BaseGraph graph; + + public static IncidentEdgeSet of(BaseGraph graph, N node) { + return graph.isDirected() ? new Directed<>(graph, node) : new Undirected<>(graph, node); + } + + private IncidentEdgeSet(BaseGraph graph, N node) { + this.graph = graph; + this.node = node; + } + + @Override + public boolean remove(Object o) { + throw new UnsupportedOperationException(); + } + + private static final class Directed extends IncidentEdgeSet { + + private Directed(BaseGraph graph, N node) { + super(graph, node); + } + + @Override + public UnmodifiableIterator> iterator() { + return Iterators.unmodifiableIterator( + Iterators.concat( + Iterators.transform( + graph.predecessors(node).iterator(), + new Function>() { + @Override + public EndpointPair apply(N predecessor) { + return EndpointPair.ordered(predecessor, node); + } + }), + Iterators.transform( + // filter out 'node' from successors (already covered by predecessors, above) + Sets.difference(graph.successors(node), ImmutableSet.of(node)).iterator(), + new Function>() { + @Override + public EndpointPair apply(N successor) { + return EndpointPair.ordered(node, successor); + } + }))); + } + + @Override + public int size() { + return graph.inDegree(node) + + graph.outDegree(node) + - (graph.successors(node).contains(node) ? 1 : 0); + } + + @Override + public boolean contains(Object obj) { + if (!(obj instanceof EndpointPair)) { + return false; + } + + EndpointPair endpointPair = (EndpointPair) obj; + if (!endpointPair.isOrdered()) { + return false; + } + + Object source = endpointPair.source(); + Object target = endpointPair.target(); + return (node.equals(source) && graph.successors(node).contains(target)) + || (node.equals(target) && graph.predecessors(node).contains(source)); + } + } + + private static final class Undirected extends IncidentEdgeSet { + private Undirected(BaseGraph graph, N node) { + super(graph, node); + } + + @Override + public UnmodifiableIterator> iterator() { + return Iterators.unmodifiableIterator( + Iterators.transform( + graph.adjacentNodes(node).iterator(), + new Function>() { + @Override + public EndpointPair apply(N adjacentNode) { + return EndpointPair.unordered(node, adjacentNode); + } + })); + } + + @Override + public int size() { + return graph.adjacentNodes(node).size(); + } + + @Override + public boolean contains(Object obj) { + if (!(obj instanceof EndpointPair)) { + return false; + } + + EndpointPair endpointPair = (EndpointPair) obj; + if (endpointPair.isOrdered()) { + return false; + } + Set adjacent = graph.adjacentNodes(node); + Object nodeU = endpointPair.nodeU(); + Object nodeV = endpointPair.nodeV(); + + return (node.equals(nodeV) && adjacent.contains(nodeU)) + || (node.equals(nodeU) && adjacent.contains(nodeV)); + } + } + } +} diff --git a/src/main/java/com/google/common/graph/AbstractDirectedNetworkConnections.java b/src/main/java/com/google/common/graph/AbstractDirectedNetworkConnections.java new file mode 100644 index 0000000..444436f --- /dev/null +++ b/src/main/java/com/google/common/graph/AbstractDirectedNetworkConnections.java @@ -0,0 +1,134 @@ +/* + * Copyright (C) 2016 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.graph; + +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.base.Preconditions.checkState; +import static com.google.common.graph.Graphs.checkNonNegative; +import static com.google.common.graph.Graphs.checkPositive; + +import com.google.common.collect.Iterables; +import com.google.common.collect.Iterators; +import com.google.common.collect.Sets; +import com.google.common.collect.UnmodifiableIterator; +import com.google.common.math.IntMath; +import java.util.AbstractSet; +import java.util.Collections; +import java.util.Map; +import java.util.Set; + + +/** + * A base implementation of {@link NetworkConnections} for directed networks. + * + * @author James Sexton + * @param Node parameter type + * @param Edge parameter type + */ +abstract class AbstractDirectedNetworkConnections implements NetworkConnections { + /** Keys are edges incoming to the origin node, values are the source node. */ + protected final Map inEdgeMap; + + /** Keys are edges outgoing from the origin node, values are the target node. */ + protected final Map outEdgeMap; + + private int selfLoopCount; + + protected AbstractDirectedNetworkConnections( + Map inEdgeMap, Map outEdgeMap, int selfLoopCount) { + this.inEdgeMap = checkNotNull(inEdgeMap); + this.outEdgeMap = checkNotNull(outEdgeMap); + this.selfLoopCount = checkNonNegative(selfLoopCount); + checkState(selfLoopCount <= inEdgeMap.size() && selfLoopCount <= outEdgeMap.size()); + } + + @Override + public Set adjacentNodes() { + return Sets.union(predecessors(), successors()); + } + + @Override + public Set incidentEdges() { + return new AbstractSet() { + @Override + public UnmodifiableIterator iterator() { + Iterable incidentEdges = + (selfLoopCount == 0) + ? Iterables.concat(inEdgeMap.keySet(), outEdgeMap.keySet()) + : Sets.union(inEdgeMap.keySet(), outEdgeMap.keySet()); + return Iterators.unmodifiableIterator(incidentEdges.iterator()); + } + + @Override + public int size() { + return IntMath.saturatedAdd(inEdgeMap.size(), outEdgeMap.size() - selfLoopCount); + } + + @Override + public boolean contains(Object obj) { + return inEdgeMap.containsKey(obj) || outEdgeMap.containsKey(obj); + } + }; + } + + @Override + public Set inEdges() { + return Collections.unmodifiableSet(inEdgeMap.keySet()); + } + + @Override + public Set outEdges() { + return Collections.unmodifiableSet(outEdgeMap.keySet()); + } + + @Override + public N adjacentNode(E edge) { + // Since the reference node is defined to be 'source' for directed graphs, + // we can assume this edge lives in the set of outgoing edges. + return checkNotNull(outEdgeMap.get(edge)); + } + + @Override + public N removeInEdge(E edge, boolean isSelfLoop) { + if (isSelfLoop) { + checkNonNegative(--selfLoopCount); + } + N previousNode = inEdgeMap.remove(edge); + return checkNotNull(previousNode); + } + + @Override + public N removeOutEdge(E edge) { + N previousNode = outEdgeMap.remove(edge); + return checkNotNull(previousNode); + } + + @Override + public void addInEdge(E edge, N node, boolean isSelfLoop) { + if (isSelfLoop) { + checkPositive(++selfLoopCount); + } + N previousNode = inEdgeMap.put(edge, node); + checkState(previousNode == null); + } + + @Override + public void addOutEdge(E edge, N node) { + N previousNode = outEdgeMap.put(edge, node); + checkState(previousNode == null); + } +} diff --git a/src/main/java/com/google/common/graph/AbstractGraph.java b/src/main/java/com/google/common/graph/AbstractGraph.java new file mode 100644 index 0000000..6561e4a --- /dev/null +++ b/src/main/java/com/google/common/graph/AbstractGraph.java @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2016 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.graph; + +import com.google.common.annotations.Beta; + + +/** + * This class provides a skeletal implementation of {@link Graph}. It is recommended to extend this + * class rather than implement {@link Graph} directly. + * + * @author James Sexton + * @param Node parameter type + * @since 20.0 + */ +@Beta +public abstract class AbstractGraph extends AbstractBaseGraph implements Graph { + + @Override + public final boolean equals(Object obj) { + if (obj == this) { + return true; + } + if (!(obj instanceof Graph)) { + return false; + } + Graph other = (Graph) obj; + + return isDirected() == other.isDirected() + && nodes().equals(other.nodes()) + && edges().equals(other.edges()); + } + + @Override + public final int hashCode() { + return edges().hashCode(); + } + + /** Returns a string representation of this graph. */ + @Override + public String toString() { + return "isDirected: " + + isDirected() + + ", allowsSelfLoops: " + + allowsSelfLoops() + + ", nodes: " + + nodes() + + ", edges: " + + edges(); + } +} diff --git a/src/main/java/com/google/common/graph/AbstractGraphBuilder.java b/src/main/java/com/google/common/graph/AbstractGraphBuilder.java new file mode 100644 index 0000000..84e461a --- /dev/null +++ b/src/main/java/com/google/common/graph/AbstractGraphBuilder.java @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2016 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.graph; + +import com.google.common.base.Optional; + +/** + * A base class for builders that construct graphs with user-defined properties. + * + * @author James Sexton + */ +abstract class AbstractGraphBuilder { + final boolean directed; + boolean allowsSelfLoops = false; + ElementOrder nodeOrder = ElementOrder.insertion(); + ElementOrder incidentEdgeOrder = ElementOrder.unordered(); + + Optional expectedNodeCount = Optional.absent(); + + /** + * Creates a new instance with the specified edge directionality. + * + * @param directed if true, creates an instance for graphs whose edges are each directed; if + * false, creates an instance for graphs whose edges are each undirected. + */ + AbstractGraphBuilder(boolean directed) { + this.directed = directed; + } +} diff --git a/src/main/java/com/google/common/graph/AbstractNetwork.java b/src/main/java/com/google/common/graph/AbstractNetwork.java new file mode 100644 index 0000000..d44df20 --- /dev/null +++ b/src/main/java/com/google/common/graph/AbstractNetwork.java @@ -0,0 +1,292 @@ +/* + * Copyright (C) 2016 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.graph; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.graph.GraphConstants.ENDPOINTS_MISMATCH; +import static com.google.common.graph.GraphConstants.MULTIPLE_EDGES_CONNECTING; +import static java.util.Collections.unmodifiableSet; + +import com.google.common.annotations.Beta; +import com.google.common.base.Function; +import com.google.common.base.Predicate; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Iterators; +import com.google.common.collect.Maps; +import com.google.common.collect.Sets; +import com.google.common.math.IntMath; +import java.util.AbstractSet; +import java.util.Iterator; +import java.util.Map; +import java.util.Optional; +import java.util.Set; + + +/** + * This class provides a skeletal implementation of {@link Network}. It is recommended to extend + * this class rather than implement {@link Network} directly. + * + *

The methods implemented in this class should not be overridden unless the subclass admits a + * more efficient implementation. + * + * @author James Sexton + * @param Node parameter type + * @param Edge parameter type + * @since 20.0 + */ +@Beta +public abstract class AbstractNetwork implements Network { + + @Override + public Graph asGraph() { + return new AbstractGraph() { + @Override + public Set nodes() { + return AbstractNetwork.this.nodes(); + } + + @Override + public Set> edges() { + if (allowsParallelEdges()) { + return super.edges(); // Defer to AbstractGraph implementation. + } + + // Optimized implementation assumes no parallel edges (1:1 edge to EndpointPair mapping). + return new AbstractSet>() { + @Override + public Iterator> iterator() { + return Iterators.transform( + AbstractNetwork.this.edges().iterator(), + new Function>() { + @Override + public EndpointPair apply(E edge) { + return incidentNodes(edge); + } + }); + } + + @Override + public int size() { + return AbstractNetwork.this.edges().size(); + } + + // Mostly safe: We check contains(u) before calling successors(u), so we perform unsafe + // operations only in weird cases like checking for an EndpointPair in a + // Network. + @SuppressWarnings("unchecked") + @Override + public boolean contains(Object obj) { + if (!(obj instanceof EndpointPair)) { + return false; + } + EndpointPair endpointPair = (EndpointPair) obj; + return isOrderingCompatible(endpointPair) + && nodes().contains(endpointPair.nodeU()) + && successors((N) endpointPair.nodeU()).contains(endpointPair.nodeV()); + } + }; + } + + @Override + public ElementOrder nodeOrder() { + return AbstractNetwork.this.nodeOrder(); + } + + @Override + public boolean isDirected() { + return AbstractNetwork.this.isDirected(); + } + + @Override + public boolean allowsSelfLoops() { + return AbstractNetwork.this.allowsSelfLoops(); + } + + @Override + public Set adjacentNodes(N node) { + return AbstractNetwork.this.adjacentNodes(node); + } + + @Override + public Set predecessors(N node) { + return AbstractNetwork.this.predecessors(node); + } + + @Override + public Set successors(N node) { + return AbstractNetwork.this.successors(node); + } + + // DO NOT override the AbstractGraph *degree() implementations. + }; + } + + @Override + public int degree(N node) { + if (isDirected()) { + return IntMath.saturatedAdd(inEdges(node).size(), outEdges(node).size()); + } else { + return IntMath.saturatedAdd(incidentEdges(node).size(), edgesConnecting(node, node).size()); + } + } + + @Override + public int inDegree(N node) { + return isDirected() ? inEdges(node).size() : degree(node); + } + + @Override + public int outDegree(N node) { + return isDirected() ? outEdges(node).size() : degree(node); + } + + @Override + public Set adjacentEdges(E edge) { + EndpointPair endpointPair = incidentNodes(edge); // Verifies that edge is in this network. + Set endpointPairIncidentEdges = + Sets.union(incidentEdges(endpointPair.nodeU()), incidentEdges(endpointPair.nodeV())); + return Sets.difference(endpointPairIncidentEdges, ImmutableSet.of(edge)); + } + + @Override + public Set edgesConnecting(N nodeU, N nodeV) { + Set outEdgesU = outEdges(nodeU); + Set inEdgesV = inEdges(nodeV); + return outEdgesU.size() <= inEdgesV.size() + ? unmodifiableSet(Sets.filter(outEdgesU, connectedPredicate(nodeU, nodeV))) + : unmodifiableSet(Sets.filter(inEdgesV, connectedPredicate(nodeV, nodeU))); + } + + @Override + public Set edgesConnecting(EndpointPair endpoints) { + validateEndpoints(endpoints); + return edgesConnecting(endpoints.nodeU(), endpoints.nodeV()); + } + + private Predicate connectedPredicate(final N nodePresent, final N nodeToCheck) { + return new Predicate() { + @Override + public boolean apply(E edge) { + return incidentNodes(edge).adjacentNode(nodePresent).equals(nodeToCheck); + } + }; + } + + @Override + public Optional edgeConnecting(N nodeU, N nodeV) { + return Optional.ofNullable(edgeConnectingOrNull(nodeU, nodeV)); + } + + @Override + public Optional edgeConnecting(EndpointPair endpoints) { + validateEndpoints(endpoints); + return edgeConnecting(endpoints.nodeU(), endpoints.nodeV()); + } + + @Override + public E edgeConnectingOrNull(N nodeU, N nodeV) { + Set edgesConnecting = edgesConnecting(nodeU, nodeV); + switch (edgesConnecting.size()) { + case 0: + return null; + case 1: + return edgesConnecting.iterator().next(); + default: + throw new IllegalArgumentException(String.format(MULTIPLE_EDGES_CONNECTING, nodeU, nodeV)); + } + } + + @Override + public E edgeConnectingOrNull(EndpointPair endpoints) { + validateEndpoints(endpoints); + return edgeConnectingOrNull(endpoints.nodeU(), endpoints.nodeV()); + } + + @Override + public boolean hasEdgeConnecting(N nodeU, N nodeV) { + return !edgesConnecting(nodeU, nodeV).isEmpty(); + } + + @Override + public boolean hasEdgeConnecting(EndpointPair endpoints) { + checkNotNull(endpoints); + if (!isOrderingCompatible(endpoints)) { + return false; + } + return !edgesConnecting(endpoints.nodeU(), endpoints.nodeV()).isEmpty(); + } + + /** + * Throws an IllegalArgumentException if the ordering of {@code endpoints} is not compatible with + * the directionality of this graph. + */ + protected final void validateEndpoints(EndpointPair endpoints) { + checkNotNull(endpoints); + checkArgument(isOrderingCompatible(endpoints), ENDPOINTS_MISMATCH); + } + + protected final boolean isOrderingCompatible(EndpointPair endpoints) { + return endpoints.isOrdered() || !this.isDirected(); + } + + @Override + public final boolean equals(Object obj) { + if (obj == this) { + return true; + } + if (!(obj instanceof Network)) { + return false; + } + Network other = (Network) obj; + + return isDirected() == other.isDirected() + && nodes().equals(other.nodes()) + && edgeIncidentNodesMap(this).equals(edgeIncidentNodesMap(other)); + } + + @Override + public final int hashCode() { + return edgeIncidentNodesMap(this).hashCode(); + } + + /** Returns a string representation of this network. */ + @Override + public String toString() { + return "isDirected: " + + isDirected() + + ", allowsParallelEdges: " + + allowsParallelEdges() + + ", allowsSelfLoops: " + + allowsSelfLoops() + + ", nodes: " + + nodes() + + ", edges: " + + edgeIncidentNodesMap(this); + } + + private static Map> edgeIncidentNodesMap(final Network network) { + Function> edgeToIncidentNodesFn = + new Function>() { + @Override + public EndpointPair apply(E edge) { + return network.incidentNodes(edge); + } + }; + return Maps.asMap(network.edges(), edgeToIncidentNodesFn); + } +} diff --git a/src/main/java/com/google/common/graph/AbstractUndirectedNetworkConnections.java b/src/main/java/com/google/common/graph/AbstractUndirectedNetworkConnections.java new file mode 100644 index 0000000..03279d0 --- /dev/null +++ b/src/main/java/com/google/common/graph/AbstractUndirectedNetworkConnections.java @@ -0,0 +1,97 @@ +/* + * Copyright (C) 2016 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.graph; + +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.base.Preconditions.checkState; + +import java.util.Collections; +import java.util.Map; +import java.util.Set; + +/** + * A base implementation of {@link NetworkConnections} for undirected networks. + * + * @author James Sexton + * @param Node parameter type + * @param Edge parameter type + */ +abstract class AbstractUndirectedNetworkConnections implements NetworkConnections { + /** Keys are edges incident to the origin node, values are the node at the other end. */ + protected final Map incidentEdgeMap; + + protected AbstractUndirectedNetworkConnections(Map incidentEdgeMap) { + this.incidentEdgeMap = checkNotNull(incidentEdgeMap); + } + + @Override + public Set predecessors() { + return adjacentNodes(); + } + + @Override + public Set successors() { + return adjacentNodes(); + } + + @Override + public Set incidentEdges() { + return Collections.unmodifiableSet(incidentEdgeMap.keySet()); + } + + @Override + public Set inEdges() { + return incidentEdges(); + } + + @Override + public Set outEdges() { + return incidentEdges(); + } + + @Override + public N adjacentNode(E edge) { + return checkNotNull(incidentEdgeMap.get(edge)); + } + + @Override + public N removeInEdge(E edge, boolean isSelfLoop) { + if (!isSelfLoop) { + return removeOutEdge(edge); + } + return null; + } + + @Override + public N removeOutEdge(E edge) { + N previousNode = incidentEdgeMap.remove(edge); + return checkNotNull(previousNode); + } + + @Override + public void addInEdge(E edge, N node, boolean isSelfLoop) { + if (!isSelfLoop) { + addOutEdge(edge, node); + } + } + + @Override + public void addOutEdge(E edge, N node) { + N previousNode = incidentEdgeMap.put(edge, node); + checkState(previousNode == null); + } +} diff --git a/src/main/java/com/google/common/graph/AbstractValueGraph.java b/src/main/java/com/google/common/graph/AbstractValueGraph.java new file mode 100644 index 0000000..6503b19 --- /dev/null +++ b/src/main/java/com/google/common/graph/AbstractValueGraph.java @@ -0,0 +1,156 @@ +/* + * Copyright (C) 2016 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.graph; + +import com.google.common.annotations.Beta; +import com.google.common.base.Function; +import com.google.common.collect.Maps; +import java.util.Map; +import java.util.Optional; +import java.util.Set; + + +/** + * This class provides a skeletal implementation of {@link ValueGraph}. It is recommended to extend + * this class rather than implement {@link ValueGraph} directly. + * + *

The methods implemented in this class should not be overridden unless the subclass admits a + * more efficient implementation. + * + * @author James Sexton + * @param Node parameter type + * @param Value parameter type + * @since 20.0 + */ +@Beta +public abstract class AbstractValueGraph extends AbstractBaseGraph + implements ValueGraph { + + @Override + public Graph asGraph() { + return new AbstractGraph() { + @Override + public Set nodes() { + return AbstractValueGraph.this.nodes(); + } + + @Override + public Set> edges() { + return AbstractValueGraph.this.edges(); + } + + @Override + public boolean isDirected() { + return AbstractValueGraph.this.isDirected(); + } + + @Override + public boolean allowsSelfLoops() { + return AbstractValueGraph.this.allowsSelfLoops(); + } + + @Override + public ElementOrder nodeOrder() { + return AbstractValueGraph.this.nodeOrder(); + } + + @Override + public Set adjacentNodes(N node) { + return AbstractValueGraph.this.adjacentNodes(node); + } + + @Override + public Set predecessors(N node) { + return AbstractValueGraph.this.predecessors(node); + } + + @Override + public Set successors(N node) { + return AbstractValueGraph.this.successors(node); + } + + @Override + public int degree(N node) { + return AbstractValueGraph.this.degree(node); + } + + @Override + public int inDegree(N node) { + return AbstractValueGraph.this.inDegree(node); + } + + @Override + public int outDegree(N node) { + return AbstractValueGraph.this.outDegree(node); + } + }; + } + + @Override + public Optional edgeValue(N nodeU, N nodeV) { + return Optional.ofNullable(edgeValueOrDefault(nodeU, nodeV, null)); + } + + @Override + public Optional edgeValue(EndpointPair endpoints) { + return Optional.ofNullable(edgeValueOrDefault(endpoints, null)); + } + + @Override + public final boolean equals(Object obj) { + if (obj == this) { + return true; + } + if (!(obj instanceof ValueGraph)) { + return false; + } + ValueGraph other = (ValueGraph) obj; + + return isDirected() == other.isDirected() + && nodes().equals(other.nodes()) + && edgeValueMap(this).equals(edgeValueMap(other)); + } + + @Override + public final int hashCode() { + return edgeValueMap(this).hashCode(); + } + + /** Returns a string representation of this graph. */ + @Override + public String toString() { + return "isDirected: " + + isDirected() + + ", allowsSelfLoops: " + + allowsSelfLoops() + + ", nodes: " + + nodes() + + ", edges: " + + edgeValueMap(this); + } + + private static Map, V> edgeValueMap(final ValueGraph graph) { + Function, V> edgeToValueFn = + new Function, V>() { + @Override + public V apply(EndpointPair edge) { + return graph.edgeValueOrDefault(edge.nodeU(), edge.nodeV(), null); + } + }; + return Maps.asMap(graph.edges(), edgeToValueFn); + } +} diff --git a/src/main/java/com/google/common/graph/BaseGraph.java b/src/main/java/com/google/common/graph/BaseGraph.java new file mode 100644 index 0000000..e6e2ad1 --- /dev/null +++ b/src/main/java/com/google/common/graph/BaseGraph.java @@ -0,0 +1,166 @@ +/* + * Copyright (C) 2017 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.graph; + +import java.util.Set; + +/** + * A non-public interface for the methods shared between {@link Graph} and {@link ValueGraph}. + * + * @author James Sexton + * @param Node parameter type + */ +interface BaseGraph extends SuccessorsFunction, PredecessorsFunction { + // + // Graph-level accessors + // + + /** Returns all nodes in this graph, in the order specified by {@link #nodeOrder()}. */ + Set nodes(); + + /** Returns all edges in this graph. */ + Set> edges(); + + // + // Graph properties + // + + /** + * Returns true if the edges in this graph are directed. Directed edges connect a {@link + * EndpointPair#source() source node} to a {@link EndpointPair#target() target node}, while + * undirected edges connect a pair of nodes to each other. + */ + boolean isDirected(); + + /** + * Returns true if this graph allows self-loops (edges that connect a node to itself). Attempting + * to add a self-loop to a graph that does not allow them will throw an {@link + * IllegalArgumentException}. + */ + boolean allowsSelfLoops(); + + /** Returns the order of iteration for the elements of {@link #nodes()}. */ + ElementOrder nodeOrder(); + + // + // Element-level accessors + // + + /** + * Returns the nodes which have an incident edge in common with {@code node} in this graph. + * + *

This is equal to the union of {@link #predecessors(Object)} and {@link #successors(Object)}. + * + * @throws IllegalArgumentException if {@code node} is not an element of this graph + */ + Set adjacentNodes(N node); + + /** + * Returns all nodes in this graph adjacent to {@code node} which can be reached by traversing + * {@code node}'s incoming edges against the direction (if any) of the edge. + * + *

In an undirected graph, this is equivalent to {@link #adjacentNodes(Object)}. + * + * @throws IllegalArgumentException if {@code node} is not an element of this graph + */ + @Override + Set predecessors(N node); + + /** + * Returns all nodes in this graph adjacent to {@code node} which can be reached by traversing + * {@code node}'s outgoing edges in the direction (if any) of the edge. + * + *

In an undirected graph, this is equivalent to {@link #adjacentNodes(Object)}. + * + *

This is not the same as "all nodes reachable from {@code node} by following outgoing + * edges". For that functionality, see {@link Graphs#reachableNodes(Graph, Object)}. + * + * @throws IllegalArgumentException if {@code node} is not an element of this graph + */ + @Override + Set successors(N node); + + /** + * Returns the edges in this graph whose endpoints include {@code node}. + * + *

This is equal to the union of incoming and outgoing edges. + * + * @throws IllegalArgumentException if {@code node} is not an element of this graph + * @since 24.0 + */ + Set> incidentEdges(N node); + + /** + * Returns the count of {@code node}'s incident edges, counting self-loops twice (equivalently, + * the number of times an edge touches {@code node}). + * + *

For directed graphs, this is equal to {@code inDegree(node) + outDegree(node)}. + * + *

For undirected graphs, this is equal to {@code incidentEdges(node).size()} + (number of + * self-loops incident to {@code node}). + * + *

If the count is greater than {@code Integer.MAX_VALUE}, returns {@code Integer.MAX_VALUE}. + * + * @throws IllegalArgumentException if {@code node} is not an element of this graph + */ + int degree(N node); + + /** + * Returns the count of {@code node}'s incoming edges (equal to {@code predecessors(node).size()}) + * in a directed graph. In an undirected graph, returns the {@link #degree(Object)}. + * + *

If the count is greater than {@code Integer.MAX_VALUE}, returns {@code Integer.MAX_VALUE}. + * + * @throws IllegalArgumentException if {@code node} is not an element of this graph + */ + int inDegree(N node); + + /** + * Returns the count of {@code node}'s outgoing edges (equal to {@code successors(node).size()}) + * in a directed graph. In an undirected graph, returns the {@link #degree(Object)}. + * + *

If the count is greater than {@code Integer.MAX_VALUE}, returns {@code Integer.MAX_VALUE}. + * + * @throws IllegalArgumentException if {@code node} is not an element of this graph + */ + int outDegree(N node); + + /** + * Returns true if there is an edge that directly connects {@code nodeU} to {@code nodeV}. This is + * equivalent to {@code nodes().contains(nodeU) && successors(nodeU).contains(nodeV)}. + * + *

In an undirected graph, this is equal to {@code hasEdgeConnecting(nodeV, nodeU)}. + * + * @since 23.0 + */ + boolean hasEdgeConnecting(N nodeU, N nodeV); + + /** + * Returns true if there is an edge that directly connects {@code endpoints} (in the order, if + * any, specified by {@code endpoints}). This is equivalent to {@code + * edges().contains(endpoints)}. + * + *

Unlike the other {@code EndpointPair}-accepting methods, this method does not throw if the + * endpoints are unordered; it simply returns false. This is for consistency with the behavior of + * {@link Collection#contains(Object)} (which does not generally throw if the object cannot be + * present in the collection), and the desire to have this method's behavior be compatible with + * {@code edges().contains(endpoints)}. + * + * @since 27.1 + */ + boolean hasEdgeConnecting(EndpointPair endpoints); +} diff --git a/src/main/java/com/google/common/graph/ConfigurableMutableGraph.java b/src/main/java/com/google/common/graph/ConfigurableMutableGraph.java new file mode 100644 index 0000000..db6bca7 --- /dev/null +++ b/src/main/java/com/google/common/graph/ConfigurableMutableGraph.java @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2016 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.graph; + +import com.google.common.graph.GraphConstants.Presence; + +/** + * Configurable implementation of {@link MutableGraph} that supports both directed and undirected + * graphs. Instances of this class should be constructed with {@link GraphBuilder}. + * + *

Time complexities for mutation methods are all O(1) except for {@code removeNode(N node)}, + * which is in O(d_node) where d_node is the degree of {@code node}. + * + * @author James Sexton + * @param Node parameter type + */ +final class ConfigurableMutableGraph extends ForwardingGraph implements MutableGraph { + private final MutableValueGraph backingValueGraph; + + /** Constructs a {@link MutableGraph} with the properties specified in {@code builder}. */ + ConfigurableMutableGraph(AbstractGraphBuilder builder) { + this.backingValueGraph = new ConfigurableMutableValueGraph<>(builder); + } + + @Override + protected BaseGraph delegate() { + return backingValueGraph; + } + + @Override + public boolean addNode(N node) { + return backingValueGraph.addNode(node); + } + + @Override + public boolean putEdge(N nodeU, N nodeV) { + return backingValueGraph.putEdgeValue(nodeU, nodeV, Presence.EDGE_EXISTS) == null; + } + + @Override + public boolean putEdge(EndpointPair endpoints) { + validateEndpoints(endpoints); + return putEdge(endpoints.nodeU(), endpoints.nodeV()); + } + + @Override + public boolean removeNode(N node) { + return backingValueGraph.removeNode(node); + } + + @Override + public boolean removeEdge(N nodeU, N nodeV) { + return backingValueGraph.removeEdge(nodeU, nodeV) != null; + } + + @Override + public boolean removeEdge(EndpointPair endpoints) { + validateEndpoints(endpoints); + return removeEdge(endpoints.nodeU(), endpoints.nodeV()); + } +} diff --git a/src/main/java/com/google/common/graph/ConfigurableMutableNetwork.java b/src/main/java/com/google/common/graph/ConfigurableMutableNetwork.java new file mode 100644 index 0000000..ead9d51 --- /dev/null +++ b/src/main/java/com/google/common/graph/ConfigurableMutableNetwork.java @@ -0,0 +1,173 @@ +/* + * Copyright (C) 2016 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.graph; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.base.Preconditions.checkState; +import static com.google.common.graph.GraphConstants.PARALLEL_EDGES_NOT_ALLOWED; +import static com.google.common.graph.GraphConstants.REUSING_EDGE; +import static com.google.common.graph.GraphConstants.SELF_LOOPS_NOT_ALLOWED; + +import com.google.common.collect.ImmutableList; + + +/** + * Configurable implementation of {@link MutableNetwork} that supports both directed and undirected + * graphs. Instances of this class should be constructed with {@link NetworkBuilder}. + * + *

Time complexities for mutation methods are all O(1) except for {@code removeNode(N node)}, + * which is in O(d_node) where d_node is the degree of {@code node}. + * + * @author James Sexton + * @author Joshua O'Madadhain + * @author Omar Darwish + * @param Node parameter type + * @param Edge parameter type + */ +final class ConfigurableMutableNetwork extends ConfigurableNetwork + implements MutableNetwork { + + /** Constructs a mutable graph with the properties specified in {@code builder}. */ + ConfigurableMutableNetwork(NetworkBuilder builder) { + super(builder); + } + + @Override + + public boolean addNode(N node) { + checkNotNull(node, "node"); + + if (containsNode(node)) { + return false; + } + + addNodeInternal(node); + return true; + } + + /** + * Adds {@code node} to the graph and returns the associated {@link NetworkConnections}. + * + * @throws IllegalStateException if {@code node} is already present + */ + + private NetworkConnections addNodeInternal(N node) { + NetworkConnections connections = newConnections(); + checkState(nodeConnections.put(node, connections) == null); + return connections; + } + + @Override + + public boolean addEdge(N nodeU, N nodeV, E edge) { + checkNotNull(nodeU, "nodeU"); + checkNotNull(nodeV, "nodeV"); + checkNotNull(edge, "edge"); + + if (containsEdge(edge)) { + EndpointPair existingIncidentNodes = incidentNodes(edge); + EndpointPair newIncidentNodes = EndpointPair.of(this, nodeU, nodeV); + checkArgument( + existingIncidentNodes.equals(newIncidentNodes), + REUSING_EDGE, + edge, + existingIncidentNodes, + newIncidentNodes); + return false; + } + NetworkConnections connectionsU = nodeConnections.get(nodeU); + if (!allowsParallelEdges()) { + checkArgument( + !(connectionsU != null && connectionsU.successors().contains(nodeV)), + PARALLEL_EDGES_NOT_ALLOWED, + nodeU, + nodeV); + } + boolean isSelfLoop = nodeU.equals(nodeV); + if (!allowsSelfLoops()) { + checkArgument(!isSelfLoop, SELF_LOOPS_NOT_ALLOWED, nodeU); + } + + if (connectionsU == null) { + connectionsU = addNodeInternal(nodeU); + } + connectionsU.addOutEdge(edge, nodeV); + NetworkConnections connectionsV = nodeConnections.get(nodeV); + if (connectionsV == null) { + connectionsV = addNodeInternal(nodeV); + } + connectionsV.addInEdge(edge, nodeU, isSelfLoop); + edgeToReferenceNode.put(edge, nodeU); + return true; + } + + @Override + + public boolean addEdge(EndpointPair endpoints, E edge) { + validateEndpoints(endpoints); + return addEdge(endpoints.nodeU(), endpoints.nodeV(), edge); + } + + @Override + + public boolean removeNode(N node) { + checkNotNull(node, "node"); + + NetworkConnections connections = nodeConnections.get(node); + if (connections == null) { + return false; + } + + // Since views are returned, we need to copy the edges that will be removed. + // Thus we avoid modifying the underlying view while iterating over it. + for (E edge : ImmutableList.copyOf(connections.incidentEdges())) { + removeEdge(edge); + } + nodeConnections.remove(node); + return true; + } + + @Override + + public boolean removeEdge(E edge) { + checkNotNull(edge, "edge"); + + N nodeU = edgeToReferenceNode.get(edge); + if (nodeU == null) { + return false; + } + + NetworkConnections connectionsU = nodeConnections.get(nodeU); + N nodeV = connectionsU.adjacentNode(edge); + NetworkConnections connectionsV = nodeConnections.get(nodeV); + connectionsU.removeOutEdge(edge); + connectionsV.removeInEdge(edge, allowsSelfLoops() && nodeU.equals(nodeV)); + edgeToReferenceNode.remove(edge); + return true; + } + + private NetworkConnections newConnections() { + return isDirected() + ? allowsParallelEdges() + ? DirectedMultiNetworkConnections.of() + : DirectedNetworkConnections.of() + : allowsParallelEdges() + ? UndirectedMultiNetworkConnections.of() + : UndirectedNetworkConnections.of(); + } +} diff --git a/src/main/java/com/google/common/graph/ConfigurableMutableValueGraph.java b/src/main/java/com/google/common/graph/ConfigurableMutableValueGraph.java new file mode 100644 index 0000000..8de0df0 --- /dev/null +++ b/src/main/java/com/google/common/graph/ConfigurableMutableValueGraph.java @@ -0,0 +1,173 @@ +/* + * Copyright (C) 2016 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.graph; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.base.Preconditions.checkState; +import static com.google.common.graph.GraphConstants.SELF_LOOPS_NOT_ALLOWED; +import static com.google.common.graph.Graphs.checkNonNegative; +import static com.google.common.graph.Graphs.checkPositive; + + + +/** + * Configurable implementation of {@link MutableValueGraph} that supports both directed and + * undirected graphs. Instances of this class should be constructed with {@link ValueGraphBuilder}. + * + *

Time complexities for mutation methods are all O(1) except for {@code removeNode(N node)}, + * which is in O(d_node) where d_node is the degree of {@code node}. + * + * @author James Sexton + * @author Joshua O'Madadhain + * @author Omar Darwish + * @param Node parameter type + * @param Value parameter type + */ +final class ConfigurableMutableValueGraph extends ConfigurableValueGraph + implements MutableValueGraph { + + /** Constructs a mutable graph with the properties specified in {@code builder}. */ + ConfigurableMutableValueGraph(AbstractGraphBuilder builder) { + super(builder); + } + + @Override + + public boolean addNode(N node) { + checkNotNull(node, "node"); + + if (containsNode(node)) { + return false; + } + + addNodeInternal(node); + return true; + } + + /** + * Adds {@code node} to the graph and returns the associated {@link GraphConnections}. + * + * @throws IllegalStateException if {@code node} is already present + */ + + private GraphConnections addNodeInternal(N node) { + GraphConnections connections = newConnections(); + checkState(nodeConnections.put(node, connections) == null); + return connections; + } + + @Override + + public V putEdgeValue(N nodeU, N nodeV, V value) { + checkNotNull(nodeU, "nodeU"); + checkNotNull(nodeV, "nodeV"); + checkNotNull(value, "value"); + + if (!allowsSelfLoops()) { + checkArgument(!nodeU.equals(nodeV), SELF_LOOPS_NOT_ALLOWED, nodeU); + } + + GraphConnections connectionsU = nodeConnections.get(nodeU); + if (connectionsU == null) { + connectionsU = addNodeInternal(nodeU); + } + V previousValue = connectionsU.addSuccessor(nodeV, value); + GraphConnections connectionsV = nodeConnections.get(nodeV); + if (connectionsV == null) { + connectionsV = addNodeInternal(nodeV); + } + connectionsV.addPredecessor(nodeU, value); + if (previousValue == null) { + checkPositive(++edgeCount); + } + return previousValue; + } + + @Override + + public V putEdgeValue(EndpointPair endpoints, V value) { + validateEndpoints(endpoints); + return putEdgeValue(endpoints.nodeU(), endpoints.nodeV(), value); + } + + @Override + + public boolean removeNode(N node) { + checkNotNull(node, "node"); + + GraphConnections connections = nodeConnections.get(node); + if (connections == null) { + return false; + } + + if (allowsSelfLoops()) { + // Remove self-loop (if any) first, so we don't get CME while removing incident edges. + if (connections.removeSuccessor(node) != null) { + connections.removePredecessor(node); + --edgeCount; + } + } + + for (N successor : connections.successors()) { + nodeConnections.getWithoutCaching(successor).removePredecessor(node); + --edgeCount; + } + if (isDirected()) { // In undirected graphs, the successor and predecessor sets are equal. + for (N predecessor : connections.predecessors()) { + checkState(nodeConnections.getWithoutCaching(predecessor).removeSuccessor(node) != null); + --edgeCount; + } + } + nodeConnections.remove(node); + checkNonNegative(edgeCount); + return true; + } + + @Override + + public V removeEdge(N nodeU, N nodeV) { + checkNotNull(nodeU, "nodeU"); + checkNotNull(nodeV, "nodeV"); + + GraphConnections connectionsU = nodeConnections.get(nodeU); + GraphConnections connectionsV = nodeConnections.get(nodeV); + if (connectionsU == null || connectionsV == null) { + return null; + } + + V previousValue = connectionsU.removeSuccessor(nodeV); + if (previousValue != null) { + connectionsV.removePredecessor(nodeU); + checkNonNegative(--edgeCount); + } + return previousValue; + } + + @Override + + public V removeEdge(EndpointPair endpoints) { + validateEndpoints(endpoints); + return removeEdge(endpoints.nodeU(), endpoints.nodeV()); + } + + private GraphConnections newConnections() { + return isDirected() + ? DirectedGraphConnections.of() + : UndirectedGraphConnections.of(); + } +} diff --git a/src/main/java/com/google/common/graph/ConfigurableNetwork.java b/src/main/java/com/google/common/graph/ConfigurableNetwork.java new file mode 100644 index 0000000..1667f23 --- /dev/null +++ b/src/main/java/com/google/common/graph/ConfigurableNetwork.java @@ -0,0 +1,202 @@ +/* + * Copyright (C) 2016 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.graph; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.graph.GraphConstants.DEFAULT_EDGE_COUNT; +import static com.google.common.graph.GraphConstants.DEFAULT_NODE_COUNT; +import static com.google.common.graph.GraphConstants.EDGE_NOT_IN_GRAPH; +import static com.google.common.graph.GraphConstants.NODE_NOT_IN_GRAPH; + +import com.google.common.collect.ImmutableSet; +import java.util.Map; +import java.util.Set; +import java.util.TreeMap; + + +/** + * Configurable implementation of {@link Network} that supports the options supplied by {@link + * NetworkBuilder}. + * + *

This class maintains a map of nodes to {@link NetworkConnections}. This class also maintains a + * map of edges to reference nodes. The reference node is defined to be the edge's source node on + * directed graphs, and an arbitrary endpoint of the edge on undirected graphs. + * + *

Collection-returning accessors return unmodifiable views: the view returned will reflect + * changes to the graph (if the graph is mutable) but may not be modified by the user. + * + *

The time complexity of all collection-returning accessors is O(1), since views are returned. + * + * @author James Sexton + * @author Joshua O'Madadhain + * @author Omar Darwish + * @param Node parameter type + * @param Edge parameter type + */ +class ConfigurableNetwork extends AbstractNetwork { + private final boolean isDirected; + private final boolean allowsParallelEdges; + private final boolean allowsSelfLoops; + private final ElementOrder nodeOrder; + private final ElementOrder edgeOrder; + + protected final MapIteratorCache> nodeConnections; + + // We could make this a Map>. It would make incidentNodes(edge) slightly + // faster, but also make Networks consume 5 to 20+% (increasing with average degree) more memory. + protected final MapIteratorCache edgeToReferenceNode; // referenceNode == source if directed + + /** Constructs a graph with the properties specified in {@code builder}. */ + ConfigurableNetwork(NetworkBuilder builder) { + this( + builder, + builder.nodeOrder.>createMap( + builder.expectedNodeCount.or(DEFAULT_NODE_COUNT)), + builder.edgeOrder.createMap(builder.expectedEdgeCount.or(DEFAULT_EDGE_COUNT))); + } + + /** + * Constructs a graph with the properties specified in {@code builder}, initialized with the given + * node and edge maps. + */ + ConfigurableNetwork( + NetworkBuilder builder, + Map> nodeConnections, + Map edgeToReferenceNode) { + this.isDirected = builder.directed; + this.allowsParallelEdges = builder.allowsParallelEdges; + this.allowsSelfLoops = builder.allowsSelfLoops; + this.nodeOrder = builder.nodeOrder.cast(); + this.edgeOrder = builder.edgeOrder.cast(); + // Prefer the heavier "MapRetrievalCache" for nodes if lookup is expensive. This optimizes + // methods that access the same node(s) repeatedly, such as Graphs.removeEdgesConnecting(). + this.nodeConnections = + (nodeConnections instanceof TreeMap) + ? new MapRetrievalCache>(nodeConnections) + : new MapIteratorCache>(nodeConnections); + this.edgeToReferenceNode = new MapIteratorCache<>(edgeToReferenceNode); + } + + @Override + public Set nodes() { + return nodeConnections.unmodifiableKeySet(); + } + + @Override + public Set edges() { + return edgeToReferenceNode.unmodifiableKeySet(); + } + + @Override + public boolean isDirected() { + return isDirected; + } + + @Override + public boolean allowsParallelEdges() { + return allowsParallelEdges; + } + + @Override + public boolean allowsSelfLoops() { + return allowsSelfLoops; + } + + @Override + public ElementOrder nodeOrder() { + return nodeOrder; + } + + @Override + public ElementOrder edgeOrder() { + return edgeOrder; + } + + @Override + public Set incidentEdges(N node) { + return checkedConnections(node).incidentEdges(); + } + + @Override + public EndpointPair incidentNodes(E edge) { + N nodeU = checkedReferenceNode(edge); + N nodeV = nodeConnections.get(nodeU).adjacentNode(edge); + return EndpointPair.of(this, nodeU, nodeV); + } + + @Override + public Set adjacentNodes(N node) { + return checkedConnections(node).adjacentNodes(); + } + + @Override + public Set edgesConnecting(N nodeU, N nodeV) { + NetworkConnections connectionsU = checkedConnections(nodeU); + if (!allowsSelfLoops && nodeU == nodeV) { // just an optimization, only check reference equality + return ImmutableSet.of(); + } + checkArgument(containsNode(nodeV), NODE_NOT_IN_GRAPH, nodeV); + return connectionsU.edgesConnecting(nodeV); + } + + @Override + public Set inEdges(N node) { + return checkedConnections(node).inEdges(); + } + + @Override + public Set outEdges(N node) { + return checkedConnections(node).outEdges(); + } + + @Override + public Set predecessors(N node) { + return checkedConnections(node).predecessors(); + } + + @Override + public Set successors(N node) { + return checkedConnections(node).successors(); + } + + protected final NetworkConnections checkedConnections(N node) { + NetworkConnections connections = nodeConnections.get(node); + if (connections == null) { + checkNotNull(node); + throw new IllegalArgumentException(String.format(NODE_NOT_IN_GRAPH, node)); + } + return connections; + } + + protected final N checkedReferenceNode(E edge) { + N referenceNode = edgeToReferenceNode.get(edge); + if (referenceNode == null) { + checkNotNull(edge); + throw new IllegalArgumentException(String.format(EDGE_NOT_IN_GRAPH, edge)); + } + return referenceNode; + } + + protected final boolean containsNode(N node) { + return nodeConnections.containsKey(node); + } + + protected final boolean containsEdge(E edge) { + return edgeToReferenceNode.containsKey(edge); + } +} diff --git a/src/main/java/com/google/common/graph/ConfigurableValueGraph.java b/src/main/java/com/google/common/graph/ConfigurableValueGraph.java new file mode 100644 index 0000000..f3efd93 --- /dev/null +++ b/src/main/java/com/google/common/graph/ConfigurableValueGraph.java @@ -0,0 +1,168 @@ +/* + * Copyright (C) 2016 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.graph; + +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.graph.GraphConstants.DEFAULT_NODE_COUNT; +import static com.google.common.graph.Graphs.checkNonNegative; + +import java.util.Map; +import java.util.Set; +import java.util.TreeMap; + + +/** + * Configurable implementation of {@link ValueGraph} that supports the options supplied by {@link + * AbstractGraphBuilder}. + * + *

This class maintains a map of nodes to {@link GraphConnections}. + * + *

Collection-returning accessors return unmodifiable views: the view returned will reflect + * changes to the graph (if the graph is mutable) but may not be modified by the user. + * + *

The time complexity of all collection-returning accessors is O(1), since views are returned. + * + * @author James Sexton + * @author Joshua O'Madadhain + * @author Omar Darwish + * @param Node parameter type + * @param Value parameter type + */ +class ConfigurableValueGraph extends AbstractValueGraph { + private final boolean isDirected; + private final boolean allowsSelfLoops; + private final ElementOrder nodeOrder; + + protected final MapIteratorCache> nodeConnections; + + protected long edgeCount; // must be updated when edges are added or removed + + /** Constructs a graph with the properties specified in {@code builder}. */ + ConfigurableValueGraph(AbstractGraphBuilder builder) { + this( + builder, + builder.nodeOrder.>createMap( + builder.expectedNodeCount.or(DEFAULT_NODE_COUNT)), + 0L); + } + + /** + * Constructs a graph with the properties specified in {@code builder}, initialized with the given + * node map. + */ + ConfigurableValueGraph( + AbstractGraphBuilder builder, + Map> nodeConnections, + long edgeCount) { + this.isDirected = builder.directed; + this.allowsSelfLoops = builder.allowsSelfLoops; + this.nodeOrder = builder.nodeOrder.cast(); + // Prefer the heavier "MapRetrievalCache" for nodes if lookup is expensive. + this.nodeConnections = + (nodeConnections instanceof TreeMap) + ? new MapRetrievalCache>(nodeConnections) + : new MapIteratorCache>(nodeConnections); + this.edgeCount = checkNonNegative(edgeCount); + } + + @Override + public Set nodes() { + return nodeConnections.unmodifiableKeySet(); + } + + @Override + public boolean isDirected() { + return isDirected; + } + + @Override + public boolean allowsSelfLoops() { + return allowsSelfLoops; + } + + @Override + public ElementOrder nodeOrder() { + return nodeOrder; + } + + @Override + public Set adjacentNodes(N node) { + return checkedConnections(node).adjacentNodes(); + } + + @Override + public Set predecessors(N node) { + return checkedConnections(node).predecessors(); + } + + @Override + public Set successors(N node) { + return checkedConnections(node).successors(); + } + + @Override + public boolean hasEdgeConnecting(N nodeU, N nodeV) { + return hasEdgeConnecting_internal(checkNotNull(nodeU), checkNotNull(nodeV)); + } + + @Override + public boolean hasEdgeConnecting(EndpointPair endpoints) { + checkNotNull(endpoints); + return isOrderingCompatible(endpoints) + && hasEdgeConnecting_internal(endpoints.nodeU(), endpoints.nodeV()); + } + + @Override + public V edgeValueOrDefault(N nodeU, N nodeV, V defaultValue) { + return edgeValueOrDefault_internal(checkNotNull(nodeU), checkNotNull(nodeV), defaultValue); + } + + @Override + public V edgeValueOrDefault(EndpointPair endpoints, V defaultValue) { + validateEndpoints(endpoints); + return edgeValueOrDefault_internal(endpoints.nodeU(), endpoints.nodeV(), defaultValue); + } + + @Override + protected long edgeCount() { + return edgeCount; + } + + protected final GraphConnections checkedConnections(N node) { + GraphConnections connections = nodeConnections.get(node); + if (connections == null) { + checkNotNull(node); + throw new IllegalArgumentException("Node " + node + " is not an element of this graph."); + } + return connections; + } + + protected final boolean containsNode(N node) { + return nodeConnections.containsKey(node); + } + + protected final boolean hasEdgeConnecting_internal(N nodeU, N nodeV) { + GraphConnections connectionsU = nodeConnections.get(nodeU); + return (connectionsU != null) && connectionsU.successors().contains(nodeV); + } + + protected final V edgeValueOrDefault_internal(N nodeU, N nodeV, V defaultValue) { + GraphConnections connectionsU = nodeConnections.get(nodeU); + V value = (connectionsU == null) ? null : connectionsU.value(nodeV); + return value == null ? defaultValue : value; + } +} diff --git a/src/main/java/com/google/common/graph/DirectedGraphConnections.java b/src/main/java/com/google/common/graph/DirectedGraphConnections.java new file mode 100644 index 0000000..6dc2305 --- /dev/null +++ b/src/main/java/com/google/common/graph/DirectedGraphConnections.java @@ -0,0 +1,251 @@ +/* + * Copyright (C) 2016 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.graph; + +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.base.Preconditions.checkState; +import static com.google.common.graph.GraphConstants.INNER_CAPACITY; +import static com.google.common.graph.GraphConstants.INNER_LOAD_FACTOR; +import static com.google.common.graph.Graphs.checkNonNegative; +import static com.google.common.graph.Graphs.checkPositive; + +import com.google.common.collect.AbstractIterator; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.UnmodifiableIterator; +import java.util.AbstractSet; +import java.util.Collections; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; + + +/** + * An implementation of {@link GraphConnections} for directed graphs. + * + * @author James Sexton + * @param Node parameter type + * @param Value parameter type + */ +final class DirectedGraphConnections implements GraphConnections { + /** + * A wrapper class to indicate a node is both a predecessor and successor while still providing + * the successor value. + */ + private static final class PredAndSucc { + private final Object successorValue; + + PredAndSucc(Object successorValue) { + this.successorValue = successorValue; + } + } + + private static final Object PRED = new Object(); + + // Every value in this map must either be an instance of PredAndSucc with a successorValue of + // type V, PRED (representing predecessor), or an instance of type V (representing successor). + private final Map adjacentNodeValues; + + private int predecessorCount; + private int successorCount; + + private DirectedGraphConnections( + Map adjacentNodeValues, int predecessorCount, int successorCount) { + this.adjacentNodeValues = checkNotNull(adjacentNodeValues); + this.predecessorCount = checkNonNegative(predecessorCount); + this.successorCount = checkNonNegative(successorCount); + checkState( + predecessorCount <= adjacentNodeValues.size() + && successorCount <= adjacentNodeValues.size()); + } + + static DirectedGraphConnections of() { + // We store predecessors and successors in the same map, so double the initial capacity. + int initialCapacity = INNER_CAPACITY * 2; + return new DirectedGraphConnections<>( + new HashMap(initialCapacity, INNER_LOAD_FACTOR), 0, 0); + } + + static DirectedGraphConnections ofImmutable( + Set predecessors, Map successorValues) { + Map adjacentNodeValues = new HashMap<>(); + adjacentNodeValues.putAll(successorValues); + for (N predecessor : predecessors) { + Object value = adjacentNodeValues.put(predecessor, PRED); + if (value != null) { + adjacentNodeValues.put(predecessor, new PredAndSucc(value)); + } + } + return new DirectedGraphConnections<>( + ImmutableMap.copyOf(adjacentNodeValues), predecessors.size(), successorValues.size()); + } + + @Override + public Set adjacentNodes() { + return Collections.unmodifiableSet(adjacentNodeValues.keySet()); + } + + @Override + public Set predecessors() { + return new AbstractSet() { + @Override + public UnmodifiableIterator iterator() { + final Iterator> entries = adjacentNodeValues.entrySet().iterator(); + return new AbstractIterator() { + @Override + protected N computeNext() { + while (entries.hasNext()) { + Entry entry = entries.next(); + if (isPredecessor(entry.getValue())) { + return entry.getKey(); + } + } + return endOfData(); + } + }; + } + + @Override + public int size() { + return predecessorCount; + } + + @Override + public boolean contains(Object obj) { + return isPredecessor(adjacentNodeValues.get(obj)); + } + }; + } + + @Override + public Set successors() { + return new AbstractSet() { + @Override + public UnmodifiableIterator iterator() { + final Iterator> entries = adjacentNodeValues.entrySet().iterator(); + return new AbstractIterator() { + @Override + protected N computeNext() { + while (entries.hasNext()) { + Entry entry = entries.next(); + if (isSuccessor(entry.getValue())) { + return entry.getKey(); + } + } + return endOfData(); + } + }; + } + + @Override + public int size() { + return successorCount; + } + + @Override + public boolean contains(Object obj) { + return isSuccessor(adjacentNodeValues.get(obj)); + } + }; + } + + @SuppressWarnings("unchecked") + @Override + public V value(N node) { + Object value = adjacentNodeValues.get(node); + if (value == PRED) { + return null; + } + if (value instanceof PredAndSucc) { + return (V) ((PredAndSucc) value).successorValue; + } + return (V) value; + } + + @SuppressWarnings("unchecked") + @Override + public void removePredecessor(N node) { + Object previousValue = adjacentNodeValues.get(node); + if (previousValue == PRED) { + adjacentNodeValues.remove(node); + checkNonNegative(--predecessorCount); + } else if (previousValue instanceof PredAndSucc) { + adjacentNodeValues.put((N) node, ((PredAndSucc) previousValue).successorValue); + checkNonNegative(--predecessorCount); + } + } + + @SuppressWarnings("unchecked") + @Override + public V removeSuccessor(Object node) { + Object previousValue = adjacentNodeValues.get(node); + if (previousValue == null || previousValue == PRED) { + return null; + } else if (previousValue instanceof PredAndSucc) { + adjacentNodeValues.put((N) node, PRED); + checkNonNegative(--successorCount); + return (V) ((PredAndSucc) previousValue).successorValue; + } else { // successor + adjacentNodeValues.remove(node); + checkNonNegative(--successorCount); + return (V) previousValue; + } + } + + @Override + public void addPredecessor(N node, V unused) { + Object previousValue = adjacentNodeValues.put(node, PRED); + if (previousValue == null) { + checkPositive(++predecessorCount); + } else if (previousValue instanceof PredAndSucc) { + // Restore previous PredAndSucc object. + adjacentNodeValues.put(node, previousValue); + } else if (previousValue != PRED) { // successor + // Do NOT use method parameter value 'unused'. In directed graphs, successors store the value. + adjacentNodeValues.put(node, new PredAndSucc(previousValue)); + checkPositive(++predecessorCount); + } + } + + @SuppressWarnings("unchecked") + @Override + public V addSuccessor(N node, V value) { + Object previousValue = adjacentNodeValues.put(node, value); + if (previousValue == null) { + checkPositive(++successorCount); + return null; + } else if (previousValue instanceof PredAndSucc) { + adjacentNodeValues.put(node, new PredAndSucc(value)); + return (V) ((PredAndSucc) previousValue).successorValue; + } else if (previousValue == PRED) { + adjacentNodeValues.put(node, new PredAndSucc(value)); + checkPositive(++successorCount); + return null; + } else { // successor + return (V) previousValue; + } + } + + private static boolean isPredecessor(Object value) { + return (value == PRED) || (value instanceof PredAndSucc); + } + + private static boolean isSuccessor(Object value) { + return (value != PRED) && (value != null); + } +} diff --git a/src/main/java/com/google/common/graph/DirectedMultiNetworkConnections.java b/src/main/java/com/google/common/graph/DirectedMultiNetworkConnections.java new file mode 100644 index 0000000..2e6833d --- /dev/null +++ b/src/main/java/com/google/common/graph/DirectedMultiNetworkConnections.java @@ -0,0 +1,145 @@ +/* + * Copyright (C) 2016 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.graph; + +import static com.google.common.base.Preconditions.checkState; +import static com.google.common.graph.GraphConstants.INNER_CAPACITY; +import static com.google.common.graph.GraphConstants.INNER_LOAD_FACTOR; + +import com.google.common.collect.HashMultiset; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Multiset; + +import java.lang.ref.Reference; +import java.lang.ref.SoftReference; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + + +/** + * An implementation of {@link NetworkConnections} for directed networks with parallel edges. + * + * @author James Sexton + * @param Node parameter type + * @param Edge parameter type + */ +final class DirectedMultiNetworkConnections extends AbstractDirectedNetworkConnections { + + private DirectedMultiNetworkConnections( + Map inEdges, Map outEdges, int selfLoopCount) { + super(inEdges, outEdges, selfLoopCount); + } + + static DirectedMultiNetworkConnections of() { + return new DirectedMultiNetworkConnections<>( + new HashMap(INNER_CAPACITY, INNER_LOAD_FACTOR), + new HashMap(INNER_CAPACITY, INNER_LOAD_FACTOR), + 0); + } + + static DirectedMultiNetworkConnections ofImmutable( + Map inEdges, Map outEdges, int selfLoopCount) { + return new DirectedMultiNetworkConnections<>( + ImmutableMap.copyOf(inEdges), ImmutableMap.copyOf(outEdges), selfLoopCount); + } + + private transient Reference> predecessorsReference; + + @Override + public Set predecessors() { + return Collections.unmodifiableSet(predecessorsMultiset().elementSet()); + } + + private Multiset predecessorsMultiset() { + Multiset predecessors = getReference(predecessorsReference); + if (predecessors == null) { + predecessors = HashMultiset.create(inEdgeMap.values()); + predecessorsReference = new SoftReference<>(predecessors); + } + return predecessors; + } + + private transient Reference> successorsReference; + + @Override + public Set successors() { + return Collections.unmodifiableSet(successorsMultiset().elementSet()); + } + + private Multiset successorsMultiset() { + Multiset successors = getReference(successorsReference); + if (successors == null) { + successors = HashMultiset.create(outEdgeMap.values()); + successorsReference = new SoftReference<>(successors); + } + return successors; + } + + @Override + public Set edgesConnecting(final N node) { + return new MultiEdgesConnecting(outEdgeMap, node) { + @Override + public int size() { + return successorsMultiset().count(node); + } + }; + } + + @Override + public N removeInEdge(E edge, boolean isSelfLoop) { + N node = super.removeInEdge(edge, isSelfLoop); + Multiset predecessors = getReference(predecessorsReference); + if (predecessors != null) { + checkState(predecessors.remove(node)); + } + return node; + } + + @Override + public N removeOutEdge(E edge) { + N node = super.removeOutEdge(edge); + Multiset successors = getReference(successorsReference); + if (successors != null) { + checkState(successors.remove(node)); + } + return node; + } + + @Override + public void addInEdge(E edge, N node, boolean isSelfLoop) { + super.addInEdge(edge, node, isSelfLoop); + Multiset predecessors = getReference(predecessorsReference); + if (predecessors != null) { + checkState(predecessors.add(node)); + } + } + + @Override + public void addOutEdge(E edge, N node) { + super.addOutEdge(edge, node); + Multiset successors = getReference(successorsReference); + if (successors != null) { + checkState(successors.add(node)); + } + } + + private static T getReference(Reference reference) { + return (reference == null) ? null : reference.get(); + } +} diff --git a/src/main/java/com/google/common/graph/DirectedNetworkConnections.java b/src/main/java/com/google/common/graph/DirectedNetworkConnections.java new file mode 100644 index 0000000..2a0b010 --- /dev/null +++ b/src/main/java/com/google/common/graph/DirectedNetworkConnections.java @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2016 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.graph; + +import static com.google.common.graph.GraphConstants.EXPECTED_DEGREE; + +import com.google.common.collect.BiMap; +import com.google.common.collect.HashBiMap; +import com.google.common.collect.ImmutableBiMap; +import java.util.Collections; +import java.util.Map; +import java.util.Set; + +/** + * An implementation of {@link NetworkConnections} for directed networks. + * + * @author James Sexton + * @param Node parameter type + * @param Edge parameter type + */ +final class DirectedNetworkConnections extends AbstractDirectedNetworkConnections { + + protected DirectedNetworkConnections( + Map inEdgeMap, Map outEdgeMap, int selfLoopCount) { + super(inEdgeMap, outEdgeMap, selfLoopCount); + } + + static DirectedNetworkConnections of() { + return new DirectedNetworkConnections<>( + HashBiMap.create(EXPECTED_DEGREE), HashBiMap.create(EXPECTED_DEGREE), 0); + } + + static DirectedNetworkConnections ofImmutable( + Map inEdges, Map outEdges, int selfLoopCount) { + return new DirectedNetworkConnections<>( + ImmutableBiMap.copyOf(inEdges), ImmutableBiMap.copyOf(outEdges), selfLoopCount); + } + + @Override + public Set predecessors() { + return Collections.unmodifiableSet(((BiMap) inEdgeMap).values()); + } + + @Override + public Set successors() { + return Collections.unmodifiableSet(((BiMap) outEdgeMap).values()); + } + + @Override + public Set edgesConnecting(N node) { + return new EdgesConnecting(((BiMap) outEdgeMap).inverse(), node); + } +} diff --git a/src/main/java/com/google/common/graph/EdgesConnecting.java b/src/main/java/com/google/common/graph/EdgesConnecting.java new file mode 100644 index 0000000..a9d5681 --- /dev/null +++ b/src/main/java/com/google/common/graph/EdgesConnecting.java @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2016 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.graph; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Iterators; +import com.google.common.collect.UnmodifiableIterator; +import java.util.AbstractSet; +import java.util.Map; + + +/** + * A class to represent the set of edges connecting an (implicit) origin node to a target node. + * + *

The {@link #nodeToOutEdge} map means this class only works on networks without parallel edges. + * See {@link MultiEdgesConnecting} for a class that works with parallel edges. + * + * @author James Sexton + * @param Edge parameter type + */ +final class EdgesConnecting extends AbstractSet { + + private final Map nodeToOutEdge; + private final Object targetNode; + + EdgesConnecting(Map nodeToEdgeMap, Object targetNode) { + this.nodeToOutEdge = checkNotNull(nodeToEdgeMap); + this.targetNode = checkNotNull(targetNode); + } + + @Override + public UnmodifiableIterator iterator() { + E connectingEdge = getConnectingEdge(); + return (connectingEdge == null) + ? ImmutableSet.of().iterator() + : Iterators.singletonIterator(connectingEdge); + } + + @Override + public int size() { + return getConnectingEdge() == null ? 0 : 1; + } + + @Override + public boolean contains(Object edge) { + E connectingEdge = getConnectingEdge(); + return (connectingEdge != null && connectingEdge.equals(edge)); + } + + private E getConnectingEdge() { + return nodeToOutEdge.get(targetNode); + } +} diff --git a/src/main/java/com/google/common/graph/ElementOrder.java b/src/main/java/com/google/common/graph/ElementOrder.java new file mode 100644 index 0000000..592568f --- /dev/null +++ b/src/main/java/com/google/common/graph/ElementOrder.java @@ -0,0 +1,205 @@ +/* + * Copyright (C) 2016 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.graph; + +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.base.Preconditions.checkState; + +import com.google.common.annotations.Beta; +import com.google.common.base.MoreObjects; +import com.google.common.base.MoreObjects.ToStringHelper; +import com.google.common.base.Objects; +import com.google.common.collect.Maps; +import com.google.common.collect.Ordering; + +import java.util.Comparator; +import java.util.Map; + + +/** + * Used to represent the order of elements in a data structure that supports different options for + * iteration order guarantees. + * + *

Example usage: + * + *

{@code
+ * MutableGraph graph =
+ *     GraphBuilder.directed().nodeOrder(ElementOrder.natural()).build();
+ * }
+ * + * @author Joshua O'Madadhain + * @since 20.0 + */ +@Beta +public final class ElementOrder { + private final Type type; + + @SuppressWarnings("Immutable") // Hopefully the comparator provided is immutable! + private final Comparator comparator; + + /** + * The type of ordering that this object specifies. + * + *
    + *
  • UNORDERED: no order is guaranteed. + *
  • STABLE: ordering is guaranteed to follow a pattern that won't change between releases. + * Some methods may have stronger guarantees. + *
  • INSERTION: insertion ordering is guaranteed. + *
  • SORTED: ordering according to a supplied comparator is guaranteed. + *
+ */ + public enum Type { + UNORDERED, + STABLE, + INSERTION, + SORTED + } + + private ElementOrder(Type type, Comparator comparator) { + this.type = checkNotNull(type); + this.comparator = comparator; + checkState((type == Type.SORTED) == (comparator != null)); + } + + /** Returns an instance which specifies that no ordering is guaranteed. */ + public static ElementOrder unordered() { + return new ElementOrder(Type.UNORDERED, null); + } + + /** + * Returns an instance which specifies that ordering is guaranteed to be always be the same across + * iterations, and across releases. Some methods may have stronger guarantees. + * + *

This instance is only useful in combination with {@code incidentEdgeOrder}, e.g. {@code + * graphBuilder.incidentEdgeOrder(ElementOrder.stable())}. + * + *

In combination with {@code incidentEdgeOrder}

+ * + *

{@code incidentEdgeOrder(ElementOrder.stable())} guarantees the ordering of the returned + * collections of the following methods: + * + *

    + *
  • For {@link Graph} and {@link ValueGraph}: + *
      + *
    • {@code edges()}: Stable order + *
    • {@code adjacentNodes(node)}: Connecting edge insertion order + *
    • {@code predecessors(node)}: Connecting edge insertion order + *
    • {@code successors(node)}: Connecting edge insertion order + *
    • {@code incidentEdges(node)}: Stable order + *
    + *
  • For {@link Network}: + *
      + *
    • {@code adjacentNodes(node)}: Stable order + *
    • {@code predecessors(node)}: Connecting edge insertion order + *
    • {@code successors(node)}: Connecting edge insertion order + *
    • {@code incidentEdges(node)}: Stable order + *
    • {@code inEdges(node)}: Edge insertion order + *
    • {@code outEdges(node)}: Edge insertion order + *
    • {@code adjacentEdges(edge)}: Stable order + *
    • {@code edgesConnecting(nodeU, nodeV)}: Edge insertion order + *
    + *
+ */ + // TODO(b/142723300): Make this method public + static ElementOrder stable() { + return new ElementOrder(Type.STABLE, null); + } + + /** Returns an instance which specifies that insertion ordering is guaranteed. */ + public static ElementOrder insertion() { + return new ElementOrder(Type.INSERTION, null); + } + + /** + * Returns an instance which specifies that the natural ordering of the elements is guaranteed. + */ + public static > ElementOrder natural() { + return new ElementOrder(Type.SORTED, Ordering.natural()); + } + + /** + * Returns an instance which specifies that the ordering of the elements is guaranteed to be + * determined by {@code comparator}. + */ + public static ElementOrder sorted(Comparator comparator) { + return new ElementOrder(Type.SORTED, comparator); + } + + /** Returns the type of ordering used. */ + public Type type() { + return type; + } + + /** + * Returns the {@link Comparator} used. + * + * @throws UnsupportedOperationException if comparator is not defined + */ + public Comparator comparator() { + if (comparator != null) { + return comparator; + } + throw new UnsupportedOperationException("This ordering does not define a comparator."); + } + + @Override + public boolean equals(Object obj) { + if (obj == this) { + return true; + } + if (!(obj instanceof ElementOrder)) { + return false; + } + + ElementOrder other = (ElementOrder) obj; + return (type == other.type) && Objects.equal(comparator, other.comparator); + } + + @Override + public int hashCode() { + return Objects.hashCode(type, comparator); + } + + @Override + public String toString() { + ToStringHelper helper = MoreObjects.toStringHelper(this).add("type", type); + if (comparator != null) { + helper.add("comparator", comparator); + } + return helper.toString(); + } + + /** Returns an empty mutable map whose keys will respect this {@link ElementOrder}. */ + Map createMap(int expectedSize) { + switch (type) { + case UNORDERED: + return Maps.newHashMapWithExpectedSize(expectedSize); + case INSERTION: + case STABLE: + return Maps.newLinkedHashMapWithExpectedSize(expectedSize); + case SORTED: + return Maps.newTreeMap(comparator()); + default: + throw new AssertionError(); + } + } + + @SuppressWarnings("unchecked") + ElementOrder cast() { + return (ElementOrder) this; + } +} diff --git a/src/main/java/com/google/common/graph/EndpointPair.java b/src/main/java/com/google/common/graph/EndpointPair.java new file mode 100644 index 0000000..4d06199 --- /dev/null +++ b/src/main/java/com/google/common/graph/EndpointPair.java @@ -0,0 +1,251 @@ +/* + * Copyright (C) 2016 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.graph; + +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.graph.GraphConstants.NOT_AVAILABLE_ON_UNDIRECTED; + +import com.google.common.annotations.Beta; +import com.google.common.base.Objects; +import com.google.common.collect.Iterators; +import com.google.common.collect.UnmodifiableIterator; + + + +/** + * An immutable pair representing the two endpoints of an edge in a graph. The {@link EndpointPair} + * of a directed edge is an ordered pair of nodes ({@link #source()} and {@link #target()}). The + * {@link EndpointPair} of an undirected edge is an unordered pair of nodes ({@link #nodeU()} and + * {@link #nodeV()}). + * + *

The edge is a self-loop if, and only if, the two endpoints are equal. + * + * @author James Sexton + * @since 20.0 + */ +@Beta +public abstract class EndpointPair implements Iterable { + private final N nodeU; + private final N nodeV; + + private EndpointPair(N nodeU, N nodeV) { + this.nodeU = checkNotNull(nodeU); + this.nodeV = checkNotNull(nodeV); + } + + /** Returns an {@link EndpointPair} representing the endpoints of a directed edge. */ + public static EndpointPair ordered(N source, N target) { + return new Ordered(source, target); + } + + /** Returns an {@link EndpointPair} representing the endpoints of an undirected edge. */ + public static EndpointPair unordered(N nodeU, N nodeV) { + // Swap nodes on purpose to prevent callers from relying on the "ordering" of an unordered pair. + return new Unordered(nodeV, nodeU); + } + + /** Returns an {@link EndpointPair} representing the endpoints of an edge in {@code graph}. */ + static EndpointPair of(Graph graph, N nodeU, N nodeV) { + return graph.isDirected() ? ordered(nodeU, nodeV) : unordered(nodeU, nodeV); + } + + /** Returns an {@link EndpointPair} representing the endpoints of an edge in {@code network}. */ + static EndpointPair of(Network network, N nodeU, N nodeV) { + return network.isDirected() ? ordered(nodeU, nodeV) : unordered(nodeU, nodeV); + } + + /** + * If this {@link EndpointPair} {@link #isOrdered()}, returns the node which is the source. + * + * @throws UnsupportedOperationException if this {@link EndpointPair} is not ordered + */ + public abstract N source(); + + /** + * If this {@link EndpointPair} {@link #isOrdered()}, returns the node which is the target. + * + * @throws UnsupportedOperationException if this {@link EndpointPair} is not ordered + */ + public abstract N target(); + + /** + * If this {@link EndpointPair} {@link #isOrdered()} returns the {@link #source()}; otherwise, + * returns an arbitrary (but consistent) endpoint of the origin edge. + */ + public final N nodeU() { + return nodeU; + } + + /** + * Returns the node {@link #adjacentNode(Object) adjacent} to {@link #nodeU()} along the origin + * edge. If this {@link EndpointPair} {@link #isOrdered()}, this is equal to {@link #target()}. + */ + public final N nodeV() { + return nodeV; + } + + /** + * Returns the node that is adjacent to {@code node} along the origin edge. + * + * @throws IllegalArgumentException if this {@link EndpointPair} does not contain {@code node} + */ + public final N adjacentNode(Object node) { + if (node.equals(nodeU)) { + return nodeV; + } else if (node.equals(nodeV)) { + return nodeU; + } else { + throw new IllegalArgumentException("EndpointPair " + this + " does not contain node " + node); + } + } + + /** + * Returns {@code true} if this {@link EndpointPair} is an ordered pair (i.e. represents the + * endpoints of a directed edge). + */ + public abstract boolean isOrdered(); + + /** Iterates in the order {@link #nodeU()}, {@link #nodeV()}. */ + @Override + public final UnmodifiableIterator iterator() { + return Iterators.forArray(nodeU, nodeV); + } + + /** + * Two ordered {@link EndpointPair}s are equal if their {@link #source()} and {@link #target()} + * are equal. Two unordered {@link EndpointPair}s are equal if they contain the same nodes. An + * ordered {@link EndpointPair} is never equal to an unordered {@link EndpointPair}. + */ + @Override + public abstract boolean equals(Object obj); + + /** + * The hashcode of an ordered {@link EndpointPair} is equal to {@code Objects.hashCode(source(), + * target())}. The hashcode of an unordered {@link EndpointPair} is equal to {@code + * nodeU().hashCode() + nodeV().hashCode()}. + */ + @Override + public abstract int hashCode(); + + private static final class Ordered extends EndpointPair { + private Ordered(N source, N target) { + super(source, target); + } + + @Override + public N source() { + return nodeU(); + } + + @Override + public N target() { + return nodeV(); + } + + @Override + public boolean isOrdered() { + return true; + } + + @Override + public boolean equals(Object obj) { + if (obj == this) { + return true; + } + if (!(obj instanceof EndpointPair)) { + return false; + } + + EndpointPair other = (EndpointPair) obj; + if (isOrdered() != other.isOrdered()) { + return false; + } + + return source().equals(other.source()) && target().equals(other.target()); + } + + @Override + public int hashCode() { + return Objects.hashCode(source(), target()); + } + + @Override + public String toString() { + return "<" + source() + " -> " + target() + ">"; + } + } + + private static final class Unordered extends EndpointPair { + private Unordered(N nodeU, N nodeV) { + super(nodeU, nodeV); + } + + @Override + public N source() { + throw new UnsupportedOperationException(NOT_AVAILABLE_ON_UNDIRECTED); + } + + @Override + public N target() { + throw new UnsupportedOperationException(NOT_AVAILABLE_ON_UNDIRECTED); + } + + @Override + public boolean isOrdered() { + return false; + } + + @Override + public boolean equals(Object obj) { + if (obj == this) { + return true; + } + if (!(obj instanceof EndpointPair)) { + return false; + } + + EndpointPair other = (EndpointPair) obj; + if (isOrdered() != other.isOrdered()) { + return false; + } + + // Equivalent to the following simple implementation: + // boolean condition1 = nodeU().equals(other.nodeU()) && nodeV().equals(other.nodeV()); + // boolean condition2 = nodeU().equals(other.nodeV()) && nodeV().equals(other.nodeU()); + // return condition1 || condition2; + if (nodeU().equals(other.nodeU())) { // check condition1 + // Here's the tricky bit. We don't have to explicitly check for condition2 in this case. + // Why? The second half of condition2 requires that nodeV equals other.nodeU. + // We already know that nodeU equals other.nodeU. Combined with the earlier statement, + // and the transitive property of equality, this implies that nodeU equals nodeV. + // If nodeU equals nodeV, condition1 == condition2, so checking condition1 is sufficient. + return nodeV().equals(other.nodeV()); + } + return nodeU().equals(other.nodeV()) && nodeV().equals(other.nodeU()); // check condition2 + } + + @Override + public int hashCode() { + return nodeU().hashCode() + nodeV().hashCode(); + } + + @Override + public String toString() { + return "[" + nodeU() + ", " + nodeV() + "]"; + } + } +} diff --git a/src/main/java/com/google/common/graph/EndpointPairIterator.java b/src/main/java/com/google/common/graph/EndpointPairIterator.java new file mode 100644 index 0000000..c4e6e07 --- /dev/null +++ b/src/main/java/com/google/common/graph/EndpointPairIterator.java @@ -0,0 +1,136 @@ +/* + * Copyright (C) 2016 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.graph; + +import static com.google.common.base.Preconditions.checkState; + +import com.google.common.collect.AbstractIterator; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Sets; +import java.util.Iterator; +import java.util.Set; + +/** + * A class to facilitate the set returned by {@link Graph#edges()}. + * + * @author James Sexton + */ +abstract class EndpointPairIterator extends AbstractIterator> { + private final BaseGraph graph; + private final Iterator nodeIterator; + + protected N node = null; // null is safe as an initial value because graphs don't allow null nodes + protected Iterator successorIterator = ImmutableSet.of().iterator(); + + static EndpointPairIterator of(BaseGraph graph) { + return graph.isDirected() ? new Directed(graph) : new Undirected(graph); + } + + private EndpointPairIterator(BaseGraph graph) { + this.graph = graph; + this.nodeIterator = graph.nodes().iterator(); + } + + /** + * Called after {@link #successorIterator} is exhausted. Advances {@link #node} to the next node + * and updates {@link #successorIterator} to iterate through the successors of {@link #node}. + */ + protected final boolean advance() { + checkState(!successorIterator.hasNext()); + if (!nodeIterator.hasNext()) { + return false; + } + node = nodeIterator.next(); + successorIterator = graph.successors(node).iterator(); + return true; + } + + /** + * If the graph is directed, each ordered [source, target] pair will be visited once if there is + * an edge connecting them. + */ + private static final class Directed extends EndpointPairIterator { + private Directed(BaseGraph graph) { + super(graph); + } + + @Override + protected EndpointPair computeNext() { + while (true) { + if (successorIterator.hasNext()) { + return EndpointPair.ordered(node, successorIterator.next()); + } + if (!advance()) { + return endOfData(); + } + } + } + } + + /** + * If the graph is undirected, each unordered [node, otherNode] pair (except self-loops) will be + * visited twice if there is an edge connecting them. To avoid returning duplicate {@link + * EndpointPair}s, we keep track of the nodes that we have visited. When processing endpoint + * pairs, we skip if the "other node" is in the visited set, as shown below: + * + *

+   * Nodes = {N1, N2, N3, N4}
+   *    N2           __
+   *   /  \         |  |
+   * N1----N3      N4__|
+   *
+   * Visited Nodes = {}
+   * EndpointPair [N1, N2] - return
+   * EndpointPair [N1, N3] - return
+   * Visited Nodes = {N1}
+   * EndpointPair [N2, N1] - skip
+   * EndpointPair [N2, N3] - return
+   * Visited Nodes = {N1, N2}
+   * EndpointPair [N3, N1] - skip
+   * EndpointPair [N3, N2] - skip
+   * Visited Nodes = {N1, N2, N3}
+   * EndpointPair [N4, N4] - return
+   * Visited Nodes = {N1, N2, N3, N4}
+   * 
+ */ + private static final class Undirected extends EndpointPairIterator { + private Set visitedNodes; + + private Undirected(BaseGraph graph) { + super(graph); + this.visitedNodes = Sets.newHashSetWithExpectedSize(graph.nodes().size()); + } + + @Override + protected EndpointPair computeNext() { + while (true) { + while (successorIterator.hasNext()) { + N otherNode = successorIterator.next(); + if (!visitedNodes.contains(otherNode)) { + return EndpointPair.unordered(node, otherNode); + } + } + // Add to visited set *after* processing neighbors so we still include self-loops. + visitedNodes.add(node); + if (!advance()) { + visitedNodes = null; + return endOfData(); + } + } + } + } +} diff --git a/src/main/java/com/google/common/graph/ForwardingGraph.java b/src/main/java/com/google/common/graph/ForwardingGraph.java new file mode 100644 index 0000000..a72abc0 --- /dev/null +++ b/src/main/java/com/google/common/graph/ForwardingGraph.java @@ -0,0 +1,99 @@ +/* + * Copyright (C) 2016 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.graph; + +import java.util.Set; + +/** + * A class to allow {@link Graph} implementations to be backed by a {@link BaseGraph}. This is not + * currently planned to be released as a general-purpose forwarding class. + * + * @author James Sexton + */ +abstract class ForwardingGraph extends AbstractGraph { + + protected abstract BaseGraph delegate(); + + @Override + public Set nodes() { + return delegate().nodes(); + } + + /** + * Defer to {@link AbstractGraph#edges()} (based on {@link #successors(Object)}) for full edges() + * implementation. + */ + @Override + protected long edgeCount() { + return delegate().edges().size(); + } + + @Override + public boolean isDirected() { + return delegate().isDirected(); + } + + @Override + public boolean allowsSelfLoops() { + return delegate().allowsSelfLoops(); + } + + @Override + public ElementOrder nodeOrder() { + return delegate().nodeOrder(); + } + + @Override + public Set adjacentNodes(N node) { + return delegate().adjacentNodes(node); + } + + @Override + public Set predecessors(N node) { + return delegate().predecessors(node); + } + + @Override + public Set successors(N node) { + return delegate().successors(node); + } + + @Override + public int degree(N node) { + return delegate().degree(node); + } + + @Override + public int inDegree(N node) { + return delegate().inDegree(node); + } + + @Override + public int outDegree(N node) { + return delegate().outDegree(node); + } + + @Override + public boolean hasEdgeConnecting(N nodeU, N nodeV) { + return delegate().hasEdgeConnecting(nodeU, nodeV); + } + + @Override + public boolean hasEdgeConnecting(EndpointPair endpoints) { + return delegate().hasEdgeConnecting(endpoints); + } +} diff --git a/src/main/java/com/google/common/graph/ForwardingNetwork.java b/src/main/java/com/google/common/graph/ForwardingNetwork.java new file mode 100644 index 0000000..76347b1 --- /dev/null +++ b/src/main/java/com/google/common/graph/ForwardingNetwork.java @@ -0,0 +1,162 @@ +/* + * Copyright (C) 2016 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.graph; + +import java.util.Optional; +import java.util.Set; + +/** + * A class to allow {@link Network} implementations to be backed by a provided delegate. This is not + * currently planned to be released as a general-purpose forwarding class. + * + * @author James Sexton + * @author Joshua O'Madadhain + */ +abstract class ForwardingNetwork extends AbstractNetwork { + + protected abstract Network delegate(); + + @Override + public Set nodes() { + return delegate().nodes(); + } + + @Override + public Set edges() { + return delegate().edges(); + } + + @Override + public boolean isDirected() { + return delegate().isDirected(); + } + + @Override + public boolean allowsParallelEdges() { + return delegate().allowsParallelEdges(); + } + + @Override + public boolean allowsSelfLoops() { + return delegate().allowsSelfLoops(); + } + + @Override + public ElementOrder nodeOrder() { + return delegate().nodeOrder(); + } + + @Override + public ElementOrder edgeOrder() { + return delegate().edgeOrder(); + } + + @Override + public Set adjacentNodes(N node) { + return delegate().adjacentNodes(node); + } + + @Override + public Set predecessors(N node) { + return delegate().predecessors(node); + } + + @Override + public Set successors(N node) { + return delegate().successors(node); + } + + @Override + public Set incidentEdges(N node) { + return delegate().incidentEdges(node); + } + + @Override + public Set inEdges(N node) { + return delegate().inEdges(node); + } + + @Override + public Set outEdges(N node) { + return delegate().outEdges(node); + } + + @Override + public EndpointPair incidentNodes(E edge) { + return delegate().incidentNodes(edge); + } + + @Override + public Set adjacentEdges(E edge) { + return delegate().adjacentEdges(edge); + } + + @Override + public int degree(N node) { + return delegate().degree(node); + } + + @Override + public int inDegree(N node) { + return delegate().inDegree(node); + } + + @Override + public int outDegree(N node) { + return delegate().outDegree(node); + } + + @Override + public Set edgesConnecting(N nodeU, N nodeV) { + return delegate().edgesConnecting(nodeU, nodeV); + } + + @Override + public Set edgesConnecting(EndpointPair endpoints) { + return delegate().edgesConnecting(endpoints); + } + + @Override + public Optional edgeConnecting(N nodeU, N nodeV) { + return delegate().edgeConnecting(nodeU, nodeV); + } + + @Override + public Optional edgeConnecting(EndpointPair endpoints) { + return delegate().edgeConnecting(endpoints); + } + + @Override + public E edgeConnectingOrNull(N nodeU, N nodeV) { + return delegate().edgeConnectingOrNull(nodeU, nodeV); + } + + @Override + public E edgeConnectingOrNull(EndpointPair endpoints) { + return delegate().edgeConnectingOrNull(endpoints); + } + + @Override + public boolean hasEdgeConnecting(N nodeU, N nodeV) { + return delegate().hasEdgeConnecting(nodeU, nodeV); + } + + @Override + public boolean hasEdgeConnecting(EndpointPair endpoints) { + return delegate().hasEdgeConnecting(endpoints); + } +} diff --git a/src/main/java/com/google/common/graph/ForwardingValueGraph.java b/src/main/java/com/google/common/graph/ForwardingValueGraph.java new file mode 100644 index 0000000..1e74ee5 --- /dev/null +++ b/src/main/java/com/google/common/graph/ForwardingValueGraph.java @@ -0,0 +1,122 @@ +/* + * Copyright (C) 2016 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.graph; + +import java.util.Optional; +import java.util.Set; + + +/** + * A class to allow {@link ValueGraph} implementations to be backed by a provided delegate. This is + * not currently planned to be released as a general-purpose forwarding class. + * + * @author James Sexton + * @author Joshua O'Madadhain + */ +abstract class ForwardingValueGraph extends AbstractValueGraph { + + protected abstract ValueGraph delegate(); + + @Override + public Set nodes() { + return delegate().nodes(); + } + + /** + * Defer to {@link AbstractValueGraph#edges()} (based on {@link #successors(Object)}) for full + * edges() implementation. + */ + @Override + protected long edgeCount() { + return delegate().edges().size(); + } + + @Override + public boolean isDirected() { + return delegate().isDirected(); + } + + @Override + public boolean allowsSelfLoops() { + return delegate().allowsSelfLoops(); + } + + @Override + public ElementOrder nodeOrder() { + return delegate().nodeOrder(); + } + + @Override + public Set adjacentNodes(N node) { + return delegate().adjacentNodes(node); + } + + @Override + public Set predecessors(N node) { + return delegate().predecessors(node); + } + + @Override + public Set successors(N node) { + return delegate().successors(node); + } + + @Override + public int degree(N node) { + return delegate().degree(node); + } + + @Override + public int inDegree(N node) { + return delegate().inDegree(node); + } + + @Override + public int outDegree(N node) { + return delegate().outDegree(node); + } + + @Override + public boolean hasEdgeConnecting(N nodeU, N nodeV) { + return delegate().hasEdgeConnecting(nodeU, nodeV); + } + + @Override + public boolean hasEdgeConnecting(EndpointPair endpoints) { + return delegate().hasEdgeConnecting(endpoints); + } + + @Override + public Optional edgeValue(N nodeU, N nodeV) { + return delegate().edgeValue(nodeU, nodeV); + } + + @Override + public Optional edgeValue(EndpointPair endpoints) { + return delegate().edgeValue(endpoints); + } + + @Override + public V edgeValueOrDefault(N nodeU, N nodeV, V defaultValue) { + return delegate().edgeValueOrDefault(nodeU, nodeV, defaultValue); + } + + @Override + public V edgeValueOrDefault(EndpointPair endpoints, V defaultValue) { + return delegate().edgeValueOrDefault(endpoints, defaultValue); + } +} diff --git a/src/main/java/com/google/common/graph/Graph.java b/src/main/java/com/google/common/graph/Graph.java new file mode 100644 index 0000000..2b0d273 --- /dev/null +++ b/src/main/java/com/google/common/graph/Graph.java @@ -0,0 +1,290 @@ +/* + * Copyright (C) 2014 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.graph; + +import com.google.common.annotations.Beta; +import java.util.Collection; +import java.util.Set; + + +/** + * An interface for graph-structured data, + * whose edges are anonymous entities with no identity or information of their own. + * + *

A graph is composed of a set of nodes and a set of edges connecting pairs of nodes. + * + *

There are three primary interfaces provided to represent graphs. In order of increasing + * complexity they are: {@link Graph}, {@link ValueGraph}, and {@link Network}. You should generally + * prefer the simplest interface that satisfies your use case. See the + * "Choosing the right graph type" section of the Guava User Guide for more details. + * + *

Capabilities

+ * + *

{@code Graph} supports the following use cases (definitions of + * terms): + * + *

    + *
  • directed graphs + *
  • undirected graphs + *
  • graphs that do/don't allow self-loops + *
  • graphs whose nodes/edges are insertion-ordered, sorted, or unordered + *
+ * + *

{@code Graph} explicitly does not support parallel edges, and forbids implementations or + * extensions with parallel edges. If you need parallel edges, use {@link Network}. + * + *

Building a {@code Graph}

+ * + *

The implementation classes that {@code common.graph} provides are not public, by design. To + * create an instance of one of the built-in implementations of {@code Graph}, use the {@link + * GraphBuilder} class: + * + *

{@code
+ * MutableGraph graph = GraphBuilder.undirected().build();
+ * }
+ * + *

{@link GraphBuilder#build()} returns an instance of {@link MutableGraph}, which is a subtype + * of {@code Graph} that provides methods for adding and removing nodes and edges. If you do not + * need to mutate a graph (e.g. if you write a method than runs a read-only algorithm on the graph), + * you should use the non-mutating {@link Graph} interface, or an {@link ImmutableGraph}. + * + *

You can create an immutable copy of an existing {@code Graph} using {@link + * ImmutableGraph#copyOf(Graph)}: + * + *

{@code
+ * ImmutableGraph immutableGraph = ImmutableGraph.copyOf(graph);
+ * }
+ * + *

Instances of {@link ImmutableGraph} do not implement {@link MutableGraph} (obviously!) and are + * contractually guaranteed to be unmodifiable and thread-safe. + * + *

The Guava User Guide has more + * information on (and examples of) building graphs. + * + *

Additional documentation

+ * + *

See the Guava User Guide for the {@code common.graph} package ("Graphs Explained") for + * additional documentation, including: + * + *

+ * + * @author James Sexton + * @author Joshua O'Madadhain + * @param Node parameter type + * @since 20.0 + */ +@Beta +public interface Graph extends BaseGraph { + // + // Graph-level accessors + // + + /** Returns all nodes in this graph, in the order specified by {@link #nodeOrder()}. */ + @Override + Set nodes(); + + /** Returns all edges in this graph. */ + @Override + Set> edges(); + + // + // Graph properties + // + + /** + * Returns true if the edges in this graph are directed. Directed edges connect a {@link + * EndpointPair#source() source node} to a {@link EndpointPair#target() target node}, while + * undirected edges connect a pair of nodes to each other. + */ + @Override + boolean isDirected(); + + /** + * Returns true if this graph allows self-loops (edges that connect a node to itself). Attempting + * to add a self-loop to a graph that does not allow them will throw an {@link + * IllegalArgumentException}. + */ + @Override + boolean allowsSelfLoops(); + + /** Returns the order of iteration for the elements of {@link #nodes()}. */ + @Override + ElementOrder nodeOrder(); + + // + // Element-level accessors + // + + /** + * Returns the nodes which have an incident edge in common with {@code node} in this graph. + * + *

This is equal to the union of {@link #predecessors(Object)} and {@link #successors(Object)}. + * + * @throws IllegalArgumentException if {@code node} is not an element of this graph + */ + @Override + Set adjacentNodes(N node); + + /** + * Returns all nodes in this graph adjacent to {@code node} which can be reached by traversing + * {@code node}'s incoming edges against the direction (if any) of the edge. + * + *

In an undirected graph, this is equivalent to {@link #adjacentNodes(Object)}. + * + * @throws IllegalArgumentException if {@code node} is not an element of this graph + */ + @Override + Set predecessors(N node); + + /** + * Returns all nodes in this graph adjacent to {@code node} which can be reached by traversing + * {@code node}'s outgoing edges in the direction (if any) of the edge. + * + *

In an undirected graph, this is equivalent to {@link #adjacentNodes(Object)}. + * + *

This is not the same as "all nodes reachable from {@code node} by following outgoing + * edges". For that functionality, see {@link Graphs#reachableNodes(Graph, Object)}. + * + * @throws IllegalArgumentException if {@code node} is not an element of this graph + */ + @Override + Set successors(N node); + + /** + * Returns the edges in this graph whose endpoints include {@code node}. + * + *

This is equal to the union of incoming and outgoing edges. + * + * @throws IllegalArgumentException if {@code node} is not an element of this graph + * @since 24.0 + */ + @Override + Set> incidentEdges(N node); + + /** + * Returns the count of {@code node}'s incident edges, counting self-loops twice (equivalently, + * the number of times an edge touches {@code node}). + * + *

For directed graphs, this is equal to {@code inDegree(node) + outDegree(node)}. + * + *

For undirected graphs, this is equal to {@code incidentEdges(node).size()} + (number of + * self-loops incident to {@code node}). + * + *

If the count is greater than {@code Integer.MAX_VALUE}, returns {@code Integer.MAX_VALUE}. + * + * @throws IllegalArgumentException if {@code node} is not an element of this graph + */ + @Override + int degree(N node); + + /** + * Returns the count of {@code node}'s incoming edges (equal to {@code predecessors(node).size()}) + * in a directed graph. In an undirected graph, returns the {@link #degree(Object)}. + * + *

If the count is greater than {@code Integer.MAX_VALUE}, returns {@code Integer.MAX_VALUE}. + * + * @throws IllegalArgumentException if {@code node} is not an element of this graph + */ + @Override + int inDegree(N node); + + /** + * Returns the count of {@code node}'s outgoing edges (equal to {@code successors(node).size()}) + * in a directed graph. In an undirected graph, returns the {@link #degree(Object)}. + * + *

If the count is greater than {@code Integer.MAX_VALUE}, returns {@code Integer.MAX_VALUE}. + * + * @throws IllegalArgumentException if {@code node} is not an element of this graph + */ + @Override + int outDegree(N node); + + /** + * Returns true if there is an edge that directly connects {@code nodeU} to {@code nodeV}. This is + * equivalent to {@code nodes().contains(nodeU) && successors(nodeU).contains(nodeV)}. + * + *

In an undirected graph, this is equal to {@code hasEdgeConnecting(nodeV, nodeU)}. + * + * @since 23.0 + */ + @Override + boolean hasEdgeConnecting(N nodeU, N nodeV); + + /** + * Returns true if there is an edge that directly connects {@code endpoints} (in the order, if + * any, specified by {@code endpoints}). This is equivalent to {@code + * edges().contains(endpoints)}. + * + *

Unlike the other {@code EndpointPair}-accepting methods, this method does not throw if the + * endpoints are unordered and the graph is directed; it simply returns {@code false}. This is for + * consistency with the behavior of {@link Collection#contains(Object)} (which does not generally + * throw if the object cannot be present in the collection), and the desire to have this method's + * behavior be compatible with {@code edges().contains(endpoints)}. + * + * @since 27.1 + */ + @Override + boolean hasEdgeConnecting(EndpointPair endpoints); + + // + // Graph identity + // + + /** + * Returns {@code true} iff {@code object} is a {@link Graph} that has the same elements and the + * same structural relationships as those in this graph. + * + *

Thus, two graphs A and B are equal if all of the following are true: + * + *

    + *
  • A and B have equal {@link #isDirected() directedness}. + *
  • A and B have equal {@link #nodes() node sets}. + *
  • A and B have equal {@link #edges() edge sets}. + *
+ * + *

Graph properties besides {@link #isDirected() directedness} do not affect equality. + * For example, two graphs may be considered equal even if one allows self-loops and the other + * doesn't. Additionally, the order in which nodes or edges are added to the graph, and the order + * in which they are iterated over, are irrelevant. + * + *

A reference implementation of this is provided by {@link AbstractGraph#equals(Object)}. + */ + @Override + boolean equals(Object object); + + /** + * Returns the hash code for this graph. The hash code of a graph is defined as the hash code of + * the set returned by {@link #edges()}. + * + *

A reference implementation of this is provided by {@link AbstractGraph#hashCode()}. + */ + @Override + int hashCode(); +} diff --git a/src/main/java/com/google/common/graph/GraphBuilder.java b/src/main/java/com/google/common/graph/GraphBuilder.java new file mode 100644 index 0000000..dca2680 --- /dev/null +++ b/src/main/java/com/google/common/graph/GraphBuilder.java @@ -0,0 +1,187 @@ +/* + * Copyright (C) 2016 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.graph; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.graph.Graphs.checkNonNegative; + +import com.google.common.annotations.Beta; +import com.google.common.base.Optional; + +/** + * A builder for constructing instances of {@link MutableGraph} or {@link ImmutableGraph} with + * user-defined properties. + * + *

A graph built by this class will have the following properties by default: + * + *

    + *
  • does not allow self-loops + *
  • orders {@link Graph#nodes()} in the order in which the elements were added + *
+ * + *

Examples of use: + * + *

{@code
+ * // Building a mutable graph
+ * MutableGraph graph = GraphBuilder.undirected().allowsSelfLoops(true).build();
+ * graph.putEdge("bread", "bread");
+ * graph.putEdge("chocolate", "peanut butter");
+ * graph.putEdge("peanut butter", "jelly");
+ *
+ * // Building an immutable graph
+ * ImmutableGraph immutableGraph =
+ *     GraphBuilder.undirected()
+ *         .allowsSelfLoops(true)
+ *         .immutable()
+ *         .putEdge("bread", "bread")
+ *         .putEdge("chocolate", "peanut butter")
+ *         .putEdge("peanut butter", "jelly")
+ *         .build();
+ * }
+ * + * @author James Sexton + * @author Joshua O'Madadhain + * @param The most general node type this builder will support. This is normally {@code Object} + * unless it is constrained by using a method like {@link #nodeOrder}, or the builder is + * constructed based on an existing {@code Graph} using {@link #from(Graph)}. + * @since 20.0 + */ +@Beta +public final class GraphBuilder extends AbstractGraphBuilder { + + /** Creates a new instance with the specified edge directionality. */ + private GraphBuilder(boolean directed) { + super(directed); + } + + /** Returns a {@link GraphBuilder} for building directed graphs. */ + public static GraphBuilder directed() { + return new GraphBuilder<>(true); + } + + /** Returns a {@link GraphBuilder} for building undirected graphs. */ + public static GraphBuilder undirected() { + return new GraphBuilder<>(false); + } + + /** + * Returns a {@link GraphBuilder} initialized with all properties queryable from {@code graph}. + * + *

The "queryable" properties are those that are exposed through the {@link Graph} interface, + * such as {@link Graph#isDirected()}. Other properties, such as {@link #expectedNodeCount(int)}, + * are not set in the new builder. + */ + public static GraphBuilder from(Graph graph) { + return new GraphBuilder(graph.isDirected()) + .allowsSelfLoops(graph.allowsSelfLoops()) + .nodeOrder(graph.nodeOrder()); + // TODO(b/142723300): Add incidentEdgeOrder + } + + /** + * Returns an {@link ImmutableGraph.Builder} with the properties of this {@link GraphBuilder}. + * + *

The returned builder can be used for populating an {@link ImmutableGraph}. + * + *

Note that the returned builder will always have {@link #incidentEdgeOrder} set to {@link + * ElementOrder#stable()}, regardless of the value that was set in this builder. + * + * @since 28.0 + */ + public ImmutableGraph.Builder immutable() { + GraphBuilder castBuilder = cast(); + return new ImmutableGraph.Builder<>(castBuilder); + } + + /** + * Specifies whether the graph will allow self-loops (edges that connect a node to itself). + * Attempting to add a self-loop to a graph that does not allow them will throw an {@link + * UnsupportedOperationException}. + * + *

The default value is {@code false}. + */ + public GraphBuilder allowsSelfLoops(boolean allowsSelfLoops) { + this.allowsSelfLoops = allowsSelfLoops; + return this; + } + + /** + * Specifies the expected number of nodes in the graph. + * + * @throws IllegalArgumentException if {@code expectedNodeCount} is negative + */ + public GraphBuilder expectedNodeCount(int expectedNodeCount) { + this.expectedNodeCount = Optional.of(checkNonNegative(expectedNodeCount)); + return this; + } + + /** + * Specifies the order of iteration for the elements of {@link Graph#nodes()}. + * + *

The default value is {@link ElementOrder#insertion() insertion order}. + */ + public GraphBuilder nodeOrder(ElementOrder nodeOrder) { + GraphBuilder newBuilder = cast(); + newBuilder.nodeOrder = checkNotNull(nodeOrder); + return newBuilder; + } + + /** + * Specifies the order of iteration for the elements of {@link Graph#edges()}, {@link + * Graph#adjacentNodes(Object)}, {@link Graph#predecessors(Object)}, {@link + * Graph#successors(Object)} and {@link Graph#incidentEdges(Object)}. + * + *

The default value is {@link ElementOrder#unordered() unordered} for mutable graphs. For + * immutable graphs, this value is ignored; they always have a {@link ElementOrder#stable() + * stable} order. + * + * @throws IllegalArgumentException if {@code incidentEdgeOrder} is not either {@code + * ElementOrder.unordered()} or {@code ElementOrder.stable()}. + */ + // TODO(b/142723300): Make this method public + GraphBuilder incidentEdgeOrder(ElementOrder incidentEdgeOrder) { + checkArgument( + incidentEdgeOrder.type() == ElementOrder.Type.UNORDERED + || incidentEdgeOrder.type() == ElementOrder.Type.STABLE, + "The given elementOrder (%s) is unsupported. incidentEdgeOrder() only supports" + + " ElementOrder.unordered() and ElementOrder.stable().", + incidentEdgeOrder); + GraphBuilder newBuilder = cast(); + newBuilder.incidentEdgeOrder = checkNotNull(incidentEdgeOrder); + return newBuilder; + } + + /** Returns an empty {@link MutableGraph} with the properties of this {@link GraphBuilder}. */ + public MutableGraph build() { + return new ConfigurableMutableGraph(this); + } + + GraphBuilder copy() { + GraphBuilder newBuilder = new GraphBuilder<>(directed); + newBuilder.allowsSelfLoops = allowsSelfLoops; + newBuilder.nodeOrder = nodeOrder; + newBuilder.expectedNodeCount = expectedNodeCount; + newBuilder.incidentEdgeOrder = incidentEdgeOrder; + return newBuilder; + } + + @SuppressWarnings("unchecked") + private GraphBuilder cast() { + return (GraphBuilder) this; + } +} diff --git a/src/main/java/com/google/common/graph/GraphConnections.java b/src/main/java/com/google/common/graph/GraphConnections.java new file mode 100644 index 0000000..09661d0 --- /dev/null +++ b/src/main/java/com/google/common/graph/GraphConnections.java @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2016 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.graph; + + +import java.util.Set; + + +/** + * An interface for representing and manipulating an origin node's adjacent nodes and edge values in + * a {@link Graph}. + * + * @author James Sexton + * @param Node parameter type + * @param Value parameter type + */ +interface GraphConnections { + + Set adjacentNodes(); + + Set predecessors(); + + Set successors(); + + /** + * Returns the value associated with the edge connecting the origin node to {@code node}, or null + * if there is no such edge. + */ + + V value(N node); + + /** Remove {@code node} from the set of predecessors. */ + void removePredecessor(N node); + + /** + * Remove {@code node} from the set of successors. Returns the value previously associated with + * the edge connecting the two nodes. + */ + + V removeSuccessor(N node); + + /** + * Add {@code node} as a predecessor to the origin node. In the case of an undirected graph, it + * also becomes a successor. Associates {@code value} with the edge connecting the two nodes. + */ + void addPredecessor(N node, V value); + + /** + * Add {@code node} as a successor to the origin node. In the case of an undirected graph, it also + * becomes a predecessor. Associates {@code value} with the edge connecting the two nodes. Returns + * the value previously associated with the edge connecting the two nodes. + */ + + V addSuccessor(N node, V value); +} diff --git a/src/main/java/com/google/common/graph/GraphConstants.java b/src/main/java/com/google/common/graph/GraphConstants.java new file mode 100644 index 0000000..224c6d2 --- /dev/null +++ b/src/main/java/com/google/common/graph/GraphConstants.java @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2016 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.graph; + +/** A utility class to hold various constants used by the Guava Graph library. */ +final class GraphConstants { + + private GraphConstants() {} + + static final int EXPECTED_DEGREE = 2; + + static final int DEFAULT_NODE_COUNT = 10; + static final int DEFAULT_EDGE_COUNT = DEFAULT_NODE_COUNT * EXPECTED_DEGREE; + + // Load factor and capacity for "inner" (i.e. per node/edge element) hash sets or maps + static final float INNER_LOAD_FACTOR = 1.0f; + static final int INNER_CAPACITY = 2; // ceiling(EXPECTED_DEGREE / INNER_LOAD_FACTOR) + + // Error messages + static final String NODE_NOT_IN_GRAPH = "Node %s is not an element of this graph."; + static final String EDGE_NOT_IN_GRAPH = "Edge %s is not an element of this graph."; + static final String REUSING_EDGE = + "Edge %s already exists between the following nodes: %s, " + + "so it cannot be reused to connect the following nodes: %s."; + static final String MULTIPLE_EDGES_CONNECTING = + "Cannot call edgeConnecting() when parallel edges exist between %s and %s. Consider calling " + + "edgesConnecting() instead."; + static final String PARALLEL_EDGES_NOT_ALLOWED = + "Nodes %s and %s are already connected by a different edge. To construct a graph " + + "that allows parallel edges, call allowsParallelEdges(true) on the Builder."; + static final String SELF_LOOPS_NOT_ALLOWED = + "Cannot add self-loop edge on node %s, as self-loops are not allowed. To construct a graph " + + "that allows self-loops, call allowsSelfLoops(true) on the Builder."; + static final String NOT_AVAILABLE_ON_UNDIRECTED = + "Cannot call source()/target() on a EndpointPair from an undirected graph. Consider calling " + + "adjacentNode(node) if you already have a node, or nodeU()/nodeV() if you don't."; + static final String EDGE_ALREADY_EXISTS = "Edge %s already exists in the graph."; + static final String ENDPOINTS_MISMATCH = + "Mismatch: unordered endpoints cannot be used with directed graphs"; + + /** Singleton edge value for {@link Graph} implementations backed by {@link ValueGraph}s. */ + enum Presence { + EDGE_EXISTS + } +} diff --git a/src/main/java/com/google/common/graph/Graphs.java b/src/main/java/com/google/common/graph/Graphs.java new file mode 100644 index 0000000..a8107a2 --- /dev/null +++ b/src/main/java/com/google/common/graph/Graphs.java @@ -0,0 +1,611 @@ +/* + * Copyright (C) 2014 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.graph; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.graph.GraphConstants.NODE_NOT_IN_GRAPH; + +import com.google.common.annotations.Beta; +import com.google.common.base.Objects; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Iterables; +import com.google.common.collect.Maps; + +import java.util.Collection; +import java.util.HashSet; +import java.util.Map; +import java.util.Optional; +import java.util.Set; + + +/** + * Static utility methods for {@link Graph}, {@link ValueGraph}, and {@link Network} instances. + * + * @author James Sexton + * @author Joshua O'Madadhain + * @since 20.0 + */ +@Beta +public final class Graphs { + + private Graphs() {} + + // Graph query methods + + /** + * Returns true if {@code graph} has at least one cycle. A cycle is defined as a non-empty subset + * of edges in a graph arranged to form a path (a sequence of adjacent outgoing edges) starting + * and ending with the same node. + * + *

This method will detect any non-empty cycle, including self-loops (a cycle of length 1). + */ + public static boolean hasCycle(Graph graph) { + int numEdges = graph.edges().size(); + if (numEdges == 0) { + return false; // An edge-free graph is acyclic by definition. + } + if (!graph.isDirected() && numEdges >= graph.nodes().size()) { + return true; // Optimization for the undirected case: at least one cycle must exist. + } + + Map visitedNodes = + Maps.newHashMapWithExpectedSize(graph.nodes().size()); + for (N node : graph.nodes()) { + if (subgraphHasCycle(graph, visitedNodes, node, null)) { + return true; + } + } + return false; + } + + /** + * Returns true if {@code network} has at least one cycle. A cycle is defined as a non-empty + * subset of edges in a graph arranged to form a path (a sequence of adjacent outgoing edges) + * starting and ending with the same node. + * + *

This method will detect any non-empty cycle, including self-loops (a cycle of length 1). + */ + public static boolean hasCycle(Network network) { + // In a directed graph, parallel edges cannot introduce a cycle in an acyclic graph. + // However, in an undirected graph, any parallel edge induces a cycle in the graph. + if (!network.isDirected() + && network.allowsParallelEdges() + && network.edges().size() > network.asGraph().edges().size()) { + return true; + } + return hasCycle(network.asGraph()); + } + + /** + * Performs a traversal of the nodes reachable from {@code node}. If we ever reach a node we've + * already visited (following only outgoing edges and without reusing edges), we know there's a + * cycle in the graph. + */ + private static boolean subgraphHasCycle( + Graph graph, Map visitedNodes, N node, N previousNode) { + NodeVisitState state = visitedNodes.get(node); + if (state == NodeVisitState.COMPLETE) { + return false; + } + if (state == NodeVisitState.PENDING) { + return true; + } + + visitedNodes.put(node, NodeVisitState.PENDING); + for (N nextNode : graph.successors(node)) { + if (canTraverseWithoutReusingEdge(graph, nextNode, previousNode) + && subgraphHasCycle(graph, visitedNodes, nextNode, node)) { + return true; + } + } + visitedNodes.put(node, NodeVisitState.COMPLETE); + return false; + } + + /** + * Determines whether an edge has already been used during traversal. In the directed case a cycle + * is always detected before reusing an edge, so no special logic is required. In the undirected + * case, we must take care not to "backtrack" over an edge (i.e. going from A to B and then going + * from B to A). + */ + private static boolean canTraverseWithoutReusingEdge( + Graph graph, Object nextNode, Object previousNode) { + if (graph.isDirected() || !Objects.equal(previousNode, nextNode)) { + return true; + } + // This falls into the undirected A->B->A case. The Graph interface does not support parallel + // edges, so this traversal would require reusing the undirected AB edge. + return false; + } + + /** + * Returns the transitive closure of {@code graph}. The transitive closure of a graph is another + * graph with an edge connecting node A to node B if node B is {@link #reachableNodes(Graph, + * Object) reachable} from node A. + * + *

This is a "snapshot" based on the current topology of {@code graph}, rather than a live view + * of the transitive closure of {@code graph}. In other words, the returned {@link Graph} will not + * be updated after modifications to {@code graph}. + */ + // TODO(b/31438252): Consider potential optimizations for this algorithm. + public static Graph transitiveClosure(Graph graph) { + MutableGraph transitiveClosure = GraphBuilder.from(graph).allowsSelfLoops(true).build(); + // Every node is, at a minimum, reachable from itself. Since the resulting transitive closure + // will have no isolated nodes, we can skip adding nodes explicitly and let putEdge() do it. + + if (graph.isDirected()) { + // Note: works for both directed and undirected graphs, but we only use in the directed case. + for (N node : graph.nodes()) { + for (N reachableNode : reachableNodes(graph, node)) { + transitiveClosure.putEdge(node, reachableNode); + } + } + } else { + // An optimization for the undirected case: for every node B reachable from node A, + // node A and node B have the same reachability set. + Set visitedNodes = new HashSet(); + for (N node : graph.nodes()) { + if (!visitedNodes.contains(node)) { + Set reachableNodes = reachableNodes(graph, node); + visitedNodes.addAll(reachableNodes); + int pairwiseMatch = 1; // start at 1 to include self-loops + for (N nodeU : reachableNodes) { + for (N nodeV : Iterables.limit(reachableNodes, pairwiseMatch++)) { + transitiveClosure.putEdge(nodeU, nodeV); + } + } + } + } + } + + return transitiveClosure; + } + + /** + * Returns the set of nodes that are reachable from {@code node}. Node B is defined as reachable + * from node A if there exists a path (a sequence of adjacent outgoing edges) starting at node A + * and ending at node B. Note that a node is always reachable from itself via a zero-length path. + * + *

This is a "snapshot" based on the current topology of {@code graph}, rather than a live view + * of the set of nodes reachable from {@code node}. In other words, the returned {@link Set} will + * not be updated after modifications to {@code graph}. + * + * @throws IllegalArgumentException if {@code node} is not present in {@code graph} + */ + public static Set reachableNodes(Graph graph, N node) { + checkArgument(graph.nodes().contains(node), NODE_NOT_IN_GRAPH, node); + return ImmutableSet.copyOf(Traverser.forGraph(graph).breadthFirst(node)); + } + + // Graph mutation methods + + // Graph view methods + + /** + * Returns a view of {@code graph} with the direction (if any) of every edge reversed. All other + * properties remain intact, and further updates to {@code graph} will be reflected in the view. + */ + public static Graph transpose(Graph graph) { + if (!graph.isDirected()) { + return graph; // the transpose of an undirected graph is an identical graph + } + + if (graph instanceof TransposedGraph) { + return ((TransposedGraph) graph).graph; + } + + return new TransposedGraph(graph); + } + + /** + * Returns a view of {@code graph} with the direction (if any) of every edge reversed. All other + * properties remain intact, and further updates to {@code graph} will be reflected in the view. + */ + public static ValueGraph transpose(ValueGraph graph) { + if (!graph.isDirected()) { + return graph; // the transpose of an undirected graph is an identical graph + } + + if (graph instanceof TransposedValueGraph) { + return ((TransposedValueGraph) graph).graph; + } + + return new TransposedValueGraph<>(graph); + } + + /** + * Returns a view of {@code network} with the direction (if any) of every edge reversed. All other + * properties remain intact, and further updates to {@code network} will be reflected in the view. + */ + public static Network transpose(Network network) { + if (!network.isDirected()) { + return network; // the transpose of an undirected network is an identical network + } + + if (network instanceof TransposedNetwork) { + return ((TransposedNetwork) network).network; + } + + return new TransposedNetwork<>(network); + } + + static EndpointPair transpose(EndpointPair endpoints) { + if (endpoints.isOrdered()) { + return EndpointPair.ordered(endpoints.target(), endpoints.source()); + } + return endpoints; + } + + // NOTE: this should work as long as the delegate graph's implementation of edges() (like that of + // AbstractGraph) derives its behavior from calling successors(). + private static class TransposedGraph extends ForwardingGraph { + private final Graph graph; + + TransposedGraph(Graph graph) { + this.graph = graph; + } + + @Override + protected Graph delegate() { + return graph; + } + + @Override + public Set predecessors(N node) { + return delegate().successors(node); // transpose + } + + @Override + public Set successors(N node) { + return delegate().predecessors(node); // transpose + } + + @Override + public int inDegree(N node) { + return delegate().outDegree(node); // transpose + } + + @Override + public int outDegree(N node) { + return delegate().inDegree(node); // transpose + } + + @Override + public boolean hasEdgeConnecting(N nodeU, N nodeV) { + return delegate().hasEdgeConnecting(nodeV, nodeU); // transpose + } + + @Override + public boolean hasEdgeConnecting(EndpointPair endpoints) { + return delegate().hasEdgeConnecting(transpose(endpoints)); + } + } + + // NOTE: this should work as long as the delegate graph's implementation of edges() (like that of + // AbstractValueGraph) derives its behavior from calling successors(). + private static class TransposedValueGraph extends ForwardingValueGraph { + private final ValueGraph graph; + + TransposedValueGraph(ValueGraph graph) { + this.graph = graph; + } + + @Override + protected ValueGraph delegate() { + return graph; + } + + @Override + public Set predecessors(N node) { + return delegate().successors(node); // transpose + } + + @Override + public Set successors(N node) { + return delegate().predecessors(node); // transpose + } + + @Override + public int inDegree(N node) { + return delegate().outDegree(node); // transpose + } + + @Override + public int outDegree(N node) { + return delegate().inDegree(node); // transpose + } + + @Override + public boolean hasEdgeConnecting(N nodeU, N nodeV) { + return delegate().hasEdgeConnecting(nodeV, nodeU); // transpose + } + + @Override + public boolean hasEdgeConnecting(EndpointPair endpoints) { + return delegate().hasEdgeConnecting(transpose(endpoints)); + } + + @Override + public Optional edgeValue(N nodeU, N nodeV) { + return delegate().edgeValue(nodeV, nodeU); // transpose + } + + @Override + public Optional edgeValue(EndpointPair endpoints) { + return delegate().edgeValue(transpose(endpoints)); + } + + @Override + public V edgeValueOrDefault(N nodeU, N nodeV, V defaultValue) { + return delegate().edgeValueOrDefault(nodeV, nodeU, defaultValue); // transpose + } + + @Override + public V edgeValueOrDefault(EndpointPair endpoints, V defaultValue) { + return delegate().edgeValueOrDefault(transpose(endpoints), defaultValue); + } + } + + private static class TransposedNetwork extends ForwardingNetwork { + private final Network network; + + TransposedNetwork(Network network) { + this.network = network; + } + + @Override + protected Network delegate() { + return network; + } + + @Override + public Set predecessors(N node) { + return delegate().successors(node); // transpose + } + + @Override + public Set successors(N node) { + return delegate().predecessors(node); // transpose + } + + @Override + public int inDegree(N node) { + return delegate().outDegree(node); // transpose + } + + @Override + public int outDegree(N node) { + return delegate().inDegree(node); // transpose + } + + @Override + public Set inEdges(N node) { + return delegate().outEdges(node); // transpose + } + + @Override + public Set outEdges(N node) { + return delegate().inEdges(node); // transpose + } + + @Override + public EndpointPair incidentNodes(E edge) { + EndpointPair endpointPair = delegate().incidentNodes(edge); + return EndpointPair.of(network, endpointPair.nodeV(), endpointPair.nodeU()); // transpose + } + + @Override + public Set edgesConnecting(N nodeU, N nodeV) { + return delegate().edgesConnecting(nodeV, nodeU); // transpose + } + + @Override + public Set edgesConnecting(EndpointPair endpoints) { + return delegate().edgesConnecting(transpose(endpoints)); + } + + @Override + public Optional edgeConnecting(N nodeU, N nodeV) { + return delegate().edgeConnecting(nodeV, nodeU); // transpose + } + + @Override + public Optional edgeConnecting(EndpointPair endpoints) { + return delegate().edgeConnecting(transpose(endpoints)); + } + + @Override + public E edgeConnectingOrNull(N nodeU, N nodeV) { + return delegate().edgeConnectingOrNull(nodeV, nodeU); // transpose + } + + @Override + public E edgeConnectingOrNull(EndpointPair endpoints) { + return delegate().edgeConnectingOrNull(transpose(endpoints)); + } + + @Override + public boolean hasEdgeConnecting(N nodeU, N nodeV) { + return delegate().hasEdgeConnecting(nodeV, nodeU); // transpose + } + + @Override + public boolean hasEdgeConnecting(EndpointPair endpoints) { + return delegate().hasEdgeConnecting(transpose(endpoints)); + } + } + + // Graph copy methods + + /** + * Returns the subgraph of {@code graph} induced by {@code nodes}. This subgraph is a new graph + * that contains all of the nodes in {@code nodes}, and all of the {@link Graph#edges() edges} + * from {@code graph} for which both nodes are contained by {@code nodes}. + * + * @throws IllegalArgumentException if any element in {@code nodes} is not a node in the graph + */ + public static MutableGraph inducedSubgraph(Graph graph, Iterable nodes) { + MutableGraph subgraph = + (nodes instanceof Collection) + ? GraphBuilder.from(graph).expectedNodeCount(((Collection) nodes).size()).build() + : GraphBuilder.from(graph).build(); + for (N node : nodes) { + subgraph.addNode(node); + } + for (N node : subgraph.nodes()) { + for (N successorNode : graph.successors(node)) { + if (subgraph.nodes().contains(successorNode)) { + subgraph.putEdge(node, successorNode); + } + } + } + return subgraph; + } + + /** + * Returns the subgraph of {@code graph} induced by {@code nodes}. This subgraph is a new graph + * that contains all of the nodes in {@code nodes}, and all of the {@link Graph#edges() edges} + * (and associated edge values) from {@code graph} for which both nodes are contained by {@code + * nodes}. + * + * @throws IllegalArgumentException if any element in {@code nodes} is not a node in the graph + */ + public static MutableValueGraph inducedSubgraph( + ValueGraph graph, Iterable nodes) { + MutableValueGraph subgraph = + (nodes instanceof Collection) + ? ValueGraphBuilder.from(graph).expectedNodeCount(((Collection) nodes).size()).build() + : ValueGraphBuilder.from(graph).build(); + for (N node : nodes) { + subgraph.addNode(node); + } + for (N node : subgraph.nodes()) { + for (N successorNode : graph.successors(node)) { + if (subgraph.nodes().contains(successorNode)) { + subgraph.putEdgeValue( + node, successorNode, graph.edgeValueOrDefault(node, successorNode, null)); + } + } + } + return subgraph; + } + + /** + * Returns the subgraph of {@code network} induced by {@code nodes}. This subgraph is a new graph + * that contains all of the nodes in {@code nodes}, and all of the {@link Network#edges() edges} + * from {@code network} for which the {@link Network#incidentNodes(Object) incident nodes} are + * both contained by {@code nodes}. + * + * @throws IllegalArgumentException if any element in {@code nodes} is not a node in the graph + */ + public static MutableNetwork inducedSubgraph( + Network network, Iterable nodes) { + MutableNetwork subgraph = + (nodes instanceof Collection) + ? NetworkBuilder.from(network).expectedNodeCount(((Collection) nodes).size()).build() + : NetworkBuilder.from(network).build(); + for (N node : nodes) { + subgraph.addNode(node); + } + for (N node : subgraph.nodes()) { + for (E edge : network.outEdges(node)) { + N successorNode = network.incidentNodes(edge).adjacentNode(node); + if (subgraph.nodes().contains(successorNode)) { + subgraph.addEdge(node, successorNode, edge); + } + } + } + return subgraph; + } + + /** Creates a mutable copy of {@code graph} with the same nodes and edges. */ + public static MutableGraph copyOf(Graph graph) { + MutableGraph copy = GraphBuilder.from(graph).expectedNodeCount(graph.nodes().size()).build(); + for (N node : graph.nodes()) { + copy.addNode(node); + } + for (EndpointPair edge : graph.edges()) { + copy.putEdge(edge.nodeU(), edge.nodeV()); + } + return copy; + } + + /** Creates a mutable copy of {@code graph} with the same nodes, edges, and edge values. */ + public static MutableValueGraph copyOf(ValueGraph graph) { + MutableValueGraph copy = + ValueGraphBuilder.from(graph).expectedNodeCount(graph.nodes().size()).build(); + for (N node : graph.nodes()) { + copy.addNode(node); + } + for (EndpointPair edge : graph.edges()) { + copy.putEdgeValue( + edge.nodeU(), edge.nodeV(), graph.edgeValueOrDefault(edge.nodeU(), edge.nodeV(), null)); + } + return copy; + } + + /** Creates a mutable copy of {@code network} with the same nodes and edges. */ + public static MutableNetwork copyOf(Network network) { + MutableNetwork copy = + NetworkBuilder.from(network) + .expectedNodeCount(network.nodes().size()) + .expectedEdgeCount(network.edges().size()) + .build(); + for (N node : network.nodes()) { + copy.addNode(node); + } + for (E edge : network.edges()) { + EndpointPair endpointPair = network.incidentNodes(edge); + copy.addEdge(endpointPair.nodeU(), endpointPair.nodeV(), edge); + } + return copy; + } + + + static int checkNonNegative(int value) { + checkArgument(value >= 0, "Not true that %s is non-negative.", value); + return value; + } + + + static long checkNonNegative(long value) { + checkArgument(value >= 0, "Not true that %s is non-negative.", value); + return value; + } + + + static int checkPositive(int value) { + checkArgument(value > 0, "Not true that %s is positive.", value); + return value; + } + + + static long checkPositive(long value) { + checkArgument(value > 0, "Not true that %s is positive.", value); + return value; + } + + /** + * An enum representing the state of a node during DFS. {@code PENDING} means that the node is on + * the stack of the DFS, while {@code COMPLETE} means that the node and all its successors have + * been already explored. Any node that has not been explored will not have a state at all. + */ + private enum NodeVisitState { + PENDING, + COMPLETE + } +} diff --git a/src/main/java/com/google/common/graph/ImmutableGraph.java b/src/main/java/com/google/common/graph/ImmutableGraph.java new file mode 100644 index 0000000..6eaf1b1 --- /dev/null +++ b/src/main/java/com/google/common/graph/ImmutableGraph.java @@ -0,0 +1,193 @@ +/* + * Copyright (C) 2014 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.graph; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.annotations.Beta; +import com.google.common.base.Function; +import com.google.common.base.Functions; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Maps; +import com.google.common.graph.GraphConstants.Presence; + + + +/** + * A {@link Graph} whose elements and structural relationships will never change. Instances of this + * class may be obtained with {@link #copyOf(Graph)}. + * + *

See the Guava User's Guide's discussion + * of the {@code Immutable*} types for more information on the properties and guarantees + * provided by this class. + * + * @author James Sexton + * @author Joshua O'Madadhain + * @author Omar Darwish + * @author Jens Nyman + * @param Node parameter type + * @since 20.0 + */ +@Beta +public class ImmutableGraph extends ForwardingGraph { + @SuppressWarnings("Immutable") // The backing graph must be immutable. + private final BaseGraph backingGraph; + + ImmutableGraph(BaseGraph backingGraph) { + this.backingGraph = backingGraph; + } + + /** Returns an immutable copy of {@code graph}. */ + public static ImmutableGraph copyOf(Graph graph) { + return (graph instanceof ImmutableGraph) + ? (ImmutableGraph) graph + : new ImmutableGraph( + new ConfigurableValueGraph( + GraphBuilder.from(graph), getNodeConnections(graph), graph.edges().size())); + } + + /** + * Simply returns its argument. + * + * @deprecated no need to use this + */ + @Deprecated + public static ImmutableGraph copyOf(ImmutableGraph graph) { + return checkNotNull(graph); + } + + private static ImmutableMap> getNodeConnections( + Graph graph) { + // ImmutableMap.Builder maintains the order of the elements as inserted, so the map will have + // whatever ordering the graph's nodes do, so ImmutableSortedMap is unnecessary even if the + // input nodes are sorted. + ImmutableMap.Builder> nodeConnections = ImmutableMap.builder(); + for (N node : graph.nodes()) { + nodeConnections.put(node, connectionsOf(graph, node)); + } + return nodeConnections.build(); + } + + private static GraphConnections connectionsOf(Graph graph, N node) { + Function edgeValueFn = Functions.constant(Presence.EDGE_EXISTS); + return graph.isDirected() + ? DirectedGraphConnections.ofImmutable( + graph.predecessors(node), Maps.asMap(graph.successors(node), edgeValueFn)) + : UndirectedGraphConnections.ofImmutable( + Maps.asMap(graph.adjacentNodes(node), edgeValueFn)); + } + + @Override + protected BaseGraph delegate() { + return backingGraph; + } + + /** + * A builder for creating {@link ImmutableGraph} instances, especially {@code static final} + * graphs. Example: + * + *

{@code
+   * static final ImmutableGraph COUNTRY_ADJACENCY_GRAPH =
+   *     GraphBuilder.undirected()
+   *         .immutable()
+   *         .putEdge(FRANCE, GERMANY)
+   *         .putEdge(FRANCE, BELGIUM)
+   *         .putEdge(GERMANY, BELGIUM)
+   *         .addNode(ICELAND)
+   *         .build();
+   * }
+ * + *

Builder instances can be reused; it is safe to call {@link #build} multiple times to build + * multiple graphs in series. Each new graph contains all the elements of the ones created before + * it. + * + * @since 28.0 + */ + public static class Builder { + + private final MutableGraph mutableGraph; + + Builder(GraphBuilder graphBuilder) { + // The incidentEdgeOrder for immutable graphs is always stable. However, we don't want to + // modify this builder, so we make a copy instead. + this.mutableGraph = graphBuilder.copy().incidentEdgeOrder(ElementOrder.stable()).build(); + } + + /** + * Adds {@code node} if it is not already present. + * + *

Nodes must be unique, just as {@code Map} keys must be. They must also be non-null. + * + * @return this {@code Builder} object + */ + + public Builder addNode(N node) { + mutableGraph.addNode(node); + return this; + } + + /** + * Adds an edge connecting {@code nodeU} to {@code nodeV} if one is not already present. + * + *

If the graph is directed, the resultant edge will be directed; otherwise, it will be + * undirected. + * + *

If {@code nodeU} and {@code nodeV} are not already present in this graph, this method will + * silently {@link #addNode(Object) add} {@code nodeU} and {@code nodeV} to the graph. + * + * @return this {@code Builder} object + * @throws IllegalArgumentException if the introduction of the edge would violate {@link + * #allowsSelfLoops()} + */ + + public Builder putEdge(N nodeU, N nodeV) { + mutableGraph.putEdge(nodeU, nodeV); + return this; + } + + /** + * Adds an edge connecting {@code endpoints} (in the order, if any, specified by {@code + * endpoints}) if one is not already present. + * + *

If this graph is directed, {@code endpoints} must be ordered and the added edge will be + * directed; if it is undirected, the added edge will be undirected. + * + *

If this graph is directed, {@code endpoints} must be ordered. + * + *

If either or both endpoints are not already present in this graph, this method will + * silently {@link #addNode(Object) add} each missing endpoint to the graph. + * + * @return this {@code Builder} object + * @throws IllegalArgumentException if the introduction of the edge would violate {@link + * #allowsSelfLoops()} + * @throws IllegalArgumentException if the endpoints are unordered and the graph is directed + */ + + public Builder putEdge(EndpointPair endpoints) { + mutableGraph.putEdge(endpoints); + return this; + } + + /** + * Returns a newly-created {@code ImmutableGraph} based on the contents of this {@code Builder}. + */ + public ImmutableGraph build() { + return ImmutableGraph.copyOf(mutableGraph); + } + } +} diff --git a/src/main/java/com/google/common/graph/ImmutableNetwork.java b/src/main/java/com/google/common/graph/ImmutableNetwork.java new file mode 100644 index 0000000..03a71d5 --- /dev/null +++ b/src/main/java/com/google/common/graph/ImmutableNetwork.java @@ -0,0 +1,253 @@ +/* + * Copyright (C) 2014 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.graph; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.annotations.Beta; +import com.google.common.base.Function; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Maps; + + +import java.util.Map; + +/** + * A {@link Network} whose elements and structural relationships will never change. Instances of + * this class may be obtained with {@link #copyOf(Network)}. + * + *

See the Guava User's Guide's discussion + * of the {@code Immutable*} types for more information on the properties and guarantees + * provided by this class. + * + * @author James Sexton + * @author Joshua O'Madadhain + * @author Omar Darwish + * @author Jens Nyman + * @param Node parameter type + * @param Edge parameter type + * @since 20.0 + */ +@Beta +@SuppressWarnings("Immutable") // Extends ConfigurableNetwork but uses ImmutableMaps. +public final class ImmutableNetwork extends ConfigurableNetwork { + + private ImmutableNetwork(Network network) { + super( + NetworkBuilder.from(network), getNodeConnections(network), getEdgeToReferenceNode(network)); + } + + /** Returns an immutable copy of {@code network}. */ + public static ImmutableNetwork copyOf(Network network) { + return (network instanceof ImmutableNetwork) + ? (ImmutableNetwork) network + : new ImmutableNetwork(network); + } + + /** + * Simply returns its argument. + * + * @deprecated no need to use this + */ + @Deprecated + public static ImmutableNetwork copyOf(ImmutableNetwork network) { + return checkNotNull(network); + } + + @Override + public ImmutableGraph asGraph() { + return new ImmutableGraph(super.asGraph()); // safe because the view is effectively immutable + } + + private static Map> getNodeConnections(Network network) { + // ImmutableMap.Builder maintains the order of the elements as inserted, so the map will have + // whatever ordering the network's nodes do, so ImmutableSortedMap is unnecessary even if the + // input nodes are sorted. + ImmutableMap.Builder> nodeConnections = ImmutableMap.builder(); + for (N node : network.nodes()) { + nodeConnections.put(node, connectionsOf(network, node)); + } + return nodeConnections.build(); + } + + private static Map getEdgeToReferenceNode(Network network) { + // ImmutableMap.Builder maintains the order of the elements as inserted, so the map will have + // whatever ordering the network's edges do, so ImmutableSortedMap is unnecessary even if the + // input edges are sorted. + ImmutableMap.Builder edgeToReferenceNode = ImmutableMap.builder(); + for (E edge : network.edges()) { + edgeToReferenceNode.put(edge, network.incidentNodes(edge).nodeU()); + } + return edgeToReferenceNode.build(); + } + + private static NetworkConnections connectionsOf(Network network, N node) { + if (network.isDirected()) { + Map inEdgeMap = Maps.asMap(network.inEdges(node), sourceNodeFn(network)); + Map outEdgeMap = Maps.asMap(network.outEdges(node), targetNodeFn(network)); + int selfLoopCount = network.edgesConnecting(node, node).size(); + return network.allowsParallelEdges() + ? DirectedMultiNetworkConnections.ofImmutable(inEdgeMap, outEdgeMap, selfLoopCount) + : DirectedNetworkConnections.ofImmutable(inEdgeMap, outEdgeMap, selfLoopCount); + } else { + Map incidentEdgeMap = + Maps.asMap(network.incidentEdges(node), adjacentNodeFn(network, node)); + return network.allowsParallelEdges() + ? UndirectedMultiNetworkConnections.ofImmutable(incidentEdgeMap) + : UndirectedNetworkConnections.ofImmutable(incidentEdgeMap); + } + } + + private static Function sourceNodeFn(final Network network) { + return new Function() { + @Override + public N apply(E edge) { + return network.incidentNodes(edge).source(); + } + }; + } + + private static Function targetNodeFn(final Network network) { + return new Function() { + @Override + public N apply(E edge) { + return network.incidentNodes(edge).target(); + } + }; + } + + private static Function adjacentNodeFn(final Network network, final N node) { + return new Function() { + @Override + public N apply(E edge) { + return network.incidentNodes(edge).adjacentNode(node); + } + }; + } + + /** + * A builder for creating {@link ImmutableNetwork} instances, especially {@code static final} + * networks. Example: + * + *

{@code
+   * static final ImmutableNetwork TRAIN_NETWORK =
+   *     NetworkBuilder.undirected()
+   *         .allowsParallelEdges(true)
+   *         .immutable()
+   *         .addEdge(PARIS, BRUSSELS, Thalys.trainNumber("1111"))
+   *         .addEdge(PARIS, BRUSSELS, RegionalTrain.trainNumber("2222"))
+   *         .addEdge(LONDON, PARIS, Eurostar.trainNumber("3333"))
+   *         .addEdge(LONDON, BRUSSELS, Eurostar.trainNumber("4444"))
+   *         .addNode(REYKJAVIK)
+   *         .build();
+   * }
+ * + *

Builder instances can be reused; it is safe to call {@link #build} multiple times to build + * multiple networks in series. Each new network contains all the elements of the ones created + * before it. + * + * @since 28.0 + */ + public static class Builder { + + private final MutableNetwork mutableNetwork; + + Builder(NetworkBuilder networkBuilder) { + this.mutableNetwork = networkBuilder.build(); + } + + /** + * Adds {@code node} if it is not already present. + * + *

Nodes must be unique, just as {@code Map} keys must be. They must also be non-null. + * + * @return this {@code Builder} object + */ + + public ImmutableNetwork.Builder addNode(N node) { + mutableNetwork.addNode(node); + return this; + } + + /** + * Adds {@code edge} connecting {@code nodeU} to {@code nodeV}. + * + *

If the network is directed, {@code edge} will be directed in this network; otherwise, it + * will be undirected. + * + *

{@code edge} must be unique to this network, just as a {@code Map} key must be. It + * must also be non-null. + * + *

If {@code nodeU} and {@code nodeV} are not already present in this network, this method + * will silently {@link #addNode(Object) add} {@code nodeU} and {@code nodeV} to the network. + * + *

If {@code edge} already connects {@code nodeU} to {@code nodeV} (in the specified order if + * this network {@link #isDirected()}, else in any order), then this method will have no effect. + * + * @return this {@code Builder} object + * @throws IllegalArgumentException if {@code edge} already exists in the network and does not + * connect {@code nodeU} to {@code nodeV} + * @throws IllegalArgumentException if the introduction of the edge would violate {@link + * #allowsParallelEdges()} or {@link #allowsSelfLoops()} + */ + + public ImmutableNetwork.Builder addEdge(N nodeU, N nodeV, E edge) { + mutableNetwork.addEdge(nodeU, nodeV, edge); + return this; + } + + /** + * Adds {@code edge} connecting {@code endpoints}. In an undirected network, {@code edge} will + * also connect {@code nodeV} to {@code nodeU}. + * + *

If this network is directed, {@code edge} will be directed in this network; if it is + * undirected, {@code edge} will be undirected in this network. + * + *

If this network is directed, {@code endpoints} must be ordered. + * + *

{@code edge} must be unique to this network, just as a {@code Map} key must be. It + * must also be non-null. + * + *

If either or both endpoints are not already present in this network, this method will + * silently {@link #addNode(Object) add} each missing endpoint to the network. + * + *

If {@code edge} already connects an endpoint pair equal to {@code endpoints}, then this + * method will have no effect. + * + * @return this {@code Builder} object + * @throws IllegalArgumentException if {@code edge} already exists in the network and connects + * some other endpoint pair that is not equal to {@code endpoints} + * @throws IllegalArgumentException if the introduction of the edge would violate {@link + * #allowsParallelEdges()} or {@link #allowsSelfLoops()} + * @throws IllegalArgumentException if the endpoints are unordered and the network is directed + */ + + public ImmutableNetwork.Builder addEdge(EndpointPair endpoints, E edge) { + mutableNetwork.addEdge(endpoints, edge); + return this; + } + + /** + * Returns a newly-created {@code ImmutableNetwork} based on the contents of this {@code + * Builder}. + */ + public ImmutableNetwork build() { + return ImmutableNetwork.copyOf(mutableNetwork); + } + } +} diff --git a/src/main/java/com/google/common/graph/ImmutableValueGraph.java b/src/main/java/com/google/common/graph/ImmutableValueGraph.java new file mode 100644 index 0000000..fae2780 --- /dev/null +++ b/src/main/java/com/google/common/graph/ImmutableValueGraph.java @@ -0,0 +1,198 @@ +/* + * Copyright (C) 2016 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.graph; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.annotations.Beta; +import com.google.common.base.Function; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Maps; + + + +/** + * A {@link ValueGraph} whose elements and structural relationships will never change. Instances of + * this class may be obtained with {@link #copyOf(ValueGraph)}. + * + *

See the Guava User's Guide's discussion + * of the {@code Immutable*} types for more information on the properties and guarantees + * provided by this class. + * + * @author James Sexton + * @author Jens Nyman + * @param Node parameter type + * @param Value parameter type + * @since 20.0 + */ +@Beta +@SuppressWarnings("Immutable") // Extends ConfigurableValueGraph but uses ImmutableMaps. +public final class ImmutableValueGraph extends ConfigurableValueGraph { + + private ImmutableValueGraph(ValueGraph graph) { + super(ValueGraphBuilder.from(graph), getNodeConnections(graph), graph.edges().size()); + } + + /** Returns an immutable copy of {@code graph}. */ + public static ImmutableValueGraph copyOf(ValueGraph graph) { + return (graph instanceof ImmutableValueGraph) + ? (ImmutableValueGraph) graph + : new ImmutableValueGraph(graph); + } + + /** + * Simply returns its argument. + * + * @deprecated no need to use this + */ + @Deprecated + public static ImmutableValueGraph copyOf(ImmutableValueGraph graph) { + return checkNotNull(graph); + } + + @Override + public ImmutableGraph asGraph() { + return new ImmutableGraph(this); // safe because the view is effectively immutable + } + + private static ImmutableMap> getNodeConnections( + ValueGraph graph) { + // ImmutableMap.Builder maintains the order of the elements as inserted, so the map will have + // whatever ordering the graph's nodes do, so ImmutableSortedMap is unnecessary even if the + // input nodes are sorted. + ImmutableMap.Builder> nodeConnections = ImmutableMap.builder(); + for (N node : graph.nodes()) { + nodeConnections.put(node, connectionsOf(graph, node)); + } + return nodeConnections.build(); + } + + private static GraphConnections connectionsOf( + final ValueGraph graph, final N node) { + Function successorNodeToValueFn = + new Function() { + @Override + public V apply(N successorNode) { + return graph.edgeValueOrDefault(node, successorNode, null); + } + }; + return graph.isDirected() + ? DirectedGraphConnections.ofImmutable( + graph.predecessors(node), Maps.asMap(graph.successors(node), successorNodeToValueFn)) + : UndirectedGraphConnections.ofImmutable( + Maps.asMap(graph.adjacentNodes(node), successorNodeToValueFn)); + } + + /** + * A builder for creating {@link ImmutableValueGraph} instances, especially {@code static final} + * graphs. Example: + * + *

{@code
+   * static final ImmutableValueGraph CITY_ROAD_DISTANCE_GRAPH =
+   *     ValueGraphBuilder.undirected()
+   *         .immutable()
+   *         .putEdgeValue(PARIS, BERLIN, kilometers(1060))
+   *         .putEdgeValue(PARIS, BRUSSELS, kilometers(317))
+   *         .putEdgeValue(BERLIN, BRUSSELS, kilometers(764))
+   *         .addNode(REYKJAVIK)
+   *         .build();
+   * }
+ * + *

Builder instances can be reused; it is safe to call {@link #build} multiple times to build + * multiple graphs in series. Each new graph contains all the elements of the ones created before + * it. + * + * @since 28.0 + */ + public static class Builder { + + private final MutableValueGraph mutableValueGraph; + + Builder(ValueGraphBuilder graphBuilder) { + this.mutableValueGraph = graphBuilder.build(); + } + + /** + * Adds {@code node} if it is not already present. + * + *

Nodes must be unique, just as {@code Map} keys must be. They must also be non-null. + * + * @return this {@code Builder} object + */ + + public ImmutableValueGraph.Builder addNode(N node) { + mutableValueGraph.addNode(node); + return this; + } + + /** + * Adds an edge connecting {@code nodeU} to {@code nodeV} if one is not already present, and + * sets a value for that edge to {@code value} (overwriting the existing value, if any). + * + *

If the graph is directed, the resultant edge will be directed; otherwise, it will be + * undirected. + * + *

Values do not have to be unique. However, values must be non-null. + * + *

If {@code nodeU} and {@code nodeV} are not already present in this graph, this method will + * silently {@link #addNode(Object) add} {@code nodeU} and {@code nodeV} to the graph. + * + * @return this {@code Builder} object + * @throws IllegalArgumentException if the introduction of the edge would violate {@link + * #allowsSelfLoops()} + */ + + public ImmutableValueGraph.Builder putEdgeValue(N nodeU, N nodeV, V value) { + mutableValueGraph.putEdgeValue(nodeU, nodeV, value); + return this; + } + + /** + * Adds an edge connecting {@code endpoints} if one is not already present, and sets a value for + * that edge to {@code value} (overwriting the existing value, if any). + * + *

If the graph is directed, the resultant edge will be directed; otherwise, it will be + * undirected. + * + *

If this graph is directed, {@code endpoints} must be ordered. + * + *

Values do not have to be unique. However, values must be non-null. + * + *

If either or both endpoints are not already present in this graph, this method will + * silently {@link #addNode(Object) add} each missing endpoint to the graph. + * + * @return this {@code Builder} object + * @throws IllegalArgumentException if the introduction of the edge would violate {@link + * #allowsSelfLoops()} + * @throws IllegalArgumentException if the endpoints are unordered and the graph is directed + */ + + public ImmutableValueGraph.Builder putEdgeValue(EndpointPair endpoints, V value) { + mutableValueGraph.putEdgeValue(endpoints, value); + return this; + } + + /** + * Returns a newly-created {@code ImmutableValueGraph} based on the contents of this {@code + * Builder}. + */ + public ImmutableValueGraph build() { + return ImmutableValueGraph.copyOf(mutableValueGraph); + } + } +} diff --git a/src/main/java/com/google/common/graph/MapIteratorCache.java b/src/main/java/com/google/common/graph/MapIteratorCache.java new file mode 100644 index 0000000..2473ca3 --- /dev/null +++ b/src/main/java/com/google/common/graph/MapIteratorCache.java @@ -0,0 +1,140 @@ +/* + * Copyright (C) 2016 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.graph; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.collect.UnmodifiableIterator; + +import java.util.AbstractSet; +import java.util.Iterator; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; + + +/** + * A map-like data structure that wraps a backing map and caches values while iterating through + * {@link #unmodifiableKeySet()}. By design, the cache is cleared when this structure is mutated. If + * this structure is never mutated, it provides a thread-safe view of the backing map. + * + *

The {@link MapIteratorCache} assumes ownership of the backing map, and cannot guarantee + * correctness in the face of external mutations to the backing map. As such, it is strongly + * recommended that the caller does not persist a reference to the backing map (unless the backing + * map is immutable). + * + *

This class is tailored toward use cases in common.graph. It is *NOT* a general purpose map. + * + * @author James Sexton + */ +class MapIteratorCache { + private final Map backingMap; + + /* + * Per JDK: "the behavior of a map entry is undefined if the backing map has been modified after + * the entry was returned by the iterator, except through the setValue operation on the map entry" + * As such, this field must be cleared before every map mutation. + * + * Note about volatile: volatile doesn't make it safe to read from a mutable graph in one thread + * while writing to it in another. All it does is help with _reading_ from multiple threads + * concurrently. For more information, see AbstractNetworkTest.concurrentIteration. + */ + private transient volatile Entry cacheEntry; + + MapIteratorCache(Map backingMap) { + this.backingMap = checkNotNull(backingMap); + } + + + public final V put(K key, V value) { + clearCache(); + return backingMap.put(key, value); + } + + + public final V remove(Object key) { + clearCache(); + return backingMap.remove(key); + } + + public final void clear() { + clearCache(); + backingMap.clear(); + } + + public V get(Object key) { + V value = getIfCached(key); + return (value != null) ? value : getWithoutCaching(key); + } + + public final V getWithoutCaching(Object key) { + return backingMap.get(key); + } + + public final boolean containsKey(Object key) { + return getIfCached(key) != null || backingMap.containsKey(key); + } + + public final Set unmodifiableKeySet() { + return new AbstractSet() { + @Override + public UnmodifiableIterator iterator() { + final Iterator> entryIterator = backingMap.entrySet().iterator(); + + return new UnmodifiableIterator() { + @Override + public boolean hasNext() { + return entryIterator.hasNext(); + } + + @Override + public K next() { + Entry entry = entryIterator.next(); // store local reference for thread-safety + cacheEntry = entry; + return entry.getKey(); + } + }; + } + + @Override + public int size() { + return backingMap.size(); + } + + @Override + public boolean contains(Object key) { + return containsKey(key); + } + }; + } + + // Internal methods ('protected' is still package-visible, but treat as only subclass-visible) + + protected V getIfCached(Object key) { + Entry entry = cacheEntry; // store local reference for thread-safety + + // Check cache. We use == on purpose because it's cheaper and a cache miss is ok. + if (entry != null && entry.getKey() == key) { + return entry.getValue(); + } + return null; + } + + protected void clearCache() { + cacheEntry = null; + } +} diff --git a/src/main/java/com/google/common/graph/MapRetrievalCache.java b/src/main/java/com/google/common/graph/MapRetrievalCache.java new file mode 100644 index 0000000..80aa6ee --- /dev/null +++ b/src/main/java/com/google/common/graph/MapRetrievalCache.java @@ -0,0 +1,106 @@ +/* + * Copyright (C) 2016 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.graph; + +import java.util.Map; + + +/** + * A {@link MapIteratorCache} that adds additional caching. In addition to the caching provided by + * {@link MapIteratorCache}, this structure caches values for the two most recently retrieved keys. + * + * @author James Sexton + */ +class MapRetrievalCache extends MapIteratorCache { + // See the note about volatile in the superclass. + private transient volatile CacheEntry cacheEntry1; + private transient volatile CacheEntry cacheEntry2; + + MapRetrievalCache(Map backingMap) { + super(backingMap); + } + + @SuppressWarnings("unchecked") // Safe because we only cast if key is found in map. + @Override + public V get(Object key) { + V value = getIfCached(key); + if (value != null) { + return value; + } + + value = getWithoutCaching(key); + if (value != null) { + addToCache((K) key, value); + } + return value; + } + + // Internal methods ('protected' is still package-visible, but treat as only subclass-visible) + + @Override + protected V getIfCached(Object key) { + V value = super.getIfCached(key); + if (value != null) { + return value; + } + + // Store a local reference to the cache entry. If the backing map is immutable, this, + // in combination with immutable cache entries, will ensure a thread-safe cache. + CacheEntry entry; + + // Check cache. We use == on purpose because it's cheaper and a cache miss is ok. + entry = cacheEntry1; + if (entry != null && entry.key == key) { + return entry.value; + } + entry = cacheEntry2; + if (entry != null && entry.key == key) { + // Promote second cache entry to first so the access pattern + // [K1, K2, K1, K3, K1, K4...] still hits the cache half the time. + addToCache(entry); + return entry.value; + } + return null; + } + + @Override + protected void clearCache() { + super.clearCache(); + cacheEntry1 = null; + cacheEntry2 = null; + } + + private void addToCache(K key, V value) { + addToCache(new CacheEntry(key, value)); + } + + private void addToCache(CacheEntry entry) { + // Slide new entry into first cache position. Drop previous entry in second cache position. + cacheEntry2 = cacheEntry1; + cacheEntry1 = entry; + } + + private static final class CacheEntry { + final K key; + final V value; + + CacheEntry(K key, V value) { + this.key = key; + this.value = value; + } + } +} diff --git a/src/main/java/com/google/common/graph/MultiEdgesConnecting.java b/src/main/java/com/google/common/graph/MultiEdgesConnecting.java new file mode 100644 index 0000000..e90b6a9 --- /dev/null +++ b/src/main/java/com/google/common/graph/MultiEdgesConnecting.java @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2016 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.graph; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.collect.AbstractIterator; +import com.google.common.collect.UnmodifiableIterator; +import java.util.AbstractSet; +import java.util.Iterator; +import java.util.Map; +import java.util.Map.Entry; + + +/** + * A class to represent the set of edges connecting an (implicit) origin node to a target node. + * + *

The {@link #outEdgeToNode} map allows this class to work on networks with parallel edges. See + * {@link EdgesConnecting} for a class that is more efficient but forbids parallel edges. + * + * @author James Sexton + * @param Edge parameter type + */ +abstract class MultiEdgesConnecting extends AbstractSet { + + private final Map outEdgeToNode; + private final Object targetNode; + + MultiEdgesConnecting(Map outEdgeToNode, Object targetNode) { + this.outEdgeToNode = checkNotNull(outEdgeToNode); + this.targetNode = checkNotNull(targetNode); + } + + @Override + public UnmodifiableIterator iterator() { + final Iterator> entries = outEdgeToNode.entrySet().iterator(); + return new AbstractIterator() { + @Override + protected E computeNext() { + while (entries.hasNext()) { + Entry entry = entries.next(); + if (targetNode.equals(entry.getValue())) { + return entry.getKey(); + } + } + return endOfData(); + } + }; + } + + @Override + public boolean contains(Object edge) { + return targetNode.equals(outEdgeToNode.get(edge)); + } +} diff --git a/src/main/java/com/google/common/graph/MutableGraph.java b/src/main/java/com/google/common/graph/MutableGraph.java new file mode 100644 index 0000000..097370f --- /dev/null +++ b/src/main/java/com/google/common/graph/MutableGraph.java @@ -0,0 +1,108 @@ +/* + * Copyright (C) 2014 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.graph; + +import com.google.common.annotations.Beta; + + +/** + * A subinterface of {@link Graph} which adds mutation methods. When mutation is not required, users + * should prefer the {@link Graph} interface. + * + * @author James Sexton + * @author Joshua O'Madadhain + * @param Node parameter type + * @since 20.0 + */ +@Beta +public interface MutableGraph extends Graph { + + /** + * Adds {@code node} if it is not already present. + * + *

Nodes must be unique, just as {@code Map} keys must be. They must also be non-null. + * + * @return {@code true} if the graph was modified as a result of this call + */ + + boolean addNode(N node); + + /** + * Adds an edge connecting {@code nodeU} to {@code nodeV} if one is not already present. + * + *

If the graph is directed, the resultant edge will be directed; otherwise, it will be + * undirected. + * + *

If {@code nodeU} and {@code nodeV} are not already present in this graph, this method will + * silently {@link #addNode(Object) add} {@code nodeU} and {@code nodeV} to the graph. + * + * @return {@code true} if the graph was modified as a result of this call + * @throws IllegalArgumentException if the introduction of the edge would violate {@link + * #allowsSelfLoops()} + */ + + boolean putEdge(N nodeU, N nodeV); + + /** + * Adds an edge connecting {@code endpoints} (in the order, if any, specified by {@code + * endpoints}) if one is not already present. + * + *

If this graph is directed, {@code endpoints} must be ordered and the added edge will be + * directed; if it is undirected, the added edge will be undirected. + * + *

If this graph is directed, {@code endpoints} must be ordered. + * + *

If either or both endpoints are not already present in this graph, this method will silently + * {@link #addNode(Object) add} each missing endpoint to the graph. + * + * @return {@code true} if the graph was modified as a result of this call + * @throws IllegalArgumentException if the introduction of the edge would violate {@link + * #allowsSelfLoops()} + * @throws IllegalArgumentException if the endpoints are unordered and the graph is directed + * @since 27.1 + */ + + boolean putEdge(EndpointPair endpoints); + + /** + * Removes {@code node} if it is present; all edges incident to {@code node} will also be removed. + * + * @return {@code true} if the graph was modified as a result of this call + */ + + boolean removeNode(N node); + + /** + * Removes the edge connecting {@code nodeU} to {@code nodeV}, if it is present. + * + * @return {@code true} if the graph was modified as a result of this call + */ + + boolean removeEdge(N nodeU, N nodeV); + + /** + * Removes the edge connecting {@code endpoints}, if it is present. + * + *

If this graph is directed, {@code endpoints} must be ordered. + * + * @throws IllegalArgumentException if the endpoints are unordered and the graph is directed + * @return {@code true} if the graph was modified as a result of this call + * @since 27.1 + */ + + boolean removeEdge(EndpointPair endpoints); +} diff --git a/src/main/java/com/google/common/graph/MutableNetwork.java b/src/main/java/com/google/common/graph/MutableNetwork.java new file mode 100644 index 0000000..b1ff6cd --- /dev/null +++ b/src/main/java/com/google/common/graph/MutableNetwork.java @@ -0,0 +1,113 @@ +/* + * Copyright (C) 2014 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.graph; + +import com.google.common.annotations.Beta; + + +/** + * A subinterface of {@link Network} which adds mutation methods. When mutation is not required, + * users should prefer the {@link Network} interface. + * + * @author James Sexton + * @author Joshua O'Madadhain + * @param Node parameter type + * @param Edge parameter type + * @since 20.0 + */ +@Beta +public interface MutableNetwork extends Network { + + /** + * Adds {@code node} if it is not already present. + * + *

Nodes must be unique, just as {@code Map} keys must be. They must also be non-null. + * + * @return {@code true} if the network was modified as a result of this call + */ + + boolean addNode(N node); + + /** + * Adds {@code edge} connecting {@code nodeU} to {@code nodeV}. + * + *

If the graph is directed, {@code edge} will be directed in this graph; otherwise, it will be + * undirected. + * + *

{@code edge} must be unique to this graph, just as a {@code Map} key must be. It must + * also be non-null. + * + *

If {@code nodeU} and {@code nodeV} are not already present in this graph, this method will + * silently {@link #addNode(Object) add} {@code nodeU} and {@code nodeV} to the graph. + * + *

If {@code edge} already connects {@code nodeU} to {@code nodeV} (in the specified order if + * this network {@link #isDirected()}, else in any order), then this method will have no effect. + * + * @return {@code true} if the network was modified as a result of this call + * @throws IllegalArgumentException if {@code edge} already exists in the graph and does not + * connect {@code nodeU} to {@code nodeV} + * @throws IllegalArgumentException if the introduction of the edge would violate {@link + * #allowsParallelEdges()} or {@link #allowsSelfLoops()} + */ + + boolean addEdge(N nodeU, N nodeV, E edge); + + /** + * Adds {@code edge} connecting {@code endpoints}. In an undirected network, {@code edge} will + * also connect {@code nodeV} to {@code nodeU}. + * + *

If this graph is directed, {@code edge} will be directed in this graph; if it is undirected, + * {@code edge} will be undirected in this graph. + * + *

If this graph is directed, {@code endpoints} must be ordered. + * + *

{@code edge} must be unique to this graph, just as a {@code Map} key must be. It must + * also be non-null. + * + *

If either or both endpoints are not already present in this graph, this method will silently + * {@link #addNode(Object) add} each missing endpoint to the graph. + * + *

If {@code edge} already connects an endpoint pair equal to {@code endpoints}, then this + * method will have no effect. + * + * @return {@code true} if the network was modified as a result of this call + * @throws IllegalArgumentException if {@code edge} already exists in the graph and connects some + * other endpoint pair that is not equal to {@code endpoints} + * @throws IllegalArgumentException if the introduction of the edge would violate {@link + * #allowsParallelEdges()} or {@link #allowsSelfLoops()} + * @throws IllegalArgumentException if the endpoints are unordered and the graph is directed + * @since 27.1 + */ + + boolean addEdge(EndpointPair endpoints, E edge); + + /** + * Removes {@code node} if it is present; all edges incident to {@code node} will also be removed. + * + * @return {@code true} if the network was modified as a result of this call + */ + + boolean removeNode(N node); + + /** + * Removes {@code edge} from this network, if it is present. + * + * @return {@code true} if the network was modified as a result of this call + */ + + boolean removeEdge(E edge); +} diff --git a/src/main/java/com/google/common/graph/MutableValueGraph.java b/src/main/java/com/google/common/graph/MutableValueGraph.java new file mode 100644 index 0000000..c7bbb43 --- /dev/null +++ b/src/main/java/com/google/common/graph/MutableValueGraph.java @@ -0,0 +1,116 @@ +/* + * Copyright (C) 2016 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.graph; + +import com.google.common.annotations.Beta; + + +/** + * A subinterface of {@link ValueGraph} which adds mutation methods. When mutation is not required, + * users should prefer the {@link ValueGraph} interface. + * + * @author James Sexton + * @param Node parameter type + * @param Value parameter type + * @since 20.0 + */ +@Beta +public interface MutableValueGraph extends ValueGraph { + + /** + * Adds {@code node} if it is not already present. + * + *

Nodes must be unique, just as {@code Map} keys must be. They must also be non-null. + * + * @return {@code true} if the graph was modified as a result of this call + */ + + boolean addNode(N node); + + /** + * Adds an edge connecting {@code nodeU} to {@code nodeV} if one is not already present, and sets + * a value for that edge to {@code value} (overwriting the existing value, if any). + * + *

If the graph is directed, the resultant edge will be directed; otherwise, it will be + * undirected. + * + *

Values do not have to be unique. However, values must be non-null. + * + *

If {@code nodeU} and {@code nodeV} are not already present in this graph, this method will + * silently {@link #addNode(Object) add} {@code nodeU} and {@code nodeV} to the graph. + * + * @return the value previously associated with the edge connecting {@code nodeU} to {@code + * nodeV}, or null if there was no such edge. + * @throws IllegalArgumentException if the introduction of the edge would violate {@link + * #allowsSelfLoops()} + */ + + V putEdgeValue(N nodeU, N nodeV, V value); + + /** + * Adds an edge connecting {@code endpoints} if one is not already present, and sets a value for + * that edge to {@code value} (overwriting the existing value, if any). + * + *

If the graph is directed, the resultant edge will be directed; otherwise, it will be + * undirected. + * + *

If this graph is directed, {@code endpoints} must be ordered. + * + *

Values do not have to be unique. However, values must be non-null. + * + *

If either or both endpoints are not already present in this graph, this method will silently + * {@link #addNode(Object) add} each missing endpoint to the graph. + * + * @return the value previously associated with the edge connecting {@code nodeU} to {@code + * nodeV}, or null if there was no such edge. + * @throws IllegalArgumentException if the introduction of the edge would violate {@link + * #allowsSelfLoops()} + * @throws IllegalArgumentException if the endpoints are unordered and the graph is directed + * @since 27.1 + */ + + V putEdgeValue(EndpointPair endpoints, V value); + + /** + * Removes {@code node} if it is present; all edges incident to {@code node} will also be removed. + * + * @return {@code true} if the graph was modified as a result of this call + */ + + boolean removeNode(N node); + + /** + * Removes the edge connecting {@code nodeU} to {@code nodeV}, if it is present. + * + * @return the value previously associated with the edge connecting {@code nodeU} to {@code + * nodeV}, or null if there was no such edge. + */ + + V removeEdge(N nodeU, N nodeV); + + /** + * Removes the edge connecting {@code endpoints}, if it is present. + * + *

If this graph is directed, {@code endpoints} must be ordered. + * + * @return the value previously associated with the edge connecting {@code endpoints}, or null if + * there was no such edge. + * @since 27.1 + */ + + V removeEdge(EndpointPair endpoints); +} diff --git a/src/main/java/com/google/common/graph/Network.java b/src/main/java/com/google/common/graph/Network.java new file mode 100644 index 0000000..d2d9d24 --- /dev/null +++ b/src/main/java/com/google/common/graph/Network.java @@ -0,0 +1,427 @@ +/* + * Copyright (C) 2014 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.graph; + +import com.google.common.annotations.Beta; +import java.util.Optional; +import java.util.Set; + + +/** + * An interface for graph-structured data, + * whose edges are unique objects. + * + *

A graph is composed of a set of nodes and a set of edges connecting pairs of nodes. + * + *

There are three primary interfaces provided to represent graphs. In order of increasing + * complexity they are: {@link Graph}, {@link ValueGraph}, and {@link Network}. You should generally + * prefer the simplest interface that satisfies your use case. See the + * "Choosing the right graph type" section of the Guava User Guide for more details. + * + *

Capabilities

+ * + *

{@code Network} supports the following use cases (definitions of + * terms): + * + *

    + *
  • directed graphs + *
  • undirected graphs + *
  • graphs that do/don't allow parallel edges + *
  • graphs that do/don't allow self-loops + *
  • graphs whose nodes/edges are insertion-ordered, sorted, or unordered + *
  • graphs whose edges are unique objects + *
+ * + *

Building a {@code Network}

+ * + *

The implementation classes that {@code common.graph} provides are not public, by design. To + * create an instance of one of the built-in implementations of {@code Network}, use the {@link + * NetworkBuilder} class: + * + *

{@code
+ * MutableNetwork graph = NetworkBuilder.directed().build();
+ * }
+ * + *

{@link NetworkBuilder#build()} returns an instance of {@link MutableNetwork}, which is a + * subtype of {@code Network} that provides methods for adding and removing nodes and edges. If you + * do not need to mutate a graph (e.g. if you write a method than runs a read-only algorithm on the + * graph), you should use the non-mutating {@link Network} interface, or an {@link + * ImmutableNetwork}. + * + *

You can create an immutable copy of an existing {@code Network} using {@link + * ImmutableNetwork#copyOf(Network)}: + * + *

{@code
+ * ImmutableNetwork immutableGraph = ImmutableNetwork.copyOf(graph);
+ * }
+ * + *

Instances of {@link ImmutableNetwork} do not implement {@link MutableNetwork} (obviously!) and + * are contractually guaranteed to be unmodifiable and thread-safe. + * + *

The Guava User Guide has more + * information on (and examples of) building graphs. + * + *

Additional documentation

+ * + *

See the Guava User Guide for the {@code common.graph} package ("Graphs Explained") for + * additional documentation, including: + * + *

+ * + * @author James Sexton + * @author Joshua O'Madadhain + * @param Node parameter type + * @param Edge parameter type + * @since 20.0 + */ +@Beta +public interface Network extends SuccessorsFunction, PredecessorsFunction { + // + // Network-level accessors + // + + /** Returns all nodes in this network, in the order specified by {@link #nodeOrder()}. */ + Set nodes(); + + /** Returns all edges in this network, in the order specified by {@link #edgeOrder()}. */ + Set edges(); + + /** + * Returns a live view of this network as a {@link Graph}. The resulting {@link Graph} will have + * an edge connecting node A to node B if this {@link Network} has an edge connecting A to B. + * + *

If this network {@link #allowsParallelEdges() allows parallel edges}, parallel edges will be + * treated as if collapsed into a single edge. For example, the {@link #degree(Object)} of a node + * in the {@link Graph} view may be less than the degree of the same node in this {@link Network}. + */ + Graph asGraph(); + + // + // Network properties + // + + /** + * Returns true if the edges in this network are directed. Directed edges connect a {@link + * EndpointPair#source() source node} to a {@link EndpointPair#target() target node}, while + * undirected edges connect a pair of nodes to each other. + */ + boolean isDirected(); + + /** + * Returns true if this network allows parallel edges. Attempting to add a parallel edge to a + * network that does not allow them will throw an {@link IllegalArgumentException}. + */ + boolean allowsParallelEdges(); + + /** + * Returns true if this network allows self-loops (edges that connect a node to itself). + * Attempting to add a self-loop to a network that does not allow them will throw an {@link + * IllegalArgumentException}. + */ + boolean allowsSelfLoops(); + + /** Returns the order of iteration for the elements of {@link #nodes()}. */ + ElementOrder nodeOrder(); + + /** Returns the order of iteration for the elements of {@link #edges()}. */ + ElementOrder edgeOrder(); + + // + // Element-level accessors + // + + /** + * Returns the nodes which have an incident edge in common with {@code node} in this network. + * + *

This is equal to the union of {@link #predecessors(Object)} and {@link #successors(Object)}. + * + * @throws IllegalArgumentException if {@code node} is not an element of this network + */ + Set adjacentNodes(N node); + + /** + * Returns all nodes in this network adjacent to {@code node} which can be reached by traversing + * {@code node}'s incoming edges against the direction (if any) of the edge. + * + *

In an undirected network, this is equivalent to {@link #adjacentNodes(Object)}. + * + * @throws IllegalArgumentException if {@code node} is not an element of this network + */ + @Override + Set predecessors(N node); + + /** + * Returns all nodes in this network adjacent to {@code node} which can be reached by traversing + * {@code node}'s outgoing edges in the direction (if any) of the edge. + * + *

In an undirected network, this is equivalent to {@link #adjacentNodes(Object)}. + * + *

This is not the same as "all nodes reachable from {@code node} by following outgoing + * edges". For that functionality, see {@link Graphs#reachableNodes(Graph, Object)}. + * + * @throws IllegalArgumentException if {@code node} is not an element of this network + */ + @Override + Set successors(N node); + + /** + * Returns the edges whose {@link #incidentNodes(Object) incident nodes} in this network include + * {@code node}. + * + *

This is equal to the union of {@link #inEdges(Object)} and {@link #outEdges(Object)}. + * + * @throws IllegalArgumentException if {@code node} is not an element of this network + */ + Set incidentEdges(N node); + + /** + * Returns all edges in this network which can be traversed in the direction (if any) of the edge + * to end at {@code node}. + * + *

In a directed network, an incoming edge's {@link EndpointPair#target()} equals {@code node}. + * + *

In an undirected network, this is equivalent to {@link #incidentEdges(Object)}. + * + * @throws IllegalArgumentException if {@code node} is not an element of this network + */ + Set inEdges(N node); + + /** + * Returns all edges in this network which can be traversed in the direction (if any) of the edge + * starting from {@code node}. + * + *

In a directed network, an outgoing edge's {@link EndpointPair#source()} equals {@code node}. + * + *

In an undirected network, this is equivalent to {@link #incidentEdges(Object)}. + * + * @throws IllegalArgumentException if {@code node} is not an element of this network + */ + Set outEdges(N node); + + /** + * Returns the count of {@code node}'s {@link #incidentEdges(Object) incident edges}, counting + * self-loops twice (equivalently, the number of times an edge touches {@code node}). + * + *

For directed networks, this is equal to {@code inDegree(node) + outDegree(node)}. + * + *

For undirected networks, this is equal to {@code incidentEdges(node).size()} + (number of + * self-loops incident to {@code node}). + * + *

If the count is greater than {@code Integer.MAX_VALUE}, returns {@code Integer.MAX_VALUE}. + * + * @throws IllegalArgumentException if {@code node} is not an element of this network + */ + int degree(N node); + + /** + * Returns the count of {@code node}'s {@link #inEdges(Object) incoming edges} in a directed + * network. In an undirected network, returns the {@link #degree(Object)}. + * + *

If the count is greater than {@code Integer.MAX_VALUE}, returns {@code Integer.MAX_VALUE}. + * + * @throws IllegalArgumentException if {@code node} is not an element of this network + */ + int inDegree(N node); + + /** + * Returns the count of {@code node}'s {@link #outEdges(Object) outgoing edges} in a directed + * network. In an undirected network, returns the {@link #degree(Object)}. + * + *

If the count is greater than {@code Integer.MAX_VALUE}, returns {@code Integer.MAX_VALUE}. + * + * @throws IllegalArgumentException if {@code node} is not an element of this network + */ + int outDegree(N node); + + /** + * Returns the nodes which are the endpoints of {@code edge} in this network. + * + * @throws IllegalArgumentException if {@code edge} is not an element of this network + */ + EndpointPair incidentNodes(E edge); + + /** + * Returns the edges which have an {@link #incidentNodes(Object) incident node} in common with + * {@code edge}. An edge is not considered adjacent to itself. + * + * @throws IllegalArgumentException if {@code edge} is not an element of this network + */ + Set adjacentEdges(E edge); + + /** + * Returns the set of edges that each directly connect {@code nodeU} to {@code nodeV}. + * + *

In an undirected network, this is equal to {@code edgesConnecting(nodeV, nodeU)}. + * + *

The resulting set of edges will be parallel (i.e. have equal {@link #incidentNodes(Object)}. + * If this network does not {@link #allowsParallelEdges() allow parallel edges}, the resulting set + * will contain at most one edge (equivalent to {@code edgeConnecting(nodeU, nodeV).asSet()}). + * + * @throws IllegalArgumentException if {@code nodeU} or {@code nodeV} is not an element of this + * network + */ + Set edgesConnecting(N nodeU, N nodeV); + + /** + * Returns the set of edges that each directly connect {@code endpoints} (in the order, if any, + * specified by {@code endpoints}). + * + *

The resulting set of edges will be parallel (i.e. have equal {@link #incidentNodes(Object)}. + * If this network does not {@link #allowsParallelEdges() allow parallel edges}, the resulting set + * will contain at most one edge (equivalent to {@code edgeConnecting(endpoints).asSet()}). + * + *

If this network is directed, {@code endpoints} must be ordered. + * + * @throws IllegalArgumentException if either endpoint is not an element of this network + * @throws IllegalArgumentException if the endpoints are unordered and the graph is directed + * @since 27.1 + */ + Set edgesConnecting(EndpointPair endpoints); + + /** + * Returns the single edge that directly connects {@code nodeU} to {@code nodeV}, if one is + * present, or {@code Optional.empty()} if no such edge exists. + * + *

In an undirected network, this is equal to {@code edgeConnecting(nodeV, nodeU)}. + * + * @throws IllegalArgumentException if there are multiple parallel edges connecting {@code nodeU} + * to {@code nodeV} + * @throws IllegalArgumentException if {@code nodeU} or {@code nodeV} is not an element of this + * network + * @since 23.0 + */ + Optional edgeConnecting(N nodeU, N nodeV); + + /** + * Returns the single edge that directly connects {@code endpoints} (in the order, if any, + * specified by {@code endpoints}), if one is present, or {@code Optional.empty()} if no such edge + * exists. + * + *

If this graph is directed, the endpoints must be ordered. + * + * @throws IllegalArgumentException if there are multiple parallel edges connecting {@code nodeU} + * to {@code nodeV} + * @throws IllegalArgumentException if either endpoint is not an element of this network + * @throws IllegalArgumentException if the endpoints are unordered and the graph is directed + * @since 27.1 + */ + Optional edgeConnecting(EndpointPair endpoints); + + /** + * Returns the single edge that directly connects {@code nodeU} to {@code nodeV}, if one is + * present, or {@code null} if no such edge exists. + * + *

In an undirected network, this is equal to {@code edgeConnectingOrNull(nodeV, nodeU)}. + * + * @throws IllegalArgumentException if there are multiple parallel edges connecting {@code nodeU} + * to {@code nodeV} + * @throws IllegalArgumentException if {@code nodeU} or {@code nodeV} is not an element of this + * network + * @since 23.0 + */ + + E edgeConnectingOrNull(N nodeU, N nodeV); + + /** + * Returns the single edge that directly connects {@code endpoints} (in the order, if any, + * specified by {@code endpoints}), if one is present, or {@code null} if no such edge exists. + * + *

If this graph is directed, the endpoints must be ordered. + * + * @throws IllegalArgumentException if there are multiple parallel edges connecting {@code nodeU} + * to {@code nodeV} + * @throws IllegalArgumentException if either endpoint is not an element of this network + * @throws IllegalArgumentException if the endpoints are unordered and the graph is directed + * @since 27.1 + */ + + E edgeConnectingOrNull(EndpointPair endpoints); + + /** + * Returns true if there is an edge that directly connects {@code nodeU} to {@code nodeV}. This is + * equivalent to {@code nodes().contains(nodeU) && successors(nodeU).contains(nodeV)}, and to + * {@code edgeConnectingOrNull(nodeU, nodeV) != null}. + * + *

In an undirected graph, this is equal to {@code hasEdgeConnecting(nodeV, nodeU)}. + * + * @since 23.0 + */ + boolean hasEdgeConnecting(N nodeU, N nodeV); + + /** + * Returns true if there is an edge that directly connects {@code endpoints} (in the order, if + * any, specified by {@code endpoints}). + * + *

Unlike the other {@code EndpointPair}-accepting methods, this method does not throw if the + * endpoints are unordered and the graph is directed; it simply returns {@code false}. This is for + * consistency with {@link Graph#hasEdgeConnecting(EndpointPair)} and {@link + * ValueGraph#hasEdgeConnecting(EndpointPair)}. + * + * @since 27.1 + */ + boolean hasEdgeConnecting(EndpointPair endpoints); + + // + // Network identity + // + + /** + * Returns {@code true} iff {@code object} is a {@link Network} that has the same elements and the + * same structural relationships as those in this network. + * + *

Thus, two networks A and B are equal if all of the following are true: + * + *

    + *
  • A and B have equal {@link #isDirected() directedness}. + *
  • A and B have equal {@link #nodes() node sets}. + *
  • A and B have equal {@link #edges() edge sets}. + *
  • Every edge in A and B connects the same nodes in the same direction (if any). + *
+ * + *

Network properties besides {@link #isDirected() directedness} do not affect equality. + * For example, two networks may be considered equal even if one allows parallel edges and the + * other doesn't. Additionally, the order in which nodes or edges are added to the network, and + * the order in which they are iterated over, are irrelevant. + * + *

A reference implementation of this is provided by {@link AbstractNetwork#equals(Object)}. + */ + @Override + boolean equals(Object object); + + /** + * Returns the hash code for this network. The hash code of a network is defined as the hash code + * of a map from each of its {@link #edges() edges} to their {@link #incidentNodes(Object) + * incident nodes}. + * + *

A reference implementation of this is provided by {@link AbstractNetwork#hashCode()}. + */ + @Override + int hashCode(); +} diff --git a/src/main/java/com/google/common/graph/NetworkBuilder.java b/src/main/java/com/google/common/graph/NetworkBuilder.java new file mode 100644 index 0000000..b7db74c --- /dev/null +++ b/src/main/java/com/google/common/graph/NetworkBuilder.java @@ -0,0 +1,192 @@ +/* + * Copyright (C) 2016 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.graph; + +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.graph.Graphs.checkNonNegative; + +import com.google.common.annotations.Beta; +import com.google.common.base.Optional; + +/** + * A builder for constructing instances of {@link MutableNetwork} or {@link ImmutableNetwork} with + * user-defined properties. + * + *

A network built by this class will have the following properties by default: + * + *

    + *
  • does not allow parallel edges + *
  • does not allow self-loops + *
  • orders {@link Network#nodes()} and {@link Network#edges()} in the order in which the + * elements were added + *
+ * + *

Examples of use: + * + *

{@code
+ * // Building a mutable network
+ * MutableNetwork network =
+ *     NetworkBuilder.directed().allowsParallelEdges(true).build();
+ * flightNetwork.addEdge("LAX", "ATL", 3025);
+ * flightNetwork.addEdge("LAX", "ATL", 1598);
+ * flightNetwork.addEdge("ATL", "LAX", 2450);
+ *
+ * // Building a immutable network
+ * ImmutableNetwork immutableNetwork =
+ *     NetworkBuilder.directed()
+ *         .allowsParallelEdges(true)
+ *         .immutable()
+ *         .addEdge("LAX", "ATL", 3025)
+ *         .addEdge("LAX", "ATL", 1598)
+ *         .addEdge("ATL", "LAX", 2450)
+ *         .build();
+ * }
+ * + * @author James Sexton + * @author Joshua O'Madadhain + * @param The most general node type this builder will support. This is normally {@code Object} + * unless it is constrained by using a method like {@link #nodeOrder}, or the builder is + * constructed based on an existing {@code Network} using {@link #from(Network)}. + * @param The most general edge type this builder will support. This is normally {@code Object} + * unless it is constrained by using a method like {@link #edgeOrder}, or the builder is + * constructed based on an existing {@code Network} using {@link #from(Network)}. + * @since 20.0 + */ +@Beta +public final class NetworkBuilder extends AbstractGraphBuilder { + boolean allowsParallelEdges = false; + ElementOrder edgeOrder = ElementOrder.insertion(); + Optional expectedEdgeCount = Optional.absent(); + + /** Creates a new instance with the specified edge directionality. */ + private NetworkBuilder(boolean directed) { + super(directed); + } + + /** Returns a {@link NetworkBuilder} for building directed networks. */ + public static NetworkBuilder directed() { + return new NetworkBuilder<>(true); + } + + /** Returns a {@link NetworkBuilder} for building undirected networks. */ + public static NetworkBuilder undirected() { + return new NetworkBuilder<>(false); + } + + /** + * Returns a {@link NetworkBuilder} initialized with all properties queryable from {@code + * network}. + * + *

The "queryable" properties are those that are exposed through the {@link Network} interface, + * such as {@link Network#isDirected()}. Other properties, such as {@link + * #expectedNodeCount(int)}, are not set in the new builder. + */ + public static NetworkBuilder from(Network network) { + return new NetworkBuilder(network.isDirected()) + .allowsParallelEdges(network.allowsParallelEdges()) + .allowsSelfLoops(network.allowsSelfLoops()) + .nodeOrder(network.nodeOrder()) + .edgeOrder(network.edgeOrder()); + } + + /** + * Returns an {@link ImmutableNetwork.Builder} with the properties of this {@link NetworkBuilder}. + * + *

The returned builder can be used for populating an {@link ImmutableNetwork}. + * + * @since 28.0 + */ + public ImmutableNetwork.Builder immutable() { + NetworkBuilder castBuilder = cast(); + return new ImmutableNetwork.Builder<>(castBuilder); + } + + /** + * Specifies whether the network will allow parallel edges. Attempting to add a parallel edge to a + * network that does not allow them will throw an {@link UnsupportedOperationException}. + * + *

The default value is {@code false}. + */ + public NetworkBuilder allowsParallelEdges(boolean allowsParallelEdges) { + this.allowsParallelEdges = allowsParallelEdges; + return this; + } + + /** + * Specifies whether the network will allow self-loops (edges that connect a node to itself). + * Attempting to add a self-loop to a network that does not allow them will throw an {@link + * UnsupportedOperationException}. + * + *

The default value is {@code false}. + */ + public NetworkBuilder allowsSelfLoops(boolean allowsSelfLoops) { + this.allowsSelfLoops = allowsSelfLoops; + return this; + } + + /** + * Specifies the expected number of nodes in the network. + * + * @throws IllegalArgumentException if {@code expectedNodeCount} is negative + */ + public NetworkBuilder expectedNodeCount(int expectedNodeCount) { + this.expectedNodeCount = Optional.of(checkNonNegative(expectedNodeCount)); + return this; + } + + /** + * Specifies the expected number of edges in the network. + * + * @throws IllegalArgumentException if {@code expectedEdgeCount} is negative + */ + public NetworkBuilder expectedEdgeCount(int expectedEdgeCount) { + this.expectedEdgeCount = Optional.of(checkNonNegative(expectedEdgeCount)); + return this; + } + + /** + * Specifies the order of iteration for the elements of {@link Network#nodes()}. + * + *

The default value is {@link ElementOrder#insertion() insertion order}. + */ + public NetworkBuilder nodeOrder(ElementOrder nodeOrder) { + NetworkBuilder newBuilder = cast(); + newBuilder.nodeOrder = checkNotNull(nodeOrder); + return newBuilder; + } + + /** + * Specifies the order of iteration for the elements of {@link Network#edges()}. + * + *

The default value is {@link ElementOrder#insertion() insertion order}. + */ + public NetworkBuilder edgeOrder(ElementOrder edgeOrder) { + NetworkBuilder newBuilder = cast(); + newBuilder.edgeOrder = checkNotNull(edgeOrder); + return newBuilder; + } + + /** Returns an empty {@link MutableNetwork} with the properties of this {@link NetworkBuilder}. */ + public MutableNetwork build() { + return new ConfigurableMutableNetwork<>(this); + } + + @SuppressWarnings("unchecked") + private NetworkBuilder cast() { + return (NetworkBuilder) this; + } +} diff --git a/src/main/java/com/google/common/graph/NetworkConnections.java b/src/main/java/com/google/common/graph/NetworkConnections.java new file mode 100644 index 0000000..45a2793 --- /dev/null +++ b/src/main/java/com/google/common/graph/NetworkConnections.java @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2016 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.graph; + + +import java.util.Set; + +/** + * An interface for representing and manipulating an origin node's adjacent nodes and incident edges + * in a {@link Network}. + * + * @author James Sexton + * @param Node parameter type + * @param Edge parameter type + */ +interface NetworkConnections { + + Set adjacentNodes(); + + Set predecessors(); + + Set successors(); + + Set incidentEdges(); + + Set inEdges(); + + Set outEdges(); + + /** + * Returns the set of edges connecting the origin node to {@code node}. For networks without + * parallel edges, this set cannot be of size greater than one. + */ + Set edgesConnecting(N node); + + /** + * Returns the node that is adjacent to the origin node along {@code edge}. + * + *

In the directed case, {@code edge} is assumed to be an outgoing edge. + */ + N adjacentNode(E edge); + + /** + * Remove {@code edge} from the set of incoming edges. Returns the former predecessor node. + * + *

In the undirected case, returns {@code null} if {@code isSelfLoop} is true. + */ + + N removeInEdge(E edge, boolean isSelfLoop); + + /** Remove {@code edge} from the set of outgoing edges. Returns the former successor node. */ + + N removeOutEdge(E edge); + + /** + * Add {@code edge} to the set of incoming edges. Implicitly adds {@code node} as a predecessor. + */ + void addInEdge(E edge, N node, boolean isSelfLoop); + + /** Add {@code edge} to the set of outgoing edges. Implicitly adds {@code node} as a successor. */ + void addOutEdge(E edge, N node); +} diff --git a/src/main/java/com/google/common/graph/PredecessorsFunction.java b/src/main/java/com/google/common/graph/PredecessorsFunction.java new file mode 100644 index 0000000..b8f7ea5 --- /dev/null +++ b/src/main/java/com/google/common/graph/PredecessorsFunction.java @@ -0,0 +1,103 @@ +/* + * Copyright (C) 2014 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.graph; + +import com.google.common.annotations.Beta; + +/** + * A functional interface for graph-structured data. + * + *

This interface is meant to be used as the type of a parameter to graph algorithms (such as + * topological sort) that only need a way of accessing the predecessors of a node in a graph. + * + *

Usage

+ * + * Given an algorithm, for example: + * + *
{@code
+ * public  someGraphAlgorithm(N startNode, PredecessorsFunction predecessorsFunction);
+ * }
+ * + * you will invoke it depending on the graph representation you're using. + * + *

If you have an instance of one of the primary {@code common.graph} types ({@link Graph}, + * {@link ValueGraph}, and {@link Network}): + * + *

{@code
+ * someGraphAlgorithm(startNode, graph);
+ * }
+ * + * This works because those types each implement {@code PredecessorsFunction}. It will also work + * with any other implementation of this interface. + * + *

If you have your own graph implementation based around a custom node type {@code MyNode}, + * which has a method {@code getParents()} that retrieves its predecessors in a graph: + * + *

{@code
+ * someGraphAlgorithm(startNode, MyNode::getParents);
+ * }
+ * + *

If you have some other mechanism for returning the predecessors of a node, or one that doesn't + * return a {@code Iterable}, then you can use a lambda to perform a more general + * transformation: + * + *

{@code
+ * someGraphAlgorithm(startNode, node -> ImmutableList.of(node.mother(), node.father()));
+ * }
+ * + *

Graph algorithms that need additional capabilities (accessing both predecessors and + * successors, iterating over the edges, etc.) should declare their input to be of a type that + * provides those capabilities, such as {@link Graph}, {@link ValueGraph}, or {@link Network}. + * + *

Additional documentation

+ * + *

See the Guava User Guide for the {@code common.graph} package ("Graphs Explained") for + * additional documentation, including notes for + * implementors + * + * @author Joshua O'Madadhain + * @author Jens Nyman + * @param Node parameter type + * @since 23.0 + */ +@Beta +public interface PredecessorsFunction { + + /** + * Returns all nodes in this graph adjacent to {@code node} which can be reached by traversing + * {@code node}'s incoming edges against the direction (if any) of the edge. + * + *

Some algorithms that operate on a {@code PredecessorsFunction} may produce undesired results + * if the returned {@link Iterable} contains duplicate elements. Implementations of such + * algorithms should document their behavior in the presence of duplicates. + * + *

The elements of the returned {@code Iterable} must each be: + * + *

    + *
  • Non-null + *
  • Usable as {@code Map} keys (see the Guava User Guide's section on + * graph elements for details) + *
+ * + * @throws IllegalArgumentException if {@code node} is not an element of this graph + */ + Iterable predecessors(N node); +} diff --git a/src/main/java/com/google/common/graph/SuccessorsFunction.java b/src/main/java/com/google/common/graph/SuccessorsFunction.java new file mode 100644 index 0000000..ed60a5d --- /dev/null +++ b/src/main/java/com/google/common/graph/SuccessorsFunction.java @@ -0,0 +1,106 @@ +/* + * Copyright (C) 2014 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.graph; + +import com.google.common.annotations.Beta; + +/** + * A functional interface for graph-structured data. + * + *

This interface is meant to be used as the type of a parameter to graph algorithms (such as + * breadth first traversal) that only need a way of accessing the successors of a node in a graph. + * + *

Usage

+ * + * Given an algorithm, for example: + * + *
{@code
+ * public  someGraphAlgorithm(N startNode, SuccessorsFunction successorsFunction);
+ * }
+ * + * you will invoke it depending on the graph representation you're using. + * + *

If you have an instance of one of the primary {@code common.graph} types ({@link Graph}, + * {@link ValueGraph}, and {@link Network}): + * + *

{@code
+ * someGraphAlgorithm(startNode, graph);
+ * }
+ * + * This works because those types each implement {@code SuccessorsFunction}. It will also work with + * any other implementation of this interface. + * + *

If you have your own graph implementation based around a custom node type {@code MyNode}, + * which has a method {@code getChildren()} that retrieves its successors in a graph: + * + *

{@code
+ * someGraphAlgorithm(startNode, MyNode::getChildren);
+ * }
+ * + *

If you have some other mechanism for returning the successors of a node, or one that doesn't + * return an {@code Iterable}, then you can use a lambda to perform a more general + * transformation: + * + *

{@code
+ * someGraphAlgorithm(startNode, node -> ImmutableList.of(node.leftChild(), node.rightChild()));
+ * }
+ * + *

Graph algorithms that need additional capabilities (accessing both predecessors and + * successors, iterating over the edges, etc.) should declare their input to be of a type that + * provides those capabilities, such as {@link Graph}, {@link ValueGraph}, or {@link Network}. + * + *

Additional documentation

+ * + *

See the Guava User Guide for the {@code common.graph} package ("Graphs Explained") for + * additional documentation, including notes for + * implementors + * + * @author Joshua O'Madadhain + * @author Jens Nyman + * @param Node parameter type + * @since 23.0 + */ +@Beta +public interface SuccessorsFunction { + + /** + * Returns all nodes in this graph adjacent to {@code node} which can be reached by traversing + * {@code node}'s outgoing edges in the direction (if any) of the edge. + * + *

This is not the same as "all nodes reachable from {@code node} by following outgoing + * edges". For that functionality, see {@link Graphs#reachableNodes(Graph, Object)}. + * + *

Some algorithms that operate on a {@code SuccessorsFunction} may produce undesired results + * if the returned {@link Iterable} contains duplicate elements. Implementations of such + * algorithms should document their behavior in the presence of duplicates. + * + *

The elements of the returned {@code Iterable} must each be: + * + *

    + *
  • Non-null + *
  • Usable as {@code Map} keys (see the Guava User Guide's section on + * graph elements for details) + *
+ * + * @throws IllegalArgumentException if {@code node} is not an element of this graph + */ + Iterable successors(N node); +} diff --git a/src/main/java/com/google/common/graph/Traverser.java b/src/main/java/com/google/common/graph/Traverser.java new file mode 100644 index 0000000..4a341fd --- /dev/null +++ b/src/main/java/com/google/common/graph/Traverser.java @@ -0,0 +1,661 @@ +/* + * Copyright (C) 2017 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.graph; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.annotations.Beta; +import com.google.common.collect.AbstractIterator; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Iterables; +import com.google.common.collect.UnmodifiableIterator; +import java.util.ArrayDeque; +import java.util.Deque; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Queue; +import java.util.Set; + + +/** + * An object that can traverse the nodes that are reachable from a specified (set of) start node(s) + * using a specified {@link SuccessorsFunction}. + * + *

There are two entry points for creating a {@code Traverser}: {@link + * #forTree(SuccessorsFunction)} and {@link #forGraph(SuccessorsFunction)}. You should choose one + * based on your answers to the following questions: + * + *

    + *
  1. Is there only one path to any node that's reachable from any start node? (If so, the graph + * to be traversed is a tree or forest even if it is a subgraph of a graph which is neither.) + *
  2. Are the node objects' implementations of {@code equals()}/{@code hashCode()} recursive? + *
+ * + *

If your answers are: + * + *

    + *
  • (1) "no" and (2) "no", use {@link #forGraph(SuccessorsFunction)}. + *
  • (1) "yes" and (2) "yes", use {@link #forTree(SuccessorsFunction)}. + *
  • (1) "yes" and (2) "no", you can use either, but {@code forTree()} will be more efficient. + *
  • (1) "no" and (2) "yes", neither will work, but if you transform your node + * objects into a non-recursive form, you can use {@code forGraph()}. + *
+ * + * @author Jens Nyman + * @param Node parameter type + * @since 23.1 + */ +@Beta +public abstract class Traverser { + + /** + * Creates a new traverser for the given general {@code graph}. + * + *

Traversers created using this method are guaranteed to visit each node reachable from the + * start node(s) at most once. + * + *

If you know that no node in {@code graph} is reachable by more than one path from the start + * node(s), consider using {@link #forTree(SuccessorsFunction)} instead. + * + *

Performance notes + * + *

    + *
  • Traversals require O(n) time (where n is the number of nodes reachable from + * the start node), assuming that the node objects have O(1) {@code equals()} and + * {@code hashCode()} implementations. (See the + * notes on element objects for more information.) + *
  • While traversing, the traverser will use O(n) space (where n is the number + * of nodes that have thus far been visited), plus O(H) space (where H is the + * number of nodes that have been seen but not yet visited, that is, the "horizon"). + *
+ * + * @param graph {@link SuccessorsFunction} representing a general graph that may have cycles. + */ + public static Traverser forGraph(SuccessorsFunction graph) { + checkNotNull(graph); + return new GraphTraverser<>(graph); + } + + /** + * Creates a new traverser for a directed acyclic graph that has at most one path from the start + * node(s) to any node reachable from the start node(s), and has no paths from any start node to + * any other start node, such as a tree or forest. + * + *

{@code forTree()} is especially useful (versus {@code forGraph()}) in cases where the data + * structure being traversed is, in addition to being a tree/forest, also defined recursively. + * This is because the {@code forTree()}-based implementations don't keep track of visited nodes, + * and therefore don't need to call `equals()` or `hashCode()` on the node objects; this saves + * both time and space versus traversing the same graph using {@code forGraph()}. + * + *

Providing a graph to be traversed for which there is more than one path from the start + * node(s) to any node may lead to: + * + *

    + *
  • Traversal not terminating (if the graph has cycles) + *
  • Nodes being visited multiple times (if multiple paths exist from any start node to any + * node reachable from any start node) + *
+ * + *

Performance notes + * + *

    + *
  • Traversals require O(n) time (where n is the number of nodes reachable from + * the start node). + *
  • While traversing, the traverser will use O(H) space (where H is the number + * of nodes that have been seen but not yet visited, that is, the "horizon"). + *
+ * + *

Examples (all edges are directed facing downwards) + * + *

The graph below would be valid input with start nodes of {@code a, f, c}. However, if {@code + * b} were also a start node, then there would be multiple paths to reach {@code e} and + * {@code h}. + * + *

{@code
+   *    a     b      c
+   *   / \   / \     |
+   *  /   \ /   \    |
+   * d     e     f   g
+   *       |
+   *       |
+   *       h
+   * }
+ * + *

. + * + *

The graph below would be a valid input with start nodes of {@code a, f}. However, if {@code + * b} were a start node, there would be multiple paths to {@code f}. + * + *

{@code
+   *    a     b
+   *   / \   / \
+   *  /   \ /   \
+   * c     d     e
+   *        \   /
+   *         \ /
+   *          f
+   * }
+ * + *

Note on binary trees + * + *

This method can be used to traverse over a binary tree. Given methods {@code + * leftChild(node)} and {@code rightChild(node)}, this method can be called as + * + *

{@code
+   * Traverser.forTree(node -> ImmutableList.of(leftChild(node), rightChild(node)));
+   * }
+ * + * @param tree {@link SuccessorsFunction} representing a directed acyclic graph that has at most + * one path between any two nodes + */ + public static Traverser forTree(SuccessorsFunction tree) { + checkNotNull(tree); + if (tree instanceof BaseGraph) { + checkArgument(((BaseGraph) tree).isDirected(), "Undirected graphs can never be trees."); + } + if (tree instanceof Network) { + checkArgument(((Network) tree).isDirected(), "Undirected networks can never be trees."); + } + return new TreeTraverser<>(tree); + } + + /** + * Returns an unmodifiable {@code Iterable} over the nodes reachable from {@code startNode}, in + * the order of a breadth-first traversal. That is, all the nodes of depth 0 are returned, then + * depth 1, then 2, and so on. + * + *

Example: The following graph with {@code startNode} {@code a} would return nodes in + * the order {@code abcdef} (assuming successors are returned in alphabetical order). + * + *

{@code
+   * b ---- a ---- d
+   * |      |
+   * |      |
+   * e ---- c ---- f
+   * }
+ * + *

The behavior of this method is undefined if the nodes, or the topology of the graph, change + * while iteration is in progress. + * + *

The returned {@code Iterable} can be iterated over multiple times. Every iterator will + * compute its next element on the fly. It is thus possible to limit the traversal to a certain + * number of nodes as follows: + * + *

{@code
+   * Iterables.limit(Traverser.forGraph(graph).breadthFirst(node), maxNumberOfNodes);
+   * }
+ * + *

See Wikipedia for more + * info. + * + * @throws IllegalArgumentException if {@code startNode} is not an element of the graph + */ + public abstract Iterable breadthFirst(N startNode); + + /** + * Returns an unmodifiable {@code Iterable} over the nodes reachable from any of the {@code + * startNodes}, in the order of a breadth-first traversal. This is equivalent to a breadth-first + * traversal of a graph with an additional root node whose successors are the listed {@code + * startNodes}. + * + * @throws IllegalArgumentException if any of {@code startNodes} is not an element of the graph + * @see #breadthFirst(Object) + * @since 24.1 + */ + public abstract Iterable breadthFirst(Iterable startNodes); + + /** + * Returns an unmodifiable {@code Iterable} over the nodes reachable from {@code startNode}, in + * the order of a depth-first pre-order traversal. "Pre-order" implies that nodes appear in the + * {@code Iterable} in the order in which they are first visited. + * + *

Example: The following graph with {@code startNode} {@code a} would return nodes in + * the order {@code abecfd} (assuming successors are returned in alphabetical order). + * + *

{@code
+   * b ---- a ---- d
+   * |      |
+   * |      |
+   * e ---- c ---- f
+   * }
+ * + *

The behavior of this method is undefined if the nodes, or the topology of the graph, change + * while iteration is in progress. + * + *

The returned {@code Iterable} can be iterated over multiple times. Every iterator will + * compute its next element on the fly. It is thus possible to limit the traversal to a certain + * number of nodes as follows: + * + *

{@code
+   * Iterables.limit(
+   *     Traverser.forGraph(graph).depthFirstPreOrder(node), maxNumberOfNodes);
+   * }
+ * + *

See Wikipedia for more info. + * + * @throws IllegalArgumentException if {@code startNode} is not an element of the graph + */ + public abstract Iterable depthFirstPreOrder(N startNode); + + /** + * Returns an unmodifiable {@code Iterable} over the nodes reachable from any of the {@code + * startNodes}, in the order of a depth-first pre-order traversal. This is equivalent to a + * depth-first pre-order traversal of a graph with an additional root node whose successors are + * the listed {@code startNodes}. + * + * @throws IllegalArgumentException if any of {@code startNodes} is not an element of the graph + * @see #depthFirstPreOrder(Object) + * @since 24.1 + */ + public abstract Iterable depthFirstPreOrder(Iterable startNodes); + + /** + * Returns an unmodifiable {@code Iterable} over the nodes reachable from {@code startNode}, in + * the order of a depth-first post-order traversal. "Post-order" implies that nodes appear in the + * {@code Iterable} in the order in which they are visited for the last time. + * + *

Example: The following graph with {@code startNode} {@code a} would return nodes in + * the order {@code fcebda} (assuming successors are returned in alphabetical order). + * + *

{@code
+   * b ---- a ---- d
+   * |      |
+   * |      |
+   * e ---- c ---- f
+   * }
+ * + *

The behavior of this method is undefined if the nodes, or the topology of the graph, change + * while iteration is in progress. + * + *

The returned {@code Iterable} can be iterated over multiple times. Every iterator will + * compute its next element on the fly. It is thus possible to limit the traversal to a certain + * number of nodes as follows: + * + *

{@code
+   * Iterables.limit(
+   *     Traverser.forGraph(graph).depthFirstPostOrder(node), maxNumberOfNodes);
+   * }
+ * + *

See Wikipedia for more info. + * + * @throws IllegalArgumentException if {@code startNode} is not an element of the graph + */ + public abstract Iterable depthFirstPostOrder(N startNode); + + /** + * Returns an unmodifiable {@code Iterable} over the nodes reachable from any of the {@code + * startNodes}, in the order of a depth-first post-order traversal. This is equivalent to a + * depth-first post-order traversal of a graph with an additional root node whose successors are + * the listed {@code startNodes}. + * + * @throws IllegalArgumentException if any of {@code startNodes} is not an element of the graph + * @see #depthFirstPostOrder(Object) + * @since 24.1 + */ + public abstract Iterable depthFirstPostOrder(Iterable startNodes); + + // Avoid subclasses outside of this class + private Traverser() {} + + private static final class GraphTraverser extends Traverser { + private final SuccessorsFunction graph; + + GraphTraverser(SuccessorsFunction graph) { + this.graph = checkNotNull(graph); + } + + @Override + public Iterable breadthFirst(final N startNode) { + checkNotNull(startNode); + return breadthFirst(ImmutableSet.of(startNode)); + } + + @Override + public Iterable breadthFirst(final Iterable startNodes) { + checkNotNull(startNodes); + if (Iterables.isEmpty(startNodes)) { + return ImmutableSet.of(); + } + for (N startNode : startNodes) { + checkThatNodeIsInGraph(startNode); + } + return new Iterable() { + @Override + public Iterator iterator() { + return new BreadthFirstIterator(startNodes); + } + }; + } + + @Override + public Iterable depthFirstPreOrder(final N startNode) { + checkNotNull(startNode); + return depthFirstPreOrder(ImmutableSet.of(startNode)); + } + + @Override + public Iterable depthFirstPreOrder(final Iterable startNodes) { + checkNotNull(startNodes); + if (Iterables.isEmpty(startNodes)) { + return ImmutableSet.of(); + } + for (N startNode : startNodes) { + checkThatNodeIsInGraph(startNode); + } + return new Iterable() { + @Override + public Iterator iterator() { + return new DepthFirstIterator(startNodes, Order.PREORDER); + } + }; + } + + @Override + public Iterable depthFirstPostOrder(final N startNode) { + checkNotNull(startNode); + return depthFirstPostOrder(ImmutableSet.of(startNode)); + } + + @Override + public Iterable depthFirstPostOrder(final Iterable startNodes) { + checkNotNull(startNodes); + if (Iterables.isEmpty(startNodes)) { + return ImmutableSet.of(); + } + for (N startNode : startNodes) { + checkThatNodeIsInGraph(startNode); + } + return new Iterable() { + @Override + public Iterator iterator() { + return new DepthFirstIterator(startNodes, Order.POSTORDER); + } + }; + } + + @SuppressWarnings("CheckReturnValue") + private void checkThatNodeIsInGraph(N startNode) { + // successors() throws an IllegalArgumentException for nodes that are not an element of the + // graph. + graph.successors(startNode); + } + + private final class BreadthFirstIterator extends UnmodifiableIterator { + private final Queue queue = new ArrayDeque<>(); + private final Set visited = new HashSet<>(); + + BreadthFirstIterator(Iterable roots) { + for (N root : roots) { + // add all roots to the queue, skipping duplicates + if (visited.add(root)) { + queue.add(root); + } + } + } + + @Override + public boolean hasNext() { + return !queue.isEmpty(); + } + + @Override + public N next() { + N current = queue.remove(); + for (N neighbor : graph.successors(current)) { + if (visited.add(neighbor)) { + queue.add(neighbor); + } + } + return current; + } + } + + private final class DepthFirstIterator extends AbstractIterator { + private final Deque stack = new ArrayDeque<>(); + private final Set visited = new HashSet<>(); + private final Order order; + + DepthFirstIterator(Iterable roots, Order order) { + stack.push(new NodeAndSuccessors(null, roots)); + this.order = order; + } + + @Override + protected N computeNext() { + while (true) { + if (stack.isEmpty()) { + return endOfData(); + } + NodeAndSuccessors nodeAndSuccessors = stack.getFirst(); + boolean firstVisit = visited.add(nodeAndSuccessors.node); + boolean lastVisit = !nodeAndSuccessors.successorIterator.hasNext(); + boolean produceNode = + (firstVisit && order == Order.PREORDER) || (lastVisit && order == Order.POSTORDER); + if (lastVisit) { + stack.pop(); + } else { + // we need to push a neighbor, but only if we haven't already seen it + N successor = nodeAndSuccessors.successorIterator.next(); + if (!visited.contains(successor)) { + stack.push(withSuccessors(successor)); + } + } + if (produceNode && nodeAndSuccessors.node != null) { + return nodeAndSuccessors.node; + } + } + } + + NodeAndSuccessors withSuccessors(N node) { + return new NodeAndSuccessors(node, graph.successors(node)); + } + + /** A simple tuple of a node and a partially iterated {@link Iterator} of its successors. */ + private final class NodeAndSuccessors { + final N node; + final Iterator successorIterator; + + NodeAndSuccessors(N node, Iterable successors) { + this.node = node; + this.successorIterator = successors.iterator(); + } + } + } + } + + private static final class TreeTraverser extends Traverser { + private final SuccessorsFunction tree; + + TreeTraverser(SuccessorsFunction tree) { + this.tree = checkNotNull(tree); + } + + @Override + public Iterable breadthFirst(final N startNode) { + checkNotNull(startNode); + return breadthFirst(ImmutableSet.of(startNode)); + } + + @Override + public Iterable breadthFirst(final Iterable startNodes) { + checkNotNull(startNodes); + if (Iterables.isEmpty(startNodes)) { + return ImmutableSet.of(); + } + for (N startNode : startNodes) { + checkThatNodeIsInTree(startNode); + } + return new Iterable() { + @Override + public Iterator iterator() { + return new BreadthFirstIterator(startNodes); + } + }; + } + + @Override + public Iterable depthFirstPreOrder(final N startNode) { + checkNotNull(startNode); + return depthFirstPreOrder(ImmutableSet.of(startNode)); + } + + @Override + public Iterable depthFirstPreOrder(final Iterable startNodes) { + checkNotNull(startNodes); + if (Iterables.isEmpty(startNodes)) { + return ImmutableSet.of(); + } + for (N node : startNodes) { + checkThatNodeIsInTree(node); + } + return new Iterable() { + @Override + public Iterator iterator() { + return new DepthFirstPreOrderIterator(startNodes); + } + }; + } + + @Override + public Iterable depthFirstPostOrder(final N startNode) { + checkNotNull(startNode); + return depthFirstPostOrder(ImmutableSet.of(startNode)); + } + + @Override + public Iterable depthFirstPostOrder(final Iterable startNodes) { + checkNotNull(startNodes); + if (Iterables.isEmpty(startNodes)) { + return ImmutableSet.of(); + } + for (N startNode : startNodes) { + checkThatNodeIsInTree(startNode); + } + return new Iterable() { + @Override + public Iterator iterator() { + return new DepthFirstPostOrderIterator(startNodes); + } + }; + } + + @SuppressWarnings("CheckReturnValue") + private void checkThatNodeIsInTree(N startNode) { + // successors() throws an IllegalArgumentException for nodes that are not an element of the + // graph. + tree.successors(startNode); + } + + private final class BreadthFirstIterator extends UnmodifiableIterator { + private final Queue queue = new ArrayDeque<>(); + + BreadthFirstIterator(Iterable roots) { + for (N root : roots) { + queue.add(root); + } + } + + @Override + public boolean hasNext() { + return !queue.isEmpty(); + } + + @Override + public N next() { + N current = queue.remove(); + Iterables.addAll(queue, tree.successors(current)); + return current; + } + } + + private final class DepthFirstPreOrderIterator extends UnmodifiableIterator { + private final Deque> stack = new ArrayDeque<>(); + + DepthFirstPreOrderIterator(Iterable roots) { + stack.addLast(roots.iterator()); + } + + @Override + public boolean hasNext() { + return !stack.isEmpty(); + } + + @Override + public N next() { + Iterator iterator = stack.getLast(); // throws NoSuchElementException if empty + N result = checkNotNull(iterator.next()); + if (!iterator.hasNext()) { + stack.removeLast(); + } + Iterator childIterator = tree.successors(result).iterator(); + if (childIterator.hasNext()) { + stack.addLast(childIterator); + } + return result; + } + } + + private final class DepthFirstPostOrderIterator extends AbstractIterator { + private final ArrayDeque stack = new ArrayDeque<>(); + + DepthFirstPostOrderIterator(Iterable roots) { + stack.addLast(new NodeAndChildren(null, roots)); + } + + @Override + protected N computeNext() { + while (!stack.isEmpty()) { + NodeAndChildren top = stack.getLast(); + if (top.childIterator.hasNext()) { + N child = top.childIterator.next(); + stack.addLast(withChildren(child)); + } else { + stack.removeLast(); + if (top.node != null) { + return top.node; + } + } + } + return endOfData(); + } + + NodeAndChildren withChildren(N node) { + return new NodeAndChildren(node, tree.successors(node)); + } + + /** A simple tuple of a node and a partially iterated {@link Iterator} of its children. */ + private final class NodeAndChildren { + final N node; + final Iterator childIterator; + + NodeAndChildren(N node, Iterable children) { + this.node = node; + this.childIterator = children.iterator(); + } + } + } + } + + private enum Order { + PREORDER, + POSTORDER + } +} diff --git a/src/main/java/com/google/common/graph/UndirectedGraphConnections.java b/src/main/java/com/google/common/graph/UndirectedGraphConnections.java new file mode 100644 index 0000000..9636ccb --- /dev/null +++ b/src/main/java/com/google/common/graph/UndirectedGraphConnections.java @@ -0,0 +1,92 @@ +/* + * Copyright (C) 2016 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.graph; + +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.graph.GraphConstants.INNER_CAPACITY; +import static com.google.common.graph.GraphConstants.INNER_LOAD_FACTOR; + +import com.google.common.collect.ImmutableMap; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +/** + * An implementation of {@link GraphConnections} for undirected graphs. + * + * @author James Sexton + * @param Node parameter type + * @param Value parameter type + */ +final class UndirectedGraphConnections implements GraphConnections { + private final Map adjacentNodeValues; + + private UndirectedGraphConnections(Map adjacentNodeValues) { + this.adjacentNodeValues = checkNotNull(adjacentNodeValues); + } + + static UndirectedGraphConnections of() { + return new UndirectedGraphConnections<>(new HashMap(INNER_CAPACITY, INNER_LOAD_FACTOR)); + } + + static UndirectedGraphConnections ofImmutable(Map adjacentNodeValues) { + return new UndirectedGraphConnections<>(ImmutableMap.copyOf(adjacentNodeValues)); + } + + @Override + public Set adjacentNodes() { + return Collections.unmodifiableSet(adjacentNodeValues.keySet()); + } + + @Override + public Set predecessors() { + return adjacentNodes(); + } + + @Override + public Set successors() { + return adjacentNodes(); + } + + @Override + public V value(N node) { + return adjacentNodeValues.get(node); + } + + @Override + public void removePredecessor(N node) { + @SuppressWarnings("unused") + V unused = removeSuccessor(node); + } + + @Override + public V removeSuccessor(N node) { + return adjacentNodeValues.remove(node); + } + + @Override + public void addPredecessor(N node, V value) { + @SuppressWarnings("unused") + V unused = addSuccessor(node, value); + } + + @Override + public V addSuccessor(N node, V value) { + return adjacentNodeValues.put(node, value); + } +} diff --git a/src/main/java/com/google/common/graph/UndirectedMultiNetworkConnections.java b/src/main/java/com/google/common/graph/UndirectedMultiNetworkConnections.java new file mode 100644 index 0000000..c4ae3bc --- /dev/null +++ b/src/main/java/com/google/common/graph/UndirectedMultiNetworkConnections.java @@ -0,0 +1,121 @@ +/* + * Copyright (C) 2016 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.graph; + +import static com.google.common.base.Preconditions.checkState; +import static com.google.common.graph.GraphConstants.INNER_CAPACITY; +import static com.google.common.graph.GraphConstants.INNER_LOAD_FACTOR; + +import com.google.common.collect.HashMultiset; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Multiset; + +import java.lang.ref.Reference; +import java.lang.ref.SoftReference; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + + +/** + * An implementation of {@link NetworkConnections} for undirected networks with parallel edges. + * + * @author James Sexton + * @param Node parameter type + * @param Edge parameter type + */ +final class UndirectedMultiNetworkConnections + extends AbstractUndirectedNetworkConnections { + + private UndirectedMultiNetworkConnections(Map incidentEdges) { + super(incidentEdges); + } + + static UndirectedMultiNetworkConnections of() { + return new UndirectedMultiNetworkConnections<>( + new HashMap(INNER_CAPACITY, INNER_LOAD_FACTOR)); + } + + static UndirectedMultiNetworkConnections ofImmutable(Map incidentEdges) { + return new UndirectedMultiNetworkConnections<>(ImmutableMap.copyOf(incidentEdges)); + } + + private transient Reference> adjacentNodesReference; + + @Override + public Set adjacentNodes() { + return Collections.unmodifiableSet(adjacentNodesMultiset().elementSet()); + } + + private Multiset adjacentNodesMultiset() { + Multiset adjacentNodes = getReference(adjacentNodesReference); + if (adjacentNodes == null) { + adjacentNodes = HashMultiset.create(incidentEdgeMap.values()); + adjacentNodesReference = new SoftReference<>(adjacentNodes); + } + return adjacentNodes; + } + + @Override + public Set edgesConnecting(final N node) { + return new MultiEdgesConnecting(incidentEdgeMap, node) { + @Override + public int size() { + return adjacentNodesMultiset().count(node); + } + }; + } + + @Override + public N removeInEdge(E edge, boolean isSelfLoop) { + if (!isSelfLoop) { + return removeOutEdge(edge); + } + return null; + } + + @Override + public N removeOutEdge(E edge) { + N node = super.removeOutEdge(edge); + Multiset adjacentNodes = getReference(adjacentNodesReference); + if (adjacentNodes != null) { + checkState(adjacentNodes.remove(node)); + } + return node; + } + + @Override + public void addInEdge(E edge, N node, boolean isSelfLoop) { + if (!isSelfLoop) { + addOutEdge(edge, node); + } + } + + @Override + public void addOutEdge(E edge, N node) { + super.addOutEdge(edge, node); + Multiset adjacentNodes = getReference(adjacentNodesReference); + if (adjacentNodes != null) { + checkState(adjacentNodes.add(node)); + } + } + + private static T getReference(Reference reference) { + return (reference == null) ? null : reference.get(); + } +} diff --git a/src/main/java/com/google/common/graph/UndirectedNetworkConnections.java b/src/main/java/com/google/common/graph/UndirectedNetworkConnections.java new file mode 100644 index 0000000..1e253dd --- /dev/null +++ b/src/main/java/com/google/common/graph/UndirectedNetworkConnections.java @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2016 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.graph; + +import static com.google.common.graph.GraphConstants.EXPECTED_DEGREE; + +import com.google.common.collect.BiMap; +import com.google.common.collect.HashBiMap; +import com.google.common.collect.ImmutableBiMap; +import java.util.Collections; +import java.util.Map; +import java.util.Set; + +/** + * An implementation of {@link NetworkConnections} for undirected networks. + * + * @author James Sexton + * @param Node parameter type + * @param Edge parameter type + */ +final class UndirectedNetworkConnections extends AbstractUndirectedNetworkConnections { + + protected UndirectedNetworkConnections(Map incidentEdgeMap) { + super(incidentEdgeMap); + } + + static UndirectedNetworkConnections of() { + return new UndirectedNetworkConnections<>(HashBiMap.create(EXPECTED_DEGREE)); + } + + static UndirectedNetworkConnections ofImmutable(Map incidentEdges) { + return new UndirectedNetworkConnections<>(ImmutableBiMap.copyOf(incidentEdges)); + } + + @Override + public Set adjacentNodes() { + return Collections.unmodifiableSet(((BiMap) incidentEdgeMap).values()); + } + + @Override + public Set edgesConnecting(N node) { + return new EdgesConnecting(((BiMap) incidentEdgeMap).inverse(), node); + } +} diff --git a/src/main/java/com/google/common/graph/ValueGraph.java b/src/main/java/com/google/common/graph/ValueGraph.java new file mode 100644 index 0000000..f8f59cf --- /dev/null +++ b/src/main/java/com/google/common/graph/ValueGraph.java @@ -0,0 +1,354 @@ +/* + * Copyright (C) 2016 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.graph; + +import com.google.common.annotations.Beta; +import java.util.Collection; +import java.util.Optional; +import java.util.Set; + + +/** + * An interface for graph-structured data, + * whose edges have associated non-unique values. + * + *

A graph is composed of a set of nodes and a set of edges connecting pairs of nodes. + * + *

There are three primary interfaces provided to represent graphs. In order of increasing + * complexity they are: {@link Graph}, {@link ValueGraph}, and {@link Network}. You should generally + * prefer the simplest interface that satisfies your use case. See the + * "Choosing the right graph type" section of the Guava User Guide for more details. + * + *

Capabilities

+ * + *

{@code ValueGraph} supports the following use cases (definitions of + * terms): + * + *

    + *
  • directed graphs + *
  • undirected graphs + *
  • graphs that do/don't allow self-loops + *
  • graphs whose nodes/edges are insertion-ordered, sorted, or unordered + *
  • graphs whose edges have associated values + *
+ * + *

{@code ValueGraph}, as a subtype of {@code Graph}, explicitly does not support parallel edges, + * and forbids implementations or extensions with parallel edges. If you need parallel edges, use + * {@link Network}. (You can use a positive {@code Integer} edge value as a loose representation of + * edge multiplicity, but the {@code *degree()} and mutation methods will not reflect your + * interpretation of the edge value as its multiplicity.) + * + *

Building a {@code ValueGraph}

+ * + *

The implementation classes that {@code common.graph} provides are not public, by design. To + * create an instance of one of the built-in implementations of {@code ValueGraph}, use the {@link + * ValueGraphBuilder} class: + * + *

{@code
+ * MutableValueGraph graph = ValueGraphBuilder.directed().build();
+ * }
+ * + *

{@link ValueGraphBuilder#build()} returns an instance of {@link MutableValueGraph}, which is a + * subtype of {@code ValueGraph} that provides methods for adding and removing nodes and edges. If + * you do not need to mutate a graph (e.g. if you write a method than runs a read-only algorithm on + * the graph), you should use the non-mutating {@link ValueGraph} interface, or an {@link + * ImmutableValueGraph}. + * + *

You can create an immutable copy of an existing {@code ValueGraph} using {@link + * ImmutableValueGraph#copyOf(ValueGraph)}: + * + *

{@code
+ * ImmutableValueGraph immutableGraph = ImmutableValueGraph.copyOf(graph);
+ * }
+ * + *

Instances of {@link ImmutableValueGraph} do not implement {@link MutableValueGraph} + * (obviously!) and are contractually guaranteed to be unmodifiable and thread-safe. + * + *

The Guava User Guide has more + * information on (and examples of) building graphs. + * + *

Additional documentation

+ * + *

See the Guava User Guide for the {@code common.graph} package ("Graphs Explained") for + * additional documentation, including: + * + *

+ * + * @author James Sexton + * @author Joshua O'Madadhain + * @param Node parameter type + * @param Value parameter type + * @since 20.0 + */ +@Beta +public interface ValueGraph extends BaseGraph { + // + // ValueGraph-level accessors + // + + /** Returns all nodes in this graph, in the order specified by {@link #nodeOrder()}. */ + @Override + Set nodes(); + + /** Returns all edges in this graph. */ + @Override + Set> edges(); + + /** + * Returns a live view of this graph as a {@link Graph}. The resulting {@link Graph} will have an + * edge connecting node A to node B if this {@link ValueGraph} has an edge connecting A to B. + */ + Graph asGraph(); + + // + // ValueGraph properties + // + + /** + * Returns true if the edges in this graph are directed. Directed edges connect a {@link + * EndpointPair#source() source node} to a {@link EndpointPair#target() target node}, while + * undirected edges connect a pair of nodes to each other. + */ + @Override + boolean isDirected(); + + /** + * Returns true if this graph allows self-loops (edges that connect a node to itself). Attempting + * to add a self-loop to a graph that does not allow them will throw an {@link + * IllegalArgumentException}. + */ + @Override + boolean allowsSelfLoops(); + + /** Returns the order of iteration for the elements of {@link #nodes()}. */ + @Override + ElementOrder nodeOrder(); + + // + // Element-level accessors + // + + /** + * Returns the nodes which have an incident edge in common with {@code node} in this graph. + * + *

This is equal to the union of {@link #predecessors(Object)} and {@link #successors(Object)}. + * + * @throws IllegalArgumentException if {@code node} is not an element of this graph + */ + @Override + Set adjacentNodes(N node); + + /** + * Returns all nodes in this graph adjacent to {@code node} which can be reached by traversing + * {@code node}'s incoming edges against the direction (if any) of the edge. + * + *

In an undirected graph, this is equivalent to {@link #adjacentNodes(Object)}. + * + * @throws IllegalArgumentException if {@code node} is not an element of this graph + */ + @Override + Set predecessors(N node); + + /** + * Returns all nodes in this graph adjacent to {@code node} which can be reached by traversing + * {@code node}'s outgoing edges in the direction (if any) of the edge. + * + *

In an undirected graph, this is equivalent to {@link #adjacentNodes(Object)}. + * + *

This is not the same as "all nodes reachable from {@code node} by following outgoing + * edges". For that functionality, see {@link Graphs#reachableNodes(Graph, Object)}. + * + * @throws IllegalArgumentException if {@code node} is not an element of this graph + */ + @Override + Set successors(N node); + + /** + * Returns the edges in this graph whose endpoints include {@code node}. + * + *

This is equal to the union of incoming and outgoing edges. + * + * @throws IllegalArgumentException if {@code node} is not an element of this graph + * @since 24.0 + */ + @Override + Set> incidentEdges(N node); + + /** + * Returns the count of {@code node}'s incident edges, counting self-loops twice (equivalently, + * the number of times an edge touches {@code node}). + * + *

For directed graphs, this is equal to {@code inDegree(node) + outDegree(node)}. + * + *

For undirected graphs, this is equal to {@code incidentEdges(node).size()} + (number of + * self-loops incident to {@code node}). + * + *

If the count is greater than {@code Integer.MAX_VALUE}, returns {@code Integer.MAX_VALUE}. + * + * @throws IllegalArgumentException if {@code node} is not an element of this graph + */ + @Override + int degree(N node); + + /** + * Returns the count of {@code node}'s incoming edges (equal to {@code predecessors(node).size()}) + * in a directed graph. In an undirected graph, returns the {@link #degree(Object)}. + * + *

If the count is greater than {@code Integer.MAX_VALUE}, returns {@code Integer.MAX_VALUE}. + * + * @throws IllegalArgumentException if {@code node} is not an element of this graph + */ + @Override + int inDegree(N node); + + /** + * Returns the count of {@code node}'s outgoing edges (equal to {@code successors(node).size()}) + * in a directed graph. In an undirected graph, returns the {@link #degree(Object)}. + * + *

If the count is greater than {@code Integer.MAX_VALUE}, returns {@code Integer.MAX_VALUE}. + * + * @throws IllegalArgumentException if {@code node} is not an element of this graph + */ + @Override + int outDegree(N node); + + /** + * Returns true if there is an edge that directly connects {@code nodeU} to {@code nodeV}. This is + * equivalent to {@code nodes().contains(nodeU) && successors(nodeU).contains(nodeV)}. + * + *

In an undirected graph, this is equal to {@code hasEdgeConnecting(nodeV, nodeU)}. + * + * @since 23.0 + */ + @Override + boolean hasEdgeConnecting(N nodeU, N nodeV); + + /** + * Returns true if there is an edge that directly connects {@code endpoints} (in the order, if + * any, specified by {@code endpoints}). This is equivalent to {@code + * edges().contains(endpoints)}. + * + *

Unlike the other {@code EndpointPair}-accepting methods, this method does not throw if the + * endpoints are unordered and the graph is directed; it simply returns {@code false}. This is for + * consistency with the behavior of {@link Collection#contains(Object)} (which does not generally + * throw if the object cannot be present in the collection), and the desire to have this method's + * behavior be compatible with {@code edges().contains(endpoints)}. + * + * @since 27.1 + */ + @Override + boolean hasEdgeConnecting(EndpointPair endpoints); + + /** + * Returns the value of the edge that connects {@code nodeU} to {@code nodeV} (in the order, if + * any, specified by {@code endpoints}), if one is present; otherwise, returns {@code + * Optional.empty()}. + * + * @throws IllegalArgumentException if {@code nodeU} or {@code nodeV} is not an element of this + * graph + * @since 23.0 (since 20.0 with return type {@code V}) + */ + Optional edgeValue(N nodeU, N nodeV); + + /** + * Returns the value of the edge that connects {@code endpoints} (in the order, if any, specified + * by {@code endpoints}), if one is present; otherwise, returns {@code Optional.empty()}. + * + *

If this graph is directed, the endpoints must be ordered. + * + * @throws IllegalArgumentException if either endpoint is not an element of this graph + * @throws IllegalArgumentException if the endpoints are unordered and the graph is directed + * @since 27.1 + */ + Optional edgeValue(EndpointPair endpoints); + + /** + * Returns the value of the edge that connects {@code nodeU} to {@code nodeV}, if one is present; + * otherwise, returns {@code defaultValue}. + * + *

In an undirected graph, this is equal to {@code edgeValueOrDefault(nodeV, nodeU, + * defaultValue)}. + * + * @throws IllegalArgumentException if {@code nodeU} or {@code nodeV} is not an element of this + * graph + */ + + V edgeValueOrDefault(N nodeU, N nodeV, V defaultValue); + + /** + * Returns the value of the edge that connects {@code endpoints} (in the order, if any, specified + * by {@code endpoints}), if one is present; otherwise, returns {@code defaultValue}. + * + *

If this graph is directed, the endpoints must be ordered. + * + * @throws IllegalArgumentException if either endpoint is not an element of this graph + * @throws IllegalArgumentException if the endpoints are unordered and the graph is directed + * @since 27.1 + */ + + V edgeValueOrDefault(EndpointPair endpoints, V defaultValue); + + // + // ValueGraph identity + // + + /** + * Returns {@code true} iff {@code object} is a {@link ValueGraph} that has the same elements and + * the same structural relationships as those in this graph. + * + *

Thus, two value graphs A and B are equal if all of the following are true: + * + *

    + *
  • A and B have equal {@link #isDirected() directedness}. + *
  • A and B have equal {@link #nodes() node sets}. + *
  • A and B have equal {@link #edges() edge sets}. + *
  • The {@link #edgeValue(Object, Object) value} of a given edge is the same in both A and B. + *
+ * + *

Graph properties besides {@link #isDirected() directedness} do not affect equality. + * For example, two graphs may be considered equal even if one allows self-loops and the other + * doesn't. Additionally, the order in which nodes or edges are added to the graph, and the order + * in which they are iterated over, are irrelevant. + * + *

A reference implementation of this is provided by {@link AbstractValueGraph#equals(Object)}. + */ + @Override + boolean equals(Object object); + + /** + * Returns the hash code for this graph. The hash code of a graph is defined as the hash code of a + * map from each of its {@link #edges() edges} to the associated {@link #edgeValue(Object, Object) + * edge value}. + * + *

A reference implementation of this is provided by {@link AbstractValueGraph#hashCode()}. + */ + @Override + int hashCode(); +} diff --git a/src/main/java/com/google/common/graph/ValueGraphBuilder.java b/src/main/java/com/google/common/graph/ValueGraphBuilder.java new file mode 100644 index 0000000..1ef7647 --- /dev/null +++ b/src/main/java/com/google/common/graph/ValueGraphBuilder.java @@ -0,0 +1,157 @@ +/* + * Copyright (C) 2016 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.graph; + +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.graph.Graphs.checkNonNegative; + +import com.google.common.annotations.Beta; +import com.google.common.base.Optional; + +/** + * A builder for constructing instances of {@link MutableValueGraph} or {@link ImmutableValueGraph} + * with user-defined properties. + * + *

A graph built by this class will have the following properties by default: + * + *

    + *
  • does not allow self-loops + *
  • orders {@link Graph#nodes()} in the order in which the elements were added + *
+ * + *

Examples of use: + * + *

{@code
+ * // Building a mutable value graph
+ * MutableValueGraph graph =
+ *     ValueGraphBuilder.undirected().allowsSelfLoops(true).build();
+ * graph.putEdgeValue("San Francisco", "San Francisco", 0.0);
+ * graph.putEdgeValue("San Jose", "San Jose", 0.0);
+ * graph.putEdgeValue("San Francisco", "San Jose", 48.4);
+ *
+ * // Building an immutable value graph
+ * ImmutableValueGraph immutableGraph =
+ *     ValueGraphBuilder.undirected()
+ *         .allowsSelfLoops(true)
+ *         .immutable()
+ *         .putEdgeValue("San Francisco", "San Francisco", 0.0)
+ *         .putEdgeValue("San Jose", "San Jose", 0.0)
+ *         .putEdgeValue("San Francisco", "San Jose", 48.4)
+ *         .build();
+ * }
+ * + * @author James Sexton + * @author Joshua O'Madadhain + * @param The most general node type this builder will support. This is normally {@code Object} + * unless it is constrained by using a method like {@link #nodeOrder}, or the builder is + * constructed based on an existing {@code ValueGraph} using {@link #from(ValueGraph)}. + * @param The most general value type this builder will support. This is normally {@code Object} + * unless the builder is constructed based on an existing {@code Graph} using {@link + * #from(ValueGraph)}. + * @since 20.0 + */ +@Beta +public final class ValueGraphBuilder extends AbstractGraphBuilder { + + /** Creates a new instance with the specified edge directionality. */ + private ValueGraphBuilder(boolean directed) { + super(directed); + } + + /** Returns a {@link ValueGraphBuilder} for building directed graphs. */ + public static ValueGraphBuilder directed() { + return new ValueGraphBuilder<>(true); + } + + /** Returns a {@link ValueGraphBuilder} for building undirected graphs. */ + public static ValueGraphBuilder undirected() { + return new ValueGraphBuilder<>(false); + } + + /** + * Returns a {@link ValueGraphBuilder} initialized with all properties queryable from {@code + * graph}. + * + *

The "queryable" properties are those that are exposed through the {@link ValueGraph} + * interface, such as {@link ValueGraph#isDirected()}. Other properties, such as {@link + * #expectedNodeCount(int)}, are not set in the new builder. + */ + public static ValueGraphBuilder from(ValueGraph graph) { + return new ValueGraphBuilder(graph.isDirected()) + .allowsSelfLoops(graph.allowsSelfLoops()) + .nodeOrder(graph.nodeOrder()); + } + + /** + * Returns an {@link ImmutableValueGraph.Builder} with the properties of this {@link + * ValueGraphBuilder}. + * + *

The returned builder can be used for populating an {@link ImmutableValueGraph}. + * + * @since 28.0 + */ + public ImmutableValueGraph.Builder immutable() { + ValueGraphBuilder castBuilder = cast(); + return new ImmutableValueGraph.Builder<>(castBuilder); + } + + /** + * Specifies whether the graph will allow self-loops (edges that connect a node to itself). + * Attempting to add a self-loop to a graph that does not allow them will throw an {@link + * UnsupportedOperationException}. + * + *

The default value is {@code false}. + */ + public ValueGraphBuilder allowsSelfLoops(boolean allowsSelfLoops) { + this.allowsSelfLoops = allowsSelfLoops; + return this; + } + + /** + * Specifies the expected number of nodes in the graph. + * + * @throws IllegalArgumentException if {@code expectedNodeCount} is negative + */ + public ValueGraphBuilder expectedNodeCount(int expectedNodeCount) { + this.expectedNodeCount = Optional.of(checkNonNegative(expectedNodeCount)); + return this; + } + + /** + * Specifies the order of iteration for the elements of {@link Graph#nodes()}. + * + *

The default value is {@link ElementOrder#insertion() insertion order}. + */ + public ValueGraphBuilder nodeOrder(ElementOrder nodeOrder) { + ValueGraphBuilder newBuilder = cast(); + newBuilder.nodeOrder = checkNotNull(nodeOrder); + return newBuilder; + } + + /** + * Returns an empty {@link MutableValueGraph} with the properties of this {@link + * ValueGraphBuilder}. + */ + public MutableValueGraph build() { + return new ConfigurableMutableValueGraph<>(this); + } + + @SuppressWarnings("unchecked") + private ValueGraphBuilder cast() { + return (ValueGraphBuilder) this; + } +} diff --git a/src/main/java/com/google/common/hash/AbstractByteHasher.java b/src/main/java/com/google/common/hash/AbstractByteHasher.java new file mode 100644 index 0000000..b36a846 --- /dev/null +++ b/src/main/java/com/google/common/hash/AbstractByteHasher.java @@ -0,0 +1,124 @@ +/* + * Copyright (C) 2012 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.hash; + +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.base.Preconditions.checkPositionIndexes; + +import com.google.common.primitives.Chars; +import com.google.common.primitives.Ints; +import com.google.common.primitives.Longs; +import com.google.common.primitives.Shorts; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + +/** + * Abstract {@link Hasher} that handles converting primitives to bytes using a scratch {@code + * ByteBuffer} and streams all bytes to a sink to compute the hash. + * + * @author Colin Decker + */ + +abstract class AbstractByteHasher extends AbstractHasher { + private final ByteBuffer scratch = ByteBuffer.allocate(8).order(ByteOrder.LITTLE_ENDIAN); + + /** Updates this hasher with the given byte. */ + protected abstract void update(byte b); + + /** Updates this hasher with the given bytes. */ + protected void update(byte[] b) { + update(b, 0, b.length); + } + + /** Updates this hasher with {@code len} bytes starting at {@code off} in the given buffer. */ + protected void update(byte[] b, int off, int len) { + for (int i = off; i < off + len; i++) { + update(b[i]); + } + } + + /** Updates this hasher with bytes from the given buffer. */ + protected void update(ByteBuffer b) { + if (b.hasArray()) { + update(b.array(), b.arrayOffset() + b.position(), b.remaining()); + b.position(b.limit()); + } else { + for (int remaining = b.remaining(); remaining > 0; remaining--) { + update(b.get()); + } + } + } + + /** Updates the sink with the given number of bytes from the buffer. */ + private Hasher update(int bytes) { + try { + update(scratch.array(), 0, bytes); + } finally { + scratch.clear(); + } + return this; + } + + @Override + public Hasher putByte(byte b) { + update(b); + return this; + } + + @Override + public Hasher putBytes(byte[] bytes) { + checkNotNull(bytes); + update(bytes); + return this; + } + + @Override + public Hasher putBytes(byte[] bytes, int off, int len) { + checkPositionIndexes(off, off + len, bytes.length); + update(bytes, off, len); + return this; + } + + @Override + public Hasher putBytes(ByteBuffer bytes) { + update(bytes); + return this; + } + + @Override + public Hasher putShort(short s) { + scratch.putShort(s); + return update(Shorts.BYTES); + } + + @Override + public Hasher putInt(int i) { + scratch.putInt(i); + return update(Ints.BYTES); + } + + @Override + public Hasher putLong(long l) { + scratch.putLong(l); + return update(Longs.BYTES); + } + + @Override + public Hasher putChar(char c) { + scratch.putChar(c); + return update(Chars.BYTES); + } +} diff --git a/src/main/java/com/google/common/hash/AbstractCompositeHashFunction.java b/src/main/java/com/google/common/hash/AbstractCompositeHashFunction.java new file mode 100644 index 0000000..d959a07 --- /dev/null +++ b/src/main/java/com/google/common/hash/AbstractCompositeHashFunction.java @@ -0,0 +1,194 @@ +/* + * Copyright (C) 2011 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.hash; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; + + +import java.nio.ByteBuffer; +import java.nio.charset.Charset; + +/** + * An abstract composition of multiple hash functions. {@linkplain #newHasher()} delegates to the + * {@code Hasher} objects of the delegate hash functions, and in the end, they are used by + * {@linkplain #makeHash(Hasher[])} that constructs the final {@code HashCode}. + * + * @author Dimitris Andreou + */ +abstract class AbstractCompositeHashFunction extends AbstractHashFunction { + + @SuppressWarnings("Immutable") // array not modified after creation + final HashFunction[] functions; + + AbstractCompositeHashFunction(HashFunction... functions) { + for (HashFunction function : functions) { + checkNotNull(function); + } + this.functions = functions; + } + + /** + * Constructs a {@code HashCode} from the {@code Hasher} objects of the functions. Each of them + * has consumed the entire input and they are ready to output a {@code HashCode}. The order of the + * hashers are the same order as the functions given to the constructor. + */ + // this could be cleaner if it passed HashCode[], but that would create yet another array... + /* protected */ abstract HashCode makeHash(Hasher[] hashers); + + @Override + public Hasher newHasher() { + Hasher[] hashers = new Hasher[functions.length]; + for (int i = 0; i < hashers.length; i++) { + hashers[i] = functions[i].newHasher(); + } + return fromHashers(hashers); + } + + @Override + public Hasher newHasher(int expectedInputSize) { + checkArgument(expectedInputSize >= 0); + Hasher[] hashers = new Hasher[functions.length]; + for (int i = 0; i < hashers.length; i++) { + hashers[i] = functions[i].newHasher(expectedInputSize); + } + return fromHashers(hashers); + } + + private Hasher fromHashers(final Hasher[] hashers) { + return new Hasher() { + @Override + public Hasher putByte(byte b) { + for (Hasher hasher : hashers) { + hasher.putByte(b); + } + return this; + } + + @Override + public Hasher putBytes(byte[] bytes) { + for (Hasher hasher : hashers) { + hasher.putBytes(bytes); + } + return this; + } + + @Override + public Hasher putBytes(byte[] bytes, int off, int len) { + for (Hasher hasher : hashers) { + hasher.putBytes(bytes, off, len); + } + return this; + } + + @Override + public Hasher putBytes(ByteBuffer bytes) { + int pos = bytes.position(); + for (Hasher hasher : hashers) { + bytes.position(pos); + hasher.putBytes(bytes); + } + return this; + } + + @Override + public Hasher putShort(short s) { + for (Hasher hasher : hashers) { + hasher.putShort(s); + } + return this; + } + + @Override + public Hasher putInt(int i) { + for (Hasher hasher : hashers) { + hasher.putInt(i); + } + return this; + } + + @Override + public Hasher putLong(long l) { + for (Hasher hasher : hashers) { + hasher.putLong(l); + } + return this; + } + + @Override + public Hasher putFloat(float f) { + for (Hasher hasher : hashers) { + hasher.putFloat(f); + } + return this; + } + + @Override + public Hasher putDouble(double d) { + for (Hasher hasher : hashers) { + hasher.putDouble(d); + } + return this; + } + + @Override + public Hasher putBoolean(boolean b) { + for (Hasher hasher : hashers) { + hasher.putBoolean(b); + } + return this; + } + + @Override + public Hasher putChar(char c) { + for (Hasher hasher : hashers) { + hasher.putChar(c); + } + return this; + } + + @Override + public Hasher putUnencodedChars(CharSequence chars) { + for (Hasher hasher : hashers) { + hasher.putUnencodedChars(chars); + } + return this; + } + + @Override + public Hasher putString(CharSequence chars, Charset charset) { + for (Hasher hasher : hashers) { + hasher.putString(chars, charset); + } + return this; + } + + @Override + public Hasher putObject(T instance, Funnel funnel) { + for (Hasher hasher : hashers) { + hasher.putObject(instance, funnel); + } + return this; + } + + @Override + public HashCode hash() { + return makeHash(hashers); + } + }; + } + + private static final long serialVersionUID = 0L; +} diff --git a/src/main/java/com/google/common/hash/AbstractHashFunction.java b/src/main/java/com/google/common/hash/AbstractHashFunction.java new file mode 100644 index 0000000..20f58a3 --- /dev/null +++ b/src/main/java/com/google/common/hash/AbstractHashFunction.java @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2017 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.hash; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkPositionIndexes; + + +import java.nio.ByteBuffer; +import java.nio.charset.Charset; + +/** + * Skeleton implementation of {@link HashFunction} in terms of {@link #newHasher()}. + * + *

TODO(lowasser): make public + */ +abstract class AbstractHashFunction implements HashFunction { + @Override + public HashCode hashObject(T instance, Funnel funnel) { + return newHasher().putObject(instance, funnel).hash(); + } + + @Override + public HashCode hashUnencodedChars(CharSequence input) { + int len = input.length(); + return newHasher(len * 2).putUnencodedChars(input).hash(); + } + + @Override + public HashCode hashString(CharSequence input, Charset charset) { + return newHasher().putString(input, charset).hash(); + } + + @Override + public HashCode hashInt(int input) { + return newHasher(4).putInt(input).hash(); + } + + @Override + public HashCode hashLong(long input) { + return newHasher(8).putLong(input).hash(); + } + + @Override + public HashCode hashBytes(byte[] input) { + return hashBytes(input, 0, input.length); + } + + @Override + public HashCode hashBytes(byte[] input, int off, int len) { + checkPositionIndexes(off, off + len, input.length); + return newHasher(len).putBytes(input, off, len).hash(); + } + + @Override + public HashCode hashBytes(ByteBuffer input) { + return newHasher(input.remaining()).putBytes(input).hash(); + } + + @Override + public Hasher newHasher(int expectedInputSize) { + checkArgument( + expectedInputSize >= 0, "expectedInputSize must be >= 0 but was %s", expectedInputSize); + return newHasher(); + } +} diff --git a/src/main/java/com/google/common/hash/AbstractHasher.java b/src/main/java/com/google/common/hash/AbstractHasher.java new file mode 100644 index 0000000..2dfc860 --- /dev/null +++ b/src/main/java/com/google/common/hash/AbstractHasher.java @@ -0,0 +1,121 @@ +/* + * Copyright (C) 2011 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.hash; + +import com.google.common.base.Preconditions; + +import java.nio.ByteBuffer; +import java.nio.charset.Charset; + +/** + * An abstract implementation of {@link Hasher}, which only requires subtypes to implement {@link + * #putByte}. Subtypes may provide more efficient implementations, however. + * + * @author Dimitris Andreou + */ + +abstract class AbstractHasher implements Hasher { + @Override + public final Hasher putBoolean(boolean b) { + return putByte(b ? (byte) 1 : (byte) 0); + } + + @Override + public final Hasher putDouble(double d) { + return putLong(Double.doubleToRawLongBits(d)); + } + + @Override + public final Hasher putFloat(float f) { + return putInt(Float.floatToRawIntBits(f)); + } + + @Override + public Hasher putUnencodedChars(CharSequence charSequence) { + for (int i = 0, len = charSequence.length(); i < len; i++) { + putChar(charSequence.charAt(i)); + } + return this; + } + + @Override + public Hasher putString(CharSequence charSequence, Charset charset) { + return putBytes(charSequence.toString().getBytes(charset)); + } + + @Override + public Hasher putBytes(byte[] bytes) { + return putBytes(bytes, 0, bytes.length); + } + + @Override + public Hasher putBytes(byte[] bytes, int off, int len) { + Preconditions.checkPositionIndexes(off, off + len, bytes.length); + for (int i = 0; i < len; i++) { + putByte(bytes[off + i]); + } + return this; + } + + @Override + public Hasher putBytes(ByteBuffer b) { + if (b.hasArray()) { + putBytes(b.array(), b.arrayOffset() + b.position(), b.remaining()); + b.position(b.limit()); + } else { + for (int remaining = b.remaining(); remaining > 0; remaining--) { + putByte(b.get()); + } + } + return this; + } + + @Override + public Hasher putShort(short s) { + putByte((byte) s); + putByte((byte) (s >>> 8)); + return this; + } + + @Override + public Hasher putInt(int i) { + putByte((byte) i); + putByte((byte) (i >>> 8)); + putByte((byte) (i >>> 16)); + putByte((byte) (i >>> 24)); + return this; + } + + @Override + public Hasher putLong(long l) { + for (int i = 0; i < 64; i += 8) { + putByte((byte) (l >>> i)); + } + return this; + } + + @Override + public Hasher putChar(char c) { + putByte((byte) c); + putByte((byte) (c >>> 8)); + return this; + } + + @Override + public Hasher putObject(T instance, Funnel funnel) { + funnel.funnel(instance, this); + return this; + } +} diff --git a/src/main/java/com/google/common/hash/AbstractNonStreamingHashFunction.java b/src/main/java/com/google/common/hash/AbstractNonStreamingHashFunction.java new file mode 100644 index 0000000..7543adf --- /dev/null +++ b/src/main/java/com/google/common/hash/AbstractNonStreamingHashFunction.java @@ -0,0 +1,132 @@ +/* + * Copyright (C) 2011 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.hash; + +import com.google.common.base.Preconditions; + +import java.io.ByteArrayOutputStream; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.charset.Charset; +import java.util.Arrays; + +/** + * Skeleton implementation of {@link HashFunction}, appropriate for non-streaming algorithms. All + * the hash computation done using {@linkplain #newHasher()} are delegated to the {@linkplain + * #hashBytes(byte[], int, int)} method. + * + * @author Dimitris Andreou + */ +abstract class AbstractNonStreamingHashFunction extends AbstractHashFunction { + @Override + public Hasher newHasher() { + return newHasher(32); + } + + @Override + public Hasher newHasher(int expectedInputSize) { + Preconditions.checkArgument(expectedInputSize >= 0); + return new BufferingHasher(expectedInputSize); + } + + @Override + public HashCode hashInt(int input) { + return hashBytes(ByteBuffer.allocate(4).order(ByteOrder.LITTLE_ENDIAN).putInt(input).array()); + } + + @Override + public HashCode hashLong(long input) { + return hashBytes(ByteBuffer.allocate(8).order(ByteOrder.LITTLE_ENDIAN).putLong(input).array()); + } + + @Override + public HashCode hashUnencodedChars(CharSequence input) { + int len = input.length(); + ByteBuffer buffer = ByteBuffer.allocate(len * 2).order(ByteOrder.LITTLE_ENDIAN); + for (int i = 0; i < len; i++) { + buffer.putChar(input.charAt(i)); + } + return hashBytes(buffer.array()); + } + + @Override + public HashCode hashString(CharSequence input, Charset charset) { + return hashBytes(input.toString().getBytes(charset)); + } + + @Override + public abstract HashCode hashBytes(byte[] input, int off, int len); + + @Override + public HashCode hashBytes(ByteBuffer input) { + return newHasher(input.remaining()).putBytes(input).hash(); + } + + /** In-memory stream-based implementation of Hasher. */ + private final class BufferingHasher extends AbstractHasher { + final ExposedByteArrayOutputStream stream; + + BufferingHasher(int expectedInputSize) { + this.stream = new ExposedByteArrayOutputStream(expectedInputSize); + } + + @Override + public Hasher putByte(byte b) { + stream.write(b); + return this; + } + + @Override + public Hasher putBytes(byte[] bytes, int off, int len) { + stream.write(bytes, off, len); + return this; + } + + @Override + public Hasher putBytes(ByteBuffer bytes) { + stream.write(bytes); + return this; + } + + @Override + public HashCode hash() { + return hashBytes(stream.byteArray(), 0, stream.length()); + } + } + + // Just to access the byte[] without introducing an unnecessary copy + private static final class ExposedByteArrayOutputStream extends ByteArrayOutputStream { + ExposedByteArrayOutputStream(int expectedInputSize) { + super(expectedInputSize); + } + + void write(ByteBuffer input) { + int remaining = input.remaining(); + if (count + remaining > buf.length) { + buf = Arrays.copyOf(buf, count + remaining); + } + input.get(buf, count, remaining); + count += remaining; + } + + byte[] byteArray() { + return buf; + } + + int length() { + return count; + } + } +} diff --git a/src/main/java/com/google/common/hash/AbstractStreamingHasher.java b/src/main/java/com/google/common/hash/AbstractStreamingHasher.java new file mode 100644 index 0000000..4756be7 --- /dev/null +++ b/src/main/java/com/google/common/hash/AbstractStreamingHasher.java @@ -0,0 +1,214 @@ +/* + * Copyright (C) 2011 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.hash; + +import static com.google.common.base.Preconditions.checkArgument; + + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + +/** + * A convenience base class for implementors of {@code Hasher}; handles accumulating data until an + * entire "chunk" (of implementation-dependent length) is ready to be hashed. + * + * @author Kevin Bourrillion + * @author Dimitris Andreou + */ +// TODO(kevinb): this class still needs some design-and-document-for-inheritance love + +abstract class AbstractStreamingHasher extends AbstractHasher { + /** Buffer via which we pass data to the hash algorithm (the implementor) */ + private final ByteBuffer buffer; + + /** Number of bytes to be filled before process() invocation(s). */ + private final int bufferSize; + + /** Number of bytes processed per process() invocation. */ + private final int chunkSize; + + /** + * Constructor for use by subclasses. This hasher instance will process chunks of the specified + * size. + * + * @param chunkSize the number of bytes available per {@link #process(ByteBuffer)} invocation; + * must be at least 4 + */ + protected AbstractStreamingHasher(int chunkSize) { + this(chunkSize, chunkSize); + } + + /** + * Constructor for use by subclasses. This hasher instance will process chunks of the specified + * size, using an internal buffer of {@code bufferSize} size, which must be a multiple of {@code + * chunkSize}. + * + * @param chunkSize the number of bytes available per {@link #process(ByteBuffer)} invocation; + * must be at least 4 + * @param bufferSize the size of the internal buffer. Must be a multiple of chunkSize + */ + protected AbstractStreamingHasher(int chunkSize, int bufferSize) { + // TODO(kevinb): check more preconditions (as bufferSize >= chunkSize) if this is ever public + checkArgument(bufferSize % chunkSize == 0); + + // TODO(user): benchmark performance difference with longer buffer + // always space for a single primitive + this.buffer = ByteBuffer.allocate(bufferSize + 7).order(ByteOrder.LITTLE_ENDIAN); + this.bufferSize = bufferSize; + this.chunkSize = chunkSize; + } + + /** Processes the available bytes of the buffer (at most {@code chunk} bytes). */ + protected abstract void process(ByteBuffer bb); + + /** + * This is invoked for the last bytes of the input, which are not enough to fill a whole chunk. + * The passed {@code ByteBuffer} is guaranteed to be non-empty. + * + *

This implementation simply pads with zeros and delegates to {@link #process(ByteBuffer)}. + */ + protected void processRemaining(ByteBuffer bb) { + bb.position(bb.limit()); // move at the end + bb.limit(chunkSize + 7); // get ready to pad with longs + while (bb.position() < chunkSize) { + bb.putLong(0); + } + bb.limit(chunkSize); + bb.flip(); + process(bb); + } + + @Override + public final Hasher putBytes(byte[] bytes, int off, int len) { + return putBytesInternal(ByteBuffer.wrap(bytes, off, len).order(ByteOrder.LITTLE_ENDIAN)); + } + + @Override + public final Hasher putBytes(ByteBuffer readBuffer) { + ByteOrder order = readBuffer.order(); + try { + readBuffer.order(ByteOrder.LITTLE_ENDIAN); + return putBytesInternal(readBuffer); + } finally { + readBuffer.order(order); + } + } + + private Hasher putBytesInternal(ByteBuffer readBuffer) { + // If we have room for all of it, this is easy + if (readBuffer.remaining() <= buffer.remaining()) { + buffer.put(readBuffer); + munchIfFull(); + return this; + } + + // First add just enough to fill buffer size, and munch that + int bytesToCopy = bufferSize - buffer.position(); + for (int i = 0; i < bytesToCopy; i++) { + buffer.put(readBuffer.get()); + } + munch(); // buffer becomes empty here, since chunkSize divides bufferSize + + // Now process directly from the rest of the input buffer + while (readBuffer.remaining() >= chunkSize) { + process(readBuffer); + } + + // Finally stick the remainder back in our usual buffer + buffer.put(readBuffer); + return this; + } + + /* + * Note: hashString(CharSequence, Charset) is intentionally not overridden. + * + * While intuitively, using CharsetEncoder to encode the CharSequence directly to the buffer (or + * even to an intermediate buffer) should be considerably more efficient than potentially + * copying the CharSequence to a String and then calling getBytes(Charset) on that String, in + * reality there are optimizations that make the getBytes(Charset) approach considerably faster, + * at least for commonly used charsets like UTF-8. + */ + + @Override + public final Hasher putByte(byte b) { + buffer.put(b); + munchIfFull(); + return this; + } + + @Override + public final Hasher putShort(short s) { + buffer.putShort(s); + munchIfFull(); + return this; + } + + @Override + public final Hasher putChar(char c) { + buffer.putChar(c); + munchIfFull(); + return this; + } + + @Override + public final Hasher putInt(int i) { + buffer.putInt(i); + munchIfFull(); + return this; + } + + @Override + public final Hasher putLong(long l) { + buffer.putLong(l); + munchIfFull(); + return this; + } + + @Override + public final HashCode hash() { + munch(); + buffer.flip(); + if (buffer.remaining() > 0) { + processRemaining(buffer); + buffer.position(buffer.limit()); + } + return makeHash(); + } + + /** + * Computes a hash code based on the data that have been provided to this hasher. This is called + * after all chunks are handled with {@link #process} and any leftover bytes that did not make a + * complete chunk are handled with {@link #processRemaining}. + */ + protected abstract HashCode makeHash(); + + // Process pent-up data in chunks + private void munchIfFull() { + if (buffer.remaining() < 8) { + // buffer is full; not enough room for a primitive. We have at least one full chunk. + munch(); + } + } + + private void munch() { + buffer.flip(); + while (buffer.remaining() >= chunkSize) { + // we could limit the buffer to ensure process() does not read more than + // chunkSize number of bytes, but we trust the implementations + process(buffer); + } + buffer.compact(); // preserve any remaining data that do not make a full chunk + } +} diff --git a/src/main/java/com/google/common/hash/BloomFilter.java b/src/main/java/com/google/common/hash/BloomFilter.java new file mode 100644 index 0000000..f6158db --- /dev/null +++ b/src/main/java/com/google/common/hash/BloomFilter.java @@ -0,0 +1,618 @@ +/* + * Copyright (C) 2011 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.hash; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.annotations.Beta; +import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.Objects; +import com.google.common.base.Predicate; +import com.google.common.hash.BloomFilterStrategies.LockFreeBitArray; +import com.google.common.math.DoubleMath; +import com.google.common.primitives.SignedBytes; +import com.google.common.primitives.UnsignedBytes; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.Serializable; +import java.math.RoundingMode; +import java.util.stream.Collector; + + +/** + * A Bloom filter for instances of {@code T}. A Bloom filter offers an approximate containment test + * with one-sided error: if it claims that an element is contained in it, this might be in error, + * but if it claims that an element is not contained in it, then this is definitely true. + * + *

If you are unfamiliar with Bloom filters, this nice tutorial may help you understand how + * they work. + * + *

The false positive probability ({@code FPP}) of a Bloom filter is defined as the probability + * that {@linkplain #mightContain(Object)} will erroneously return {@code true} for an object that + * has not actually been put in the {@code BloomFilter}. + * + *

Bloom filters are serializable. They also support a more compact serial representation via the + * {@link #writeTo} and {@link #readFrom} methods. Both serialized forms will continue to be + * supported by future versions of this library. However, serial forms generated by newer versions + * of the code may not be readable by older versions of the code (e.g., a serialized Bloom filter + * generated today may not be readable by a binary that was compiled 6 months ago). + * + *

As of Guava 23.0, this class is thread-safe and lock-free. It internally uses atomics and + * compare-and-swap to ensure correctness when multiple threads are used to access it. + * + * @param the type of instances that the {@code BloomFilter} accepts + * @author Dimitris Andreou + * @author Kevin Bourrillion + * @since 11.0 (thread-safe since 23.0) + */ +@Beta +public final class BloomFilter implements Predicate, Serializable { + /** + * A strategy to translate T instances, to {@code numHashFunctions} bit indexes. + * + *

Implementations should be collections of pure functions (i.e. stateless). + */ + interface Strategy extends java.io.Serializable { + + /** + * Sets {@code numHashFunctions} bits of the given bit array, by hashing a user element. + * + *

Returns whether any bits changed as a result of this operation. + */ + boolean put( + T object, Funnel funnel, int numHashFunctions, LockFreeBitArray bits); + + /** + * Queries {@code numHashFunctions} bits of the given bit array, by hashing a user element; + * returns {@code true} if and only if all selected bits are set. + */ + boolean mightContain( + T object, Funnel funnel, int numHashFunctions, LockFreeBitArray bits); + + /** + * Identifier used to encode this strategy, when marshalled as part of a BloomFilter. Only + * values in the [-128, 127] range are valid for the compact serial form. Non-negative values + * are reserved for enums defined in BloomFilterStrategies; negative values are reserved for any + * custom, stateful strategy we may define (e.g. any kind of strategy that would depend on user + * input). + */ + int ordinal(); + } + + /** The bit set of the BloomFilter (not necessarily power of 2!) */ + private final LockFreeBitArray bits; + + /** Number of hashes per element */ + private final int numHashFunctions; + + /** The funnel to translate Ts to bytes */ + private final Funnel funnel; + + /** The strategy we employ to map an element T to {@code numHashFunctions} bit indexes. */ + private final Strategy strategy; + + /** Creates a BloomFilter. */ + private BloomFilter( + LockFreeBitArray bits, int numHashFunctions, Funnel funnel, Strategy strategy) { + checkArgument(numHashFunctions > 0, "numHashFunctions (%s) must be > 0", numHashFunctions); + checkArgument( + numHashFunctions <= 255, "numHashFunctions (%s) must be <= 255", numHashFunctions); + this.bits = checkNotNull(bits); + this.numHashFunctions = numHashFunctions; + this.funnel = checkNotNull(funnel); + this.strategy = checkNotNull(strategy); + } + + /** + * Creates a new {@code BloomFilter} that's a copy of this instance. The new instance is equal to + * this instance but shares no mutable state. + * + * @since 12.0 + */ + public BloomFilter copy() { + return new BloomFilter(bits.copy(), numHashFunctions, funnel, strategy); + } + + /** + * Returns {@code true} if the element might have been put in this Bloom filter, {@code + * false} if this is definitely not the case. + */ + public boolean mightContain(T object) { + return strategy.mightContain(object, funnel, numHashFunctions, bits); + } + + /** + * @deprecated Provided only to satisfy the {@link Predicate} interface; use {@link #mightContain} + * instead. + */ + @Deprecated + @Override + public boolean apply(T input) { + return mightContain(input); + } + + /** + * Puts an element into this {@code BloomFilter}. Ensures that subsequent invocations of {@link + * #mightContain(Object)} with the same element will always return {@code true}. + * + * @return true if the Bloom filter's bits changed as a result of this operation. If the bits + * changed, this is definitely the first time {@code object} has been added to the + * filter. If the bits haven't changed, this might be the first time {@code object} has + * been added to the filter. Note that {@code put(t)} always returns the opposite + * result to what {@code mightContain(t)} would have returned at the time it is called. + * @since 12.0 (present in 11.0 with {@code void} return type}) + */ + + public boolean put(T object) { + return strategy.put(object, funnel, numHashFunctions, bits); + } + + /** + * Returns the probability that {@linkplain #mightContain(Object)} will erroneously return {@code + * true} for an object that has not actually been put in the {@code BloomFilter}. + * + *

Ideally, this number should be close to the {@code fpp} parameter passed in {@linkplain + * #create(Funnel, int, double)}, or smaller. If it is significantly higher, it is usually the + * case that too many elements (more than expected) have been put in the {@code BloomFilter}, + * degenerating it. + * + * @since 14.0 (since 11.0 as expectedFalsePositiveProbability()) + */ + public double expectedFpp() { + // You down with FPP? (Yeah you know me!) Who's down with FPP? (Every last homie!) + return Math.pow((double) bits.bitCount() / bitSize(), numHashFunctions); + } + + /** + * Returns an estimate for the total number of distinct elements that have been added to this + * Bloom filter. This approximation is reasonably accurate if it does not exceed the value of + * {@code expectedInsertions} that was used when constructing the filter. + * + * @since 22.0 + */ + public long approximateElementCount() { + long bitSize = bits.bitSize(); + long bitCount = bits.bitCount(); + + /** + * Each insertion is expected to reduce the # of clear bits by a factor of + * `numHashFunctions/bitSize`. So, after n insertions, expected bitCount is `bitSize * (1 - (1 - + * numHashFunctions/bitSize)^n)`. Solving that for n, and approximating `ln x` as `x - 1` when x + * is close to 1 (why?), gives the following formula. + */ + double fractionOfBitsSet = (double) bitCount / bitSize; + return DoubleMath.roundToLong( + -Math.log1p(-fractionOfBitsSet) * bitSize / numHashFunctions, RoundingMode.HALF_UP); + } + + /** Returns the number of bits in the underlying bit array. */ + @VisibleForTesting + long bitSize() { + return bits.bitSize(); + } + + /** + * Determines whether a given Bloom filter is compatible with this Bloom filter. For two Bloom + * filters to be compatible, they must: + * + *

    + *
  • not be the same instance + *
  • have the same number of hash functions + *
  • have the same bit size + *
  • have the same strategy + *
  • have equal funnels + *
+ * + * @param that The Bloom filter to check for compatibility. + * @since 15.0 + */ + public boolean isCompatible(BloomFilter that) { + checkNotNull(that); + return this != that + && this.numHashFunctions == that.numHashFunctions + && this.bitSize() == that.bitSize() + && this.strategy.equals(that.strategy) + && this.funnel.equals(that.funnel); + } + + /** + * Combines this Bloom filter with another Bloom filter by performing a bitwise OR of the + * underlying data. The mutations happen to this instance. Callers must ensure the Bloom + * filters are appropriately sized to avoid saturating them. + * + * @param that The Bloom filter to combine this Bloom filter with. It is not mutated. + * @throws IllegalArgumentException if {@code isCompatible(that) == false} + * @since 15.0 + */ + public void putAll(BloomFilter that) { + checkNotNull(that); + checkArgument(this != that, "Cannot combine a BloomFilter with itself."); + checkArgument( + this.numHashFunctions == that.numHashFunctions, + "BloomFilters must have the same number of hash functions (%s != %s)", + this.numHashFunctions, + that.numHashFunctions); + checkArgument( + this.bitSize() == that.bitSize(), + "BloomFilters must have the same size underlying bit arrays (%s != %s)", + this.bitSize(), + that.bitSize()); + checkArgument( + this.strategy.equals(that.strategy), + "BloomFilters must have equal strategies (%s != %s)", + this.strategy, + that.strategy); + checkArgument( + this.funnel.equals(that.funnel), + "BloomFilters must have equal funnels (%s != %s)", + this.funnel, + that.funnel); + this.bits.putAll(that.bits); + } + + @Override + public boolean equals(Object object) { + if (object == this) { + return true; + } + if (object instanceof BloomFilter) { + BloomFilter that = (BloomFilter) object; + return this.numHashFunctions == that.numHashFunctions + && this.funnel.equals(that.funnel) + && this.bits.equals(that.bits) + && this.strategy.equals(that.strategy); + } + return false; + } + + @Override + public int hashCode() { + return Objects.hashCode(numHashFunctions, funnel, strategy, bits); + } + + /** + * Returns a {@code Collector} expecting the specified number of insertions, and yielding a {@link + * BloomFilter} with false positive probability 3%. + * + *

Note that if the {@code Collector} receives significantly more elements than specified, the + * resulting {@code BloomFilter} will suffer a sharp deterioration of its false positive + * probability. + * + *

The constructed {@code BloomFilter} will be serializable if the provided {@code Funnel} + * is. + * + *

It is recommended that the funnel be implemented as a Java enum. This has the benefit of + * ensuring proper serialization and deserialization, which is important since {@link #equals} + * also relies on object identity of funnels. + * + * @param funnel the funnel of T's that the constructed {@code BloomFilter} will use + * @param expectedInsertions the number of expected insertions to the constructed {@code + * BloomFilter}; must be positive + * @return a {@code Collector} generating a {@code BloomFilter} of the received elements + * @since 23.0 + */ + public static Collector> toBloomFilter( + Funnel funnel, long expectedInsertions) { + return toBloomFilter(funnel, expectedInsertions, 0.03); + } + + /** + * Returns a {@code Collector} expecting the specified number of insertions, and yielding a {@link + * BloomFilter} with the specified expected false positive probability. + * + *

Note that if the {@code Collector} receives significantly more elements than specified, the + * resulting {@code BloomFilter} will suffer a sharp deterioration of its false positive + * probability. + * + *

The constructed {@code BloomFilter} will be serializable if the provided {@code Funnel} + * is. + * + *

It is recommended that the funnel be implemented as a Java enum. This has the benefit of + * ensuring proper serialization and deserialization, which is important since {@link #equals} + * also relies on object identity of funnels. + * + * @param funnel the funnel of T's that the constructed {@code BloomFilter} will use + * @param expectedInsertions the number of expected insertions to the constructed {@code + * BloomFilter}; must be positive + * @param fpp the desired false positive probability (must be positive and less than 1.0) + * @return a {@code Collector} generating a {@code BloomFilter} of the received elements + * @since 23.0 + */ + public static Collector> toBloomFilter( + Funnel funnel, long expectedInsertions, double fpp) { + checkNotNull(funnel); + checkArgument( + expectedInsertions >= 0, "Expected insertions (%s) must be >= 0", expectedInsertions); + checkArgument(fpp > 0.0, "False positive probability (%s) must be > 0.0", fpp); + checkArgument(fpp < 1.0, "False positive probability (%s) must be < 1.0", fpp); + return Collector.of( + () -> BloomFilter.create(funnel, expectedInsertions, fpp), + BloomFilter::put, + (bf1, bf2) -> { + bf1.putAll(bf2); + return bf1; + }, + Collector.Characteristics.UNORDERED, + Collector.Characteristics.CONCURRENT); + } + + /** + * Creates a {@link BloomFilter} with the expected number of insertions and expected false + * positive probability. + * + *

Note that overflowing a {@code BloomFilter} with significantly more elements than specified, + * will result in its saturation, and a sharp deterioration of its false positive probability. + * + *

The constructed {@code BloomFilter} will be serializable if the provided {@code Funnel} + * is. + * + *

It is recommended that the funnel be implemented as a Java enum. This has the benefit of + * ensuring proper serialization and deserialization, which is important since {@link #equals} + * also relies on object identity of funnels. + * + * @param funnel the funnel of T's that the constructed {@code BloomFilter} will use + * @param expectedInsertions the number of expected insertions to the constructed {@code + * BloomFilter}; must be positive + * @param fpp the desired false positive probability (must be positive and less than 1.0) + * @return a {@code BloomFilter} + */ + public static BloomFilter create( + Funnel funnel, int expectedInsertions, double fpp) { + return create(funnel, (long) expectedInsertions, fpp); + } + + /** + * Creates a {@link BloomFilter} with the expected number of insertions and expected false + * positive probability. + * + *

Note that overflowing a {@code BloomFilter} with significantly more elements than specified, + * will result in its saturation, and a sharp deterioration of its false positive probability. + * + *

The constructed {@code BloomFilter} will be serializable if the provided {@code Funnel} + * is. + * + *

It is recommended that the funnel be implemented as a Java enum. This has the benefit of + * ensuring proper serialization and deserialization, which is important since {@link #equals} + * also relies on object identity of funnels. + * + * @param funnel the funnel of T's that the constructed {@code BloomFilter} will use + * @param expectedInsertions the number of expected insertions to the constructed {@code + * BloomFilter}; must be positive + * @param fpp the desired false positive probability (must be positive and less than 1.0) + * @return a {@code BloomFilter} + * @since 19.0 + */ + public static BloomFilter create( + Funnel funnel, long expectedInsertions, double fpp) { + return create(funnel, expectedInsertions, fpp, BloomFilterStrategies.MURMUR128_MITZ_64); + } + + @VisibleForTesting + static BloomFilter create( + Funnel funnel, long expectedInsertions, double fpp, Strategy strategy) { + checkNotNull(funnel); + checkArgument( + expectedInsertions >= 0, "Expected insertions (%s) must be >= 0", expectedInsertions); + checkArgument(fpp > 0.0, "False positive probability (%s) must be > 0.0", fpp); + checkArgument(fpp < 1.0, "False positive probability (%s) must be < 1.0", fpp); + checkNotNull(strategy); + + if (expectedInsertions == 0) { + expectedInsertions = 1; + } + /* + * TODO(user): Put a warning in the javadoc about tiny fpp values, since the resulting size + * is proportional to -log(p), but there is not much of a point after all, e.g. + * optimalM(1000, 0.0000000000000001) = 76680 which is less than 10kb. Who cares! + */ + long numBits = optimalNumOfBits(expectedInsertions, fpp); + int numHashFunctions = optimalNumOfHashFunctions(expectedInsertions, numBits); + try { + return new BloomFilter(new LockFreeBitArray(numBits), numHashFunctions, funnel, strategy); + } catch (IllegalArgumentException e) { + throw new IllegalArgumentException("Could not create BloomFilter of " + numBits + " bits", e); + } + } + + /** + * Creates a {@link BloomFilter} with the expected number of insertions and a default expected + * false positive probability of 3%. + * + *

Note that overflowing a {@code BloomFilter} with significantly more elements than specified, + * will result in its saturation, and a sharp deterioration of its false positive probability. + * + *

The constructed {@code BloomFilter} will be serializable if the provided {@code Funnel} + * is. + * + *

It is recommended that the funnel be implemented as a Java enum. This has the benefit of + * ensuring proper serialization and deserialization, which is important since {@link #equals} + * also relies on object identity of funnels. + * + * @param funnel the funnel of T's that the constructed {@code BloomFilter} will use + * @param expectedInsertions the number of expected insertions to the constructed {@code + * BloomFilter}; must be positive + * @return a {@code BloomFilter} + */ + public static BloomFilter create(Funnel funnel, int expectedInsertions) { + return create(funnel, (long) expectedInsertions); + } + + /** + * Creates a {@link BloomFilter} with the expected number of insertions and a default expected + * false positive probability of 3%. + * + *

Note that overflowing a {@code BloomFilter} with significantly more elements than specified, + * will result in its saturation, and a sharp deterioration of its false positive probability. + * + *

The constructed {@code BloomFilter} will be serializable if the provided {@code Funnel} + * is. + * + *

It is recommended that the funnel be implemented as a Java enum. This has the benefit of + * ensuring proper serialization and deserialization, which is important since {@link #equals} + * also relies on object identity of funnels. + * + * @param funnel the funnel of T's that the constructed {@code BloomFilter} will use + * @param expectedInsertions the number of expected insertions to the constructed {@code + * BloomFilter}; must be positive + * @return a {@code BloomFilter} + * @since 19.0 + */ + public static BloomFilter create(Funnel funnel, long expectedInsertions) { + return create(funnel, expectedInsertions, 0.03); // FYI, for 3%, we always get 5 hash functions + } + + // Cheat sheet: + // + // m: total bits + // n: expected insertions + // b: m/n, bits per insertion + // p: expected false positive probability + // + // 1) Optimal k = b * ln2 + // 2) p = (1 - e ^ (-kn/m))^k + // 3) For optimal k: p = 2 ^ (-k) ~= 0.6185^b + // 4) For optimal k: m = -nlnp / ((ln2) ^ 2) + + /** + * Computes the optimal k (number of hashes per element inserted in Bloom filter), given the + * expected insertions and total number of bits in the Bloom filter. + * + *

See http://en.wikipedia.org/wiki/File:Bloom_filter_fp_probability.svg for the formula. + * + * @param n expected insertions (must be positive) + * @param m total number of bits in Bloom filter (must be positive) + */ + @VisibleForTesting + static int optimalNumOfHashFunctions(long n, long m) { + // (m / n) * log(2), but avoid truncation due to division! + return Math.max(1, (int) Math.round((double) m / n * Math.log(2))); + } + + /** + * Computes m (total bits of Bloom filter) which is expected to achieve, for the specified + * expected insertions, the required false positive probability. + * + *

See http://en.wikipedia.org/wiki/Bloom_filter#Probability_of_false_positives for the + * formula. + * + * @param n expected insertions (must be positive) + * @param p false positive rate (must be 0 < p < 1) + */ + @VisibleForTesting + static long optimalNumOfBits(long n, double p) { + if (p == 0) { + p = Double.MIN_VALUE; + } + return (long) (-n * Math.log(p) / (Math.log(2) * Math.log(2))); + } + + private Object writeReplace() { + return new SerialForm(this); + } + + private static class SerialForm implements Serializable { + final long[] data; + final int numHashFunctions; + final Funnel funnel; + final Strategy strategy; + + SerialForm(BloomFilter bf) { + this.data = LockFreeBitArray.toPlainArray(bf.bits.data); + this.numHashFunctions = bf.numHashFunctions; + this.funnel = bf.funnel; + this.strategy = bf.strategy; + } + + Object readResolve() { + return new BloomFilter(new LockFreeBitArray(data), numHashFunctions, funnel, strategy); + } + + private static final long serialVersionUID = 1; + } + + /** + * Writes this {@code BloomFilter} to an output stream, with a custom format (not Java + * serialization). This has been measured to save at least 400 bytes compared to regular + * serialization. + * + *

Use {@linkplain #readFrom(InputStream, Funnel)} to reconstruct the written BloomFilter. + */ + public void writeTo(OutputStream out) throws IOException { + // Serial form: + // 1 signed byte for the strategy + // 1 unsigned byte for the number of hash functions + // 1 big endian int, the number of longs in our bitset + // N big endian longs of our bitset + DataOutputStream dout = new DataOutputStream(out); + dout.writeByte(SignedBytes.checkedCast(strategy.ordinal())); + dout.writeByte(UnsignedBytes.checkedCast(numHashFunctions)); // note: checked at the c'tor + dout.writeInt(bits.data.length()); + for (int i = 0; i < bits.data.length(); i++) { + dout.writeLong(bits.data.get(i)); + } + } + + /** + * Reads a byte stream, which was written by {@linkplain #writeTo(OutputStream)}, into a {@code + * BloomFilter}. + * + *

The {@code Funnel} to be used is not encoded in the stream, so it must be provided here. + * Warning: the funnel provided must behave identically to the one used to populate + * the original Bloom filter! + * + * @throws IOException if the InputStream throws an {@code IOException}, or if its data does not + * appear to be a BloomFilter serialized using the {@linkplain #writeTo(OutputStream)} method. + */ + public static BloomFilter readFrom(InputStream in, Funnel funnel) + throws IOException { + checkNotNull(in, "InputStream"); + checkNotNull(funnel, "Funnel"); + int strategyOrdinal = -1; + int numHashFunctions = -1; + int dataLength = -1; + try { + DataInputStream din = new DataInputStream(in); + // currently this assumes there is no negative ordinal; will have to be updated if we + // add non-stateless strategies (for which we've reserved negative ordinals; see + // Strategy.ordinal()). + strategyOrdinal = din.readByte(); + numHashFunctions = UnsignedBytes.toInt(din.readByte()); + dataLength = din.readInt(); + + Strategy strategy = BloomFilterStrategies.values()[strategyOrdinal]; + long[] data = new long[dataLength]; + for (int i = 0; i < data.length; i++) { + data[i] = din.readLong(); + } + return new BloomFilter(new LockFreeBitArray(data), numHashFunctions, funnel, strategy); + } catch (RuntimeException e) { + String message = + "Unable to deserialize BloomFilter from InputStream." + + " strategyOrdinal: " + + strategyOrdinal + + " numHashFunctions: " + + numHashFunctions + + " dataLength: " + + dataLength; + throw new IOException(message, e); + } + } +} diff --git a/src/main/java/com/google/common/hash/BloomFilterStrategies.java b/src/main/java/com/google/common/hash/BloomFilterStrategies.java new file mode 100644 index 0000000..69971b5 --- /dev/null +++ b/src/main/java/com/google/common/hash/BloomFilterStrategies.java @@ -0,0 +1,289 @@ +/* + * Copyright (C) 2011 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.hash; + +import static com.google.common.base.Preconditions.checkArgument; + +import com.google.common.math.LongMath; +import com.google.common.primitives.Ints; +import com.google.common.primitives.Longs; +import java.math.RoundingMode; +import java.util.Arrays; +import java.util.concurrent.atomic.AtomicLongArray; + + +/** + * Collections of strategies of generating the k * log(M) bits required for an element to be mapped + * to a BloomFilter of M bits and k hash functions. These strategies are part of the serialized form + * of the Bloom filters that use them, thus they must be preserved as is (no updates allowed, only + * introduction of new versions). + * + *

Important: the order of the constants cannot change, and they cannot be deleted - we depend on + * their ordinal for BloomFilter serialization. + * + * @author Dimitris Andreou + * @author Kurt Alfred Kluever + */ +enum BloomFilterStrategies implements BloomFilter.Strategy { + /** + * See "Less Hashing, Same Performance: Building a Better Bloom Filter" by Adam Kirsch and Michael + * Mitzenmacher. The paper argues that this trick doesn't significantly deteriorate the + * performance of a Bloom filter (yet only needs two 32bit hash functions). + */ + MURMUR128_MITZ_32() { + @Override + public boolean put( + T object, Funnel funnel, int numHashFunctions, LockFreeBitArray bits) { + long bitSize = bits.bitSize(); + long hash64 = Hashing.murmur3_128().hashObject(object, funnel).asLong(); + int hash1 = (int) hash64; + int hash2 = (int) (hash64 >>> 32); + + boolean bitsChanged = false; + for (int i = 1; i <= numHashFunctions; i++) { + int combinedHash = hash1 + (i * hash2); + // Flip all the bits if it's negative (guaranteed positive number) + if (combinedHash < 0) { + combinedHash = ~combinedHash; + } + bitsChanged |= bits.set(combinedHash % bitSize); + } + return bitsChanged; + } + + @Override + public boolean mightContain( + T object, Funnel funnel, int numHashFunctions, LockFreeBitArray bits) { + long bitSize = bits.bitSize(); + long hash64 = Hashing.murmur3_128().hashObject(object, funnel).asLong(); + int hash1 = (int) hash64; + int hash2 = (int) (hash64 >>> 32); + + for (int i = 1; i <= numHashFunctions; i++) { + int combinedHash = hash1 + (i * hash2); + // Flip all the bits if it's negative (guaranteed positive number) + if (combinedHash < 0) { + combinedHash = ~combinedHash; + } + if (!bits.get(combinedHash % bitSize)) { + return false; + } + } + return true; + } + }, + /** + * This strategy uses all 128 bits of {@link Hashing#murmur3_128} when hashing. It looks different + * than the implementation in MURMUR128_MITZ_32 because we're avoiding the multiplication in the + * loop and doing a (much simpler) += hash2. We're also changing the index to a positive number by + * AND'ing with Long.MAX_VALUE instead of flipping the bits. + */ + MURMUR128_MITZ_64() { + @Override + public boolean put( + T object, Funnel funnel, int numHashFunctions, LockFreeBitArray bits) { + long bitSize = bits.bitSize(); + byte[] bytes = Hashing.murmur3_128().hashObject(object, funnel).getBytesInternal(); + long hash1 = lowerEight(bytes); + long hash2 = upperEight(bytes); + + boolean bitsChanged = false; + long combinedHash = hash1; + for (int i = 0; i < numHashFunctions; i++) { + // Make the combined hash positive and indexable + bitsChanged |= bits.set((combinedHash & Long.MAX_VALUE) % bitSize); + combinedHash += hash2; + } + return bitsChanged; + } + + @Override + public boolean mightContain( + T object, Funnel funnel, int numHashFunctions, LockFreeBitArray bits) { + long bitSize = bits.bitSize(); + byte[] bytes = Hashing.murmur3_128().hashObject(object, funnel).getBytesInternal(); + long hash1 = lowerEight(bytes); + long hash2 = upperEight(bytes); + + long combinedHash = hash1; + for (int i = 0; i < numHashFunctions; i++) { + // Make the combined hash positive and indexable + if (!bits.get((combinedHash & Long.MAX_VALUE) % bitSize)) { + return false; + } + combinedHash += hash2; + } + return true; + } + + private /* static */ long lowerEight(byte[] bytes) { + return Longs.fromBytes( + bytes[7], bytes[6], bytes[5], bytes[4], bytes[3], bytes[2], bytes[1], bytes[0]); + } + + private /* static */ long upperEight(byte[] bytes) { + return Longs.fromBytes( + bytes[15], bytes[14], bytes[13], bytes[12], bytes[11], bytes[10], bytes[9], bytes[8]); + } + }; + + /** + * Models a lock-free array of bits. + * + *

We use this instead of java.util.BitSet because we need access to the array of longs and we + * need compare-and-swap. + */ + static final class LockFreeBitArray { + private static final int LONG_ADDRESSABLE_BITS = 6; + final AtomicLongArray data; + private final LongAddable bitCount; + + LockFreeBitArray(long bits) { + checkArgument(bits > 0, "data length is zero!"); + // Avoid delegating to this(long[]), since AtomicLongArray(long[]) will clone its input and + // thus double memory usage. + this.data = + new AtomicLongArray(Ints.checkedCast(LongMath.divide(bits, 64, RoundingMode.CEILING))); + this.bitCount = LongAddables.create(); + } + + // Used by serialization + LockFreeBitArray(long[] data) { + checkArgument(data.length > 0, "data length is zero!"); + this.data = new AtomicLongArray(data); + this.bitCount = LongAddables.create(); + long bitCount = 0; + for (long value : data) { + bitCount += Long.bitCount(value); + } + this.bitCount.add(bitCount); + } + + /** Returns true if the bit changed value. */ + boolean set(long bitIndex) { + if (get(bitIndex)) { + return false; + } + + int longIndex = (int) (bitIndex >>> LONG_ADDRESSABLE_BITS); + long mask = 1L << bitIndex; // only cares about low 6 bits of bitIndex + + long oldValue; + long newValue; + do { + oldValue = data.get(longIndex); + newValue = oldValue | mask; + if (oldValue == newValue) { + return false; + } + } while (!data.compareAndSet(longIndex, oldValue, newValue)); + + // We turned the bit on, so increment bitCount. + bitCount.increment(); + return true; + } + + boolean get(long bitIndex) { + return (data.get((int) (bitIndex >>> LONG_ADDRESSABLE_BITS)) & (1L << bitIndex)) != 0; + } + + /** + * Careful here: if threads are mutating the atomicLongArray while this method is executing, the + * final long[] will be a "rolling snapshot" of the state of the bit array. This is usually good + * enough, but should be kept in mind. + */ + public static long[] toPlainArray(AtomicLongArray atomicLongArray) { + long[] array = new long[atomicLongArray.length()]; + for (int i = 0; i < array.length; ++i) { + array[i] = atomicLongArray.get(i); + } + return array; + } + + /** Number of bits */ + long bitSize() { + return (long) data.length() * Long.SIZE; + } + + /** + * Number of set bits (1s). + * + *

Note that because of concurrent set calls and uses of atomics, this bitCount is a (very) + * close *estimate* of the actual number of bits set. It's not possible to do better than an + * estimate without locking. Note that the number, if not exactly accurate, is *always* + * underestimating, never overestimating. + */ + long bitCount() { + return bitCount.sum(); + } + + LockFreeBitArray copy() { + return new LockFreeBitArray(toPlainArray(data)); + } + + /** + * Combines the two BitArrays using bitwise OR. + * + *

NOTE: Because of the use of atomics, if the other LockFreeBitArray is being mutated while + * this operation is executing, not all of those new 1's may be set in the final state of this + * LockFreeBitArray. The ONLY guarantee provided is that all the bits that were set in the other + * LockFreeBitArray at the start of this method will be set in this LockFreeBitArray at the end + * of this method. + */ + void putAll(LockFreeBitArray other) { + checkArgument( + data.length() == other.data.length(), + "BitArrays must be of equal length (%s != %s)", + data.length(), + other.data.length()); + for (int i = 0; i < data.length(); i++) { + long otherLong = other.data.get(i); + + long ourLongOld; + long ourLongNew; + boolean changedAnyBits = true; + do { + ourLongOld = data.get(i); + ourLongNew = ourLongOld | otherLong; + if (ourLongOld == ourLongNew) { + changedAnyBits = false; + break; + } + } while (!data.compareAndSet(i, ourLongOld, ourLongNew)); + + if (changedAnyBits) { + int bitsAdded = Long.bitCount(ourLongNew) - Long.bitCount(ourLongOld); + bitCount.add(bitsAdded); + } + } + } + + @Override + public boolean equals(Object o) { + if (o instanceof LockFreeBitArray) { + LockFreeBitArray lockFreeBitArray = (LockFreeBitArray) o; + // TODO(lowasser): avoid allocation here + return Arrays.equals(toPlainArray(data), toPlainArray(lockFreeBitArray.data)); + } + return false; + } + + @Override + public int hashCode() { + // TODO(lowasser): avoid allocation here + return Arrays.hashCode(toPlainArray(data)); + } + } +} diff --git a/src/main/java/com/google/common/hash/ChecksumHashFunction.java b/src/main/java/com/google/common/hash/ChecksumHashFunction.java new file mode 100644 index 0000000..e585f0f --- /dev/null +++ b/src/main/java/com/google/common/hash/ChecksumHashFunction.java @@ -0,0 +1,92 @@ +/* + * Copyright (C) 2012 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.hash; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; + + +import java.io.Serializable; +import java.util.zip.Checksum; + +/** + * {@link HashFunction} adapter for {@link Checksum} instances. + * + * @author Colin Decker + */ +final class ChecksumHashFunction extends AbstractHashFunction implements Serializable { + private final ImmutableSupplier checksumSupplier; + private final int bits; + private final String toString; + + ChecksumHashFunction( + ImmutableSupplier checksumSupplier, int bits, String toString) { + this.checksumSupplier = checkNotNull(checksumSupplier); + checkArgument(bits == 32 || bits == 64, "bits (%s) must be either 32 or 64", bits); + this.bits = bits; + this.toString = checkNotNull(toString); + } + + @Override + public int bits() { + return bits; + } + + @Override + public Hasher newHasher() { + return new ChecksumHasher(checksumSupplier.get()); + } + + @Override + public String toString() { + return toString; + } + + /** Hasher that updates a checksum. */ + private final class ChecksumHasher extends AbstractByteHasher { + private final Checksum checksum; + + private ChecksumHasher(Checksum checksum) { + this.checksum = checkNotNull(checksum); + } + + @Override + protected void update(byte b) { + checksum.update(b); + } + + @Override + protected void update(byte[] bytes, int off, int len) { + checksum.update(bytes, off, len); + } + + @Override + public HashCode hash() { + long value = checksum.getValue(); + if (bits == 32) { + /* + * The long returned from a 32-bit Checksum will have all 0s for its second word, so the + * cast won't lose any information and is necessary to return a HashCode of the correct + * size. + */ + return HashCode.fromInt((int) value); + } else { + return HashCode.fromLong(value); + } + } + } + + private static final long serialVersionUID = 0L; +} diff --git a/src/main/java/com/google/common/hash/Crc32cHashFunction.java b/src/main/java/com/google/common/hash/Crc32cHashFunction.java new file mode 100644 index 0000000..6e9c2df --- /dev/null +++ b/src/main/java/com/google/common/hash/Crc32cHashFunction.java @@ -0,0 +1,127 @@ +/* + * Copyright (C) 2011 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.hash; + + + +/** + * This class generates a CRC32C checksum, defined by RFC 3720, Section 12.1. The generator + * polynomial for this checksum is {@code 0x11EDC6F41}. + * + * @author Kurt Alfred Kluever + */ +final class Crc32cHashFunction extends AbstractHashFunction { + static final HashFunction CRC_32_C = new Crc32cHashFunction(); + + @Override + public int bits() { + return 32; + } + + @Override + public Hasher newHasher() { + return new Crc32cHasher(); + } + + @Override + public String toString() { + return "Hashing.crc32c()"; + } + + static final class Crc32cHasher extends AbstractByteHasher { + + // The CRC table, generated from the polynomial 0x11EDC6F41. + static final int[] CRC_TABLE = { + 0x00000000, 0xf26b8303, 0xe13b70f7, 0x1350f3f4, + 0xc79a971f, 0x35f1141c, 0x26a1e7e8, 0xd4ca64eb, + 0x8ad958cf, 0x78b2dbcc, 0x6be22838, 0x9989ab3b, + 0x4d43cfd0, 0xbf284cd3, 0xac78bf27, 0x5e133c24, + 0x105ec76f, 0xe235446c, 0xf165b798, 0x030e349b, + 0xd7c45070, 0x25afd373, 0x36ff2087, 0xc494a384, + 0x9a879fa0, 0x68ec1ca3, 0x7bbcef57, 0x89d76c54, + 0x5d1d08bf, 0xaf768bbc, 0xbc267848, 0x4e4dfb4b, + 0x20bd8ede, 0xd2d60ddd, 0xc186fe29, 0x33ed7d2a, + 0xe72719c1, 0x154c9ac2, 0x061c6936, 0xf477ea35, + 0xaa64d611, 0x580f5512, 0x4b5fa6e6, 0xb93425e5, + 0x6dfe410e, 0x9f95c20d, 0x8cc531f9, 0x7eaeb2fa, + 0x30e349b1, 0xc288cab2, 0xd1d83946, 0x23b3ba45, + 0xf779deae, 0x05125dad, 0x1642ae59, 0xe4292d5a, + 0xba3a117e, 0x4851927d, 0x5b016189, 0xa96ae28a, + 0x7da08661, 0x8fcb0562, 0x9c9bf696, 0x6ef07595, + 0x417b1dbc, 0xb3109ebf, 0xa0406d4b, 0x522bee48, + 0x86e18aa3, 0x748a09a0, 0x67dafa54, 0x95b17957, + 0xcba24573, 0x39c9c670, 0x2a993584, 0xd8f2b687, + 0x0c38d26c, 0xfe53516f, 0xed03a29b, 0x1f682198, + 0x5125dad3, 0xa34e59d0, 0xb01eaa24, 0x42752927, + 0x96bf4dcc, 0x64d4cecf, 0x77843d3b, 0x85efbe38, + 0xdbfc821c, 0x2997011f, 0x3ac7f2eb, 0xc8ac71e8, + 0x1c661503, 0xee0d9600, 0xfd5d65f4, 0x0f36e6f7, + 0x61c69362, 0x93ad1061, 0x80fde395, 0x72966096, + 0xa65c047d, 0x5437877e, 0x4767748a, 0xb50cf789, + 0xeb1fcbad, 0x197448ae, 0x0a24bb5a, 0xf84f3859, + 0x2c855cb2, 0xdeeedfb1, 0xcdbe2c45, 0x3fd5af46, + 0x7198540d, 0x83f3d70e, 0x90a324fa, 0x62c8a7f9, + 0xb602c312, 0x44694011, 0x5739b3e5, 0xa55230e6, + 0xfb410cc2, 0x092a8fc1, 0x1a7a7c35, 0xe811ff36, + 0x3cdb9bdd, 0xceb018de, 0xdde0eb2a, 0x2f8b6829, + 0x82f63b78, 0x709db87b, 0x63cd4b8f, 0x91a6c88c, + 0x456cac67, 0xb7072f64, 0xa457dc90, 0x563c5f93, + 0x082f63b7, 0xfa44e0b4, 0xe9141340, 0x1b7f9043, + 0xcfb5f4a8, 0x3dde77ab, 0x2e8e845f, 0xdce5075c, + 0x92a8fc17, 0x60c37f14, 0x73938ce0, 0x81f80fe3, + 0x55326b08, 0xa759e80b, 0xb4091bff, 0x466298fc, + 0x1871a4d8, 0xea1a27db, 0xf94ad42f, 0x0b21572c, + 0xdfeb33c7, 0x2d80b0c4, 0x3ed04330, 0xccbbc033, + 0xa24bb5a6, 0x502036a5, 0x4370c551, 0xb11b4652, + 0x65d122b9, 0x97baa1ba, 0x84ea524e, 0x7681d14d, + 0x2892ed69, 0xdaf96e6a, 0xc9a99d9e, 0x3bc21e9d, + 0xef087a76, 0x1d63f975, 0x0e330a81, 0xfc588982, + 0xb21572c9, 0x407ef1ca, 0x532e023e, 0xa145813d, + 0x758fe5d6, 0x87e466d5, 0x94b49521, 0x66df1622, + 0x38cc2a06, 0xcaa7a905, 0xd9f75af1, 0x2b9cd9f2, + 0xff56bd19, 0x0d3d3e1a, 0x1e6dcdee, 0xec064eed, + 0xc38d26c4, 0x31e6a5c7, 0x22b65633, 0xd0ddd530, + 0x0417b1db, 0xf67c32d8, 0xe52cc12c, 0x1747422f, + 0x49547e0b, 0xbb3ffd08, 0xa86f0efc, 0x5a048dff, + 0x8ecee914, 0x7ca56a17, 0x6ff599e3, 0x9d9e1ae0, + 0xd3d3e1ab, 0x21b862a8, 0x32e8915c, 0xc083125f, + 0x144976b4, 0xe622f5b7, 0xf5720643, 0x07198540, + 0x590ab964, 0xab613a67, 0xb831c993, 0x4a5a4a90, + 0x9e902e7b, 0x6cfbad78, 0x7fab5e8c, 0x8dc0dd8f, + 0xe330a81a, 0x115b2b19, 0x020bd8ed, 0xf0605bee, + 0x24aa3f05, 0xd6c1bc06, 0xc5914ff2, 0x37faccf1, + 0x69e9f0d5, 0x9b8273d6, 0x88d28022, 0x7ab90321, + 0xae7367ca, 0x5c18e4c9, 0x4f48173d, 0xbd23943e, + 0xf36e6f75, 0x0105ec76, 0x12551f82, 0xe03e9c81, + 0x34f4f86a, 0xc69f7b69, 0xd5cf889d, 0x27a40b9e, + 0x79b737ba, 0x8bdcb4b9, 0x988c474d, 0x6ae7c44e, + 0xbe2da0a5, 0x4c4623a6, 0x5f16d052, 0xad7d5351 + }; + + private int crc = 0; + + @Override + public void update(byte b) { + crc ^= 0xFFFFFFFF; + // See Hacker's Delight 2nd Edition, Figure 14-7. + crc = ~((crc >>> 8) ^ CRC_TABLE[(crc ^ b) & 0xFF]); + } + + @Override + public HashCode hash() { + return HashCode.fromInt(crc); + } + } +} diff --git a/src/main/java/com/google/common/hash/FarmHashFingerprint64.java b/src/main/java/com/google/common/hash/FarmHashFingerprint64.java new file mode 100644 index 0000000..30eab5f --- /dev/null +++ b/src/main/java/com/google/common/hash/FarmHashFingerprint64.java @@ -0,0 +1,216 @@ +/* + * Copyright (C) 2015 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.hash; + +import static com.google.common.base.Preconditions.checkPositionIndexes; +import static com.google.common.hash.LittleEndianByteArray.load32; +import static com.google.common.hash.LittleEndianByteArray.load64; +import static java.lang.Long.rotateRight; + +import com.google.common.annotations.VisibleForTesting; + +/** + * Implementation of FarmHash Fingerprint64, an open-source fingerprinting algorithm for strings. + * + *

Its speed is comparable to CityHash64, and its quality of hashing is at least as good. + * + *

Note to maintainers: This implementation relies on signed arithmetic being bit-wise equivalent + * to unsigned arithmetic in all cases except: + * + *

    + *
  • comparisons (signed values can be negative) + *
  • division (avoided here) + *
  • shifting (right shift must be unsigned) + *
+ * + * @author Kyle Maddison + * @author Geoff Pike + */ +final class FarmHashFingerprint64 extends AbstractNonStreamingHashFunction { + static final HashFunction FARMHASH_FINGERPRINT_64 = new FarmHashFingerprint64(); + + // Some primes between 2^63 and 2^64 for various uses. + private static final long K0 = 0xc3a5c85c97cb3127L; + private static final long K1 = 0xb492b66fbe98f273L; + private static final long K2 = 0x9ae16a3b2f90404fL; + + @Override + public HashCode hashBytes(byte[] input, int off, int len) { + checkPositionIndexes(off, off + len, input.length); + return HashCode.fromLong(fingerprint(input, off, len)); + } + + @Override + public int bits() { + return 64; + } + + @Override + public String toString() { + return "Hashing.farmHashFingerprint64()"; + } + + // End of public functions. + + @VisibleForTesting + static long fingerprint(byte[] bytes, int offset, int length) { + if (length <= 32) { + if (length <= 16) { + return hashLength0to16(bytes, offset, length); + } else { + return hashLength17to32(bytes, offset, length); + } + } else if (length <= 64) { + return hashLength33To64(bytes, offset, length); + } else { + return hashLength65Plus(bytes, offset, length); + } + } + + private static long shiftMix(long val) { + return val ^ (val >>> 47); + } + + private static long hashLength16(long u, long v, long mul) { + long a = (u ^ v) * mul; + a ^= (a >>> 47); + long b = (v ^ a) * mul; + b ^= (b >>> 47); + b *= mul; + return b; + } + + /** + * Computes intermediate hash of 32 bytes of byte array from the given offset. Results are + * returned in the output array because when we last measured, this was 12% faster than allocating + * new arrays every time. + */ + private static void weakHashLength32WithSeeds( + byte[] bytes, int offset, long seedA, long seedB, long[] output) { + long part1 = load64(bytes, offset); + long part2 = load64(bytes, offset + 8); + long part3 = load64(bytes, offset + 16); + long part4 = load64(bytes, offset + 24); + + seedA += part1; + seedB = rotateRight(seedB + seedA + part4, 21); + long c = seedA; + seedA += part2; + seedA += part3; + seedB += rotateRight(seedA, 44); + output[0] = seedA + part4; + output[1] = seedB + c; + } + + private static long hashLength0to16(byte[] bytes, int offset, int length) { + if (length >= 8) { + long mul = K2 + length * 2; + long a = load64(bytes, offset) + K2; + long b = load64(bytes, offset + length - 8); + long c = rotateRight(b, 37) * mul + a; + long d = (rotateRight(a, 25) + b) * mul; + return hashLength16(c, d, mul); + } + if (length >= 4) { + long mul = K2 + length * 2; + long a = load32(bytes, offset) & 0xFFFFFFFFL; + return hashLength16(length + (a << 3), load32(bytes, offset + length - 4) & 0xFFFFFFFFL, mul); + } + if (length > 0) { + byte a = bytes[offset]; + byte b = bytes[offset + (length >> 1)]; + byte c = bytes[offset + (length - 1)]; + int y = (a & 0xFF) + ((b & 0xFF) << 8); + int z = length + ((c & 0xFF) << 2); + return shiftMix(y * K2 ^ z * K0) * K2; + } + return K2; + } + + private static long hashLength17to32(byte[] bytes, int offset, int length) { + long mul = K2 + length * 2; + long a = load64(bytes, offset) * K1; + long b = load64(bytes, offset + 8); + long c = load64(bytes, offset + length - 8) * mul; + long d = load64(bytes, offset + length - 16) * K2; + return hashLength16( + rotateRight(a + b, 43) + rotateRight(c, 30) + d, a + rotateRight(b + K2, 18) + c, mul); + } + + private static long hashLength33To64(byte[] bytes, int offset, int length) { + long mul = K2 + length * 2; + long a = load64(bytes, offset) * K2; + long b = load64(bytes, offset + 8); + long c = load64(bytes, offset + length - 8) * mul; + long d = load64(bytes, offset + length - 16) * K2; + long y = rotateRight(a + b, 43) + rotateRight(c, 30) + d; + long z = hashLength16(y, a + rotateRight(b + K2, 18) + c, mul); + long e = load64(bytes, offset + 16) * mul; + long f = load64(bytes, offset + 24); + long g = (y + load64(bytes, offset + length - 32)) * mul; + long h = (z + load64(bytes, offset + length - 24)) * mul; + return hashLength16( + rotateRight(e + f, 43) + rotateRight(g, 30) + h, e + rotateRight(f + a, 18) + g, mul); + } + + /* + * Compute an 8-byte hash of a byte array of length greater than 64 bytes. + */ + private static long hashLength65Plus(byte[] bytes, int offset, int length) { + final int seed = 81; + // For strings over 64 bytes we loop. Internal state consists of 56 bytes: v, w, x, y, and z. + long x = seed; + @SuppressWarnings("ConstantOverflow") + long y = seed * K1 + 113; + long z = shiftMix(y * K2 + 113) * K2; + long[] v = new long[2]; + long[] w = new long[2]; + x = x * K2 + load64(bytes, offset); + + // Set end so that after the loop we have 1 to 64 bytes left to process. + int end = offset + ((length - 1) / 64) * 64; + int last64offset = end + ((length - 1) & 63) - 63; + do { + x = rotateRight(x + y + v[0] + load64(bytes, offset + 8), 37) * K1; + y = rotateRight(y + v[1] + load64(bytes, offset + 48), 42) * K1; + x ^= w[1]; + y += v[0] + load64(bytes, offset + 40); + z = rotateRight(z + w[0], 33) * K1; + weakHashLength32WithSeeds(bytes, offset, v[1] * K1, x + w[0], v); + weakHashLength32WithSeeds(bytes, offset + 32, z + w[1], y + load64(bytes, offset + 16), w); + long tmp = x; + x = z; + z = tmp; + offset += 64; + } while (offset != end); + long mul = K1 + ((z & 0xFF) << 1); + // Operate on the last 64 bytes of input. + offset = last64offset; + w[0] += ((length - 1) & 63); + v[0] += w[0]; + w[0] += v[0]; + x = rotateRight(x + y + v[0] + load64(bytes, offset + 8), 37) * mul; + y = rotateRight(y + v[1] + load64(bytes, offset + 48), 42) * mul; + x ^= w[1] * 9; + y += v[0] * 9 + load64(bytes, offset + 40); + z = rotateRight(z + w[0], 33) * mul; + weakHashLength32WithSeeds(bytes, offset, v[1] * mul, x + w[0], v); + weakHashLength32WithSeeds(bytes, offset + 32, z + w[1], y + load64(bytes, offset + 16), w); + return hashLength16( + hashLength16(v[0], w[0], mul) + shiftMix(y) * K0 + x, + hashLength16(v[1], w[1], mul) + z, + mul); + } +} diff --git a/src/main/java/com/google/common/hash/Funnel.java b/src/main/java/com/google/common/hash/Funnel.java new file mode 100644 index 0000000..077ef7a --- /dev/null +++ b/src/main/java/com/google/common/hash/Funnel.java @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2011 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.hash; + +import com.google.common.annotations.Beta; +import java.io.Serializable; + +/** + * An object which can send data from an object of type {@code T} into a {@code PrimitiveSink}. + * Implementations for common types can be found in {@link Funnels}. + * + *

Note that serialization of {@linkplain BloomFilter bloom filters} requires the proper + * serialization of funnels. When possible, it is recommended that funnels be implemented as a + * single-element enum to maintain serialization guarantees. See Effective Java (2nd Edition), Item + * 3: "Enforce the singleton property with a private constructor or an enum type". For example: + * + *

{@code
+ * public enum PersonFunnel implements Funnel {
+ *   INSTANCE;
+ *   public void funnel(Person person, PrimitiveSink into) {
+ *     into.putUnencodedChars(person.getFirstName())
+ *         .putUnencodedChars(person.getLastName())
+ *         .putInt(person.getAge());
+ *   }
+ * }
+ * }
+ * + * @author Dimitris Andreou + * @since 11.0 + */ +@Beta +public interface Funnel extends Serializable { + + /** + * Sends a stream of data from the {@code from} object into the sink {@code into}. There is no + * requirement that this data be complete enough to fully reconstitute the object later. + * + * @since 12.0 (in Guava 11.0, {@code PrimitiveSink} was named {@code Sink}) + */ + void funnel(T from, PrimitiveSink into); +} diff --git a/src/main/java/com/google/common/hash/Funnels.java b/src/main/java/com/google/common/hash/Funnels.java new file mode 100644 index 0000000..0df45a7 --- /dev/null +++ b/src/main/java/com/google/common/hash/Funnels.java @@ -0,0 +1,269 @@ +/* + * Copyright (C) 2011 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.hash; + +import com.google.common.annotations.Beta; +import com.google.common.base.Preconditions; +import java.io.OutputStream; +import java.io.Serializable; +import java.nio.charset.Charset; + + +/** + * Funnels for common types. All implementations are serializable. + * + * @author Dimitris Andreou + * @since 11.0 + */ +@Beta +public final class Funnels { + private Funnels() {} + + /** Returns a funnel that extracts the bytes from a {@code byte} array. */ + public static Funnel byteArrayFunnel() { + return ByteArrayFunnel.INSTANCE; + } + + private enum ByteArrayFunnel implements Funnel { + INSTANCE; + + @Override + public void funnel(byte[] from, PrimitiveSink into) { + into.putBytes(from); + } + + @Override + public String toString() { + return "Funnels.byteArrayFunnel()"; + } + } + + /** + * Returns a funnel that extracts the characters from a {@code CharSequence}, a character at a + * time, without performing any encoding. If you need to use a specific encoding, use {@link + * Funnels#stringFunnel(Charset)} instead. + * + * @since 15.0 (since 11.0 as {@code Funnels.stringFunnel()}. + */ + public static Funnel unencodedCharsFunnel() { + return UnencodedCharsFunnel.INSTANCE; + } + + private enum UnencodedCharsFunnel implements Funnel { + INSTANCE; + + @Override + public void funnel(CharSequence from, PrimitiveSink into) { + into.putUnencodedChars(from); + } + + @Override + public String toString() { + return "Funnels.unencodedCharsFunnel()"; + } + } + + /** + * Returns a funnel that encodes the characters of a {@code CharSequence} with the specified + * {@code Charset}. + * + * @since 15.0 + */ + public static Funnel stringFunnel(Charset charset) { + return new StringCharsetFunnel(charset); + } + + private static class StringCharsetFunnel implements Funnel, Serializable { + private final Charset charset; + + StringCharsetFunnel(Charset charset) { + this.charset = Preconditions.checkNotNull(charset); + } + + @Override + public void funnel(CharSequence from, PrimitiveSink into) { + into.putString(from, charset); + } + + @Override + public String toString() { + return "Funnels.stringFunnel(" + charset.name() + ")"; + } + + @Override + public boolean equals(Object o) { + if (o instanceof StringCharsetFunnel) { + StringCharsetFunnel funnel = (StringCharsetFunnel) o; + return this.charset.equals(funnel.charset); + } + return false; + } + + @Override + public int hashCode() { + return StringCharsetFunnel.class.hashCode() ^ charset.hashCode(); + } + + Object writeReplace() { + return new SerializedForm(charset); + } + + private static class SerializedForm implements Serializable { + private final String charsetCanonicalName; + + SerializedForm(Charset charset) { + this.charsetCanonicalName = charset.name(); + } + + private Object readResolve() { + return stringFunnel(Charset.forName(charsetCanonicalName)); + } + + private static final long serialVersionUID = 0; + } + } + + /** + * Returns a funnel for integers. + * + * @since 13.0 + */ + public static Funnel integerFunnel() { + return IntegerFunnel.INSTANCE; + } + + private enum IntegerFunnel implements Funnel { + INSTANCE; + + @Override + public void funnel(Integer from, PrimitiveSink into) { + into.putInt(from); + } + + @Override + public String toString() { + return "Funnels.integerFunnel()"; + } + } + + /** + * Returns a funnel that processes an {@code Iterable} by funneling its elements in iteration + * order with the specified funnel. No separators are added between the elements. + * + * @since 15.0 + */ + public static Funnel> sequentialFunnel(Funnel elementFunnel) { + return new SequentialFunnel(elementFunnel); + } + + private static class SequentialFunnel implements Funnel>, Serializable { + private final Funnel elementFunnel; + + SequentialFunnel(Funnel elementFunnel) { + this.elementFunnel = Preconditions.checkNotNull(elementFunnel); + } + + @Override + public void funnel(Iterable from, PrimitiveSink into) { + for (E e : from) { + elementFunnel.funnel(e, into); + } + } + + @Override + public String toString() { + return "Funnels.sequentialFunnel(" + elementFunnel + ")"; + } + + @Override + public boolean equals(Object o) { + if (o instanceof SequentialFunnel) { + SequentialFunnel funnel = (SequentialFunnel) o; + return elementFunnel.equals(funnel.elementFunnel); + } + return false; + } + + @Override + public int hashCode() { + return SequentialFunnel.class.hashCode() ^ elementFunnel.hashCode(); + } + } + + /** + * Returns a funnel for longs. + * + * @since 13.0 + */ + public static Funnel longFunnel() { + return LongFunnel.INSTANCE; + } + + private enum LongFunnel implements Funnel { + INSTANCE; + + @Override + public void funnel(Long from, PrimitiveSink into) { + into.putLong(from); + } + + @Override + public String toString() { + return "Funnels.longFunnel()"; + } + } + + /** + * Wraps a {@code PrimitiveSink} as an {@link OutputStream}, so it is easy to {@link Funnel#funnel + * funnel} an object to a {@code PrimitiveSink} if there is already a way to write the contents of + * the object to an {@code OutputStream}. + * + *

The {@code close} and {@code flush} methods of the returned {@code OutputStream} do nothing, + * and no method throws {@code IOException}. + * + * @since 13.0 + */ + public static OutputStream asOutputStream(PrimitiveSink sink) { + return new SinkAsStream(sink); + } + + private static class SinkAsStream extends OutputStream { + final PrimitiveSink sink; + + SinkAsStream(PrimitiveSink sink) { + this.sink = Preconditions.checkNotNull(sink); + } + + @Override + public void write(int b) { + sink.putByte((byte) b); + } + + @Override + public void write(byte[] bytes) { + sink.putBytes(bytes); + } + + @Override + public void write(byte[] bytes, int off, int len) { + sink.putBytes(bytes, off, len); + } + + @Override + public String toString() { + return "Funnels.asOutputStream(" + sink + ")"; + } + } +} diff --git a/src/main/java/com/google/common/hash/HashCode.java b/src/main/java/com/google/common/hash/HashCode.java new file mode 100644 index 0000000..82a4a19 --- /dev/null +++ b/src/main/java/com/google/common/hash/HashCode.java @@ -0,0 +1,423 @@ +/* + * Copyright (C) 2011 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.hash; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.base.Preconditions.checkState; + +import com.google.common.annotations.Beta; +import com.google.common.base.Preconditions; +import com.google.common.primitives.Ints; +import com.google.common.primitives.UnsignedInts; + +import java.io.Serializable; + + +/** + * An immutable hash code of arbitrary bit length. + * + * @author Dimitris Andreou + * @author Kurt Alfred Kluever + * @since 11.0 + */ +@Beta +public abstract class HashCode { + HashCode() {} + + /** Returns the number of bits in this hash code; a positive multiple of 8. */ + public abstract int bits(); + + /** + * Returns the first four bytes of {@linkplain #asBytes() this hashcode's bytes}, converted to an + * {@code int} value in little-endian order. + * + * @throws IllegalStateException if {@code bits() < 32} + */ + public abstract int asInt(); + + /** + * Returns the first eight bytes of {@linkplain #asBytes() this hashcode's bytes}, converted to a + * {@code long} value in little-endian order. + * + * @throws IllegalStateException if {@code bits() < 64} + */ + public abstract long asLong(); + + /** + * If this hashcode has enough bits, returns {@code asLong()}, otherwise returns a {@code long} + * value with {@code asBytes()} as the least-significant bytes and {@code 0x00} as the remaining + * most-significant bytes. + * + * @since 14.0 (since 11.0 as {@code Hashing.padToLong(HashCode)}) + */ + public abstract long padToLong(); + + /** + * Returns the value of this hash code as a byte array. The caller may modify the byte array; + * changes to it will not be reflected in this {@code HashCode} object or any other arrays + * returned by this method. + */ + // TODO(user): consider ByteString here, when that is available + public abstract byte[] asBytes(); + + /** + * Copies bytes from this hash code into {@code dest}. + * + * @param dest the byte array into which the hash code will be written + * @param offset the start offset in the data + * @param maxLength the maximum number of bytes to write + * @return the number of bytes written to {@code dest} + * @throws IndexOutOfBoundsException if there is not enough room in {@code dest} + */ + + public int writeBytesTo(byte[] dest, int offset, int maxLength) { + maxLength = Ints.min(maxLength, bits() / 8); + Preconditions.checkPositionIndexes(offset, offset + maxLength, dest.length); + writeBytesToImpl(dest, offset, maxLength); + return maxLength; + } + + abstract void writeBytesToImpl(byte[] dest, int offset, int maxLength); + + /** + * Returns a mutable view of the underlying bytes for the given {@code HashCode} if it is a + * byte-based hashcode. Otherwise it returns {@link HashCode#asBytes}. Do not mutate this + * array or else you will break the immutability contract of {@code HashCode}. + */ + byte[] getBytesInternal() { + return asBytes(); + } + + /** + * Returns whether this {@code HashCode} and that {@code HashCode} have the same value, given that + * they have the same number of bits. + */ + abstract boolean equalsSameBits(HashCode that); + + /** + * Creates a 32-bit {@code HashCode} representation of the given int value. The underlying bytes + * are interpreted in little endian order. + * + * @since 15.0 (since 12.0 in HashCodes) + */ + public static HashCode fromInt(int hash) { + return new IntHashCode(hash); + } + + private static final class IntHashCode extends HashCode implements Serializable { + final int hash; + + IntHashCode(int hash) { + this.hash = hash; + } + + @Override + public int bits() { + return 32; + } + + @Override + public byte[] asBytes() { + return new byte[] {(byte) hash, (byte) (hash >> 8), (byte) (hash >> 16), (byte) (hash >> 24)}; + } + + @Override + public int asInt() { + return hash; + } + + @Override + public long asLong() { + throw new IllegalStateException("this HashCode only has 32 bits; cannot create a long"); + } + + @Override + public long padToLong() { + return UnsignedInts.toLong(hash); + } + + @Override + void writeBytesToImpl(byte[] dest, int offset, int maxLength) { + for (int i = 0; i < maxLength; i++) { + dest[offset + i] = (byte) (hash >> (i * 8)); + } + } + + @Override + boolean equalsSameBits(HashCode that) { + return hash == that.asInt(); + } + + private static final long serialVersionUID = 0; + } + + /** + * Creates a 64-bit {@code HashCode} representation of the given long value. The underlying bytes + * are interpreted in little endian order. + * + * @since 15.0 (since 12.0 in HashCodes) + */ + public static HashCode fromLong(long hash) { + return new LongHashCode(hash); + } + + private static final class LongHashCode extends HashCode implements Serializable { + final long hash; + + LongHashCode(long hash) { + this.hash = hash; + } + + @Override + public int bits() { + return 64; + } + + @Override + public byte[] asBytes() { + return new byte[] { + (byte) hash, + (byte) (hash >> 8), + (byte) (hash >> 16), + (byte) (hash >> 24), + (byte) (hash >> 32), + (byte) (hash >> 40), + (byte) (hash >> 48), + (byte) (hash >> 56) + }; + } + + @Override + public int asInt() { + return (int) hash; + } + + @Override + public long asLong() { + return hash; + } + + @Override + public long padToLong() { + return hash; + } + + @Override + void writeBytesToImpl(byte[] dest, int offset, int maxLength) { + for (int i = 0; i < maxLength; i++) { + dest[offset + i] = (byte) (hash >> (i * 8)); + } + } + + @Override + boolean equalsSameBits(HashCode that) { + return hash == that.asLong(); + } + + private static final long serialVersionUID = 0; + } + + /** + * Creates a {@code HashCode} from a byte array. The array is defensively copied to preserve the + * immutability contract of {@code HashCode}. The array cannot be empty. + * + * @since 15.0 (since 12.0 in HashCodes) + */ + public static HashCode fromBytes(byte[] bytes) { + checkArgument(bytes.length >= 1, "A HashCode must contain at least 1 byte."); + return fromBytesNoCopy(bytes.clone()); + } + + /** + * Creates a {@code HashCode} from a byte array. The array is not copied defensively, so it + * must be handed-off so as to preserve the immutability contract of {@code HashCode}. + */ + static HashCode fromBytesNoCopy(byte[] bytes) { + return new BytesHashCode(bytes); + } + + private static final class BytesHashCode extends HashCode implements Serializable { + final byte[] bytes; + + BytesHashCode(byte[] bytes) { + this.bytes = checkNotNull(bytes); + } + + @Override + public int bits() { + return bytes.length * 8; + } + + @Override + public byte[] asBytes() { + return bytes.clone(); + } + + @Override + public int asInt() { + checkState( + bytes.length >= 4, + "HashCode#asInt() requires >= 4 bytes (it only has %s bytes).", + bytes.length); + return (bytes[0] & 0xFF) + | ((bytes[1] & 0xFF) << 8) + | ((bytes[2] & 0xFF) << 16) + | ((bytes[3] & 0xFF) << 24); + } + + @Override + public long asLong() { + checkState( + bytes.length >= 8, + "HashCode#asLong() requires >= 8 bytes (it only has %s bytes).", + bytes.length); + return padToLong(); + } + + @Override + public long padToLong() { + long retVal = (bytes[0] & 0xFF); + for (int i = 1; i < Math.min(bytes.length, 8); i++) { + retVal |= (bytes[i] & 0xFFL) << (i * 8); + } + return retVal; + } + + @Override + void writeBytesToImpl(byte[] dest, int offset, int maxLength) { + System.arraycopy(bytes, 0, dest, offset, maxLength); + } + + @Override + byte[] getBytesInternal() { + return bytes; + } + + @Override + boolean equalsSameBits(HashCode that) { + // We don't use MessageDigest.isEqual() here because its contract does not guarantee + // constant-time evaluation (no short-circuiting). + if (this.bytes.length != that.getBytesInternal().length) { + return false; + } + + boolean areEqual = true; + for (int i = 0; i < this.bytes.length; i++) { + areEqual &= (this.bytes[i] == that.getBytesInternal()[i]); + } + return areEqual; + } + + private static final long serialVersionUID = 0; + } + + /** + * Creates a {@code HashCode} from a hexadecimal ({@code base 16}) encoded string. The string must + * be at least 2 characters long, and contain only valid, lower-cased hexadecimal characters. + * + *

This method accepts the exact format generated by {@link #toString}. If you require more + * lenient {@code base 16} decoding, please use {@link com.google.common.io.BaseEncoding#decode} + * (and pass the result to {@link #fromBytes}). + * + * @since 15.0 + */ + public static HashCode fromString(String string) { + checkArgument( + string.length() >= 2, "input string (%s) must have at least 2 characters", string); + checkArgument( + string.length() % 2 == 0, + "input string (%s) must have an even number of characters", + string); + + byte[] bytes = new byte[string.length() / 2]; + for (int i = 0; i < string.length(); i += 2) { + int ch1 = decode(string.charAt(i)) << 4; + int ch2 = decode(string.charAt(i + 1)); + bytes[i / 2] = (byte) (ch1 + ch2); + } + return fromBytesNoCopy(bytes); + } + + private static int decode(char ch) { + if (ch >= '0' && ch <= '9') { + return ch - '0'; + } + if (ch >= 'a' && ch <= 'f') { + return ch - 'a' + 10; + } + throw new IllegalArgumentException("Illegal hexadecimal character: " + ch); + } + + /** + * Returns {@code true} if {@code object} is a {@link HashCode} instance with the identical byte + * representation to this hash code. + * + *

Security note: this method uses a constant-time (not short-circuiting) implementation + * to protect against timing attacks. + */ + @Override + public final boolean equals(Object object) { + if (object instanceof HashCode) { + HashCode that = (HashCode) object; + return bits() == that.bits() && equalsSameBits(that); + } + return false; + } + + /** + * Returns a "Java hash code" for this {@code HashCode} instance; this is well-defined (so, for + * example, you can safely put {@code HashCode} instances into a {@code HashSet}) but is otherwise + * probably not what you want to use. + */ + @Override + public final int hashCode() { + // If we have at least 4 bytes (32 bits), just take the first 4 bytes. Since this is + // already a (presumably) high-quality hash code, any four bytes of it will do. + if (bits() >= 32) { + return asInt(); + } + // If we have less than 4 bytes, use them all. + byte[] bytes = getBytesInternal(); + int val = (bytes[0] & 0xFF); + for (int i = 1; i < bytes.length; i++) { + val |= ((bytes[i] & 0xFF) << (i * 8)); + } + return val; + } + + /** + * Returns a string containing each byte of {@link #asBytes}, in order, as a two-digit unsigned + * hexadecimal number in lower case. + * + *

Note that if the output is considered to be a single hexadecimal number, this hash code's + * bytes are the big-endian representation of that number. This may be surprising since + * everything else in the hashing API uniformly treats multibyte values as little-endian. But this + * format conveniently matches that of utilities such as the UNIX {@code md5sum} command. + * + *

To create a {@code HashCode} from its string representation, see {@link #fromString}. + */ + @Override + public final String toString() { + byte[] bytes = getBytesInternal(); + StringBuilder sb = new StringBuilder(2 * bytes.length); + for (byte b : bytes) { + sb.append(hexDigits[(b >> 4) & 0xf]).append(hexDigits[b & 0xf]); + } + return sb.toString(); + } + + private static final char[] hexDigits = "0123456789abcdef".toCharArray(); +} diff --git a/src/main/java/com/google/common/hash/HashFunction.java b/src/main/java/com/google/common/hash/HashFunction.java new file mode 100644 index 0000000..5be78c5 --- /dev/null +++ b/src/main/java/com/google/common/hash/HashFunction.java @@ -0,0 +1,220 @@ +/* + * Copyright (C) 2011 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.hash; + +import com.google.common.annotations.Beta; +import com.google.common.primitives.Ints; + +import java.nio.ByteBuffer; +import java.nio.charset.Charset; + +/** + * A hash function is a collision-averse pure function that maps an arbitrary block of data to a + * number called a hash code. + * + *

Definition

+ * + *

Unpacking this definition: + * + *

    + *
  • block of data: the input for a hash function is always, in concept, an ordered byte + * array. This hashing API accepts an arbitrary sequence of byte and multibyte values (via + * {@link Hasher}), but this is merely a convenience; these are always translated into raw + * byte sequences under the covers. + *
  • hash code: each hash function always yields hash codes of the same fixed bit length + * (given by {@link #bits}). For example, {@link Hashing#sha1} produces a 160-bit number, + * while {@link Hashing#murmur3_32()} yields only 32 bits. Because a {@code long} value is + * clearly insufficient to hold all hash code values, this API represents a hash code as an + * instance of {@link HashCode}. + *
  • pure function: the value produced must depend only on the input bytes, in the order + * they appear. Input data is never modified. {@link HashFunction} instances should always be + * stateless, and therefore thread-safe. + *
  • collision-averse: while it can't be helped that a hash function will sometimes + * produce the same hash code for distinct inputs (a "collision"), every hash function strives + * to some degree to make this unlikely. (Without this condition, a function that + * always returns zero could be called a hash function. It is not.) + *
+ * + *

Summarizing the last two points: "equal yield equal always; unequal yield unequal + * often." This is the most important characteristic of all hash functions. + * + *

Desirable properties

+ * + *

A high-quality hash function strives for some subset of the following virtues: + * + *

    + *
  • collision-resistant: while the definition above requires making at least some + * token attempt, one measure of the quality of a hash function is how well it succeeds + * at this goal. Important note: it may be easy to achieve the theoretical minimum collision + * rate when using completely random sample input. The true test of a hash function is + * how it performs on representative real-world data, which tends to contain many hidden + * patterns and clumps. The goal of a good hash function is to stamp these patterns out as + * thoroughly as possible. + *
  • bit-dispersing: masking out any single bit from a hash code should yield only + * the expected twofold increase to all collision rates. Informally, the "information" + * in the hash code should be as evenly "spread out" through the hash code's bits as possible. + * The result is that, for example, when choosing a bucket in a hash table of size 2^8, + * any eight bits could be consistently used. + *
  • cryptographic: certain hash functions such as {@link Hashing#sha512} are designed to + * make it as infeasible as possible to reverse-engineer the input that produced a given hash + * code, or even to discover any two distinct inputs that yield the same result. These + * are called cryptographic hash functions. But, whenever it is learned that either of + * these feats has become computationally feasible, the function is deemed "broken" and should + * no longer be used for secure purposes. (This is the likely eventual fate of all + * cryptographic hashes.) + *
  • fast: perhaps self-explanatory, but often the most important consideration. + *
+ * + *

Providing input to a hash function

+ * + *

The primary way to provide the data that your hash function should act on is via a {@link + * Hasher}. Obtain a new hasher from the hash function using {@link #newHasher}, "push" the relevant + * data into it using methods like {@link Hasher#putBytes(byte[])}, and finally ask for the {@code + * HashCode} when finished using {@link Hasher#hash}. (See an {@linkplain #newHasher example} of + * this.) + * + *

If all you want to hash is a single byte array, string or {@code long} value, there are + * convenient shortcut methods defined directly on {@link HashFunction} to make this easier. + * + *

Hasher accepts primitive data types, but can also accept any Object of type {@code T} provided + * that you implement a {@link Funnel}{@code } to specify how to "feed" data from that object + * into the function. (See {@linkplain Hasher#putObject an example} of this.) + * + *

Compatibility note: Throughout this API, multibyte values are always interpreted in + * little-endian order. That is, hashing the byte array {@code {0x01, 0x02, 0x03, 0x04}} is + * equivalent to hashing the {@code int} value {@code 0x04030201}. If this isn't what you need, + * methods such as {@link Integer#reverseBytes} and {@link Ints#toByteArray} will help. + * + *

Relationship to {@link Object#hashCode}

+ * + *

Java's baked-in concept of hash codes is constrained to 32 bits, and provides no separation + * between hash algorithms and the data they act on, so alternate hash algorithms can't be easily + * substituted. Also, implementations of {@code hashCode} tend to be poor-quality, in part because + * they end up depending on other existing poor-quality {@code hashCode} implementations, + * including those in many JDK classes. + * + *

{@code Object.hashCode} implementations tend to be very fast, but have weak collision + * prevention and no expectation of bit dispersion. This leaves them perfectly suitable for + * use in hash tables, because extra collisions cause only a slight performance hit, while poor bit + * dispersion is easily corrected using a secondary hash function (which all reasonable hash table + * implementations in Java use). For the many uses of hash functions beyond data structures, + * however, {@code Object.hashCode} almost always falls short -- hence this library. + * + * @author Kevin Bourrillion + * @since 11.0 + */ +@Beta +public interface HashFunction { + /** + * Begins a new hash code computation by returning an initialized, stateful {@code Hasher} + * instance that is ready to receive data. Example: + * + *

{@code
+   * HashFunction hf = Hashing.md5();
+   * HashCode hc = hf.newHasher()
+   *     .putLong(id)
+   *     .putBoolean(isActive)
+   *     .hash();
+   * }
+ */ + Hasher newHasher(); + + /** + * Begins a new hash code computation as {@link #newHasher()}, but provides a hint of the expected + * size of the input (in bytes). This is only important for non-streaming hash functions (hash + * functions that need to buffer their whole input before processing any of it). + */ + Hasher newHasher(int expectedInputSize); + + /** + * Shortcut for {@code newHasher().putInt(input).hash()}; returns the hash code for the given + * {@code int} value, interpreted in little-endian byte order. The implementation might + * perform better than its longhand equivalent, but should not perform worse. + * + * @since 12.0 + */ + HashCode hashInt(int input); + + /** + * Shortcut for {@code newHasher().putLong(input).hash()}; returns the hash code for the given + * {@code long} value, interpreted in little-endian byte order. The implementation might + * perform better than its longhand equivalent, but should not perform worse. + */ + HashCode hashLong(long input); + + /** + * Shortcut for {@code newHasher().putBytes(input).hash()}. The implementation might + * perform better than its longhand equivalent, but should not perform worse. + */ + HashCode hashBytes(byte[] input); + + /** + * Shortcut for {@code newHasher().putBytes(input, off, len).hash()}. The implementation + * might perform better than its longhand equivalent, but should not perform worse. + * + * @throws IndexOutOfBoundsException if {@code off < 0} or {@code off + len > bytes.length} or + * {@code len < 0} + */ + HashCode hashBytes(byte[] input, int off, int len); + + /** + * Shortcut for {@code newHasher().putBytes(input).hash()}. The implementation might + * perform better than its longhand equivalent, but should not perform worse. + * + * @since 23.0 + */ + HashCode hashBytes(ByteBuffer input); + + /** + * Shortcut for {@code newHasher().putUnencodedChars(input).hash()}. The implementation + * might perform better than its longhand equivalent, but should not perform worse. Note + * that no character encoding is performed; the low byte and high byte of each {@code char} are + * hashed directly (in that order). + * + *

Warning: This method will produce different output than most other languages do when + * running the same hash function on the equivalent input. For cross-language compatibility, use + * {@link #hashString}, usually with a charset of UTF-8. For other use cases, use {@code + * hashUnencodedChars}. + * + * @since 15.0 (since 11.0 as hashString(CharSequence)). + */ + HashCode hashUnencodedChars(CharSequence input); + + /** + * Shortcut for {@code newHasher().putString(input, charset).hash()}. Characters are encoded using + * the given {@link Charset}. The implementation might perform better than its longhand + * equivalent, but should not perform worse. + * + *

Warning: This method, which reencodes the input before hashing it, is useful only for + * cross-language compatibility. For other use cases, prefer {@link #hashUnencodedChars}, which is + * faster, produces the same output across Java releases, and hashes every {@code char} in the + * input, even if some are invalid. + */ + HashCode hashString(CharSequence input, Charset charset); + + /** + * Shortcut for {@code newHasher().putObject(instance, funnel).hash()}. The implementation + * might perform better than its longhand equivalent, but should not perform worse. + * + * @since 14.0 + */ + HashCode hashObject(T instance, Funnel funnel); + + /** + * Returns the number of bits (a multiple of 32) that each hash code produced by this hash + * function has. + */ + int bits(); +} diff --git a/src/main/java/com/google/common/hash/Hasher.java b/src/main/java/com/google/common/hash/Hasher.java new file mode 100644 index 0000000..54c8d3a --- /dev/null +++ b/src/main/java/com/google/common/hash/Hasher.java @@ -0,0 +1,139 @@ +/* + * Copyright (C) 2011 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.hash; + +import com.google.common.annotations.Beta; + +import java.nio.ByteBuffer; +import java.nio.charset.Charset; + +/** + * A {@link PrimitiveSink} that can compute a hash code after reading the input. Each hasher should + * translate all multibyte values ({@link #putInt(int)}, {@link #putLong(long)}, etc) to bytes in + * little-endian order. + * + *

Warning: The result of calling any methods after calling {@link #hash} is undefined. + * + *

Warning: Using a specific character encoding when hashing a {@link CharSequence} with + * {@link #putString(CharSequence, Charset)} is generally only useful for cross-language + * compatibility (otherwise prefer {@link #putUnencodedChars}). However, the character encodings + * must be identical across languages. Also beware that {@link Charset} definitions may occasionally + * change between Java releases. + * + *

Warning: Chunks of data that are put into the {@link Hasher} are not delimited. The + * resulting {@link HashCode} is dependent only on the bytes inserted, and the order in which they + * were inserted, not how those bytes were chunked into discrete put() operations. For example, the + * following three expressions all generate colliding hash codes: + * + *

{@code
+ * newHasher().putByte(b1).putByte(b2).putByte(b3).hash()
+ * newHasher().putByte(b1).putBytes(new byte[] { b2, b3 }).hash()
+ * newHasher().putBytes(new byte[] { b1, b2, b3 }).hash()
+ * }
+ * + *

If you wish to avoid this, you should either prepend or append the size of each chunk. Keep in + * mind that when dealing with char sequences, the encoded form of two concatenated char sequences + * is not equivalent to the concatenation of their encoded form. Therefore, {@link + * #putString(CharSequence, Charset)} should only be used consistently with complete + * sequences and not broken into chunks. + * + * @author Kevin Bourrillion + * @since 11.0 + */ +@Beta + +public interface Hasher extends PrimitiveSink { + @Override + Hasher putByte(byte b); + + @Override + Hasher putBytes(byte[] bytes); + + @Override + Hasher putBytes(byte[] bytes, int off, int len); + + @Override + Hasher putBytes(ByteBuffer bytes); + + @Override + Hasher putShort(short s); + + @Override + Hasher putInt(int i); + + @Override + Hasher putLong(long l); + + /** Equivalent to {@code putInt(Float.floatToRawIntBits(f))}. */ + @Override + Hasher putFloat(float f); + + /** Equivalent to {@code putLong(Double.doubleToRawLongBits(d))}. */ + @Override + Hasher putDouble(double d); + + /** Equivalent to {@code putByte(b ? (byte) 1 : (byte) 0)}. */ + @Override + Hasher putBoolean(boolean b); + + @Override + Hasher putChar(char c); + + /** + * Equivalent to processing each {@code char} value in the {@code CharSequence}, in order. In + * other words, no character encoding is performed; the low byte and high byte of each {@code + * char} are hashed directly (in that order). The input must not be updated while this method is + * in progress. + * + *

Warning: This method will produce different output than most other languages do when + * running the same hash function on the equivalent input. For cross-language compatibility, use + * {@link #putString}, usually with a charset of UTF-8. For other use cases, use {@code + * putUnencodedChars}. + * + * @since 15.0 (since 11.0 as putString(CharSequence)). + */ + @Override + Hasher putUnencodedChars(CharSequence charSequence); + + /** + * Equivalent to {@code putBytes(charSequence.toString().getBytes(charset))}. + * + *

Warning: This method, which reencodes the input before hashing it, is useful only for + * cross-language compatibility. For other use cases, prefer {@link #putUnencodedChars}, which is + * faster, produces the same output across Java releases, and hashes every {@code char} in the + * input, even if some are invalid. + */ + @Override + Hasher putString(CharSequence charSequence, Charset charset); + + /** A simple convenience for {@code funnel.funnel(object, this)}. */ + Hasher putObject(T instance, Funnel funnel); + + /** + * Computes a hash code based on the data that have been provided to this hasher. The result is + * unspecified if this method is called more than once on the same instance. + */ + HashCode hash(); + + /** + * {@inheritDoc} + * + * @deprecated This returns {@link Object#hashCode()}; you almost certainly mean to call {@code + * hash().asInt()}. + */ + @Override + @Deprecated + int hashCode(); +} diff --git a/src/main/java/com/google/common/hash/Hashing.java b/src/main/java/com/google/common/hash/Hashing.java new file mode 100644 index 0000000..6f00aed --- /dev/null +++ b/src/main/java/com/google/common/hash/Hashing.java @@ -0,0 +1,678 @@ +/* + * Copyright (C) 2011 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.hash; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.annotations.Beta; + +import java.security.Key; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Iterator; +import java.util.List; +import java.util.zip.Adler32; +import java.util.zip.CRC32; +import java.util.zip.Checksum; +import javax.crypto.spec.SecretKeySpec; + + +/** + * Static methods to obtain {@link HashFunction} instances, and other static hashing-related + * utilities. + * + *

A comparison of the various hash functions can be found here. + * + * @author Kevin Bourrillion + * @author Dimitris Andreou + * @author Kurt Alfred Kluever + * @since 11.0 + */ +@Beta +public final class Hashing { + /** + * Returns a general-purpose, temporary-use, non-cryptographic hash function. The algorithm + * the returned function implements is unspecified and subject to change without notice. + * + *

Warning: a new random seed for these functions is chosen each time the {@code + * Hashing} class is loaded. Do not use this method if hash codes may escape the current + * process in any way, for example being sent over RPC, or saved to disk. For a general-purpose, + * non-cryptographic hash function that will never change behavior, we suggest {@link + * #murmur3_128}. + * + *

Repeated calls to this method on the same loaded {@code Hashing} class, using the same value + * for {@code minimumBits}, will return identically-behaving {@link HashFunction} instances. + * + * @param minimumBits a positive integer (can be arbitrarily large) + * @return a hash function, described above, that produces hash codes of length {@code + * minimumBits} or greater + */ + public static HashFunction goodFastHash(int minimumBits) { + int bits = checkPositiveAndMakeMultipleOf32(minimumBits); + + if (bits == 32) { + return Murmur3_32HashFunction.GOOD_FAST_HASH_32; + } + if (bits <= 128) { + return Murmur3_128HashFunction.GOOD_FAST_HASH_128; + } + + // Otherwise, join together some 128-bit murmur3s + int hashFunctionsNeeded = (bits + 127) / 128; + HashFunction[] hashFunctions = new HashFunction[hashFunctionsNeeded]; + hashFunctions[0] = Murmur3_128HashFunction.GOOD_FAST_HASH_128; + int seed = GOOD_FAST_HASH_SEED; + for (int i = 1; i < hashFunctionsNeeded; i++) { + seed += 1500450271; // a prime; shouldn't matter + hashFunctions[i] = murmur3_128(seed); + } + return new ConcatenatedHashFunction(hashFunctions); + } + + /** + * Used to randomize {@link #goodFastHash} instances, so that programs which persist anything + * dependent on the hash codes they produce will fail sooner. + */ + @SuppressWarnings("GoodTime") // reading system time without TimeSource + static final int GOOD_FAST_HASH_SEED = (int) System.currentTimeMillis(); + + /** + * Returns a hash function implementing the 32-bit murmur3 + * algorithm, x86 variant (little-endian variant), using the given seed value. + * + *

The exact C++ equivalent is the MurmurHash3_x86_32 function (Murmur3A). + */ + public static HashFunction murmur3_32(int seed) { + return new Murmur3_32HashFunction(seed); + } + + /** + * Returns a hash function implementing the 32-bit murmur3 + * algorithm, x86 variant (little-endian variant), using a seed value of zero. + * + *

The exact C++ equivalent is the MurmurHash3_x86_32 function (Murmur3A). + */ + public static HashFunction murmur3_32() { + return Murmur3_32HashFunction.MURMUR3_32; + } + + /** + * Returns a hash function implementing the 128-bit murmur3 + * algorithm, x64 variant (little-endian variant), using the given seed value. + * + *

The exact C++ equivalent is the MurmurHash3_x64_128 function (Murmur3F). + */ + public static HashFunction murmur3_128(int seed) { + return new Murmur3_128HashFunction(seed); + } + + /** + * Returns a hash function implementing the 128-bit murmur3 + * algorithm, x64 variant (little-endian variant), using a seed value of zero. + * + *

The exact C++ equivalent is the MurmurHash3_x64_128 function (Murmur3F). + */ + public static HashFunction murmur3_128() { + return Murmur3_128HashFunction.MURMUR3_128; + } + + /** + * Returns a hash function implementing the 64-bit + * SipHash-2-4 algorithm using a seed value of {@code k = 00 01 02 ...}. + * + * @since 15.0 + */ + public static HashFunction sipHash24() { + return SipHashFunction.SIP_HASH_24; + } + + /** + * Returns a hash function implementing the 64-bit + * SipHash-2-4 algorithm using the given seed. + * + * @since 15.0 + */ + public static HashFunction sipHash24(long k0, long k1) { + return new SipHashFunction(2, 4, k0, k1); + } + + /** + * Returns a hash function implementing the MD5 hash algorithm (128 hash bits). + * + * @deprecated If you must interoperate with a system that requires MD5, then use this method, + * despite its deprecation. But if you can choose your hash function, avoid MD5, which is + * neither fast nor secure. As of January 2017, we suggest: + *

    + *
  • For security: + * {@link Hashing#sha256} or a higher-level API. + *
  • For speed: {@link Hashing#goodFastHash}, though see its docs for caveats. + *
+ */ + @Deprecated + public static HashFunction md5() { + return Md5Holder.MD5; + } + + private static class Md5Holder { + static final HashFunction MD5 = new MessageDigestHashFunction("MD5", "Hashing.md5()"); + } + + /** + * Returns a hash function implementing the SHA-1 algorithm (160 hash bits). + * + * @deprecated If you must interoperate with a system that requires SHA-1, then use this method, + * despite its deprecation. But if you can choose your hash function, avoid SHA-1, which is + * neither fast nor secure. As of January 2017, we suggest: + *
    + *
  • For security: + * {@link Hashing#sha256} or a higher-level API. + *
  • For speed: {@link Hashing#goodFastHash}, though see its docs for caveats. + *
+ */ + @Deprecated + public static HashFunction sha1() { + return Sha1Holder.SHA_1; + } + + private static class Sha1Holder { + static final HashFunction SHA_1 = new MessageDigestHashFunction("SHA-1", "Hashing.sha1()"); + } + + /** Returns a hash function implementing the SHA-256 algorithm (256 hash bits). */ + public static HashFunction sha256() { + return Sha256Holder.SHA_256; + } + + private static class Sha256Holder { + static final HashFunction SHA_256 = + new MessageDigestHashFunction("SHA-256", "Hashing.sha256()"); + } + + /** + * Returns a hash function implementing the SHA-384 algorithm (384 hash bits). + * + * @since 19.0 + */ + public static HashFunction sha384() { + return Sha384Holder.SHA_384; + } + + private static class Sha384Holder { + static final HashFunction SHA_384 = + new MessageDigestHashFunction("SHA-384", "Hashing.sha384()"); + } + + /** Returns a hash function implementing the SHA-512 algorithm (512 hash bits). */ + public static HashFunction sha512() { + return Sha512Holder.SHA_512; + } + + private static class Sha512Holder { + static final HashFunction SHA_512 = + new MessageDigestHashFunction("SHA-512", "Hashing.sha512()"); + } + + /** + * Returns a hash function implementing the Message Authentication Code (MAC) algorithm, using the + * MD5 (128 hash bits) hash function and the given secret key. + * + * + * @param key the secret key + * @throws IllegalArgumentException if the given key is inappropriate for initializing this MAC + * @since 20.0 + */ + public static HashFunction hmacMd5(Key key) { + return new MacHashFunction("HmacMD5", key, hmacToString("hmacMd5", key)); + } + + /** + * Returns a hash function implementing the Message Authentication Code (MAC) algorithm, using the + * MD5 (128 hash bits) hash function and a {@link SecretKeySpec} created from the given byte array + * and the MD5 algorithm. + * + * + * @param key the key material of the secret key + * @since 20.0 + */ + public static HashFunction hmacMd5(byte[] key) { + return hmacMd5(new SecretKeySpec(checkNotNull(key), "HmacMD5")); + } + + /** + * Returns a hash function implementing the Message Authentication Code (MAC) algorithm, using the + * SHA-1 (160 hash bits) hash function and the given secret key. + * + * + * @param key the secret key + * @throws IllegalArgumentException if the given key is inappropriate for initializing this MAC + * @since 20.0 + */ + public static HashFunction hmacSha1(Key key) { + return new MacHashFunction("HmacSHA1", key, hmacToString("hmacSha1", key)); + } + + /** + * Returns a hash function implementing the Message Authentication Code (MAC) algorithm, using the + * SHA-1 (160 hash bits) hash function and a {@link SecretKeySpec} created from the given byte + * array and the SHA-1 algorithm. + * + * + * @param key the key material of the secret key + * @since 20.0 + */ + public static HashFunction hmacSha1(byte[] key) { + return hmacSha1(new SecretKeySpec(checkNotNull(key), "HmacSHA1")); + } + + /** + * Returns a hash function implementing the Message Authentication Code (MAC) algorithm, using the + * SHA-256 (256 hash bits) hash function and the given secret key. + * + * + * @param key the secret key + * @throws IllegalArgumentException if the given key is inappropriate for initializing this MAC + * @since 20.0 + */ + public static HashFunction hmacSha256(Key key) { + return new MacHashFunction("HmacSHA256", key, hmacToString("hmacSha256", key)); + } + + /** + * Returns a hash function implementing the Message Authentication Code (MAC) algorithm, using the + * SHA-256 (256 hash bits) hash function and a {@link SecretKeySpec} created from the given byte + * array and the SHA-256 algorithm. + * + * + * @param key the key material of the secret key + * @since 20.0 + */ + public static HashFunction hmacSha256(byte[] key) { + return hmacSha256(new SecretKeySpec(checkNotNull(key), "HmacSHA256")); + } + + /** + * Returns a hash function implementing the Message Authentication Code (MAC) algorithm, using the + * SHA-512 (512 hash bits) hash function and the given secret key. + * + * + * @param key the secret key + * @throws IllegalArgumentException if the given key is inappropriate for initializing this MAC + * @since 20.0 + */ + public static HashFunction hmacSha512(Key key) { + return new MacHashFunction("HmacSHA512", key, hmacToString("hmacSha512", key)); + } + + /** + * Returns a hash function implementing the Message Authentication Code (MAC) algorithm, using the + * SHA-512 (512 hash bits) hash function and a {@link SecretKeySpec} created from the given byte + * array and the SHA-512 algorithm. + * + * + * @param key the key material of the secret key + * @since 20.0 + */ + public static HashFunction hmacSha512(byte[] key) { + return hmacSha512(new SecretKeySpec(checkNotNull(key), "HmacSHA512")); + } + + private static String hmacToString(String methodName, Key key) { + return String.format( + "Hashing.%s(Key[algorithm=%s, format=%s])", + methodName, key.getAlgorithm(), key.getFormat()); + } + + /** + * Returns a hash function implementing the CRC32C checksum algorithm (32 hash bits) as described + * by RFC 3720, Section 12.1. + * + *

This function is best understood as a checksum rather than a true hash function. + * + * @since 18.0 + */ + public static HashFunction crc32c() { + return Crc32cHashFunction.CRC_32_C; + } + + /** + * Returns a hash function implementing the CRC-32 checksum algorithm (32 hash bits). + * + *

To get the {@code long} value equivalent to {@link Checksum#getValue()} for a {@code + * HashCode} produced by this function, use {@link HashCode#padToLong()}. + * + *

This function is best understood as a checksum rather than a true hash function. + * + * @since 14.0 + */ + public static HashFunction crc32() { + return ChecksumType.CRC_32.hashFunction; + } + + /** + * Returns a hash function implementing the Adler-32 checksum algorithm (32 hash bits). + * + *

To get the {@code long} value equivalent to {@link Checksum#getValue()} for a {@code + * HashCode} produced by this function, use {@link HashCode#padToLong()}. + * + *

This function is best understood as a checksum rather than a true hash function. + * + * @since 14.0 + */ + public static HashFunction adler32() { + return ChecksumType.ADLER_32.hashFunction; + } + + + enum ChecksumType implements ImmutableSupplier { + CRC_32("Hashing.crc32()") { + @Override + public Checksum get() { + return new CRC32(); + } + }, + ADLER_32("Hashing.adler32()") { + @Override + public Checksum get() { + return new Adler32(); + } + }; + + public final HashFunction hashFunction; + + ChecksumType(String toString) { + this.hashFunction = new ChecksumHashFunction(this, 32, toString); + } + } + + /** + * Returns a hash function implementing FarmHash's Fingerprint64, an open-source algorithm. + * + *

This is designed for generating persistent fingerprints of strings. It isn't + * cryptographically secure, but it produces a high-quality hash with fewer collisions than some + * alternatives we've used in the past. + * + *

FarmHash fingerprints are encoded by {@link HashCode#asBytes} in little-endian order. This + * means {@link HashCode#asLong} is guaranteed to return the same value that + * farmhash::Fingerprint64() would for the same input (when compared using {@link + * com.google.common.primitives.UnsignedLongs}'s encoding of 64-bit unsigned numbers). + * + *

This function is best understood as a fingerprint rather than a true + * hash function. + * + * @since 20.0 + */ + public static HashFunction farmHashFingerprint64() { + return FarmHashFingerprint64.FARMHASH_FINGERPRINT_64; + } + + /** + * Assigns to {@code hashCode} a "bucket" in the range {@code [0, buckets)}, in a uniform manner + * that minimizes the need for remapping as {@code buckets} grows. That is, {@code + * consistentHash(h, n)} equals: + * + *

    + *
  • {@code n - 1}, with approximate probability {@code 1/n} + *
  • {@code consistentHash(h, n - 1)}, otherwise (probability {@code 1 - 1/n}) + *
+ * + *

This method is suitable for the common use case of dividing work among buckets that meet the + * following conditions: + * + *

    + *
  • You want to assign the same fraction of inputs to each bucket. + *
  • When you reduce the number of buckets, you can accept that the most recently added + * buckets will be removed first. More concretely, if you are dividing traffic among tasks, + * you can decrease the number of tasks from 15 and 10, killing off the final 5 tasks, and + * {@code consistentHash} will handle it. If, however, you are dividing traffic among + * servers {@code alpha}, {@code bravo}, and {@code charlie} and you occasionally need to + * take each of the servers offline, {@code consistentHash} will be a poor fit: It provides + * no way for you to specify which of the three buckets is disappearing. Thus, if your + * buckets change from {@code [alpha, bravo, charlie]} to {@code [bravo, charlie]}, it will + * assign all the old {@code alpha} traffic to {@code bravo} and all the old {@code bravo} + * traffic to {@code charlie}, rather than letting {@code bravo} keep its traffic. + *
+ * + * + *

See the Wikipedia article on + * consistent hashing for more information. + */ + public static int consistentHash(HashCode hashCode, int buckets) { + return consistentHash(hashCode.padToLong(), buckets); + } + + /** + * Assigns to {@code input} a "bucket" in the range {@code [0, buckets)}, in a uniform manner that + * minimizes the need for remapping as {@code buckets} grows. That is, {@code consistentHash(h, + * n)} equals: + * + *

    + *
  • {@code n - 1}, with approximate probability {@code 1/n} + *
  • {@code consistentHash(h, n - 1)}, otherwise (probability {@code 1 - 1/n}) + *
+ * + *

This method is suitable for the common use case of dividing work among buckets that meet the + * following conditions: + * + *

    + *
  • You want to assign the same fraction of inputs to each bucket. + *
  • When you reduce the number of buckets, you can accept that the most recently added + * buckets will be removed first. More concretely, if you are dividing traffic among tasks, + * you can decrease the number of tasks from 15 and 10, killing off the final 5 tasks, and + * {@code consistentHash} will handle it. If, however, you are dividing traffic among + * servers {@code alpha}, {@code bravo}, and {@code charlie} and you occasionally need to + * take each of the servers offline, {@code consistentHash} will be a poor fit: It provides + * no way for you to specify which of the three buckets is disappearing. Thus, if your + * buckets change from {@code [alpha, bravo, charlie]} to {@code [bravo, charlie]}, it will + * assign all the old {@code alpha} traffic to {@code bravo} and all the old {@code bravo} + * traffic to {@code charlie}, rather than letting {@code bravo} keep its traffic. + *
+ * + * + *

See the Wikipedia article on + * consistent hashing for more information. + */ + public static int consistentHash(long input, int buckets) { + checkArgument(buckets > 0, "buckets must be positive: %s", buckets); + LinearCongruentialGenerator generator = new LinearCongruentialGenerator(input); + int candidate = 0; + int next; + + // Jump from bucket to bucket until we go out of range + while (true) { + next = (int) ((candidate + 1) / generator.nextDouble()); + if (next >= 0 && next < buckets) { + candidate = next; + } else { + return candidate; + } + } + } + + /** + * Returns a hash code, having the same bit length as each of the input hash codes, that combines + * the information of these hash codes in an ordered fashion. That is, whenever two equal hash + * codes are produced by two calls to this method, it is as likely as possible that each + * was computed from the same input hash codes in the same order. + * + * @throws IllegalArgumentException if {@code hashCodes} is empty, or the hash codes do not all + * have the same bit length + */ + public static HashCode combineOrdered(Iterable hashCodes) { + Iterator iterator = hashCodes.iterator(); + checkArgument(iterator.hasNext(), "Must be at least 1 hash code to combine."); + int bits = iterator.next().bits(); + byte[] resultBytes = new byte[bits / 8]; + for (HashCode hashCode : hashCodes) { + byte[] nextBytes = hashCode.asBytes(); + checkArgument( + nextBytes.length == resultBytes.length, "All hashcodes must have the same bit length."); + for (int i = 0; i < nextBytes.length; i++) { + resultBytes[i] = (byte) (resultBytes[i] * 37 ^ nextBytes[i]); + } + } + return HashCode.fromBytesNoCopy(resultBytes); + } + + /** + * Returns a hash code, having the same bit length as each of the input hash codes, that combines + * the information of these hash codes in an unordered fashion. That is, whenever two equal hash + * codes are produced by two calls to this method, it is as likely as possible that each + * was computed from the same input hash codes in some order. + * + * @throws IllegalArgumentException if {@code hashCodes} is empty, or the hash codes do not all + * have the same bit length + */ + public static HashCode combineUnordered(Iterable hashCodes) { + Iterator iterator = hashCodes.iterator(); + checkArgument(iterator.hasNext(), "Must be at least 1 hash code to combine."); + byte[] resultBytes = new byte[iterator.next().bits() / 8]; + for (HashCode hashCode : hashCodes) { + byte[] nextBytes = hashCode.asBytes(); + checkArgument( + nextBytes.length == resultBytes.length, "All hashcodes must have the same bit length."); + for (int i = 0; i < nextBytes.length; i++) { + resultBytes[i] += nextBytes[i]; + } + } + return HashCode.fromBytesNoCopy(resultBytes); + } + + /** Checks that the passed argument is positive, and ceils it to a multiple of 32. */ + static int checkPositiveAndMakeMultipleOf32(int bits) { + checkArgument(bits > 0, "Number of bits must be positive"); + return (bits + 31) & ~31; + } + + /** + * Returns a hash function which computes its hash code by concatenating the hash codes of the + * underlying hash functions together. This can be useful if you need to generate hash codes of a + * specific length. + * + *

For example, if you need 1024-bit hash codes, you could join two {@link Hashing#sha512} hash + * functions together: {@code Hashing.concatenating(Hashing.sha512(), Hashing.sha512())}. + * + * @since 19.0 + */ + public static HashFunction concatenating( + HashFunction first, HashFunction second, HashFunction... rest) { + // We can't use Lists.asList() here because there's no hash->collect dependency + List list = new ArrayList<>(); + list.add(first); + list.add(second); + list.addAll(Arrays.asList(rest)); + return new ConcatenatedHashFunction(list.toArray(new HashFunction[0])); + } + + /** + * Returns a hash function which computes its hash code by concatenating the hash codes of the + * underlying hash functions together. This can be useful if you need to generate hash codes of a + * specific length. + * + *

For example, if you need 1024-bit hash codes, you could join two {@link Hashing#sha512} hash + * functions together: {@code Hashing.concatenating(Hashing.sha512(), Hashing.sha512())}. + * + * @since 19.0 + */ + public static HashFunction concatenating(Iterable hashFunctions) { + checkNotNull(hashFunctions); + // We can't use Iterables.toArray() here because there's no hash->collect dependency + List list = new ArrayList<>(); + for (HashFunction hashFunction : hashFunctions) { + list.add(hashFunction); + } + checkArgument(list.size() > 0, "number of hash functions (%s) must be > 0", list.size()); + return new ConcatenatedHashFunction(list.toArray(new HashFunction[0])); + } + + private static final class ConcatenatedHashFunction extends AbstractCompositeHashFunction { + + private ConcatenatedHashFunction(HashFunction... functions) { + super(functions); + for (HashFunction function : functions) { + checkArgument( + function.bits() % 8 == 0, + "the number of bits (%s) in hashFunction (%s) must be divisible by 8", + function.bits(), + function); + } + } + + @Override + HashCode makeHash(Hasher[] hashers) { + byte[] bytes = new byte[bits() / 8]; + int i = 0; + for (Hasher hasher : hashers) { + HashCode newHash = hasher.hash(); + i += newHash.writeBytesTo(bytes, i, newHash.bits() / 8); + } + return HashCode.fromBytesNoCopy(bytes); + } + + @Override + public int bits() { + int bitSum = 0; + for (HashFunction function : functions) { + bitSum += function.bits(); + } + return bitSum; + } + + @Override + public boolean equals(Object object) { + if (object instanceof ConcatenatedHashFunction) { + ConcatenatedHashFunction other = (ConcatenatedHashFunction) object; + return Arrays.equals(functions, other.functions); + } + return false; + } + + @Override + public int hashCode() { + return Arrays.hashCode(functions); + } + } + + /** + * Linear CongruentialGenerator to use for consistent hashing. See + * http://en.wikipedia.org/wiki/Linear_congruential_generator + */ + private static final class LinearCongruentialGenerator { + private long state; + + public LinearCongruentialGenerator(long seed) { + this.state = seed; + } + + public double nextDouble() { + state = 2862933555777941757L * state + 1; + return ((double) ((int) (state >>> 33) + 1)) / 0x1.0p31; + } + } + + private Hashing() {} +} diff --git a/src/main/java/com/google/common/hash/HashingInputStream.java b/src/main/java/com/google/common/hash/HashingInputStream.java new file mode 100644 index 0000000..72f36fe --- /dev/null +++ b/src/main/java/com/google/common/hash/HashingInputStream.java @@ -0,0 +1,105 @@ +/* + * Copyright (C) 2013 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.hash; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.annotations.Beta; + +import java.io.FilterInputStream; +import java.io.IOException; +import java.io.InputStream; + +/** + * An {@link InputStream} that maintains a hash of the data read from it. + * + * @author Qian Huang + * @since 16.0 + */ +@Beta +public final class HashingInputStream extends FilterInputStream { + private final Hasher hasher; + + /** + * Creates an input stream that hashes using the given {@link HashFunction} and delegates all data + * read from it to the underlying {@link InputStream}. + * + *

The {@link InputStream} should not be read from before or after the hand-off. + */ + public HashingInputStream(HashFunction hashFunction, InputStream in) { + super(checkNotNull(in)); + this.hasher = checkNotNull(hashFunction.newHasher()); + } + + /** + * Reads the next byte of data from the underlying input stream and updates the hasher with the + * byte read. + */ + @Override + + public int read() throws IOException { + int b = in.read(); + if (b != -1) { + hasher.putByte((byte) b); + } + return b; + } + + /** + * Reads the specified bytes of data from the underlying input stream and updates the hasher with + * the bytes read. + */ + @Override + + public int read(byte[] bytes, int off, int len) throws IOException { + int numOfBytesRead = in.read(bytes, off, len); + if (numOfBytesRead != -1) { + hasher.putBytes(bytes, off, numOfBytesRead); + } + return numOfBytesRead; + } + + /** + * mark() is not supported for HashingInputStream + * + * @return {@code false} always + */ + @Override + public boolean markSupported() { + return false; + } + + /** mark() is not supported for HashingInputStream */ + @Override + public void mark(int readlimit) {} + + /** + * reset() is not supported for HashingInputStream. + * + * @throws IOException this operation is not supported + */ + @Override + public void reset() throws IOException { + throw new IOException("reset not supported"); + } + + /** + * Returns the {@link HashCode} based on the data read from this stream. The result is unspecified + * if this method is called more than once on the same instance. + */ + public HashCode hash() { + return hasher.hash(); + } +} diff --git a/src/main/java/com/google/common/hash/HashingOutputStream.java b/src/main/java/com/google/common/hash/HashingOutputStream.java new file mode 100644 index 0000000..7a1c8d8 --- /dev/null +++ b/src/main/java/com/google/common/hash/HashingOutputStream.java @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2011 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.hash; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.annotations.Beta; +import java.io.FilterOutputStream; +import java.io.IOException; +import java.io.OutputStream; + +/** + * An {@link OutputStream} that maintains a hash of the data written to it. + * + * @author Nick Piepmeier + * @since 16.0 + */ +@Beta +public final class HashingOutputStream extends FilterOutputStream { + private final Hasher hasher; + + /** + * Creates an output stream that hashes using the given {@link HashFunction}, and forwards all + * data written to it to the underlying {@link OutputStream}. + * + *

The {@link OutputStream} should not be written to before or after the hand-off. + */ + // TODO(user): Evaluate whether it makes sense to always piggyback the computation of a + // HashCode on an existing OutputStream, compared to creating a separate OutputStream that could + // be (optionally) be combined with another if needed (with something like + // MultiplexingOutputStream). + public HashingOutputStream(HashFunction hashFunction, OutputStream out) { + super(checkNotNull(out)); + this.hasher = checkNotNull(hashFunction.newHasher()); + } + + @Override + public void write(int b) throws IOException { + hasher.putByte((byte) b); + out.write(b); + } + + @Override + public void write(byte[] bytes, int off, int len) throws IOException { + hasher.putBytes(bytes, off, len); + out.write(bytes, off, len); + } + + /** + * Returns the {@link HashCode} based on the data written to this stream. The result is + * unspecified if this method is called more than once on the same instance. + */ + public HashCode hash() { + return hasher.hash(); + } + + // Overriding close() because FilterOutputStream's close() method pre-JDK8 has bad behavior: + // it silently ignores any exception thrown by flush(). Instead, just close the delegate stream. + // It should flush itself if necessary. + @Override + public void close() throws IOException { + out.close(); + } +} diff --git a/src/main/java/com/google/common/hash/ImmutableSupplier.java b/src/main/java/com/google/common/hash/ImmutableSupplier.java new file mode 100644 index 0000000..9232751 --- /dev/null +++ b/src/main/java/com/google/common/hash/ImmutableSupplier.java @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2018 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.hash; + +import com.google.common.base.Supplier; + + +/** + * Explicitly named subinterface of {@link Supplier}. + */ +interface ImmutableSupplier extends Supplier {} diff --git a/src/main/java/com/google/common/hash/LittleEndianByteArray.java b/src/main/java/com/google/common/hash/LittleEndianByteArray.java new file mode 100644 index 0000000..25e9aae --- /dev/null +++ b/src/main/java/com/google/common/hash/LittleEndianByteArray.java @@ -0,0 +1,143 @@ +/* + * Copyright (C) 2015 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.hash; + +import com.google.common.primitives.Longs; + +/** + * Utility functions for loading and storing values from a byte array. + * + * @author Kevin Damm + * @author Kyle Maddison + */ +final class LittleEndianByteArray { + + /** The instance that actually does the work; delegates to Unsafe or a pure-Java fallback. */ + private static final LittleEndianBytes byteArray; + + /** + * Load 8 bytes into long in a little endian manner, from the substring between position and + * position + 8. The array must have at least 8 bytes from offset (inclusive). + * + * @param input the input bytes + * @param offset the offset into the array at which to start + * @return a long of a concatenated 8 bytes + */ + static long load64(byte[] input, int offset) { + // We don't want this in production code as this is the most critical part of the loop. + assert input.length >= offset + 8; + // Delegates to the fast (unsafe) version or the fallback. + return byteArray.getLongLittleEndian(input, offset); + } + + /** + * Similar to load64, but allows offset + 8 > input.length, padding the result with zeroes. This + * has to explicitly reverse the order of the bytes as it packs them into the result which makes + * it slower than the native version. + * + * @param input the input bytes + * @param offset the offset into the array at which to start reading + * @param length the number of bytes from the input to read + * @return a long of a concatenated 8 bytes + */ + static long load64Safely(byte[] input, int offset, int length) { + long result = 0; + // Due to the way we shift, we can stop iterating once we've run out of data, the rest + // of the result already being filled with zeros. + + // This loop is critical to performance, so please check HashBenchmark if altering it. + int limit = Math.min(length, 8); + for (int i = 0; i < limit; i++) { + // Shift value left while iterating logically through the array. + result |= (input[offset + i] & 0xFFL) << (i * 8); + } + return result; + } + + /** + * Store 8 bytes into the provided array at the indicated offset, using the value provided. + * + * @param sink the output byte array + * @param offset the offset into the array at which to start writing + * @param value the value to write + */ + static void store64(byte[] sink, int offset, long value) { + // We don't want to assert in production code. + assert offset >= 0 && offset + 8 <= sink.length; + // Delegates to the fast (unsafe)version or the fallback. + byteArray.putLongLittleEndian(sink, offset, value); + } + + /** + * Load 4 bytes from the provided array at the indicated offset. + * + * @param source the input bytes + * @param offset the offset into the array at which to start + * @return the value found in the array in the form of a long + */ + static int load32(byte[] source, int offset) { + // TODO(user): Measure the benefit of delegating this to LittleEndianBytes also. + return (source[offset] & 0xFF) + | ((source[offset + 1] & 0xFF) << 8) + | ((source[offset + 2] & 0xFF) << 16) + | ((source[offset + 3] & 0xFF) << 24); + } + + /** + * Common interface for retrieving a 64-bit long from a little-endian byte array. + * + *

This abstraction allows us to use single-instruction load and put when available, or fall + * back on the slower approach of using Longs.fromBytes(byte...). + */ + private interface LittleEndianBytes { + long getLongLittleEndian(byte[] array, int offset); + + void putLongLittleEndian(byte[] array, int offset, long value); + } + + /** Fallback implementation for when Unsafe is not available in our current environment. */ + private enum JavaLittleEndianBytes implements LittleEndianBytes { + INSTANCE { + @Override + public long getLongLittleEndian(byte[] source, int offset) { + return Longs.fromBytes( + source[offset + 7], + source[offset + 6], + source[offset + 5], + source[offset + 4], + source[offset + 3], + source[offset + 2], + source[offset + 1], + source[offset]); + } + + @Override + public void putLongLittleEndian(byte[] sink, int offset, long value) { + long mask = 0xFFL; + for (int i = 0; i < 8; mask <<= 8, i++) { + sink[offset + i] = (byte) ((value & mask) >> (i * 8)); + } + } + }; + } + + static { + LittleEndianBytes theGetter = JavaLittleEndianBytes.INSTANCE; + byteArray = theGetter; + } + + /** Deter instantiation of this class. */ + private LittleEndianByteArray() {} +} diff --git a/src/main/java/com/google/common/hash/LongAddable.java b/src/main/java/com/google/common/hash/LongAddable.java new file mode 100644 index 0000000..a95eece --- /dev/null +++ b/src/main/java/com/google/common/hash/LongAddable.java @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2012 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.hash; + +/** + * Abstract interface for objects that can concurrently add longs. + * + * @author Louis Wasserman + */ +interface LongAddable { + void increment(); + + void add(long x); + + long sum(); +} diff --git a/src/main/java/com/google/common/hash/LongAddables.java b/src/main/java/com/google/common/hash/LongAddables.java new file mode 100644 index 0000000..bfb9f80 --- /dev/null +++ b/src/main/java/com/google/common/hash/LongAddables.java @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2012 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.hash; + +import com.google.common.base.Supplier; +import java.util.concurrent.atomic.AtomicLong; + +/** + * Source of {@link LongAddable} objects that deals with GWT, Unsafe, and all that. + * + * @author Louis Wasserman + */ +final class LongAddables { + private static final Supplier SUPPLIER; + + static { + SUPPLIER = PureJavaLongAddable::new; + } + + public static LongAddable create() { + return SUPPLIER.get(); + } + + private static final class PureJavaLongAddable extends AtomicLong implements LongAddable { + @Override + public void increment() { + getAndIncrement(); + } + + @Override + public void add(long x) { + getAndAdd(x); + } + + @Override + public long sum() { + return get(); + } + } +} diff --git a/src/main/java/com/google/common/hash/MacHashFunction.java b/src/main/java/com/google/common/hash/MacHashFunction.java new file mode 100644 index 0000000..64aabb9 --- /dev/null +++ b/src/main/java/com/google/common/hash/MacHashFunction.java @@ -0,0 +1,141 @@ +/* + * Copyright (C) 2015 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.hash; + +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.base.Preconditions.checkState; + + +import java.nio.ByteBuffer; +import java.security.InvalidKeyException; +import java.security.Key; +import java.security.NoSuchAlgorithmException; +import javax.crypto.Mac; + +/** + * {@link HashFunction} adapter for {@link Mac} instances. + * + * @author Kurt Alfred Kluever + */ + +final class MacHashFunction extends AbstractHashFunction { + + @SuppressWarnings("Immutable") // cloned before each use + private final Mac prototype; + + @SuppressWarnings("Immutable") // keys are immutable, but not provably so + private final Key key; + + private final String toString; + private final int bits; + private final boolean supportsClone; + + MacHashFunction(String algorithmName, Key key, String toString) { + this.prototype = getMac(algorithmName, key); + this.key = checkNotNull(key); + this.toString = checkNotNull(toString); + this.bits = prototype.getMacLength() * Byte.SIZE; + this.supportsClone = supportsClone(prototype); + } + + @Override + public int bits() { + return bits; + } + + private static boolean supportsClone(Mac mac) { + try { + mac.clone(); + return true; + } catch (CloneNotSupportedException e) { + return false; + } + } + + private static Mac getMac(String algorithmName, Key key) { + try { + Mac mac = Mac.getInstance(algorithmName); + mac.init(key); + return mac; + } catch (NoSuchAlgorithmException e) { + throw new IllegalStateException(e); + } catch (InvalidKeyException e) { + throw new IllegalArgumentException(e); + } + } + + @Override + public Hasher newHasher() { + if (supportsClone) { + try { + return new MacHasher((Mac) prototype.clone()); + } catch (CloneNotSupportedException e) { + // falls through + } + } + return new MacHasher(getMac(prototype.getAlgorithm(), key)); + } + + @Override + public String toString() { + return toString; + } + + /** Hasher that updates a {@link Mac} (message authentication code). */ + private static final class MacHasher extends AbstractByteHasher { + private final Mac mac; + private boolean done; + + private MacHasher(Mac mac) { + this.mac = mac; + } + + @Override + protected void update(byte b) { + checkNotDone(); + mac.update(b); + } + + @Override + protected void update(byte[] b) { + checkNotDone(); + mac.update(b); + } + + @Override + protected void update(byte[] b, int off, int len) { + checkNotDone(); + mac.update(b, off, len); + } + + @Override + protected void update(ByteBuffer bytes) { + checkNotDone(); + checkNotNull(bytes); + mac.update(bytes); + } + + private void checkNotDone() { + checkState(!done, "Cannot re-use a Hasher after calling hash() on it"); + } + + @Override + public HashCode hash() { + checkNotDone(); + done = true; + return HashCode.fromBytesNoCopy(mac.doFinal()); + } + } +} diff --git a/src/main/java/com/google/common/hash/MessageDigestHashFunction.java b/src/main/java/com/google/common/hash/MessageDigestHashFunction.java new file mode 100644 index 0000000..651fecd --- /dev/null +++ b/src/main/java/com/google/common/hash/MessageDigestHashFunction.java @@ -0,0 +1,164 @@ +/* + * Copyright (C) 2011 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.hash; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.base.Preconditions.checkState; + + +import java.io.Serializable; +import java.nio.ByteBuffer; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.Arrays; + +/** + * {@link HashFunction} adapter for {@link MessageDigest} instances. + * + * @author Kevin Bourrillion + * @author Dimitris Andreou + */ + +final class MessageDigestHashFunction extends AbstractHashFunction implements Serializable { + + @SuppressWarnings("Immutable") // cloned before each use + private final MessageDigest prototype; + + private final int bytes; + private final boolean supportsClone; + private final String toString; + + MessageDigestHashFunction(String algorithmName, String toString) { + this.prototype = getMessageDigest(algorithmName); + this.bytes = prototype.getDigestLength(); + this.toString = checkNotNull(toString); + this.supportsClone = supportsClone(prototype); + } + + MessageDigestHashFunction(String algorithmName, int bytes, String toString) { + this.toString = checkNotNull(toString); + this.prototype = getMessageDigest(algorithmName); + int maxLength = prototype.getDigestLength(); + checkArgument( + bytes >= 4 && bytes <= maxLength, "bytes (%s) must be >= 4 and < %s", bytes, maxLength); + this.bytes = bytes; + this.supportsClone = supportsClone(prototype); + } + + private static boolean supportsClone(MessageDigest digest) { + try { + digest.clone(); + return true; + } catch (CloneNotSupportedException e) { + return false; + } + } + + @Override + public int bits() { + return bytes * Byte.SIZE; + } + + @Override + public String toString() { + return toString; + } + + private static MessageDigest getMessageDigest(String algorithmName) { + try { + return MessageDigest.getInstance(algorithmName); + } catch (NoSuchAlgorithmException e) { + throw new AssertionError(e); + } + } + + @Override + public Hasher newHasher() { + if (supportsClone) { + try { + return new MessageDigestHasher((MessageDigest) prototype.clone(), bytes); + } catch (CloneNotSupportedException e) { + // falls through + } + } + return new MessageDigestHasher(getMessageDigest(prototype.getAlgorithm()), bytes); + } + + private static final class SerializedForm implements Serializable { + private final String algorithmName; + private final int bytes; + private final String toString; + + private SerializedForm(String algorithmName, int bytes, String toString) { + this.algorithmName = algorithmName; + this.bytes = bytes; + this.toString = toString; + } + + private Object readResolve() { + return new MessageDigestHashFunction(algorithmName, bytes, toString); + } + + private static final long serialVersionUID = 0; + } + + Object writeReplace() { + return new SerializedForm(prototype.getAlgorithm(), bytes, toString); + } + + /** Hasher that updates a message digest. */ + private static final class MessageDigestHasher extends AbstractByteHasher { + private final MessageDigest digest; + private final int bytes; + private boolean done; + + private MessageDigestHasher(MessageDigest digest, int bytes) { + this.digest = digest; + this.bytes = bytes; + } + + @Override + protected void update(byte b) { + checkNotDone(); + digest.update(b); + } + + @Override + protected void update(byte[] b, int off, int len) { + checkNotDone(); + digest.update(b, off, len); + } + + @Override + protected void update(ByteBuffer bytes) { + checkNotDone(); + digest.update(bytes); + } + + private void checkNotDone() { + checkState(!done, "Cannot re-use a Hasher after calling hash() on it"); + } + + @Override + public HashCode hash() { + checkNotDone(); + done = true; + return (bytes == digest.getDigestLength()) + ? HashCode.fromBytesNoCopy(digest.digest()) + : HashCode.fromBytesNoCopy(Arrays.copyOf(digest.digest(), bytes)); + } + } +} diff --git a/src/main/java/com/google/common/hash/Murmur3_128HashFunction.java b/src/main/java/com/google/common/hash/Murmur3_128HashFunction.java new file mode 100644 index 0000000..99c6278 --- /dev/null +++ b/src/main/java/com/google/common/hash/Murmur3_128HashFunction.java @@ -0,0 +1,215 @@ +/* + * Copyright (C) 2011 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +/* + * MurmurHash3 was written by Austin Appleby, and is placed in the public + * domain. The author hereby disclaims copyright to this source code. + */ + +/* + * Source: + * https://github.com/aappleby/smhasher/blob/master/src/MurmurHash3.cpp + * (Modified to adapt to Guava coding conventions and to use the HashFunction interface) + */ + +package com.google.common.hash; + +import static com.google.common.primitives.UnsignedBytes.toInt; + + +import java.io.Serializable; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + + +/** + * See MurmurHash3_x64_128 in the + * C++ implementation. + * + * @author Austin Appleby + * @author Dimitris Andreou + */ + +final class Murmur3_128HashFunction extends AbstractHashFunction implements Serializable { + static final HashFunction MURMUR3_128 = new Murmur3_128HashFunction(0); + + static final HashFunction GOOD_FAST_HASH_128 = + new Murmur3_128HashFunction(Hashing.GOOD_FAST_HASH_SEED); + + // TODO(user): when the shortcuts are implemented, update BloomFilterStrategies + private final int seed; + + Murmur3_128HashFunction(int seed) { + this.seed = seed; + } + + @Override + public int bits() { + return 128; + } + + @Override + public Hasher newHasher() { + return new Murmur3_128Hasher(seed); + } + + @Override + public String toString() { + return "Hashing.murmur3_128(" + seed + ")"; + } + + @Override + public boolean equals(Object object) { + if (object instanceof Murmur3_128HashFunction) { + Murmur3_128HashFunction other = (Murmur3_128HashFunction) object; + return seed == other.seed; + } + return false; + } + + @Override + public int hashCode() { + return getClass().hashCode() ^ seed; + } + + private static final class Murmur3_128Hasher extends AbstractStreamingHasher { + private static final int CHUNK_SIZE = 16; + private static final long C1 = 0x87c37b91114253d5L; + private static final long C2 = 0x4cf5ad432745937fL; + private long h1; + private long h2; + private int length; + + Murmur3_128Hasher(int seed) { + super(CHUNK_SIZE); + this.h1 = seed; + this.h2 = seed; + this.length = 0; + } + + @Override + protected void process(ByteBuffer bb) { + long k1 = bb.getLong(); + long k2 = bb.getLong(); + bmix64(k1, k2); + length += CHUNK_SIZE; + } + + private void bmix64(long k1, long k2) { + h1 ^= mixK1(k1); + + h1 = Long.rotateLeft(h1, 27); + h1 += h2; + h1 = h1 * 5 + 0x52dce729; + + h2 ^= mixK2(k2); + + h2 = Long.rotateLeft(h2, 31); + h2 += h1; + h2 = h2 * 5 + 0x38495ab5; + } + + @Override + protected void processRemaining(ByteBuffer bb) { + long k1 = 0; + long k2 = 0; + length += bb.remaining(); + switch (bb.remaining()) { + case 15: + k2 ^= (long) toInt(bb.get(14)) << 48; // fall through + case 14: + k2 ^= (long) toInt(bb.get(13)) << 40; // fall through + case 13: + k2 ^= (long) toInt(bb.get(12)) << 32; // fall through + case 12: + k2 ^= (long) toInt(bb.get(11)) << 24; // fall through + case 11: + k2 ^= (long) toInt(bb.get(10)) << 16; // fall through + case 10: + k2 ^= (long) toInt(bb.get(9)) << 8; // fall through + case 9: + k2 ^= (long) toInt(bb.get(8)); // fall through + case 8: + k1 ^= bb.getLong(); + break; + case 7: + k1 ^= (long) toInt(bb.get(6)) << 48; // fall through + case 6: + k1 ^= (long) toInt(bb.get(5)) << 40; // fall through + case 5: + k1 ^= (long) toInt(bb.get(4)) << 32; // fall through + case 4: + k1 ^= (long) toInt(bb.get(3)) << 24; // fall through + case 3: + k1 ^= (long) toInt(bb.get(2)) << 16; // fall through + case 2: + k1 ^= (long) toInt(bb.get(1)) << 8; // fall through + case 1: + k1 ^= (long) toInt(bb.get(0)); + break; + default: + throw new AssertionError("Should never get here."); + } + h1 ^= mixK1(k1); + h2 ^= mixK2(k2); + } + + @Override + public HashCode makeHash() { + h1 ^= length; + h2 ^= length; + + h1 += h2; + h2 += h1; + + h1 = fmix64(h1); + h2 = fmix64(h2); + + h1 += h2; + h2 += h1; + + return HashCode.fromBytesNoCopy( + ByteBuffer.wrap(new byte[CHUNK_SIZE]) + .order(ByteOrder.LITTLE_ENDIAN) + .putLong(h1) + .putLong(h2) + .array()); + } + + private static long fmix64(long k) { + k ^= k >>> 33; + k *= 0xff51afd7ed558ccdL; + k ^= k >>> 33; + k *= 0xc4ceb9fe1a85ec53L; + k ^= k >>> 33; + return k; + } + + private static long mixK1(long k1) { + k1 *= C1; + k1 = Long.rotateLeft(k1, 31); + k1 *= C2; + return k1; + } + + private static long mixK2(long k2) { + k2 *= C2; + k2 = Long.rotateLeft(k2, 33); + k2 *= C1; + return k2; + } + } + + private static final long serialVersionUID = 0L; +} diff --git a/src/main/java/com/google/common/hash/Murmur3_32HashFunction.java b/src/main/java/com/google/common/hash/Murmur3_32HashFunction.java new file mode 100644 index 0000000..c918460 --- /dev/null +++ b/src/main/java/com/google/common/hash/Murmur3_32HashFunction.java @@ -0,0 +1,411 @@ +/* + * Copyright (C) 2011 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +/* + * MurmurHash3 was written by Austin Appleby, and is placed in the public + * domain. The author hereby disclaims copyright to this source code. + */ + +/* + * Source: + * http://code.google.com/p/smhasher/source/browse/trunk/MurmurHash3.cpp + * (Modified to adapt to Guava coding conventions and to use the HashFunction interface) + */ + +package com.google.common.hash; + +import static com.google.common.base.Preconditions.checkPositionIndexes; +import static com.google.common.base.Preconditions.checkState; +import static com.google.common.primitives.UnsignedBytes.toInt; + +import com.google.common.base.Charsets; +import com.google.common.primitives.Chars; +import com.google.common.primitives.Ints; +import com.google.common.primitives.Longs; + + +import java.io.Serializable; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.charset.Charset; + + +/** + * See MurmurHash3_x86_32 in the C++ + * implementation. + * + * @author Austin Appleby + * @author Dimitris Andreou + * @author Kurt Alfred Kluever + */ + +final class Murmur3_32HashFunction extends AbstractHashFunction implements Serializable { + static final HashFunction MURMUR3_32 = new Murmur3_32HashFunction(0); + + static final HashFunction GOOD_FAST_HASH_32 = + new Murmur3_32HashFunction(Hashing.GOOD_FAST_HASH_SEED); + + private static final int CHUNK_SIZE = 4; + + private static final int C1 = 0xcc9e2d51; + private static final int C2 = 0x1b873593; + + private final int seed; + + Murmur3_32HashFunction(int seed) { + this.seed = seed; + } + + @Override + public int bits() { + return 32; + } + + @Override + public Hasher newHasher() { + return new Murmur3_32Hasher(seed); + } + + @Override + public String toString() { + return "Hashing.murmur3_32(" + seed + ")"; + } + + @Override + public boolean equals(Object object) { + if (object instanceof Murmur3_32HashFunction) { + Murmur3_32HashFunction other = (Murmur3_32HashFunction) object; + return seed == other.seed; + } + return false; + } + + @Override + public int hashCode() { + return getClass().hashCode() ^ seed; + } + + @Override + public HashCode hashInt(int input) { + int k1 = mixK1(input); + int h1 = mixH1(seed, k1); + + return fmix(h1, Ints.BYTES); + } + + @Override + public HashCode hashLong(long input) { + int low = (int) input; + int high = (int) (input >>> 32); + + int k1 = mixK1(low); + int h1 = mixH1(seed, k1); + + k1 = mixK1(high); + h1 = mixH1(h1, k1); + + return fmix(h1, Longs.BYTES); + } + + @Override + public HashCode hashUnencodedChars(CharSequence input) { + int h1 = seed; + + // step through the CharSequence 2 chars at a time + for (int i = 1; i < input.length(); i += 2) { + int k1 = input.charAt(i - 1) | (input.charAt(i) << 16); + k1 = mixK1(k1); + h1 = mixH1(h1, k1); + } + + // deal with any remaining characters + if ((input.length() & 1) == 1) { + int k1 = input.charAt(input.length() - 1); + k1 = mixK1(k1); + h1 ^= k1; + } + + return fmix(h1, Chars.BYTES * input.length()); + } + + @SuppressWarnings("deprecation") // need to use Charsets for Android tests to pass + @Override + public HashCode hashString(CharSequence input, Charset charset) { + if (Charsets.UTF_8.equals(charset)) { + int utf16Length = input.length(); + int h1 = seed; + int i = 0; + int len = 0; + + // This loop optimizes for pure ASCII. + while (i + 4 <= utf16Length) { + char c0 = input.charAt(i); + char c1 = input.charAt(i + 1); + char c2 = input.charAt(i + 2); + char c3 = input.charAt(i + 3); + if (c0 < 0x80 && c1 < 0x80 && c2 < 0x80 && c3 < 0x80) { + int k1 = c0 | (c1 << 8) | (c2 << 16) | (c3 << 24); + k1 = mixK1(k1); + h1 = mixH1(h1, k1); + i += 4; + len += 4; + } else { + break; + } + } + + long buffer = 0; + int shift = 0; + for (; i < utf16Length; i++) { + char c = input.charAt(i); + if (c < 0x80) { + buffer |= (long) c << shift; + shift += 8; + len++; + } else if (c < 0x800) { + buffer |= charToTwoUtf8Bytes(c) << shift; + shift += 16; + len += 2; + } else if (c < Character.MIN_SURROGATE || c > Character.MAX_SURROGATE) { + buffer |= charToThreeUtf8Bytes(c) << shift; + shift += 24; + len += 3; + } else { + int codePoint = Character.codePointAt(input, i); + if (codePoint == c) { + // not a valid code point; let the JDK handle invalid Unicode + return hashBytes(input.toString().getBytes(charset)); + } + i++; + buffer |= codePointToFourUtf8Bytes(codePoint) << shift; + len += 4; + } + + if (shift >= 32) { + int k1 = mixK1((int) buffer); + h1 = mixH1(h1, k1); + buffer = buffer >>> 32; + shift -= 32; + } + } + + int k1 = mixK1((int) buffer); + h1 ^= k1; + return fmix(h1, len); + } else { + return hashBytes(input.toString().getBytes(charset)); + } + } + + @Override + public HashCode hashBytes(byte[] input, int off, int len) { + checkPositionIndexes(off, off + len, input.length); + int h1 = seed; + int i; + for (i = 0; i + CHUNK_SIZE <= len; i += CHUNK_SIZE) { + int k1 = mixK1(getIntLittleEndian(input, off + i)); + h1 = mixH1(h1, k1); + } + + int k1 = 0; + for (int shift = 0; i < len; i++, shift += 8) { + k1 ^= toInt(input[off + i]) << shift; + } + h1 ^= mixK1(k1); + return fmix(h1, len); + } + + private static int getIntLittleEndian(byte[] input, int offset) { + return Ints.fromBytes(input[offset + 3], input[offset + 2], input[offset + 1], input[offset]); + } + + private static int mixK1(int k1) { + k1 *= C1; + k1 = Integer.rotateLeft(k1, 15); + k1 *= C2; + return k1; + } + + private static int mixH1(int h1, int k1) { + h1 ^= k1; + h1 = Integer.rotateLeft(h1, 13); + h1 = h1 * 5 + 0xe6546b64; + return h1; + } + + // Finalization mix - force all bits of a hash block to avalanche + private static HashCode fmix(int h1, int length) { + h1 ^= length; + h1 ^= h1 >>> 16; + h1 *= 0x85ebca6b; + h1 ^= h1 >>> 13; + h1 *= 0xc2b2ae35; + h1 ^= h1 >>> 16; + return HashCode.fromInt(h1); + } + + + private static final class Murmur3_32Hasher extends AbstractHasher { + private int h1; + private long buffer; + private int shift; + private int length; + private boolean isDone; + + Murmur3_32Hasher(int seed) { + this.h1 = seed; + this.length = 0; + isDone = false; + } + + private void update(int nBytes, long update) { + // 1 <= nBytes <= 4 + buffer |= (update & 0xFFFFFFFFL) << shift; + shift += nBytes * 8; + length += nBytes; + + if (shift >= 32) { + h1 = mixH1(h1, mixK1((int) buffer)); + buffer >>>= 32; + shift -= 32; + } + } + + @Override + public Hasher putByte(byte b) { + update(1, b & 0xFF); + return this; + } + + @Override + public Hasher putBytes(byte[] bytes, int off, int len) { + checkPositionIndexes(off, off + len, bytes.length); + int i; + for (i = 0; i + 4 <= len; i += 4) { + update(4, getIntLittleEndian(bytes, off + i)); + } + for (; i < len; i++) { + putByte(bytes[off + i]); + } + return this; + } + + @Override + public Hasher putBytes(ByteBuffer buffer) { + ByteOrder bo = buffer.order(); + buffer.order(ByteOrder.LITTLE_ENDIAN); + while (buffer.remaining() >= 4) { + putInt(buffer.getInt()); + } + while (buffer.hasRemaining()) { + putByte(buffer.get()); + } + buffer.order(bo); + return this; + } + + @Override + public Hasher putInt(int i) { + update(4, i); + return this; + } + + @Override + public Hasher putLong(long l) { + update(4, (int) l); + update(4, l >>> 32); + return this; + } + + @Override + public Hasher putChar(char c) { + update(2, c); + return this; + } + + @SuppressWarnings("deprecation") // need to use Charsets for Android tests to pass + @Override + public Hasher putString(CharSequence input, Charset charset) { + if (Charsets.UTF_8.equals(charset)) { + int utf16Length = input.length(); + int i = 0; + + // This loop optimizes for pure ASCII. + while (i + 4 <= utf16Length) { + char c0 = input.charAt(i); + char c1 = input.charAt(i + 1); + char c2 = input.charAt(i + 2); + char c3 = input.charAt(i + 3); + if (c0 < 0x80 && c1 < 0x80 && c2 < 0x80 && c3 < 0x80) { + update(4, c0 | (c1 << 8) | (c2 << 16) | (c3 << 24)); + i += 4; + } else { + break; + } + } + + for (; i < utf16Length; i++) { + char c = input.charAt(i); + if (c < 0x80) { + update(1, c); + } else if (c < 0x800) { + update(2, charToTwoUtf8Bytes(c)); + } else if (c < Character.MIN_SURROGATE || c > Character.MAX_SURROGATE) { + update(3, charToThreeUtf8Bytes(c)); + } else { + int codePoint = Character.codePointAt(input, i); + if (codePoint == c) { + // fall back to JDK getBytes instead of trying to handle invalid surrogates ourselves + putBytes(input.subSequence(i, utf16Length).toString().getBytes(charset)); + return this; + } + i++; + update(4, codePointToFourUtf8Bytes(codePoint)); + } + } + return this; + } else { + return super.putString(input, charset); + } + } + + @Override + public HashCode hash() { + checkState(!isDone); + isDone = true; + h1 ^= mixK1((int) buffer); + return fmix(h1, length); + } + } + + private static long codePointToFourUtf8Bytes(int codePoint) { + return (((0xFL << 4) | (codePoint >>> 18)) & 0xFF) + | ((0x80L | (0x3F & (codePoint >>> 12))) << 8) + | ((0x80L | (0x3F & (codePoint >>> 6))) << 16) + | ((0x80L | (0x3F & codePoint)) << 24); + } + + private static long charToThreeUtf8Bytes(char c) { + return (((0xF << 5) | (c >>> 12)) & 0xFF) + | ((0x80 | (0x3F & (c >>> 6))) << 8) + | ((0x80 | (0x3F & c)) << 16); + } + + private static long charToTwoUtf8Bytes(char c) { + return (((0xF << 6) | (c >>> 6)) & 0xFF) | ((0x80 | (0x3F & c)) << 8); + } + + private static final long serialVersionUID = 0L; +} diff --git a/src/main/java/com/google/common/hash/PrimitiveSink.java b/src/main/java/com/google/common/hash/PrimitiveSink.java new file mode 100644 index 0000000..8664eea --- /dev/null +++ b/src/main/java/com/google/common/hash/PrimitiveSink.java @@ -0,0 +1,112 @@ +/* + * Copyright (C) 2011 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.hash; + +import com.google.common.annotations.Beta; + +import java.nio.ByteBuffer; +import java.nio.charset.Charset; + +/** + * An object which can receive a stream of primitive values. + * + * @author Kevin Bourrillion + * @since 12.0 (in 11.0 as {@code Sink}) + */ +@Beta + +public interface PrimitiveSink { + /** + * Puts a byte into this sink. + * + * @param b a byte + * @return this instance + */ + PrimitiveSink putByte(byte b); + + /** + * Puts an array of bytes into this sink. + * + * @param bytes a byte array + * @return this instance + */ + PrimitiveSink putBytes(byte[] bytes); + + /** + * Puts a chunk of an array of bytes into this sink. {@code bytes[off]} is the first byte written, + * {@code bytes[off + len - 1]} is the last. + * + * @param bytes a byte array + * @param off the start offset in the array + * @param len the number of bytes to write + * @return this instance + * @throws IndexOutOfBoundsException if {@code off < 0} or {@code off + len > bytes.length} or + * {@code len < 0} + */ + PrimitiveSink putBytes(byte[] bytes, int off, int len); + + /** + * Puts the remaining bytes of a byte buffer into this sink. {@code bytes.position()} is the first + * byte written, {@code bytes.limit() - 1} is the last. The position of the buffer will be equal + * to the limit when this method returns. + * + * @param bytes a byte buffer + * @return this instance + * @since 23.0 + */ + PrimitiveSink putBytes(ByteBuffer bytes); + + /** Puts a short into this sink. */ + PrimitiveSink putShort(short s); + + /** Puts an int into this sink. */ + PrimitiveSink putInt(int i); + + /** Puts a long into this sink. */ + PrimitiveSink putLong(long l); + + /** Puts a float into this sink. */ + PrimitiveSink putFloat(float f); + + /** Puts a double into this sink. */ + PrimitiveSink putDouble(double d); + + /** Puts a boolean into this sink. */ + PrimitiveSink putBoolean(boolean b); + + /** Puts a character into this sink. */ + PrimitiveSink putChar(char c); + + /** + * Puts each 16-bit code unit from the {@link CharSequence} into this sink. + * + *

Warning: This method will produce different output than most other languages do when + * running on the equivalent input. For cross-language compatibility, use {@link #putString}, + * usually with a charset of UTF-8. For other use cases, use {@code putUnencodedChars}. + * + * @since 15.0 (since 11.0 as putString(CharSequence)) + */ + PrimitiveSink putUnencodedChars(CharSequence charSequence); + + /** + * Puts a string into this sink using the given charset. + * + *

Warning: This method, which reencodes the input before processing it, is useful only + * for cross-language compatibility. For other use cases, prefer {@link #putUnencodedChars}, which + * is faster, produces the same output across Java releases, and processes every {@code char} in + * the input, even if some are invalid. + */ + PrimitiveSink putString(CharSequence charSequence, Charset charset); +} diff --git a/src/main/java/com/google/common/hash/SipHashFunction.java b/src/main/java/com/google/common/hash/SipHashFunction.java new file mode 100644 index 0000000..16863f0 --- /dev/null +++ b/src/main/java/com/google/common/hash/SipHashFunction.java @@ -0,0 +1,184 @@ +/* + * Copyright (C) 2012 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +/* + * SipHash-c-d was designed by Jean-Philippe Aumasson and Daniel J. Bernstein and is described in + * "SipHash: a fast short-input PRF" (available at https://131002.net/siphash/siphash.pdf). + */ + +package com.google.common.hash; + +import static com.google.common.base.Preconditions.checkArgument; + + +import java.io.Serializable; +import java.nio.ByteBuffer; + + +/** + * {@link HashFunction} implementation of SipHash-c-d. + * + * @author Kurt Alfred Kluever + * @author Jean-Philippe Aumasson + * @author Daniel J. Bernstein + */ + +final class SipHashFunction extends AbstractHashFunction implements Serializable { + static final HashFunction SIP_HASH_24 = + new SipHashFunction(2, 4, 0x0706050403020100L, 0x0f0e0d0c0b0a0908L); + + // The number of compression rounds. + private final int c; + // The number of finalization rounds. + private final int d; + // Two 64-bit keys (represent a single 128-bit key). + private final long k0; + private final long k1; + + /** + * @param c the number of compression rounds (must be positive) + * @param d the number of finalization rounds (must be positive) + * @param k0 the first half of the key + * @param k1 the second half of the key + */ + SipHashFunction(int c, int d, long k0, long k1) { + checkArgument( + c > 0, "The number of SipRound iterations (c=%s) during Compression must be positive.", c); + checkArgument( + d > 0, "The number of SipRound iterations (d=%s) during Finalization must be positive.", d); + this.c = c; + this.d = d; + this.k0 = k0; + this.k1 = k1; + } + + @Override + public int bits() { + return 64; + } + + @Override + public Hasher newHasher() { + return new SipHasher(c, d, k0, k1); + } + + // TODO(kak): Implement and benchmark the hashFoo() shortcuts. + + @Override + public String toString() { + return "Hashing.sipHash" + c + "" + d + "(" + k0 + ", " + k1 + ")"; + } + + @Override + public boolean equals(Object object) { + if (object instanceof SipHashFunction) { + SipHashFunction other = (SipHashFunction) object; + return (c == other.c) && (d == other.d) && (k0 == other.k0) && (k1 == other.k1); + } + return false; + } + + @Override + public int hashCode() { + return (int) (getClass().hashCode() ^ c ^ d ^ k0 ^ k1); + } + + private static final class SipHasher extends AbstractStreamingHasher { + private static final int CHUNK_SIZE = 8; + + // The number of compression rounds. + private final int c; + // The number of finalization rounds. + private final int d; + + // Four 64-bit words of internal state. + // The initial state corresponds to the ASCII string "somepseudorandomlygeneratedbytes", + // big-endian encoded. There is nothing special about this value; the only requirement + // was some asymmetry so that the initial v0 and v1 differ from v2 and v3. + private long v0 = 0x736f6d6570736575L; + private long v1 = 0x646f72616e646f6dL; + private long v2 = 0x6c7967656e657261L; + private long v3 = 0x7465646279746573L; + + // The number of bytes in the input. + private long b = 0; + + // The final 64-bit chunk includes the last 0 through 7 bytes of m followed by null bytes + // and ending with a byte encoding the positive integer b mod 256. + private long finalM = 0; + + SipHasher(int c, int d, long k0, long k1) { + super(CHUNK_SIZE); + this.c = c; + this.d = d; + this.v0 ^= k0; + this.v1 ^= k1; + this.v2 ^= k0; + this.v3 ^= k1; + } + + @Override + protected void process(ByteBuffer buffer) { + b += CHUNK_SIZE; + processM(buffer.getLong()); + } + + @Override + protected void processRemaining(ByteBuffer buffer) { + b += buffer.remaining(); + for (int i = 0; buffer.hasRemaining(); i += 8) { + finalM ^= (buffer.get() & 0xFFL) << i; + } + } + + @Override + public HashCode makeHash() { + // End with a byte encoding the positive integer b mod 256. + finalM ^= b << 56; + processM(finalM); + + // Finalization + v2 ^= 0xFFL; + sipRound(d); + return HashCode.fromLong(v0 ^ v1 ^ v2 ^ v3); + } + + private void processM(long m) { + v3 ^= m; + sipRound(c); + v0 ^= m; + } + + private void sipRound(int iterations) { + for (int i = 0; i < iterations; i++) { + v0 += v1; + v2 += v3; + v1 = Long.rotateLeft(v1, 13); + v3 = Long.rotateLeft(v3, 16); + v1 ^= v0; + v3 ^= v2; + v0 = Long.rotateLeft(v0, 32); + v2 += v1; + v0 += v3; + v1 = Long.rotateLeft(v1, 17); + v3 = Long.rotateLeft(v3, 21); + v1 ^= v2; + v3 ^= v0; + v2 = Long.rotateLeft(v2, 32); + } + } + } + + private static final long serialVersionUID = 0L; +} diff --git a/src/main/java/com/google/common/html/HtmlEscapers.java b/src/main/java/com/google/common/html/HtmlEscapers.java new file mode 100644 index 0000000..29eebe8 --- /dev/null +++ b/src/main/java/com/google/common/html/HtmlEscapers.java @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2009 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.html; + +import com.google.common.annotations.GwtCompatible; +import com.google.common.escape.Escaper; +import com.google.common.escape.Escapers; + +/** + * {@code Escaper} instances suitable for strings to be included in HTML attribute values and + * most elements' text contents. When possible, avoid manual escaping by using templating + * systems and high-level APIs that provide autoescaping. + * One Google-authored templating system available for external use is Closure Templates. + * + *

HTML escaping is particularly tricky: For example, some + * elements' text contents must not be HTML escaped. As a result, it is impossible to escape an + * HTML document correctly without domain-specific knowledge beyond what {@code HtmlEscapers} + * provides. We strongly encourage the use of HTML templating systems. + * + * @author Sven Mawson + * @author David Beaumont + * @since 15.0 + */ +@GwtCompatible +public final class HtmlEscapers { + /** + * Returns an {@link Escaper} instance that escapes HTML metacharacters as specified by HTML 4.01. The resulting strings can be used both in + * attribute values and in most elements' text contents, provided that the HTML + * document's character encoding can encode any non-ASCII code points in the input (as UTF-8 and + * other Unicode encodings can). + * + *

Note: This escaper only performs minimal escaping to make content structurally + * compatible with HTML. Specifically, it does not perform entity replacement (symbolic or + * numeric), so it does not replace non-ASCII code points with character references. This escaper + * escapes only the following five ASCII characters: {@code '"&<>}. + */ + public static Escaper htmlEscaper() { + return HTML_ESCAPER; + } + + // For each xxxEscaper() method, please add links to external reference pages + // that are considered authoritative for the behavior of that escaper. + + private static final Escaper HTML_ESCAPER = + Escapers.builder() + .addEscape('"', """) + // Note: "'" is not defined in HTML 4.01. + .addEscape('\'', "'") + .addEscape('&', "&") + .addEscape('<', "<") + .addEscape('>', ">") + .build(); + + private HtmlEscapers() {} +} diff --git a/src/main/java/com/google/common/io/AppendableWriter.java b/src/main/java/com/google/common/io/AppendableWriter.java new file mode 100644 index 0000000..ce6085c --- /dev/null +++ b/src/main/java/com/google/common/io/AppendableWriter.java @@ -0,0 +1,125 @@ +/* + * Copyright (C) 2006 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.io; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.annotations.GwtIncompatible; +import java.io.Closeable; +import java.io.Flushable; +import java.io.IOException; +import java.io.Writer; + + +/** + * Writer that places all output on an {@link Appendable} target. If the target is {@link Flushable} + * or {@link Closeable}, flush()es and close()s will also be delegated to the target. + * + * @author Alan Green + * @author Sebastian Kanthak + * @since 1.0 + */ +@GwtIncompatible +class AppendableWriter extends Writer { + private final Appendable target; + private boolean closed; + + /** + * Creates a new writer that appends everything it writes to {@code target}. + * + * @param target target to which to append output + */ + AppendableWriter(Appendable target) { + this.target = checkNotNull(target); + } + + /* + * Abstract methods from Writer + */ + + @Override + public void write(char[] cbuf, int off, int len) throws IOException { + checkNotClosed(); + // It turns out that creating a new String is usually as fast, or faster + // than wrapping cbuf in a light-weight CharSequence. + target.append(new String(cbuf, off, len)); + } + + /* + * Override a few functions for performance reasons to avoid creating unnecessary strings. + */ + + @Override + public void write(int c) throws IOException { + checkNotClosed(); + target.append((char) c); + } + + @Override + public void write(String str) throws IOException { + checkNotClosed(); + target.append(str); + } + + @Override + public void write(String str, int off, int len) throws IOException { + checkNotClosed(); + // tricky: append takes start, end pair... + target.append(str, off, off + len); + } + + @Override + public void flush() throws IOException { + checkNotClosed(); + if (target instanceof Flushable) { + ((Flushable) target).flush(); + } + } + + @Override + public void close() throws IOException { + this.closed = true; + if (target instanceof Closeable) { + ((Closeable) target).close(); + } + } + + @Override + public Writer append(char c) throws IOException { + checkNotClosed(); + target.append(c); + return this; + } + + @Override + public Writer append(CharSequence charSeq) throws IOException { + checkNotClosed(); + target.append(charSeq); + return this; + } + + @Override + public Writer append(CharSequence charSeq, int start, int end) throws IOException { + checkNotClosed(); + target.append(charSeq, start, end); + return this; + } + + private void checkNotClosed() throws IOException { + if (closed) { + throw new IOException("Cannot write to a closed writer."); + } + } +} diff --git a/src/main/java/com/google/common/io/BaseEncoding.java b/src/main/java/com/google/common/io/BaseEncoding.java new file mode 100644 index 0000000..9a5decc --- /dev/null +++ b/src/main/java/com/google/common/io/BaseEncoding.java @@ -0,0 +1,1181 @@ +/* + * Copyright (C) 2012 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.io; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.base.Preconditions.checkPositionIndexes; +import static com.google.common.base.Preconditions.checkState; +import static com.google.common.math.IntMath.divide; +import static com.google.common.math.IntMath.log2; +import static java.math.RoundingMode.CEILING; +import static java.math.RoundingMode.FLOOR; +import static java.math.RoundingMode.UNNECESSARY; + +import com.google.common.annotations.GwtCompatible; +import com.google.common.annotations.GwtIncompatible; +import com.google.common.base.Ascii; +import com.google.common.base.Objects; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.Reader; +import java.io.Writer; +import java.util.Arrays; + + + +/** + * A binary encoding scheme for reversibly translating between byte sequences and printable ASCII + * strings. This class includes several constants for encoding schemes specified by RFC 4648. For example, the expression: + * + *

{@code
+ * BaseEncoding.base32().encode("foo".getBytes(Charsets.US_ASCII))
+ * }
+ * + *

returns the string {@code "MZXW6==="}, and + * + *

{@code
+ * byte[] decoded = BaseEncoding.base32().decode("MZXW6===");
+ * }
+ * + *

...returns the ASCII bytes of the string {@code "foo"}. + * + *

By default, {@code BaseEncoding}'s behavior is relatively strict and in accordance with RFC + * 4648. Decoding rejects characters in the wrong case, though padding is optional. To modify + * encoding and decoding behavior, use configuration methods to obtain a new encoding with modified + * behavior: + * + *

{@code
+ * BaseEncoding.base16().lowerCase().decode("deadbeef");
+ * }
+ * + *

Warning: BaseEncoding instances are immutable. Invoking a configuration method has no effect + * on the receiving instance; you must store and use the new encoding instance it returns, instead. + * + *

{@code
+ * // Do NOT do this
+ * BaseEncoding hex = BaseEncoding.base16();
+ * hex.lowerCase(); // does nothing!
+ * return hex.decode("deadbeef"); // throws an IllegalArgumentException
+ * }
+ * + *

It is guaranteed that {@code encoding.decode(encoding.encode(x))} is always equal to {@code + * x}, but the reverse does not necessarily hold. + * + * + * + * + * + * + * + * + * + *
Encodings
Encoding + * Alphabet + * {@code char:byte} ratio + * Default padding + * Comments + *
{@link #base16()} + * 0-9 A-F + * 2.00 + * N/A + * Traditional hexadecimal. Defaults to upper case. + *
{@link #base32()} + * A-Z 2-7 + * 1.60 + * = + * Human-readable; no possibility of mixing up 0/O or 1/I. Defaults to upper case. + *
{@link #base32Hex()} + * 0-9 A-V + * 1.60 + * = + * "Numerical" base 32; extended from the traditional hex alphabet. Defaults to upper case. + *
{@link #base64()} + * A-Z a-z 0-9 + / + * 1.33 + * = + * + *
{@link #base64Url()} + * A-Z a-z 0-9 - _ + * 1.33 + * = + * Safe to use as filenames, or to pass in URLs without escaping + *
+ * + *

All instances of this class are immutable, so they may be stored safely as static constants. + * + * @author Louis Wasserman + * @since 14.0 + */ +@GwtCompatible(emulated = true) +public abstract class BaseEncoding { + // TODO(lowasser): consider making encodeTo(Appendable, byte[], int, int) public. + + BaseEncoding() {} + + /** + * Exception indicating invalid base-encoded input encountered while decoding. + * + * @author Louis Wasserman + * @since 15.0 + */ + public static final class DecodingException extends IOException { + DecodingException(String message) { + super(message); + } + + DecodingException(Throwable cause) { + super(cause); + } + } + + /** Encodes the specified byte array, and returns the encoded {@code String}. */ + public String encode(byte[] bytes) { + return encode(bytes, 0, bytes.length); + } + + /** + * Encodes the specified range of the specified byte array, and returns the encoded {@code + * String}. + */ + public final String encode(byte[] bytes, int off, int len) { + checkPositionIndexes(off, off + len, bytes.length); + StringBuilder result = new StringBuilder(maxEncodedSize(len)); + try { + encodeTo(result, bytes, off, len); + } catch (IOException impossible) { + throw new AssertionError(impossible); + } + return result.toString(); + } + + /** + * Returns an {@code OutputStream} that encodes bytes using this encoding into the specified + * {@code Writer}. When the returned {@code OutputStream} is closed, so is the backing {@code + * Writer}. + */ + @GwtIncompatible // Writer,OutputStream + public abstract OutputStream encodingStream(Writer writer); + + /** + * Returns a {@code ByteSink} that writes base-encoded bytes to the specified {@code CharSink}. + */ + @GwtIncompatible // ByteSink,CharSink + public final ByteSink encodingSink(final CharSink encodedSink) { + checkNotNull(encodedSink); + return new ByteSink() { + @Override + public OutputStream openStream() throws IOException { + return encodingStream(encodedSink.openStream()); + } + }; + } + + // TODO(lowasser): document the extent of leniency, probably after adding ignore(CharMatcher) + + private static byte[] extract(byte[] result, int length) { + if (length == result.length) { + return result; + } else { + byte[] trunc = new byte[length]; + System.arraycopy(result, 0, trunc, 0, length); + return trunc; + } + } + + /** + * Determines whether the specified character sequence is a valid encoded string according to this + * encoding. + * + * @since 20.0 + */ + public abstract boolean canDecode(CharSequence chars); + + /** + * Decodes the specified character sequence, and returns the resulting {@code byte[]}. This is the + * inverse operation to {@link #encode(byte[])}. + * + * @throws IllegalArgumentException if the input is not a valid encoded string according to this + * encoding. + */ + public final byte[] decode(CharSequence chars) { + try { + return decodeChecked(chars); + } catch (DecodingException badInput) { + throw new IllegalArgumentException(badInput); + } + } + + /** + * Decodes the specified character sequence, and returns the resulting {@code byte[]}. This is the + * inverse operation to {@link #encode(byte[])}. + * + * @throws DecodingException if the input is not a valid encoded string according to this + * encoding. + */ final byte[] decodeChecked(CharSequence chars) + throws DecodingException { + chars = trimTrailingPadding(chars); + byte[] tmp = new byte[maxDecodedSize(chars.length())]; + int len = decodeTo(tmp, chars); + return extract(tmp, len); + } + + /** + * Returns an {@code InputStream} that decodes base-encoded input from the specified {@code + * Reader}. The returned stream throws a {@link DecodingException} upon decoding-specific errors. + */ + @GwtIncompatible // Reader,InputStream + public abstract InputStream decodingStream(Reader reader); + + /** + * Returns a {@code ByteSource} that reads base-encoded bytes from the specified {@code + * CharSource}. + */ + @GwtIncompatible // ByteSource,CharSource + public final ByteSource decodingSource(final CharSource encodedSource) { + checkNotNull(encodedSource); + return new ByteSource() { + @Override + public InputStream openStream() throws IOException { + return decodingStream(encodedSource.openStream()); + } + }; + } + + // Implementations for encoding/decoding + + abstract int maxEncodedSize(int bytes); + + abstract void encodeTo(Appendable target, byte[] bytes, int off, int len) throws IOException; + + abstract int maxDecodedSize(int chars); + + abstract int decodeTo(byte[] target, CharSequence chars) throws DecodingException; + + CharSequence trimTrailingPadding(CharSequence chars) { + return checkNotNull(chars); + } + + // Modified encoding generators + + /** + * Returns an encoding that behaves equivalently to this encoding, but omits any padding + * characters as specified by RFC 4648 + * section 3.2, Padding of Encoded Data. + */ + public abstract BaseEncoding omitPadding(); + + /** + * Returns an encoding that behaves equivalently to this encoding, but uses an alternate character + * for padding. + * + * @throws IllegalArgumentException if this padding character is already used in the alphabet or a + * separator + */ + public abstract BaseEncoding withPadChar(char padChar); + + /** + * Returns an encoding that behaves equivalently to this encoding, but adds a separator string + * after every {@code n} characters. Any occurrences of any characters that occur in the separator + * are skipped over in decoding. + * + * @throws IllegalArgumentException if any alphabet or padding characters appear in the separator + * string, or if {@code n <= 0} + * @throws UnsupportedOperationException if this encoding already uses a separator + */ + public abstract BaseEncoding withSeparator(String separator, int n); + + /** + * Returns an encoding that behaves equivalently to this encoding, but encodes and decodes with + * uppercase letters. Padding and separator characters remain in their original case. + * + * @throws IllegalStateException if the alphabet used by this encoding contains mixed upper- and + * lower-case characters + */ + public abstract BaseEncoding upperCase(); + + /** + * Returns an encoding that behaves equivalently to this encoding, but encodes and decodes with + * lowercase letters. Padding and separator characters remain in their original case. + * + * @throws IllegalStateException if the alphabet used by this encoding contains mixed upper- and + * lower-case characters + */ + public abstract BaseEncoding lowerCase(); + + private static final BaseEncoding BASE64 = + new Base64Encoding( + "base64()", "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/", '='); + + /** + * The "base64" base encoding specified by RFC 4648 section 4, Base 64 Encoding. + * (This is the same as the base 64 encoding from RFC 3548.) + * + *

The character {@code '='} is used for padding, but can be {@linkplain #omitPadding() + * omitted} or {@linkplain #withPadChar(char) replaced}. + * + *

No line feeds are added by default, as per RFC 4648 section 3.1, Line Feeds in + * Encoded Data. Line feeds may be added using {@link #withSeparator(String, int)}. + */ + public static BaseEncoding base64() { + return BASE64; + } + + private static final BaseEncoding BASE64_URL = + new Base64Encoding( + "base64Url()", "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_", '='); + + /** + * The "base64url" encoding specified by RFC 4648 section 5, Base 64 Encoding + * with URL and Filename Safe Alphabet, also sometimes referred to as the "web safe Base64." (This + * is the same as the base 64 encoding with URL and filename safe alphabet from RFC 3548.) + * + *

The character {@code '='} is used for padding, but can be {@linkplain #omitPadding() + * omitted} or {@linkplain #withPadChar(char) replaced}. + * + *

No line feeds are added by default, as per RFC 4648 section 3.1, Line Feeds in + * Encoded Data. Line feeds may be added using {@link #withSeparator(String, int)}. + */ + public static BaseEncoding base64Url() { + return BASE64_URL; + } + + private static final BaseEncoding BASE32 = + new StandardBaseEncoding("base32()", "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567", '='); + + /** + * The "base32" encoding specified by RFC + * 4648 section 6, Base 32 Encoding. (This is the same as the base 32 encoding from RFC 3548.) + * + *

The character {@code '='} is used for padding, but can be {@linkplain #omitPadding() + * omitted} or {@linkplain #withPadChar(char) replaced}. + * + *

No line feeds are added by default, as per RFC 4648 section 3.1, Line Feeds in + * Encoded Data. Line feeds may be added using {@link #withSeparator(String, int)}. + */ + public static BaseEncoding base32() { + return BASE32; + } + + private static final BaseEncoding BASE32_HEX = + new StandardBaseEncoding("base32Hex()", "0123456789ABCDEFGHIJKLMNOPQRSTUV", '='); + + /** + * The "base32hex" encoding specified by RFC 4648 section 7, Base 32 Encoding + * with Extended Hex Alphabet. There is no corresponding encoding in RFC 3548. + * + *

The character {@code '='} is used for padding, but can be {@linkplain #omitPadding() + * omitted} or {@linkplain #withPadChar(char) replaced}. + * + *

No line feeds are added by default, as per RFC 4648 section 3.1, Line Feeds in + * Encoded Data. Line feeds may be added using {@link #withSeparator(String, int)}. + */ + public static BaseEncoding base32Hex() { + return BASE32_HEX; + } + + private static final BaseEncoding BASE16 = new Base16Encoding("base16()", "0123456789ABCDEF"); + + /** + * The "base16" encoding specified by RFC + * 4648 section 8, Base 16 Encoding. (This is the same as the base 16 encoding from RFC 3548.) This is commonly known as + * "hexadecimal" format. + * + *

No padding is necessary in base 16, so {@link #withPadChar(char)} and {@link #omitPadding()} + * have no effect. + * + *

No line feeds are added by default, as per RFC 4648 section 3.1, Line Feeds in + * Encoded Data. Line feeds may be added using {@link #withSeparator(String, int)}. + */ + public static BaseEncoding base16() { + return BASE16; + } + + private static final class Alphabet { + private final String name; + // this is meant to be immutable -- don't modify it! + private final char[] chars; + final int mask; + final int bitsPerChar; + final int charsPerChunk; + final int bytesPerChunk; + private final byte[] decodabet; + private final boolean[] validPadding; + + Alphabet(String name, char[] chars) { + this.name = checkNotNull(name); + this.chars = checkNotNull(chars); + try { + this.bitsPerChar = log2(chars.length, UNNECESSARY); + } catch (ArithmeticException e) { + throw new IllegalArgumentException("Illegal alphabet length " + chars.length, e); + } + + /* + * e.g. for base64, bitsPerChar == 6, charsPerChunk == 4, and bytesPerChunk == 3. This makes + * for the smallest chunk size that still has charsPerChunk * bitsPerChar be a multiple of 8. + */ + int gcd = Math.min(8, Integer.lowestOneBit(bitsPerChar)); + try { + this.charsPerChunk = 8 / gcd; + this.bytesPerChunk = bitsPerChar / gcd; + } catch (ArithmeticException e) { + throw new IllegalArgumentException("Illegal alphabet " + new String(chars), e); + } + + this.mask = chars.length - 1; + + byte[] decodabet = new byte[Ascii.MAX + 1]; + Arrays.fill(decodabet, (byte) -1); + for (int i = 0; i < chars.length; i++) { + char c = chars[i]; + checkArgument(c < decodabet.length, "Non-ASCII character: %s", c); + checkArgument(decodabet[c] == -1, "Duplicate character: %s", c); + decodabet[c] = (byte) i; + } + this.decodabet = decodabet; + + boolean[] validPadding = new boolean[charsPerChunk]; + for (int i = 0; i < bytesPerChunk; i++) { + validPadding[divide(i * 8, bitsPerChar, CEILING)] = true; + } + this.validPadding = validPadding; + } + + char encode(int bits) { + return chars[bits]; + } + + boolean isValidPaddingStartPosition(int index) { + return validPadding[index % charsPerChunk]; + } + + boolean canDecode(char ch) { + return ch <= Ascii.MAX && decodabet[ch] != -1; + } + + int decode(char ch) throws DecodingException { + if (ch > Ascii.MAX) { + throw new DecodingException("Unrecognized character: 0x" + Integer.toHexString(ch)); + } + int result = decodabet[ch]; + if (result == -1) { + if (ch <= 0x20 || ch == Ascii.MAX) { + throw new DecodingException("Unrecognized character: 0x" + Integer.toHexString(ch)); + } else { + throw new DecodingException("Unrecognized character: " + ch); + } + } + return result; + } + + private boolean hasLowerCase() { + for (char c : chars) { + if (Ascii.isLowerCase(c)) { + return true; + } + } + return false; + } + + private boolean hasUpperCase() { + for (char c : chars) { + if (Ascii.isUpperCase(c)) { + return true; + } + } + return false; + } + + Alphabet upperCase() { + if (!hasLowerCase()) { + return this; + } else { + checkState(!hasUpperCase(), "Cannot call upperCase() on a mixed-case alphabet"); + char[] upperCased = new char[chars.length]; + for (int i = 0; i < chars.length; i++) { + upperCased[i] = Ascii.toUpperCase(chars[i]); + } + return new Alphabet(name + ".upperCase()", upperCased); + } + } + + Alphabet lowerCase() { + if (!hasUpperCase()) { + return this; + } else { + checkState(!hasLowerCase(), "Cannot call lowerCase() on a mixed-case alphabet"); + char[] lowerCased = new char[chars.length]; + for (int i = 0; i < chars.length; i++) { + lowerCased[i] = Ascii.toLowerCase(chars[i]); + } + return new Alphabet(name + ".lowerCase()", lowerCased); + } + } + + public boolean matches(char c) { + return c < decodabet.length && decodabet[c] != -1; + } + + @Override + public String toString() { + return name; + } + + @Override + public boolean equals(Object other) { + if (other instanceof Alphabet) { + Alphabet that = (Alphabet) other; + return Arrays.equals(this.chars, that.chars); + } + return false; + } + + @Override + public int hashCode() { + return Arrays.hashCode(chars); + } + } + + static class StandardBaseEncoding extends BaseEncoding { + // TODO(lowasser): provide a useful toString + final Alphabet alphabet; + + final Character paddingChar; + + StandardBaseEncoding(String name, String alphabetChars, Character paddingChar) { + this(new Alphabet(name, alphabetChars.toCharArray()), paddingChar); + } + + StandardBaseEncoding(Alphabet alphabet, Character paddingChar) { + this.alphabet = checkNotNull(alphabet); + checkArgument( + paddingChar == null || !alphabet.matches(paddingChar), + "Padding character %s was already in alphabet", + paddingChar); + this.paddingChar = paddingChar; + } + + @Override + int maxEncodedSize(int bytes) { + return alphabet.charsPerChunk * divide(bytes, alphabet.bytesPerChunk, CEILING); + } + + @GwtIncompatible // Writer,OutputStream + @Override + public OutputStream encodingStream(final Writer out) { + checkNotNull(out); + return new OutputStream() { + int bitBuffer = 0; + int bitBufferLength = 0; + int writtenChars = 0; + + @Override + public void write(int b) throws IOException { + bitBuffer <<= 8; + bitBuffer |= b & 0xFF; + bitBufferLength += 8; + while (bitBufferLength >= alphabet.bitsPerChar) { + int charIndex = (bitBuffer >> (bitBufferLength - alphabet.bitsPerChar)) & alphabet.mask; + out.write(alphabet.encode(charIndex)); + writtenChars++; + bitBufferLength -= alphabet.bitsPerChar; + } + } + + @Override + public void flush() throws IOException { + out.flush(); + } + + @Override + public void close() throws IOException { + if (bitBufferLength > 0) { + int charIndex = (bitBuffer << (alphabet.bitsPerChar - bitBufferLength)) & alphabet.mask; + out.write(alphabet.encode(charIndex)); + writtenChars++; + if (paddingChar != null) { + while (writtenChars % alphabet.charsPerChunk != 0) { + out.write(paddingChar.charValue()); + writtenChars++; + } + } + } + out.close(); + } + }; + } + + @Override + void encodeTo(Appendable target, byte[] bytes, int off, int len) throws IOException { + checkNotNull(target); + checkPositionIndexes(off, off + len, bytes.length); + for (int i = 0; i < len; i += alphabet.bytesPerChunk) { + encodeChunkTo(target, bytes, off + i, Math.min(alphabet.bytesPerChunk, len - i)); + } + } + + void encodeChunkTo(Appendable target, byte[] bytes, int off, int len) throws IOException { + checkNotNull(target); + checkPositionIndexes(off, off + len, bytes.length); + checkArgument(len <= alphabet.bytesPerChunk); + long bitBuffer = 0; + for (int i = 0; i < len; ++i) { + bitBuffer |= bytes[off + i] & 0xFF; + bitBuffer <<= 8; // Add additional zero byte in the end. + } + // Position of first character is length of bitBuffer minus bitsPerChar. + final int bitOffset = (len + 1) * 8 - alphabet.bitsPerChar; + int bitsProcessed = 0; + while (bitsProcessed < len * 8) { + int charIndex = (int) (bitBuffer >>> (bitOffset - bitsProcessed)) & alphabet.mask; + target.append(alphabet.encode(charIndex)); + bitsProcessed += alphabet.bitsPerChar; + } + if (paddingChar != null) { + while (bitsProcessed < alphabet.bytesPerChunk * 8) { + target.append(paddingChar.charValue()); + bitsProcessed += alphabet.bitsPerChar; + } + } + } + + @Override + int maxDecodedSize(int chars) { + return (int) ((alphabet.bitsPerChar * (long) chars + 7L) / 8L); + } + + @Override + CharSequence trimTrailingPadding(CharSequence chars) { + checkNotNull(chars); + if (paddingChar == null) { + return chars; + } + char padChar = paddingChar.charValue(); + int l; + for (l = chars.length() - 1; l >= 0; l--) { + if (chars.charAt(l) != padChar) { + break; + } + } + return chars.subSequence(0, l + 1); + } + + @Override + public boolean canDecode(CharSequence chars) { + checkNotNull(chars); + chars = trimTrailingPadding(chars); + if (!alphabet.isValidPaddingStartPosition(chars.length())) { + return false; + } + for (int i = 0; i < chars.length(); i++) { + if (!alphabet.canDecode(chars.charAt(i))) { + return false; + } + } + return true; + } + + @Override + int decodeTo(byte[] target, CharSequence chars) throws DecodingException { + checkNotNull(target); + chars = trimTrailingPadding(chars); + if (!alphabet.isValidPaddingStartPosition(chars.length())) { + throw new DecodingException("Invalid input length " + chars.length()); + } + int bytesWritten = 0; + for (int charIdx = 0; charIdx < chars.length(); charIdx += alphabet.charsPerChunk) { + long chunk = 0; + int charsProcessed = 0; + for (int i = 0; i < alphabet.charsPerChunk; i++) { + chunk <<= alphabet.bitsPerChar; + if (charIdx + i < chars.length()) { + chunk |= alphabet.decode(chars.charAt(charIdx + charsProcessed++)); + } + } + final int minOffset = alphabet.bytesPerChunk * 8 - charsProcessed * alphabet.bitsPerChar; + for (int offset = (alphabet.bytesPerChunk - 1) * 8; offset >= minOffset; offset -= 8) { + target[bytesWritten++] = (byte) ((chunk >>> offset) & 0xFF); + } + } + return bytesWritten; + } + + @Override + @GwtIncompatible // Reader,InputStream + public InputStream decodingStream(final Reader reader) { + checkNotNull(reader); + return new InputStream() { + int bitBuffer = 0; + int bitBufferLength = 0; + int readChars = 0; + boolean hitPadding = false; + + @Override + public int read() throws IOException { + while (true) { + int readChar = reader.read(); + if (readChar == -1) { + if (!hitPadding && !alphabet.isValidPaddingStartPosition(readChars)) { + throw new DecodingException("Invalid input length " + readChars); + } + return -1; + } + readChars++; + char ch = (char) readChar; + if (paddingChar != null && paddingChar.charValue() == ch) { + if (!hitPadding + && (readChars == 1 || !alphabet.isValidPaddingStartPosition(readChars - 1))) { + throw new DecodingException("Padding cannot start at index " + readChars); + } + hitPadding = true; + } else if (hitPadding) { + throw new DecodingException( + "Expected padding character but found '" + ch + "' at index " + readChars); + } else { + bitBuffer <<= alphabet.bitsPerChar; + bitBuffer |= alphabet.decode(ch); + bitBufferLength += alphabet.bitsPerChar; + + if (bitBufferLength >= 8) { + bitBufferLength -= 8; + return (bitBuffer >> bitBufferLength) & 0xFF; + } + } + } + } + + @Override + public int read(byte[] buf, int off, int len) throws IOException { + // Overriding this to work around the fact that InputStream's default implementation of + // this method will silently swallow exceptions thrown by the single-byte read() method + // (other than on the first call to it), which in this case can cause invalid encoded + // strings to not throw an exception. + // See https://github.com/google/guava/issues/3542 + checkPositionIndexes(off, off + len, buf.length); + + int i = off; + for (; i < off + len; i++) { + int b = read(); + if (b == -1) { + int read = i - off; + return read == 0 ? -1 : read; + } + buf[i] = (byte) b; + } + return i - off; + } + + @Override + public void close() throws IOException { + reader.close(); + } + }; + } + + @Override + public BaseEncoding omitPadding() { + return (paddingChar == null) ? this : newInstance(alphabet, null); + } + + @Override + public BaseEncoding withPadChar(char padChar) { + if (8 % alphabet.bitsPerChar == 0 + || (paddingChar != null && paddingChar.charValue() == padChar)) { + return this; + } else { + return newInstance(alphabet, padChar); + } + } + + @Override + public BaseEncoding withSeparator(String separator, int afterEveryChars) { + for (int i = 0; i < separator.length(); i++) { + checkArgument( + !alphabet.matches(separator.charAt(i)), + "Separator (%s) cannot contain alphabet characters", + separator); + } + if (paddingChar != null) { + checkArgument( + separator.indexOf(paddingChar.charValue()) < 0, + "Separator (%s) cannot contain padding character", + separator); + } + return new SeparatedBaseEncoding(this, separator, afterEveryChars); + } + + private transient BaseEncoding upperCase; + private transient BaseEncoding lowerCase; + + @Override + public BaseEncoding upperCase() { + BaseEncoding result = upperCase; + if (result == null) { + Alphabet upper = alphabet.upperCase(); + result = upperCase = (upper == alphabet) ? this : newInstance(upper, paddingChar); + } + return result; + } + + @Override + public BaseEncoding lowerCase() { + BaseEncoding result = lowerCase; + if (result == null) { + Alphabet lower = alphabet.lowerCase(); + result = lowerCase = (lower == alphabet) ? this : newInstance(lower, paddingChar); + } + return result; + } + + BaseEncoding newInstance(Alphabet alphabet, Character paddingChar) { + return new StandardBaseEncoding(alphabet, paddingChar); + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder("BaseEncoding."); + builder.append(alphabet.toString()); + if (8 % alphabet.bitsPerChar != 0) { + if (paddingChar == null) { + builder.append(".omitPadding()"); + } else { + builder.append(".withPadChar('").append(paddingChar).append("')"); + } + } + return builder.toString(); + } + + @Override + public boolean equals(Object other) { + if (other instanceof StandardBaseEncoding) { + StandardBaseEncoding that = (StandardBaseEncoding) other; + return this.alphabet.equals(that.alphabet) + && Objects.equal(this.paddingChar, that.paddingChar); + } + return false; + } + + @Override + public int hashCode() { + return alphabet.hashCode() ^ Objects.hashCode(paddingChar); + } + } + + static final class Base16Encoding extends StandardBaseEncoding { + final char[] encoding = new char[512]; + + Base16Encoding(String name, String alphabetChars) { + this(new Alphabet(name, alphabetChars.toCharArray())); + } + + private Base16Encoding(Alphabet alphabet) { + super(alphabet, null); + checkArgument(alphabet.chars.length == 16); + for (int i = 0; i < 256; ++i) { + encoding[i] = alphabet.encode(i >>> 4); + encoding[i | 0x100] = alphabet.encode(i & 0xF); + } + } + + @Override + void encodeTo(Appendable target, byte[] bytes, int off, int len) throws IOException { + checkNotNull(target); + checkPositionIndexes(off, off + len, bytes.length); + for (int i = 0; i < len; ++i) { + int b = bytes[off + i] & 0xFF; + target.append(encoding[b]); + target.append(encoding[b | 0x100]); + } + } + + @Override + int decodeTo(byte[] target, CharSequence chars) throws DecodingException { + checkNotNull(target); + if (chars.length() % 2 == 1) { + throw new DecodingException("Invalid input length " + chars.length()); + } + int bytesWritten = 0; + for (int i = 0; i < chars.length(); i += 2) { + int decoded = alphabet.decode(chars.charAt(i)) << 4 | alphabet.decode(chars.charAt(i + 1)); + target[bytesWritten++] = (byte) decoded; + } + return bytesWritten; + } + + @Override + BaseEncoding newInstance(Alphabet alphabet, Character paddingChar) { + return new Base16Encoding(alphabet); + } + } + + static final class Base64Encoding extends StandardBaseEncoding { + Base64Encoding(String name, String alphabetChars, Character paddingChar) { + this(new Alphabet(name, alphabetChars.toCharArray()), paddingChar); + } + + private Base64Encoding(Alphabet alphabet, Character paddingChar) { + super(alphabet, paddingChar); + checkArgument(alphabet.chars.length == 64); + } + + @Override + void encodeTo(Appendable target, byte[] bytes, int off, int len) throws IOException { + checkNotNull(target); + checkPositionIndexes(off, off + len, bytes.length); + int i = off; + for (int remaining = len; remaining >= 3; remaining -= 3) { + int chunk = (bytes[i++] & 0xFF) << 16 | (bytes[i++] & 0xFF) << 8 | bytes[i++] & 0xFF; + target.append(alphabet.encode(chunk >>> 18)); + target.append(alphabet.encode((chunk >>> 12) & 0x3F)); + target.append(alphabet.encode((chunk >>> 6) & 0x3F)); + target.append(alphabet.encode(chunk & 0x3F)); + } + if (i < off + len) { + encodeChunkTo(target, bytes, i, off + len - i); + } + } + + @Override + int decodeTo(byte[] target, CharSequence chars) throws DecodingException { + checkNotNull(target); + chars = trimTrailingPadding(chars); + if (!alphabet.isValidPaddingStartPosition(chars.length())) { + throw new DecodingException("Invalid input length " + chars.length()); + } + int bytesWritten = 0; + for (int i = 0; i < chars.length(); ) { + int chunk = alphabet.decode(chars.charAt(i++)) << 18; + chunk |= alphabet.decode(chars.charAt(i++)) << 12; + target[bytesWritten++] = (byte) (chunk >>> 16); + if (i < chars.length()) { + chunk |= alphabet.decode(chars.charAt(i++)) << 6; + target[bytesWritten++] = (byte) ((chunk >>> 8) & 0xFF); + if (i < chars.length()) { + chunk |= alphabet.decode(chars.charAt(i++)); + target[bytesWritten++] = (byte) (chunk & 0xFF); + } + } + } + return bytesWritten; + } + + @Override + BaseEncoding newInstance(Alphabet alphabet, Character paddingChar) { + return new Base64Encoding(alphabet, paddingChar); + } + } + + @GwtIncompatible + static Reader ignoringReader(final Reader delegate, final String toIgnore) { + checkNotNull(delegate); + checkNotNull(toIgnore); + return new Reader() { + @Override + public int read() throws IOException { + int readChar; + do { + readChar = delegate.read(); + } while (readChar != -1 && toIgnore.indexOf((char) readChar) >= 0); + return readChar; + } + + @Override + public int read(char[] cbuf, int off, int len) throws IOException { + throw new UnsupportedOperationException(); + } + + @Override + public void close() throws IOException { + delegate.close(); + } + }; + } + + static Appendable separatingAppendable( + final Appendable delegate, final String separator, final int afterEveryChars) { + checkNotNull(delegate); + checkNotNull(separator); + checkArgument(afterEveryChars > 0); + return new Appendable() { + int charsUntilSeparator = afterEveryChars; + + @Override + public Appendable append(char c) throws IOException { + if (charsUntilSeparator == 0) { + delegate.append(separator); + charsUntilSeparator = afterEveryChars; + } + delegate.append(c); + charsUntilSeparator--; + return this; + } + + @Override + public Appendable append(CharSequence chars, int off, int len) throws IOException { + throw new UnsupportedOperationException(); + } + + @Override + public Appendable append(CharSequence chars) throws IOException { + throw new UnsupportedOperationException(); + } + }; + } + + @GwtIncompatible // Writer + static Writer separatingWriter( + final Writer delegate, final String separator, final int afterEveryChars) { + final Appendable seperatingAppendable = + separatingAppendable(delegate, separator, afterEveryChars); + return new Writer() { + @Override + public void write(int c) throws IOException { + seperatingAppendable.append((char) c); + } + + @Override + public void write(char[] chars, int off, int len) throws IOException { + throw new UnsupportedOperationException(); + } + + @Override + public void flush() throws IOException { + delegate.flush(); + } + + @Override + public void close() throws IOException { + delegate.close(); + } + }; + } + + static final class SeparatedBaseEncoding extends BaseEncoding { + private final BaseEncoding delegate; + private final String separator; + private final int afterEveryChars; + + SeparatedBaseEncoding(BaseEncoding delegate, String separator, int afterEveryChars) { + this.delegate = checkNotNull(delegate); + this.separator = checkNotNull(separator); + this.afterEveryChars = afterEveryChars; + checkArgument( + afterEveryChars > 0, "Cannot add a separator after every %s chars", afterEveryChars); + } + + @Override + CharSequence trimTrailingPadding(CharSequence chars) { + return delegate.trimTrailingPadding(chars); + } + + @Override + int maxEncodedSize(int bytes) { + int unseparatedSize = delegate.maxEncodedSize(bytes); + return unseparatedSize + + separator.length() * divide(Math.max(0, unseparatedSize - 1), afterEveryChars, FLOOR); + } + + @GwtIncompatible // Writer,OutputStream + @Override + public OutputStream encodingStream(final Writer output) { + return delegate.encodingStream(separatingWriter(output, separator, afterEveryChars)); + } + + @Override + void encodeTo(Appendable target, byte[] bytes, int off, int len) throws IOException { + delegate.encodeTo(separatingAppendable(target, separator, afterEveryChars), bytes, off, len); + } + + @Override + int maxDecodedSize(int chars) { + return delegate.maxDecodedSize(chars); + } + + @Override + public boolean canDecode(CharSequence chars) { + StringBuilder builder = new StringBuilder(); + for (int i = 0; i < chars.length(); i++) { + char c = chars.charAt(i); + if (separator.indexOf(c) < 0) { + builder.append(c); + } + } + return delegate.canDecode(builder); + } + + @Override + int decodeTo(byte[] target, CharSequence chars) throws DecodingException { + StringBuilder stripped = new StringBuilder(chars.length()); + for (int i = 0; i < chars.length(); i++) { + char c = chars.charAt(i); + if (separator.indexOf(c) < 0) { + stripped.append(c); + } + } + return delegate.decodeTo(target, stripped); + } + + @Override + @GwtIncompatible // Reader,InputStream + public InputStream decodingStream(final Reader reader) { + return delegate.decodingStream(ignoringReader(reader, separator)); + } + + @Override + public BaseEncoding omitPadding() { + return delegate.omitPadding().withSeparator(separator, afterEveryChars); + } + + @Override + public BaseEncoding withPadChar(char padChar) { + return delegate.withPadChar(padChar).withSeparator(separator, afterEveryChars); + } + + @Override + public BaseEncoding withSeparator(String separator, int afterEveryChars) { + throw new UnsupportedOperationException("Already have a separator"); + } + + @Override + public BaseEncoding upperCase() { + return delegate.upperCase().withSeparator(separator, afterEveryChars); + } + + @Override + public BaseEncoding lowerCase() { + return delegate.lowerCase().withSeparator(separator, afterEveryChars); + } + + @Override + public String toString() { + return delegate + ".withSeparator(\"" + separator + "\", " + afterEveryChars + ")"; + } + } +} diff --git a/src/main/java/com/google/common/io/ByteArrayDataInput.java b/src/main/java/com/google/common/io/ByteArrayDataInput.java new file mode 100644 index 0000000..1156609 --- /dev/null +++ b/src/main/java/com/google/common/io/ByteArrayDataInput.java @@ -0,0 +1,94 @@ +/* + * Copyright (C) 2009 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.io; + +import com.google.common.annotations.GwtIncompatible; + +import java.io.DataInput; +import java.io.IOException; + +/** + * An extension of {@code DataInput} for reading from in-memory byte arrays; its methods offer + * identical functionality but do not throw {@link IOException}. + * + *

Warning: The caller is responsible for not attempting to read past the end of the + * array. If any method encounters the end of the array prematurely, it throws {@link + * IllegalStateException} to signify programmer error. This behavior is a technical violation + * of the supertype's contract, which specifies a checked exception. + * + * @author Kevin Bourrillion + * @since 1.0 + */ +@GwtIncompatible +public interface ByteArrayDataInput extends DataInput { + @Override + void readFully(byte b[]); + + @Override + void readFully(byte b[], int off, int len); + + // not guaranteed to skip n bytes so result should NOT be ignored + // use ByteStreams.skipFully or one of the read methods instead + @Override + int skipBytes(int n); + + // to skip a byte + @Override + boolean readBoolean(); + + // to skip a byte + @Override + byte readByte(); + + // to skip a byte + @Override + int readUnsignedByte(); + + // to skip some bytes + @Override + short readShort(); + + // to skip some bytes + @Override + int readUnsignedShort(); + + // to skip some bytes + @Override + char readChar(); + + // to skip some bytes + @Override + int readInt(); + + // to skip some bytes + @Override + long readLong(); + + // to skip some bytes + @Override + float readFloat(); + + // to skip some bytes + @Override + double readDouble(); + + // to skip a line + @Override + String readLine(); + + // to skip a field + @Override + String readUTF(); +} diff --git a/src/main/java/com/google/common/io/ByteArrayDataOutput.java b/src/main/java/com/google/common/io/ByteArrayDataOutput.java new file mode 100644 index 0000000..e1ad6ab --- /dev/null +++ b/src/main/java/com/google/common/io/ByteArrayDataOutput.java @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2009 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.io; + +import com.google.common.annotations.GwtIncompatible; +import java.io.DataOutput; +import java.io.IOException; + +/** + * An extension of {@code DataOutput} for writing to in-memory byte arrays; its methods offer + * identical functionality but do not throw {@link IOException}. + * + * @author Jayaprabhakar Kadarkarai + * @since 1.0 + */ +@GwtIncompatible +public interface ByteArrayDataOutput extends DataOutput { + @Override + void write(int b); + + @Override + void write(byte b[]); + + @Override + void write(byte b[], int off, int len); + + @Override + void writeBoolean(boolean v); + + @Override + void writeByte(int v); + + @Override + void writeShort(int v); + + @Override + void writeChar(int v); + + @Override + void writeInt(int v); + + @Override + void writeLong(long v); + + @Override + void writeFloat(float v); + + @Override + void writeDouble(double v); + + @Override + void writeChars(String s); + + @Override + void writeUTF(String s); + + /** + * @deprecated This method is dangerous as it discards the high byte of every character. For + * UTF-8, use {@code write(s.getBytes(StandardCharsets.UTF_8))}. + */ + @Deprecated + @Override + void writeBytes(String s); + + /** Returns the contents that have been written to this instance, as a byte array. */ + byte[] toByteArray(); +} diff --git a/src/main/java/com/google/common/io/ByteProcessor.java b/src/main/java/com/google/common/io/ByteProcessor.java new file mode 100644 index 0000000..f03f5b4 --- /dev/null +++ b/src/main/java/com/google/common/io/ByteProcessor.java @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2009 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.io; + +import com.google.common.annotations.Beta; +import com.google.common.annotations.GwtIncompatible; + +import java.io.IOException; + +/** + * A callback interface to process bytes from a stream. + * + *

{@link #processBytes} will be called for each chunk of data that is read, and should return + * {@code false} when you want to stop processing. + * + * @author Chris Nokleberg + * @since 1.0 + */ +@Beta +@GwtIncompatible +public interface ByteProcessor { + /** + * This method will be called for each chunk of bytes in an input stream. The implementation + * should process the bytes from {@code buf[off]} through {@code buf[off + len - 1]} (inclusive). + * + * @param buf the byte array containing the data to process + * @param off the initial offset into the array + * @param len the length of data to be processed + * @return true to continue processing, false to stop + */ + // some uses know that their processor never returns false + boolean processBytes(byte[] buf, int off, int len) throws IOException; + + /** Return the result of processing all the bytes. */ + T getResult(); +} diff --git a/src/main/java/com/google/common/io/ByteSink.java b/src/main/java/com/google/common/io/ByteSink.java new file mode 100644 index 0000000..7de4db1 --- /dev/null +++ b/src/main/java/com/google/common/io/ByteSink.java @@ -0,0 +1,157 @@ +/* + * Copyright (C) 2012 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.io; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.annotations.GwtIncompatible; + +import java.io.BufferedOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.io.Writer; +import java.nio.charset.Charset; + +/** + * A destination to which bytes can be written, such as a file. Unlike an {@link OutputStream}, a + * {@code ByteSink} is not an open, stateful stream that can be written to and closed. Instead, it + * is an immutable supplier of {@code OutputStream} instances. + * + *

{@code ByteSink} provides two kinds of methods: + * + *

    + *
  • Methods that return a stream: These methods should return a new, independent + * instance each time they are called. The caller is responsible for ensuring that the + * returned stream is closed. + *
  • Convenience methods: These are implementations of common operations that are + * typically implemented by opening a stream using one of the methods in the first category, + * doing something and finally closing the stream or channel that was opened. + *
+ * + * @since 14.0 + * @author Colin Decker + */ +@GwtIncompatible +public abstract class ByteSink { + + /** Constructor for use by subclasses. */ + protected ByteSink() {} + + /** + * Returns a {@link CharSink} view of this {@code ByteSink} that writes characters to this sink as + * bytes encoded with the given {@link Charset charset}. + */ + public CharSink asCharSink(Charset charset) { + return new AsCharSink(charset); + } + + /** + * Opens a new {@link OutputStream} for writing to this sink. This method returns a new, + * independent stream each time it is called. + * + *

The caller is responsible for ensuring that the returned stream is closed. + * + * @throws IOException if an I/O error occurs while opening the stream + */ + public abstract OutputStream openStream() throws IOException; + + /** + * Opens a new buffered {@link OutputStream} for writing to this sink. The returned stream is not + * required to be a {@link BufferedOutputStream} in order to allow implementations to simply + * delegate to {@link #openStream()} when the stream returned by that method does not benefit from + * additional buffering (for example, a {@code ByteArrayOutputStream}). This method returns a new, + * independent stream each time it is called. + * + *

The caller is responsible for ensuring that the returned stream is closed. + * + * @throws IOException if an I/O error occurs while opening the stream + * @since 15.0 (in 14.0 with return type {@link BufferedOutputStream}) + */ + public OutputStream openBufferedStream() throws IOException { + OutputStream out = openStream(); + return (out instanceof BufferedOutputStream) + ? (BufferedOutputStream) out + : new BufferedOutputStream(out); + } + + /** + * Writes all the given bytes to this sink. + * + * @throws IOException if an I/O occurs while writing to this sink + */ + public void write(byte[] bytes) throws IOException { + checkNotNull(bytes); + + Closer closer = Closer.create(); + try { + OutputStream out = closer.register(openStream()); + out.write(bytes); + out.flush(); // https://code.google.com/p/guava-libraries/issues/detail?id=1330 + } catch (Throwable e) { + throw closer.rethrow(e); + } finally { + closer.close(); + } + } + + /** + * Writes all the bytes from the given {@code InputStream} to this sink. Does not close {@code + * input}. + * + * @return the number of bytes written + * @throws IOException if an I/O occurs while reading from {@code input} or writing to this sink + */ + + public long writeFrom(InputStream input) throws IOException { + checkNotNull(input); + + Closer closer = Closer.create(); + try { + OutputStream out = closer.register(openStream()); + long written = ByteStreams.copy(input, out); + out.flush(); // https://code.google.com/p/guava-libraries/issues/detail?id=1330 + return written; + } catch (Throwable e) { + throw closer.rethrow(e); + } finally { + closer.close(); + } + } + + /** + * A char sink that encodes written characters with a charset and writes resulting bytes to this + * byte sink. + */ + private final class AsCharSink extends CharSink { + + private final Charset charset; + + private AsCharSink(Charset charset) { + this.charset = checkNotNull(charset); + } + + @Override + public Writer openStream() throws IOException { + return new OutputStreamWriter(ByteSink.this.openStream(), charset); + } + + @Override + public String toString() { + return ByteSink.this.toString() + ".asCharSink(" + charset + ")"; + } + } +} diff --git a/src/main/java/com/google/common/io/ByteSource.java b/src/main/java/com/google/common/io/ByteSource.java new file mode 100644 index 0000000..c50a5f9 --- /dev/null +++ b/src/main/java/com/google/common/io/ByteSource.java @@ -0,0 +1,741 @@ +/* + * Copyright (C) 2012 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.io; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.io.ByteStreams.createBuffer; +import static com.google.common.io.ByteStreams.skipUpTo; + +import com.google.common.annotations.Beta; +import com.google.common.annotations.GwtIncompatible; +import com.google.common.base.Ascii; +import com.google.common.base.Optional; +import com.google.common.collect.ImmutableList; +import com.google.common.hash.Funnels; +import com.google.common.hash.HashCode; +import com.google.common.hash.HashFunction; +import com.google.common.hash.Hasher; + +import java.io.BufferedInputStream; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.io.Reader; +import java.nio.charset.Charset; +import java.util.Arrays; +import java.util.Collection; +import java.util.Iterator; + +/** + * A readable source of bytes, such as a file. Unlike an {@link InputStream}, a {@code ByteSource} + * is not an open, stateful stream for input that can be read and closed. Instead, it is an + * immutable supplier of {@code InputStream} instances. + * + *

{@code ByteSource} provides two kinds of methods: + * + *

    + *
  • Methods that return a stream: These methods should return a new, independent + * instance each time they are called. The caller is responsible for ensuring that the + * returned stream is closed. + *
  • Convenience methods: These are implementations of common operations that are + * typically implemented by opening a stream using one of the methods in the first category, + * doing something and finally closing the stream that was opened. + *
+ * + * @since 14.0 + * @author Colin Decker + */ +@GwtIncompatible +public abstract class ByteSource { + + /** Constructor for use by subclasses. */ + protected ByteSource() {} + + /** + * Returns a {@link CharSource} view of this byte source that decodes bytes read from this source + * as characters using the given {@link Charset}. + * + *

If {@link CharSource#asByteSource} is called on the returned source with the same charset, + * the default implementation of this method will ensure that the original {@code ByteSource} is + * returned, rather than round-trip encoding. Subclasses that override this method should behave + * the same way. + */ + public CharSource asCharSource(Charset charset) { + return new AsCharSource(charset); + } + + /** + * Opens a new {@link InputStream} for reading from this source. This method returns a new, + * independent stream each time it is called. + * + *

The caller is responsible for ensuring that the returned stream is closed. + * + * @throws IOException if an I/O error occurs while opening the stream + */ + public abstract InputStream openStream() throws IOException; + + /** + * Opens a new buffered {@link InputStream} for reading from this source. The returned stream is + * not required to be a {@link BufferedInputStream} in order to allow implementations to simply + * delegate to {@link #openStream()} when the stream returned by that method does not benefit from + * additional buffering (for example, a {@code ByteArrayInputStream}). This method returns a new, + * independent stream each time it is called. + * + *

The caller is responsible for ensuring that the returned stream is closed. + * + * @throws IOException if an I/O error occurs while opening the stream + * @since 15.0 (in 14.0 with return type {@link BufferedInputStream}) + */ + public InputStream openBufferedStream() throws IOException { + InputStream in = openStream(); + return (in instanceof BufferedInputStream) + ? (BufferedInputStream) in + : new BufferedInputStream(in); + } + + /** + * Returns a view of a slice of this byte source that is at most {@code length} bytes long + * starting at the given {@code offset}. If {@code offset} is greater than the size of this + * source, the returned source will be empty. If {@code offset + length} is greater than the size + * of this source, the returned source will contain the slice starting at {@code offset} and + * ending at the end of this source. + * + * @throws IllegalArgumentException if {@code offset} or {@code length} is negative + */ + public ByteSource slice(long offset, long length) { + return new SlicedByteSource(offset, length); + } + + /** + * Returns whether the source has zero bytes. The default implementation first checks {@link + * #sizeIfKnown}, returning true if it's known to be zero and false if it's known to be non-zero. + * If the size is not known, it falls back to opening a stream and checking for EOF. + * + *

Note that, in cases where {@code sizeIfKnown} returns zero, it is possible that bytes + * are actually available for reading. (For example, some special files may return a size of 0 + * despite actually having content when read.) This means that a source may return {@code true} + * from {@code isEmpty()} despite having readable content. + * + * @throws IOException if an I/O error occurs + * @since 15.0 + */ + public boolean isEmpty() throws IOException { + Optional sizeIfKnown = sizeIfKnown(); + if (sizeIfKnown.isPresent()) { + return sizeIfKnown.get() == 0L; + } + Closer closer = Closer.create(); + try { + InputStream in = closer.register(openStream()); + return in.read() == -1; + } catch (Throwable e) { + throw closer.rethrow(e); + } finally { + closer.close(); + } + } + + /** + * Returns the size of this source in bytes, if the size can be easily determined without actually + * opening the data stream. + * + *

The default implementation returns {@link Optional#absent}. Some sources, such as a file, + * may return a non-absent value. Note that in such cases, it is possible that this method + * will return a different number of bytes than would be returned by reading all of the bytes (for + * example, some special files may return a size of 0 despite actually having content when read). + * + *

Additionally, for mutable sources such as files, a subsequent read may return a different + * number of bytes if the contents are changed. + * + * @since 19.0 + */ + @Beta + public Optional sizeIfKnown() { + return Optional.absent(); + } + + /** + * Returns the size of this source in bytes, even if doing so requires opening and traversing an + * entire stream. To avoid a potentially expensive operation, see {@link #sizeIfKnown}. + * + *

The default implementation calls {@link #sizeIfKnown} and returns the value if present. If + * absent, it will fall back to a heavyweight operation that will open a stream, read (or {@link + * InputStream#skip(long) skip}, if possible) to the end of the stream and return the total number + * of bytes that were read. + * + *

Note that for some sources that implement {@link #sizeIfKnown} to provide a more efficient + * implementation, it is possible that this method will return a different number of bytes + * than would be returned by reading all of the bytes (for example, some special files may return + * a size of 0 despite actually having content when read). + * + *

In either case, for mutable sources such as files, a subsequent read may return a different + * number of bytes if the contents are changed. + * + * @throws IOException if an I/O error occurs while reading the size of this source + */ + public long size() throws IOException { + Optional sizeIfKnown = sizeIfKnown(); + if (sizeIfKnown.isPresent()) { + return sizeIfKnown.get(); + } + + Closer closer = Closer.create(); + try { + InputStream in = closer.register(openStream()); + return countBySkipping(in); + } catch (IOException e) { + // skip may not be supported... at any rate, try reading + } finally { + closer.close(); + } + + closer = Closer.create(); + try { + InputStream in = closer.register(openStream()); + return ByteStreams.exhaust(in); + } catch (Throwable e) { + throw closer.rethrow(e); + } finally { + closer.close(); + } + } + + /** Counts the bytes in the given input stream using skip if possible. */ + private long countBySkipping(InputStream in) throws IOException { + long count = 0; + long skipped; + while ((skipped = skipUpTo(in, Integer.MAX_VALUE)) > 0) { + count += skipped; + } + return count; + } + + /** + * Copies the contents of this byte source to the given {@code OutputStream}. Does not close + * {@code output}. + * + * @return the number of bytes copied + * @throws IOException if an I/O error occurs while reading from this source or writing to {@code + * output} + */ + + public long copyTo(OutputStream output) throws IOException { + checkNotNull(output); + + Closer closer = Closer.create(); + try { + InputStream in = closer.register(openStream()); + return ByteStreams.copy(in, output); + } catch (Throwable e) { + throw closer.rethrow(e); + } finally { + closer.close(); + } + } + + /** + * Copies the contents of this byte source to the given {@code ByteSink}. + * + * @return the number of bytes copied + * @throws IOException if an I/O error occurs while reading from this source or writing to {@code + * sink} + */ + + public long copyTo(ByteSink sink) throws IOException { + checkNotNull(sink); + + Closer closer = Closer.create(); + try { + InputStream in = closer.register(openStream()); + OutputStream out = closer.register(sink.openStream()); + return ByteStreams.copy(in, out); + } catch (Throwable e) { + throw closer.rethrow(e); + } finally { + closer.close(); + } + } + + /** + * Reads the full contents of this byte source as a byte array. + * + * @throws IOException if an I/O error occurs while reading from this source + */ + public byte[] read() throws IOException { + Closer closer = Closer.create(); + try { + InputStream in = closer.register(openStream()); + Optional size = sizeIfKnown(); + return size.isPresent() + ? ByteStreams.toByteArray(in, size.get()) + : ByteStreams.toByteArray(in); + } catch (Throwable e) { + throw closer.rethrow(e); + } finally { + closer.close(); + } + } + + /** + * Reads the contents of this byte source using the given {@code processor} to process bytes as + * they are read. Stops when all bytes have been read or the consumer returns {@code false}. + * Returns the result produced by the processor. + * + * @throws IOException if an I/O error occurs while reading from this source or if {@code + * processor} throws an {@code IOException} + * @since 16.0 + */ + @Beta + // some processors won't return a useful result + public T read(ByteProcessor processor) throws IOException { + checkNotNull(processor); + + Closer closer = Closer.create(); + try { + InputStream in = closer.register(openStream()); + return ByteStreams.readBytes(in, processor); + } catch (Throwable e) { + throw closer.rethrow(e); + } finally { + closer.close(); + } + } + + /** + * Hashes the contents of this byte source using the given hash function. + * + * @throws IOException if an I/O error occurs while reading from this source + */ + public HashCode hash(HashFunction hashFunction) throws IOException { + Hasher hasher = hashFunction.newHasher(); + copyTo(Funnels.asOutputStream(hasher)); + return hasher.hash(); + } + + /** + * Checks that the contents of this byte source are equal to the contents of the given byte + * source. + * + * @throws IOException if an I/O error occurs while reading from this source or {@code other} + */ + public boolean contentEquals(ByteSource other) throws IOException { + checkNotNull(other); + + byte[] buf1 = createBuffer(); + byte[] buf2 = createBuffer(); + + Closer closer = Closer.create(); + try { + InputStream in1 = closer.register(openStream()); + InputStream in2 = closer.register(other.openStream()); + while (true) { + int read1 = ByteStreams.read(in1, buf1, 0, buf1.length); + int read2 = ByteStreams.read(in2, buf2, 0, buf2.length); + if (read1 != read2 || !Arrays.equals(buf1, buf2)) { + return false; + } else if (read1 != buf1.length) { + return true; + } + } + } catch (Throwable e) { + throw closer.rethrow(e); + } finally { + closer.close(); + } + } + + /** + * Concatenates multiple {@link ByteSource} instances into a single source. Streams returned from + * the source will contain the concatenated data from the streams of the underlying sources. + * + *

Only one underlying stream will be open at a time. Closing the concatenated stream will + * close the open underlying stream. + * + * @param sources the sources to concatenate + * @return a {@code ByteSource} containing the concatenated data + * @since 15.0 + */ + public static ByteSource concat(Iterable sources) { + return new ConcatenatedByteSource(sources); + } + + /** + * Concatenates multiple {@link ByteSource} instances into a single source. Streams returned from + * the source will contain the concatenated data from the streams of the underlying sources. + * + *

Only one underlying stream will be open at a time. Closing the concatenated stream will + * close the open underlying stream. + * + *

Note: The input {@code Iterator} will be copied to an {@code ImmutableList} when this method + * is called. This will fail if the iterator is infinite and may cause problems if the iterator + * eagerly fetches data for each source when iterated (rather than producing sources that only + * load data through their streams). Prefer using the {@link #concat(Iterable)} overload if + * possible. + * + * @param sources the sources to concatenate + * @return a {@code ByteSource} containing the concatenated data + * @throws NullPointerException if any of {@code sources} is {@code null} + * @since 15.0 + */ + public static ByteSource concat(Iterator sources) { + return concat(ImmutableList.copyOf(sources)); + } + + /** + * Concatenates multiple {@link ByteSource} instances into a single source. Streams returned from + * the source will contain the concatenated data from the streams of the underlying sources. + * + *

Only one underlying stream will be open at a time. Closing the concatenated stream will + * close the open underlying stream. + * + * @param sources the sources to concatenate + * @return a {@code ByteSource} containing the concatenated data + * @throws NullPointerException if any of {@code sources} is {@code null} + * @since 15.0 + */ + public static ByteSource concat(ByteSource... sources) { + return concat(ImmutableList.copyOf(sources)); + } + + /** + * Returns a view of the given byte array as a {@link ByteSource}. To view only a specific range + * in the array, use {@code ByteSource.wrap(b).slice(offset, length)}. + * + *

Note that the given byte array may be be passed directly to methods on, for example, {@code + * OutputStream} (when {@code copyTo(OutputStream)} is called on the resulting {@code + * ByteSource}). This could allow a malicious {@code OutputStream} implementation to modify the + * contents of the array, but provides better performance in the normal case. + * + * @since 15.0 (since 14.0 as {@code ByteStreams.asByteSource(byte[])}). + */ + public static ByteSource wrap(byte[] b) { + return new ByteArrayByteSource(b); + } + + /** + * Returns an immutable {@link ByteSource} that contains no bytes. + * + * @since 15.0 + */ + public static ByteSource empty() { + return EmptyByteSource.INSTANCE; + } + + /** + * A char source that reads bytes from this source and decodes them as characters using a charset. + */ + class AsCharSource extends CharSource { + + final Charset charset; + + AsCharSource(Charset charset) { + this.charset = checkNotNull(charset); + } + + @Override + public ByteSource asByteSource(Charset charset) { + if (charset.equals(this.charset)) { + return ByteSource.this; + } + return super.asByteSource(charset); + } + + @Override + public Reader openStream() throws IOException { + return new InputStreamReader(ByteSource.this.openStream(), charset); + } + + @Override + public String read() throws IOException { + // Reading all the data as a byte array is more efficient than the default read() + // implementation because: + // 1. the string constructor can avoid an extra copy most of the time by correctly sizing the + // internal char array (hard to avoid using StringBuilder) + // 2. we avoid extra copies into temporary buffers altogether + // The downside is that this will cause us to store the file bytes in memory twice for a short + // amount of time. + return new String(ByteSource.this.read(), charset); + } + + @Override + public String toString() { + return ByteSource.this.toString() + ".asCharSource(" + charset + ")"; + } + } + + /** A view of a subsection of the containing byte source. */ + private final class SlicedByteSource extends ByteSource { + + final long offset; + final long length; + + SlicedByteSource(long offset, long length) { + checkArgument(offset >= 0, "offset (%s) may not be negative", offset); + checkArgument(length >= 0, "length (%s) may not be negative", length); + this.offset = offset; + this.length = length; + } + + @Override + public InputStream openStream() throws IOException { + return sliceStream(ByteSource.this.openStream()); + } + + @Override + public InputStream openBufferedStream() throws IOException { + return sliceStream(ByteSource.this.openBufferedStream()); + } + + private InputStream sliceStream(InputStream in) throws IOException { + if (offset > 0) { + long skipped; + try { + skipped = ByteStreams.skipUpTo(in, offset); + } catch (Throwable e) { + Closer closer = Closer.create(); + closer.register(in); + try { + throw closer.rethrow(e); + } finally { + closer.close(); + } + } + + if (skipped < offset) { + // offset was beyond EOF + in.close(); + return new ByteArrayInputStream(new byte[0]); + } + } + return ByteStreams.limit(in, length); + } + + @Override + public ByteSource slice(long offset, long length) { + checkArgument(offset >= 0, "offset (%s) may not be negative", offset); + checkArgument(length >= 0, "length (%s) may not be negative", length); + long maxLength = this.length - offset; + return ByteSource.this.slice(this.offset + offset, Math.min(length, maxLength)); + } + + @Override + public boolean isEmpty() throws IOException { + return length == 0 || super.isEmpty(); + } + + @Override + public Optional sizeIfKnown() { + Optional optionalUnslicedSize = ByteSource.this.sizeIfKnown(); + if (optionalUnslicedSize.isPresent()) { + long unslicedSize = optionalUnslicedSize.get(); + long off = Math.min(offset, unslicedSize); + return Optional.of(Math.min(length, unslicedSize - off)); + } + return Optional.absent(); + } + + @Override + public String toString() { + return ByteSource.this.toString() + ".slice(" + offset + ", " + length + ")"; + } + } + + private static class ByteArrayByteSource extends ByteSource { + + final byte[] bytes; + final int offset; + final int length; + + ByteArrayByteSource(byte[] bytes) { + this(bytes, 0, bytes.length); + } + + // NOTE: Preconditions are enforced by slice, the only non-trivial caller. + ByteArrayByteSource(byte[] bytes, int offset, int length) { + this.bytes = bytes; + this.offset = offset; + this.length = length; + } + + @Override + public InputStream openStream() { + return new ByteArrayInputStream(bytes, offset, length); + } + + @Override + public InputStream openBufferedStream() throws IOException { + return openStream(); + } + + @Override + public boolean isEmpty() { + return length == 0; + } + + @Override + public long size() { + return length; + } + + @Override + public Optional sizeIfKnown() { + return Optional.of((long) length); + } + + @Override + public byte[] read() { + return Arrays.copyOfRange(bytes, offset, offset + length); + } + + @SuppressWarnings("CheckReturnValue") // it doesn't matter what processBytes returns here + @Override + public T read(ByteProcessor processor) throws IOException { + processor.processBytes(bytes, offset, length); + return processor.getResult(); + } + + @Override + public long copyTo(OutputStream output) throws IOException { + output.write(bytes, offset, length); + return length; + } + + @Override + public HashCode hash(HashFunction hashFunction) throws IOException { + return hashFunction.hashBytes(bytes, offset, length); + } + + @Override + public ByteSource slice(long offset, long length) { + checkArgument(offset >= 0, "offset (%s) may not be negative", offset); + checkArgument(length >= 0, "length (%s) may not be negative", length); + + offset = Math.min(offset, this.length); + length = Math.min(length, this.length - offset); + int newOffset = this.offset + (int) offset; + return new ByteArrayByteSource(bytes, newOffset, (int) length); + } + + @Override + public String toString() { + return "ByteSource.wrap(" + + Ascii.truncate(BaseEncoding.base16().encode(bytes, offset, length), 30, "...") + + ")"; + } + } + + private static final class EmptyByteSource extends ByteArrayByteSource { + + static final EmptyByteSource INSTANCE = new EmptyByteSource(); + + EmptyByteSource() { + super(new byte[0]); + } + + @Override + public CharSource asCharSource(Charset charset) { + checkNotNull(charset); + return CharSource.empty(); + } + + @Override + public byte[] read() { + return bytes; // length is 0, no need to clone + } + + @Override + public String toString() { + return "ByteSource.empty()"; + } + } + + private static final class ConcatenatedByteSource extends ByteSource { + + final Iterable sources; + + ConcatenatedByteSource(Iterable sources) { + this.sources = checkNotNull(sources); + } + + @Override + public InputStream openStream() throws IOException { + return new MultiInputStream(sources.iterator()); + } + + @Override + public boolean isEmpty() throws IOException { + for (ByteSource source : sources) { + if (!source.isEmpty()) { + return false; + } + } + return true; + } + + @Override + public Optional sizeIfKnown() { + if (!(sources instanceof Collection)) { + // Infinite Iterables can cause problems here. Of course, it's true that most of the other + // methods on this class also have potential problems with infinite Iterables. But unlike + // those, this method can cause issues even if the user is dealing with a (finite) slice() + // of this source, since the slice's sizeIfKnown() method needs to know the size of the + // underlying source to know what its size actually is. + return Optional.absent(); + } + long result = 0L; + for (ByteSource source : sources) { + Optional sizeIfKnown = source.sizeIfKnown(); + if (!sizeIfKnown.isPresent()) { + return Optional.absent(); + } + result += sizeIfKnown.get(); + if (result < 0) { + // Overflow (or one or more sources that returned a negative size, but all bets are off in + // that case) + // Can't represent anything higher, and realistically there probably isn't anything that + // can actually be done anyway with the supposed 8+ exbibytes of data the source is + // claiming to have if we get here, so just stop. + return Optional.of(Long.MAX_VALUE); + } + } + return Optional.of(result); + } + + @Override + public long size() throws IOException { + long result = 0L; + for (ByteSource source : sources) { + result += source.size(); + if (result < 0) { + // Overflow (or one or more sources that returned a negative size, but all bets are off in + // that case) + // Can't represent anything higher, and realistically there probably isn't anything that + // can actually be done anyway with the supposed 8+ exbibytes of data the source is + // claiming to have if we get here, so just stop. + return Long.MAX_VALUE; + } + } + return result; + } + + @Override + public String toString() { + return "ByteSource.concat(" + sources + ")"; + } + } +} diff --git a/src/main/java/com/google/common/io/ByteStreams.java b/src/main/java/com/google/common/io/ByteStreams.java new file mode 100644 index 0000000..bd9a05d --- /dev/null +++ b/src/main/java/com/google/common/io/ByteStreams.java @@ -0,0 +1,925 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.io; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.base.Preconditions.checkPositionIndex; + +import com.google.common.annotations.Beta; +import com.google.common.annotations.GwtIncompatible; +import com.google.common.math.IntMath; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.DataInput; +import java.io.DataInputStream; +import java.io.DataOutput; +import java.io.DataOutputStream; +import java.io.EOFException; +import java.io.FilterInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.ByteBuffer; +import java.nio.channels.FileChannel; +import java.nio.channels.ReadableByteChannel; +import java.nio.channels.WritableByteChannel; +import java.util.ArrayDeque; +import java.util.Arrays; +import java.util.Deque; + +/** + * Provides utility methods for working with byte arrays and I/O streams. + * + * @author Chris Nokleberg + * @author Colin Decker + * @since 1.0 + */ +@GwtIncompatible +public final class ByteStreams { + + private static final int BUFFER_SIZE = 8192; + + /** Creates a new byte array for buffering reads or writes. */ + static byte[] createBuffer() { + return new byte[BUFFER_SIZE]; + } + + /** + * There are three methods to implement {@link FileChannel#transferTo(long, long, + * WritableByteChannel)}: + * + *

    + *
  1. Use sendfile(2) or equivalent. Requires that both the input channel and the output + * channel have their own file descriptors. Generally this only happens when both channels + * are files or sockets. This performs zero copies - the bytes never enter userspace. + *
  2. Use mmap(2) or equivalent. Requires that either the input channel or the output channel + * have file descriptors. Bytes are copied from the file into a kernel buffer, then directly + * into the other buffer (userspace). Note that if the file is very large, a naive + * implementation will effectively put the whole file in memory. On many systems with paging + * and virtual memory, this is not a problem - because it is mapped read-only, the kernel + * can always page it to disk "for free". However, on systems where killing processes + * happens all the time in normal conditions (i.e., android) the OS must make a tradeoff + * between paging memory and killing other processes - so allocating a gigantic buffer and + * then sequentially accessing it could result in other processes dying. This is solvable + * via madvise(2), but that obviously doesn't exist in java. + *
  3. Ordinary copy. Kernel copies bytes into a kernel buffer, from a kernel buffer into a + * userspace buffer (byte[] or ByteBuffer), then copies them from that buffer into the + * destination channel. + *
+ * + * This value is intended to be large enough to make the overhead of system calls negligible, + * without being so large that it causes problems for systems with atypical memory management if + * approaches 2 or 3 are used. + */ + private static final int ZERO_COPY_CHUNK_SIZE = 512 * 1024; + + private ByteStreams() {} + + /** + * Copies all bytes from the input stream to the output stream. Does not close or flush either + * stream. + * + * @param from the input stream to read from + * @param to the output stream to write to + * @return the number of bytes copied + * @throws IOException if an I/O error occurs + */ + + public static long copy(InputStream from, OutputStream to) throws IOException { + checkNotNull(from); + checkNotNull(to); + byte[] buf = createBuffer(); + long total = 0; + while (true) { + int r = from.read(buf); + if (r == -1) { + break; + } + to.write(buf, 0, r); + total += r; + } + return total; + } + + /** + * Copies all bytes from the readable channel to the writable channel. Does not close or flush + * either channel. + * + * @param from the readable channel to read from + * @param to the writable channel to write to + * @return the number of bytes copied + * @throws IOException if an I/O error occurs + */ + + public static long copy(ReadableByteChannel from, WritableByteChannel to) throws IOException { + checkNotNull(from); + checkNotNull(to); + if (from instanceof FileChannel) { + FileChannel sourceChannel = (FileChannel) from; + long oldPosition = sourceChannel.position(); + long position = oldPosition; + long copied; + do { + copied = sourceChannel.transferTo(position, ZERO_COPY_CHUNK_SIZE, to); + position += copied; + sourceChannel.position(position); + } while (copied > 0 || position < sourceChannel.size()); + return position - oldPosition; + } + + ByteBuffer buf = ByteBuffer.wrap(createBuffer()); + long total = 0; + while (from.read(buf) != -1) { + buf.flip(); + while (buf.hasRemaining()) { + total += to.write(buf); + } + buf.clear(); + } + return total; + } + + /** Max array length on JVM. */ + private static final int MAX_ARRAY_LEN = Integer.MAX_VALUE - 8; + + /** Large enough to never need to expand, given the geometric progression of buffer sizes. */ + private static final int TO_BYTE_ARRAY_DEQUE_SIZE = 20; + + /** + * Returns a byte array containing the bytes from the buffers already in {@code bufs} (which have + * a total combined length of {@code totalLen} bytes) followed by all bytes remaining in the given + * input stream. + */ + private static byte[] toByteArrayInternal(InputStream in, Deque bufs, int totalLen) + throws IOException { + // Starting with an 8k buffer, double the size of each sucessive buffer. Buffers are retained + // in a deque so that there's no copying between buffers while reading and so all of the bytes + // in each new allocated buffer are available for reading from the stream. + for (int bufSize = BUFFER_SIZE; + totalLen < MAX_ARRAY_LEN; + bufSize = IntMath.saturatedMultiply(bufSize, 2)) { + byte[] buf = new byte[Math.min(bufSize, MAX_ARRAY_LEN - totalLen)]; + bufs.add(buf); + int off = 0; + while (off < buf.length) { + // always OK to fill buf; its size plus the rest of bufs is never more than MAX_ARRAY_LEN + int r = in.read(buf, off, buf.length - off); + if (r == -1) { + return combineBuffers(bufs, totalLen); + } + off += r; + totalLen += r; + } + } + + // read MAX_ARRAY_LEN bytes without seeing end of stream + if (in.read() == -1) { + // oh, there's the end of the stream + return combineBuffers(bufs, MAX_ARRAY_LEN); + } else { + throw new OutOfMemoryError("input is too large to fit in a byte array"); + } + } + + private static byte[] combineBuffers(Deque bufs, int totalLen) { + byte[] result = new byte[totalLen]; + int remaining = totalLen; + while (remaining > 0) { + byte[] buf = bufs.removeFirst(); + int bytesToCopy = Math.min(remaining, buf.length); + int resultOffset = totalLen - remaining; + System.arraycopy(buf, 0, result, resultOffset, bytesToCopy); + remaining -= bytesToCopy; + } + return result; + } + + /** + * Reads all bytes from an input stream into a byte array. Does not close the stream. + * + * @param in the input stream to read from + * @return a byte array containing all the bytes from the stream + * @throws IOException if an I/O error occurs + */ + public static byte[] toByteArray(InputStream in) throws IOException { + checkNotNull(in); + return toByteArrayInternal(in, new ArrayDeque(TO_BYTE_ARRAY_DEQUE_SIZE), 0); + } + + /** + * Reads all bytes from an input stream into a byte array. The given expected size is used to + * create an initial byte array, but if the actual number of bytes read from the stream differs, + * the correct result will be returned anyway. + */ + static byte[] toByteArray(InputStream in, long expectedSize) throws IOException { + checkArgument(expectedSize >= 0, "expectedSize (%s) must be non-negative", expectedSize); + if (expectedSize > MAX_ARRAY_LEN) { + throw new OutOfMemoryError(expectedSize + " bytes is too large to fit in a byte array"); + } + + byte[] bytes = new byte[(int) expectedSize]; + int remaining = (int) expectedSize; + + while (remaining > 0) { + int off = (int) expectedSize - remaining; + int read = in.read(bytes, off, remaining); + if (read == -1) { + // end of stream before reading expectedSize bytes + // just return the bytes read so far + return Arrays.copyOf(bytes, off); + } + remaining -= read; + } + + // bytes is now full + int b = in.read(); + if (b == -1) { + return bytes; + } + + // the stream was longer, so read the rest normally + Deque bufs = new ArrayDeque(TO_BYTE_ARRAY_DEQUE_SIZE + 2); + bufs.add(bytes); + bufs.add(new byte[] {(byte) b}); + return toByteArrayInternal(in, bufs, bytes.length + 1); + } + + /** + * Reads and discards data from the given {@code InputStream} until the end of the stream is + * reached. Returns the total number of bytes read. Does not close the stream. + * + * @since 20.0 + */ + + @Beta + public static long exhaust(InputStream in) throws IOException { + long total = 0; + long read; + byte[] buf = createBuffer(); + while ((read = in.read(buf)) != -1) { + total += read; + } + return total; + } + + /** + * Returns a new {@link ByteArrayDataInput} instance to read from the {@code bytes} array from the + * beginning. + */ + @Beta + public static ByteArrayDataInput newDataInput(byte[] bytes) { + return newDataInput(new ByteArrayInputStream(bytes)); + } + + /** + * Returns a new {@link ByteArrayDataInput} instance to read from the {@code bytes} array, + * starting at the given position. + * + * @throws IndexOutOfBoundsException if {@code start} is negative or greater than the length of + * the array + */ + @Beta + public static ByteArrayDataInput newDataInput(byte[] bytes, int start) { + checkPositionIndex(start, bytes.length); + return newDataInput(new ByteArrayInputStream(bytes, start, bytes.length - start)); + } + + /** + * Returns a new {@link ByteArrayDataInput} instance to read from the given {@code + * ByteArrayInputStream}. The given input stream is not reset before being read from by the + * returned {@code ByteArrayDataInput}. + * + * @since 17.0 + */ + @Beta + public static ByteArrayDataInput newDataInput(ByteArrayInputStream byteArrayInputStream) { + return new ByteArrayDataInputStream(checkNotNull(byteArrayInputStream)); + } + + private static class ByteArrayDataInputStream implements ByteArrayDataInput { + final DataInput input; + + ByteArrayDataInputStream(ByteArrayInputStream byteArrayInputStream) { + this.input = new DataInputStream(byteArrayInputStream); + } + + @Override + public void readFully(byte b[]) { + try { + input.readFully(b); + } catch (IOException e) { + throw new IllegalStateException(e); + } + } + + @Override + public void readFully(byte b[], int off, int len) { + try { + input.readFully(b, off, len); + } catch (IOException e) { + throw new IllegalStateException(e); + } + } + + @Override + public int skipBytes(int n) { + try { + return input.skipBytes(n); + } catch (IOException e) { + throw new IllegalStateException(e); + } + } + + @Override + public boolean readBoolean() { + try { + return input.readBoolean(); + } catch (IOException e) { + throw new IllegalStateException(e); + } + } + + @Override + public byte readByte() { + try { + return input.readByte(); + } catch (EOFException e) { + throw new IllegalStateException(e); + } catch (IOException impossible) { + throw new AssertionError(impossible); + } + } + + @Override + public int readUnsignedByte() { + try { + return input.readUnsignedByte(); + } catch (IOException e) { + throw new IllegalStateException(e); + } + } + + @Override + public short readShort() { + try { + return input.readShort(); + } catch (IOException e) { + throw new IllegalStateException(e); + } + } + + @Override + public int readUnsignedShort() { + try { + return input.readUnsignedShort(); + } catch (IOException e) { + throw new IllegalStateException(e); + } + } + + @Override + public char readChar() { + try { + return input.readChar(); + } catch (IOException e) { + throw new IllegalStateException(e); + } + } + + @Override + public int readInt() { + try { + return input.readInt(); + } catch (IOException e) { + throw new IllegalStateException(e); + } + } + + @Override + public long readLong() { + try { + return input.readLong(); + } catch (IOException e) { + throw new IllegalStateException(e); + } + } + + @Override + public float readFloat() { + try { + return input.readFloat(); + } catch (IOException e) { + throw new IllegalStateException(e); + } + } + + @Override + public double readDouble() { + try { + return input.readDouble(); + } catch (IOException e) { + throw new IllegalStateException(e); + } + } + + @Override + public String readLine() { + try { + return input.readLine(); + } catch (IOException e) { + throw new IllegalStateException(e); + } + } + + @Override + public String readUTF() { + try { + return input.readUTF(); + } catch (IOException e) { + throw new IllegalStateException(e); + } + } + } + + /** Returns a new {@link ByteArrayDataOutput} instance with a default size. */ + @Beta + public static ByteArrayDataOutput newDataOutput() { + return newDataOutput(new ByteArrayOutputStream()); + } + + /** + * Returns a new {@link ByteArrayDataOutput} instance sized to hold {@code size} bytes before + * resizing. + * + * @throws IllegalArgumentException if {@code size} is negative + */ + @Beta + public static ByteArrayDataOutput newDataOutput(int size) { + // When called at high frequency, boxing size generates too much garbage, + // so avoid doing that if we can. + if (size < 0) { + throw new IllegalArgumentException(String.format("Invalid size: %s", size)); + } + return newDataOutput(new ByteArrayOutputStream(size)); + } + + /** + * Returns a new {@link ByteArrayDataOutput} instance which writes to the given {@code + * ByteArrayOutputStream}. The given output stream is not reset before being written to by the + * returned {@code ByteArrayDataOutput} and new data will be appended to any existing content. + * + *

Note that if the given output stream was not empty or is modified after the {@code + * ByteArrayDataOutput} is created, the contract for {@link ByteArrayDataOutput#toByteArray} will + * not be honored (the bytes returned in the byte array may not be exactly what was written via + * calls to {@code ByteArrayDataOutput}). + * + * @since 17.0 + */ + @Beta + public static ByteArrayDataOutput newDataOutput(ByteArrayOutputStream byteArrayOutputStream) { + return new ByteArrayDataOutputStream(checkNotNull(byteArrayOutputStream)); + } + + private static class ByteArrayDataOutputStream implements ByteArrayDataOutput { + + final DataOutput output; + final ByteArrayOutputStream byteArrayOutputStream; + + ByteArrayDataOutputStream(ByteArrayOutputStream byteArrayOutputStream) { + this.byteArrayOutputStream = byteArrayOutputStream; + output = new DataOutputStream(byteArrayOutputStream); + } + + @Override + public void write(int b) { + try { + output.write(b); + } catch (IOException impossible) { + throw new AssertionError(impossible); + } + } + + @Override + public void write(byte[] b) { + try { + output.write(b); + } catch (IOException impossible) { + throw new AssertionError(impossible); + } + } + + @Override + public void write(byte[] b, int off, int len) { + try { + output.write(b, off, len); + } catch (IOException impossible) { + throw new AssertionError(impossible); + } + } + + @Override + public void writeBoolean(boolean v) { + try { + output.writeBoolean(v); + } catch (IOException impossible) { + throw new AssertionError(impossible); + } + } + + @Override + public void writeByte(int v) { + try { + output.writeByte(v); + } catch (IOException impossible) { + throw new AssertionError(impossible); + } + } + + @Override + public void writeBytes(String s) { + try { + output.writeBytes(s); + } catch (IOException impossible) { + throw new AssertionError(impossible); + } + } + + @Override + public void writeChar(int v) { + try { + output.writeChar(v); + } catch (IOException impossible) { + throw new AssertionError(impossible); + } + } + + @Override + public void writeChars(String s) { + try { + output.writeChars(s); + } catch (IOException impossible) { + throw new AssertionError(impossible); + } + } + + @Override + public void writeDouble(double v) { + try { + output.writeDouble(v); + } catch (IOException impossible) { + throw new AssertionError(impossible); + } + } + + @Override + public void writeFloat(float v) { + try { + output.writeFloat(v); + } catch (IOException impossible) { + throw new AssertionError(impossible); + } + } + + @Override + public void writeInt(int v) { + try { + output.writeInt(v); + } catch (IOException impossible) { + throw new AssertionError(impossible); + } + } + + @Override + public void writeLong(long v) { + try { + output.writeLong(v); + } catch (IOException impossible) { + throw new AssertionError(impossible); + } + } + + @Override + public void writeShort(int v) { + try { + output.writeShort(v); + } catch (IOException impossible) { + throw new AssertionError(impossible); + } + } + + @Override + public void writeUTF(String s) { + try { + output.writeUTF(s); + } catch (IOException impossible) { + throw new AssertionError(impossible); + } + } + + @Override + public byte[] toByteArray() { + return byteArrayOutputStream.toByteArray(); + } + } + + private static final OutputStream NULL_OUTPUT_STREAM = + new OutputStream() { + /** Discards the specified byte. */ + @Override + public void write(int b) {} + + /** Discards the specified byte array. */ + @Override + public void write(byte[] b) { + checkNotNull(b); + } + + /** Discards the specified byte array. */ + @Override + public void write(byte[] b, int off, int len) { + checkNotNull(b); + } + + @Override + public String toString() { + return "ByteStreams.nullOutputStream()"; + } + }; + + /** + * Returns an {@link OutputStream} that simply discards written bytes. + * + * @since 14.0 (since 1.0 as com.google.common.io.NullOutputStream) + */ + @Beta + public static OutputStream nullOutputStream() { + return NULL_OUTPUT_STREAM; + } + + /** + * Wraps a {@link InputStream}, limiting the number of bytes which can be read. + * + * @param in the input stream to be wrapped + * @param limit the maximum number of bytes to be read + * @return a length-limited {@link InputStream} + * @since 14.0 (since 1.0 as com.google.common.io.LimitInputStream) + */ + @Beta + public static InputStream limit(InputStream in, long limit) { + return new LimitedInputStream(in, limit); + } + + private static final class LimitedInputStream extends FilterInputStream { + + private long left; + private long mark = -1; + + LimitedInputStream(InputStream in, long limit) { + super(in); + checkNotNull(in); + checkArgument(limit >= 0, "limit must be non-negative"); + left = limit; + } + + @Override + public int available() throws IOException { + return (int) Math.min(in.available(), left); + } + + // it's okay to mark even if mark isn't supported, as reset won't work + @Override + public synchronized void mark(int readLimit) { + in.mark(readLimit); + mark = left; + } + + @Override + public int read() throws IOException { + if (left == 0) { + return -1; + } + + int result = in.read(); + if (result != -1) { + --left; + } + return result; + } + + @Override + public int read(byte[] b, int off, int len) throws IOException { + if (left == 0) { + return -1; + } + + len = (int) Math.min(len, left); + int result = in.read(b, off, len); + if (result != -1) { + left -= result; + } + return result; + } + + @Override + public synchronized void reset() throws IOException { + if (!in.markSupported()) { + throw new IOException("Mark not supported"); + } + if (mark == -1) { + throw new IOException("Mark not set"); + } + + in.reset(); + left = mark; + } + + @Override + public long skip(long n) throws IOException { + n = Math.min(n, left); + long skipped = in.skip(n); + left -= skipped; + return skipped; + } + } + + /** + * Attempts to read enough bytes from the stream to fill the given byte array, with the same + * behavior as {@link DataInput#readFully(byte[])}. Does not close the stream. + * + * @param in the input stream to read from. + * @param b the buffer into which the data is read. + * @throws EOFException if this stream reaches the end before reading all the bytes. + * @throws IOException if an I/O error occurs. + */ + @Beta + public static void readFully(InputStream in, byte[] b) throws IOException { + readFully(in, b, 0, b.length); + } + + /** + * Attempts to read {@code len} bytes from the stream into the given array starting at {@code + * off}, with the same behavior as {@link DataInput#readFully(byte[], int, int)}. Does not close + * the stream. + * + * @param in the input stream to read from. + * @param b the buffer into which the data is read. + * @param off an int specifying the offset into the data. + * @param len an int specifying the number of bytes to read. + * @throws EOFException if this stream reaches the end before reading all the bytes. + * @throws IOException if an I/O error occurs. + */ + @Beta + public static void readFully(InputStream in, byte[] b, int off, int len) throws IOException { + int read = read(in, b, off, len); + if (read != len) { + throw new EOFException( + "reached end of stream after reading " + read + " bytes; " + len + " bytes expected"); + } + } + + /** + * Discards {@code n} bytes of data from the input stream. This method will block until the full + * amount has been skipped. Does not close the stream. + * + * @param in the input stream to read from + * @param n the number of bytes to skip + * @throws EOFException if this stream reaches the end before skipping all the bytes + * @throws IOException if an I/O error occurs, or the stream does not support skipping + */ + @Beta + public static void skipFully(InputStream in, long n) throws IOException { + long skipped = skipUpTo(in, n); + if (skipped < n) { + throw new EOFException( + "reached end of stream after skipping " + skipped + " bytes; " + n + " bytes expected"); + } + } + + /** + * Discards up to {@code n} bytes of data from the input stream. This method will block until + * either the full amount has been skipped or until the end of the stream is reached, whichever + * happens first. Returns the total number of bytes skipped. + */ + static long skipUpTo(InputStream in, final long n) throws IOException { + long totalSkipped = 0; + // A buffer is allocated if skipSafely does not skip any bytes. + byte[] buf = null; + + while (totalSkipped < n) { + long remaining = n - totalSkipped; + long skipped = skipSafely(in, remaining); + + if (skipped == 0) { + // Do a buffered read since skipSafely could return 0 repeatedly, for example if + // in.available() always returns 0 (the default). + int skip = (int) Math.min(remaining, BUFFER_SIZE); + if (buf == null) { + // Allocate a buffer bounded by the maximum size that can be requested, for + // example an array of BUFFER_SIZE is unnecessary when the value of remaining + // is smaller. + buf = new byte[skip]; + } + if ((skipped = in.read(buf, 0, skip)) == -1) { + // Reached EOF + break; + } + } + + totalSkipped += skipped; + } + + return totalSkipped; + } + + /** + * Attempts to skip up to {@code n} bytes from the given input stream, but not more than {@code + * in.available()} bytes. This prevents {@code FileInputStream} from skipping more bytes than + * actually remain in the file, something that it {@linkplain java.io.FileInputStream#skip(long) + * specifies} it can do in its Javadoc despite the fact that it is violating the contract of + * {@code InputStream.skip()}. + */ + private static long skipSafely(InputStream in, long n) throws IOException { + int available = in.available(); + return available == 0 ? 0 : in.skip(Math.min(available, n)); + } + + /** + * Process the bytes of the given input stream using the given processor. + * + * @param input the input stream to process + * @param processor the object to which to pass the bytes of the stream + * @return the result of the byte processor + * @throws IOException if an I/O error occurs + * @since 14.0 + */ + @Beta + // some processors won't return a useful result + public static T readBytes(InputStream input, ByteProcessor processor) throws IOException { + checkNotNull(input); + checkNotNull(processor); + + byte[] buf = createBuffer(); + int read; + do { + read = input.read(buf); + } while (read != -1 && processor.processBytes(buf, 0, read)); + return processor.getResult(); + } + + /** + * Reads some bytes from an input stream and stores them into the buffer array {@code b}. This + * method blocks until {@code len} bytes of input data have been read into the array, or end of + * file is detected. The number of bytes read is returned, possibly zero. Does not close the + * stream. + * + *

A caller can detect EOF if the number of bytes read is less than {@code len}. All subsequent + * calls on the same stream will return zero. + * + *

If {@code b} is null, a {@code NullPointerException} is thrown. If {@code off} is negative, + * or {@code len} is negative, or {@code off+len} is greater than the length of the array {@code + * b}, then an {@code IndexOutOfBoundsException} is thrown. If {@code len} is zero, then no bytes + * are read. Otherwise, the first byte read is stored into element {@code b[off]}, the next one + * into {@code b[off+1]}, and so on. The number of bytes read is, at most, equal to {@code len}. + * + * @param in the input stream to read from + * @param b the buffer into which the data is read + * @param off an int specifying the offset into the data + * @param len an int specifying the number of bytes to read + * @return the number of bytes read + * @throws IOException if an I/O error occurs + */ + @Beta + + // Sometimes you don't care how many bytes you actually read, I guess. + // (You know that it's either going to read len bytes or stop at EOF.) + public static int read(InputStream in, byte[] b, int off, int len) throws IOException { + checkNotNull(in); + checkNotNull(b); + if (len < 0) { + throw new IndexOutOfBoundsException("len is negative"); + } + int total = 0; + while (total < len) { + int result = in.read(b, off + total, len - total); + if (result == -1) { + break; + } + total += result; + } + return total; + } +} diff --git a/src/main/java/com/google/common/io/CharSequenceReader.java b/src/main/java/com/google/common/io/CharSequenceReader.java new file mode 100644 index 0000000..4cbeda1 --- /dev/null +++ b/src/main/java/com/google/common/io/CharSequenceReader.java @@ -0,0 +1,130 @@ +/* + * Copyright (C) 2013 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.io; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.base.Preconditions.checkPositionIndexes; + +import com.google.common.annotations.GwtIncompatible; +import java.io.IOException; +import java.io.Reader; +import java.nio.CharBuffer; + +/** + * A {@link Reader} that reads the characters in a {@link CharSequence}. Like {@code StringReader}, + * but works with any {@link CharSequence}. + * + * @author Colin Decker + */ +// TODO(cgdecker): make this public? as a type, or a method in CharStreams? +@GwtIncompatible +final class CharSequenceReader extends Reader { + + private CharSequence seq; + private int pos; + private int mark; + + /** Creates a new reader wrapping the given character sequence. */ + public CharSequenceReader(CharSequence seq) { + this.seq = checkNotNull(seq); + } + + private void checkOpen() throws IOException { + if (seq == null) { + throw new IOException("reader closed"); + } + } + + private boolean hasRemaining() { + return remaining() > 0; + } + + private int remaining() { + return seq.length() - pos; + } + + @Override + public synchronized int read(CharBuffer target) throws IOException { + checkNotNull(target); + checkOpen(); + if (!hasRemaining()) { + return -1; + } + int charsToRead = Math.min(target.remaining(), remaining()); + for (int i = 0; i < charsToRead; i++) { + target.put(seq.charAt(pos++)); + } + return charsToRead; + } + + @Override + public synchronized int read() throws IOException { + checkOpen(); + return hasRemaining() ? seq.charAt(pos++) : -1; + } + + @Override + public synchronized int read(char[] cbuf, int off, int len) throws IOException { + checkPositionIndexes(off, off + len, cbuf.length); + checkOpen(); + if (!hasRemaining()) { + return -1; + } + int charsToRead = Math.min(len, remaining()); + for (int i = 0; i < charsToRead; i++) { + cbuf[off + i] = seq.charAt(pos++); + } + return charsToRead; + } + + @Override + public synchronized long skip(long n) throws IOException { + checkArgument(n >= 0, "n (%s) may not be negative", n); + checkOpen(); + int charsToSkip = (int) Math.min(remaining(), n); // safe because remaining is an int + pos += charsToSkip; + return charsToSkip; + } + + @Override + public synchronized boolean ready() throws IOException { + checkOpen(); + return true; + } + + @Override + public boolean markSupported() { + return true; + } + + @Override + public synchronized void mark(int readAheadLimit) throws IOException { + checkArgument(readAheadLimit >= 0, "readAheadLimit (%s) may not be negative", readAheadLimit); + checkOpen(); + mark = pos; + } + + @Override + public synchronized void reset() throws IOException { + checkOpen(); + pos = mark; + } + + @Override + public synchronized void close() throws IOException { + seq = null; + } +} diff --git a/src/main/java/com/google/common/io/CharSink.java b/src/main/java/com/google/common/io/CharSink.java new file mode 100644 index 0000000..5ba9f6f --- /dev/null +++ b/src/main/java/com/google/common/io/CharSink.java @@ -0,0 +1,190 @@ +/* + * Copyright (C) 2012 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.io; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.annotations.Beta; +import com.google.common.annotations.GwtIncompatible; + +import java.io.BufferedWriter; +import java.io.IOException; +import java.io.Reader; +import java.io.Writer; +import java.nio.charset.Charset; +import java.util.Iterator; +import java.util.stream.Stream; + +/** + * A destination to which characters can be written, such as a text file. Unlike a {@link Writer}, a + * {@code CharSink} is not an open, stateful stream that can be written to and closed. Instead, it + * is an immutable supplier of {@code Writer} instances. + * + *

{@code CharSink} provides two kinds of methods: + * + *

    + *
  • Methods that return a writer: These methods should return a new, independent + * instance each time they are called. The caller is responsible for ensuring that the + * returned writer is closed. + *
  • Convenience methods: These are implementations of common operations that are + * typically implemented by opening a writer using one of the methods in the first category, + * doing something and finally closing the writer that was opened. + *
+ * + *

Any {@link ByteSink} may be viewed as a {@code CharSink} with a specific {@linkplain Charset + * character encoding} using {@link ByteSink#asCharSink(Charset)}. Characters written to the + * resulting {@code CharSink} will written to the {@code ByteSink} as encoded bytes. + * + * @since 14.0 + * @author Colin Decker + */ +@GwtIncompatible +public abstract class CharSink { + + /** Constructor for use by subclasses. */ + protected CharSink() {} + + /** + * Opens a new {@link Writer} for writing to this sink. This method returns a new, independent + * writer each time it is called. + * + *

The caller is responsible for ensuring that the returned writer is closed. + * + * @throws IOException if an I/O error occurs while opening the writer + */ + public abstract Writer openStream() throws IOException; + + /** + * Opens a new buffered {@link Writer} for writing to this sink. The returned stream is not + * required to be a {@link BufferedWriter} in order to allow implementations to simply delegate to + * {@link #openStream()} when the stream returned by that method does not benefit from additional + * buffering. This method returns a new, independent writer each time it is called. + * + *

The caller is responsible for ensuring that the returned writer is closed. + * + * @throws IOException if an I/O error occurs while opening the writer + * @since 15.0 (in 14.0 with return type {@link BufferedWriter}) + */ + public Writer openBufferedStream() throws IOException { + Writer writer = openStream(); + return (writer instanceof BufferedWriter) + ? (BufferedWriter) writer + : new BufferedWriter(writer); + } + + /** + * Writes the given character sequence to this sink. + * + * @throws IOException if an I/O error while writing to this sink + */ + public void write(CharSequence charSequence) throws IOException { + checkNotNull(charSequence); + + Closer closer = Closer.create(); + try { + Writer out = closer.register(openStream()); + out.append(charSequence); + out.flush(); // https://code.google.com/p/guava-libraries/issues/detail?id=1330 + } catch (Throwable e) { + throw closer.rethrow(e); + } finally { + closer.close(); + } + } + + /** + * Writes the given lines of text to this sink with each line (including the last) terminated with + * the operating system's default line separator. This method is equivalent to {@code + * writeLines(lines, System.getProperty("line.separator"))}. + * + * @throws IOException if an I/O error occurs while writing to this sink + */ + public void writeLines(Iterable lines) throws IOException { + writeLines(lines, System.getProperty("line.separator")); + } + + /** + * Writes the given lines of text to this sink with each line (including the last) terminated with + * the given line separator. + * + * @throws IOException if an I/O error occurs while writing to this sink + */ + public void writeLines(Iterable lines, String lineSeparator) + throws IOException { + writeLines(lines.iterator(), lineSeparator); + } + + /** + * Writes the given lines of text to this sink with each line (including the last) terminated with + * the operating system's default line separator. This method is equivalent to {@code + * writeLines(lines, System.getProperty("line.separator"))}. + * + * @throws IOException if an I/O error occurs while writing to this sink + * @since 22.0 + */ + @Beta + public void writeLines(Stream lines) throws IOException { + writeLines(lines, System.getProperty("line.separator")); + } + + /** + * Writes the given lines of text to this sink with each line (including the last) terminated with + * the given line separator. + * + * @throws IOException if an I/O error occurs while writing to this sink + * @since 22.0 + */ + @Beta + public void writeLines(Stream lines, String lineSeparator) + throws IOException { + writeLines(lines.iterator(), lineSeparator); + } + + private void writeLines(Iterator lines, String lineSeparator) + throws IOException { + checkNotNull(lineSeparator); + + try (Writer out = openBufferedStream()) { + while (lines.hasNext()) { + out.append(lines.next()).append(lineSeparator); + } + } + } + + /** + * Writes all the text from the given {@link Readable} (such as a {@link Reader}) to this sink. + * Does not close {@code readable} if it is {@code Closeable}. + * + * @return the number of characters written + * @throws IOException if an I/O error occurs while reading from {@code readable} or writing to + * this sink + */ + + public long writeFrom(Readable readable) throws IOException { + checkNotNull(readable); + + Closer closer = Closer.create(); + try { + Writer out = closer.register(openStream()); + long written = CharStreams.copy(readable, out); + out.flush(); // https://code.google.com/p/guava-libraries/issues/detail?id=1330 + return written; + } catch (Throwable e) { + throw closer.rethrow(e); + } finally { + closer.close(); + } + } +} diff --git a/src/main/java/com/google/common/io/CharSource.java b/src/main/java/com/google/common/io/CharSource.java new file mode 100644 index 0000000..36093fd --- /dev/null +++ b/src/main/java/com/google/common/io/CharSource.java @@ -0,0 +1,721 @@ +/* + * Copyright (C) 2012 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.io; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.annotations.Beta; +import com.google.common.annotations.GwtIncompatible; +import com.google.common.base.Ascii; +import com.google.common.base.Optional; +import com.google.common.base.Splitter; +import com.google.common.collect.AbstractIterator; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Lists; +import com.google.common.collect.Streams; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.Reader; +import java.io.StringReader; +import java.io.UncheckedIOException; +import java.io.Writer; +import java.nio.charset.Charset; +import java.util.Iterator; +import java.util.List; +import java.util.function.Consumer; +import java.util.stream.Stream; + + +/** + * A readable source of characters, such as a text file. Unlike a {@link Reader}, a {@code + * CharSource} is not an open, stateful stream of characters that can be read and closed. Instead, + * it is an immutable supplier of {@code Reader} instances. + * + *

{@code CharSource} provides two kinds of methods: + * + *

    + *
  • Methods that return a reader: These methods should return a new, independent + * instance each time they are called. The caller is responsible for ensuring that the + * returned reader is closed. + *
  • Convenience methods: These are implementations of common operations that are + * typically implemented by opening a reader using one of the methods in the first category, + * doing something and finally closing the reader that was opened. + *
+ * + *

Several methods in this class, such as {@link #readLines()}, break the contents of the source + * into lines. Like {@link BufferedReader}, these methods break lines on any of {@code \n}, {@code + * \r} or {@code \r\n}, do not include the line separator in each line and do not consider there to + * be an empty line at the end if the contents are terminated with a line separator. + * + *

Any {@link ByteSource} containing text encoded with a specific {@linkplain Charset character + * encoding} may be viewed as a {@code CharSource} using {@link ByteSource#asCharSource(Charset)}. + * + * @since 14.0 + * @author Colin Decker + */ +@GwtIncompatible +public abstract class CharSource { + + /** Constructor for use by subclasses. */ + protected CharSource() {} + + /** + * Returns a {@link ByteSource} view of this char source that encodes chars read from this source + * as bytes using the given {@link Charset}. + * + *

If {@link ByteSource#asCharSource} is called on the returned source with the same charset, + * the default implementation of this method will ensure that the original {@code CharSource} is + * returned, rather than round-trip encoding. Subclasses that override this method should behave + * the same way. + * + * @since 20.0 + */ + @Beta + public ByteSource asByteSource(Charset charset) { + return new AsByteSource(charset); + } + + /** + * Opens a new {@link Reader} for reading from this source. This method returns a new, independent + * reader each time it is called. + * + *

The caller is responsible for ensuring that the returned reader is closed. + * + * @throws IOException if an I/O error occurs while opening the reader + */ + public abstract Reader openStream() throws IOException; + + /** + * Opens a new {@link BufferedReader} for reading from this source. This method returns a new, + * independent reader each time it is called. + * + *

The caller is responsible for ensuring that the returned reader is closed. + * + * @throws IOException if an I/O error occurs while of opening the reader + */ + public BufferedReader openBufferedStream() throws IOException { + Reader reader = openStream(); + return (reader instanceof BufferedReader) + ? (BufferedReader) reader + : new BufferedReader(reader); + } + + /** + * Opens a new {@link Stream} for reading text one line at a time from this source. This method + * returns a new, independent stream each time it is called. + * + *

The returned stream is lazy and only reads from the source in the terminal operation. If an + * I/O error occurs while the stream is reading from the source or when the stream is closed, an + * {@link UncheckedIOException} is thrown. + * + *

Like {@link BufferedReader#readLine()}, this method considers a line to be a sequence of + * text that is terminated by (but does not include) one of {@code \r\n}, {@code \r} or {@code + * \n}. If the source's content does not end in a line termination sequence, it is treated as if + * it does. + * + *

The caller is responsible for ensuring that the returned stream is closed. For example: + * + *

{@code
+   * try (Stream lines = source.lines()) {
+   *   lines.map(...)
+   *      .filter(...)
+   *      .forEach(...);
+   * }
+   * }
+ * + * @throws IOException if an I/O error occurs while opening the stream + * @since 22.0 + */ + @Beta + public Stream lines() throws IOException { + BufferedReader reader = openBufferedStream(); + return reader + .lines() + .onClose( + () -> { + try { + reader.close(); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + }); + } + + /** + * Returns the size of this source in chars, if the size can be easily determined without actually + * opening the data stream. + * + *

The default implementation returns {@link Optional#absent}. Some sources, such as a {@code + * CharSequence}, may return a non-absent value. Note that in such cases, it is possible + * that this method will return a different number of chars than would be returned by reading all + * of the chars. + * + *

Additionally, for mutable sources such as {@code StringBuilder}s, a subsequent read may + * return a different number of chars if the contents are changed. + * + * @since 19.0 + */ + @Beta + public Optional lengthIfKnown() { + return Optional.absent(); + } + + /** + * Returns the length of this source in chars, even if doing so requires opening and traversing an + * entire stream. To avoid a potentially expensive operation, see {@link #lengthIfKnown}. + * + *

The default implementation calls {@link #lengthIfKnown} and returns the value if present. If + * absent, it will fall back to a heavyweight operation that will open a stream, {@link + * Reader#skip(long) skip} to the end of the stream, and return the total number of chars that + * were skipped. + * + *

Note that for sources that implement {@link #lengthIfKnown} to provide a more efficient + * implementation, it is possible that this method will return a different number of chars + * than would be returned by reading all of the chars. + * + *

In either case, for mutable sources such as files, a subsequent read may return a different + * number of chars if the contents are changed. + * + * @throws IOException if an I/O error occurs while reading the length of this source + * @since 19.0 + */ + @Beta + public long length() throws IOException { + Optional lengthIfKnown = lengthIfKnown(); + if (lengthIfKnown.isPresent()) { + return lengthIfKnown.get(); + } + + Closer closer = Closer.create(); + try { + Reader reader = closer.register(openStream()); + return countBySkipping(reader); + } catch (Throwable e) { + throw closer.rethrow(e); + } finally { + closer.close(); + } + } + + private long countBySkipping(Reader reader) throws IOException { + long count = 0; + long read; + while ((read = reader.skip(Long.MAX_VALUE)) != 0) { + count += read; + } + return count; + } + + /** + * Appends the contents of this source to the given {@link Appendable} (such as a {@link Writer}). + * Does not close {@code appendable} if it is {@code Closeable}. + * + * @return the number of characters copied + * @throws IOException if an I/O error occurs while reading from this source or writing to {@code + * appendable} + */ + + public long copyTo(Appendable appendable) throws IOException { + checkNotNull(appendable); + + Closer closer = Closer.create(); + try { + Reader reader = closer.register(openStream()); + return CharStreams.copy(reader, appendable); + } catch (Throwable e) { + throw closer.rethrow(e); + } finally { + closer.close(); + } + } + + /** + * Copies the contents of this source to the given sink. + * + * @return the number of characters copied + * @throws IOException if an I/O error occurs while reading from this source or writing to {@code + * sink} + */ + + public long copyTo(CharSink sink) throws IOException { + checkNotNull(sink); + + Closer closer = Closer.create(); + try { + Reader reader = closer.register(openStream()); + Writer writer = closer.register(sink.openStream()); + return CharStreams.copy(reader, writer); + } catch (Throwable e) { + throw closer.rethrow(e); + } finally { + closer.close(); + } + } + + /** + * Reads the contents of this source as a string. + * + * @throws IOException if an I/O error occurs while reading from this source + */ + public String read() throws IOException { + Closer closer = Closer.create(); + try { + Reader reader = closer.register(openStream()); + return CharStreams.toString(reader); + } catch (Throwable e) { + throw closer.rethrow(e); + } finally { + closer.close(); + } + } + + /** + * Reads the first line of this source as a string. Returns {@code null} if this source is empty. + * + *

Like {@link BufferedReader#readLine()}, this method considers a line to be a sequence of + * text that is terminated by (but does not include) one of {@code \r\n}, {@code \r} or {@code + * \n}. If the source's content does not end in a line termination sequence, it is treated as if + * it does. + * + * @throws IOException if an I/O error occurs while reading from this source + */ + public String readFirstLine() throws IOException { + Closer closer = Closer.create(); + try { + BufferedReader reader = closer.register(openBufferedStream()); + return reader.readLine(); + } catch (Throwable e) { + throw closer.rethrow(e); + } finally { + closer.close(); + } + } + + /** + * Reads all the lines of this source as a list of strings. The returned list will be empty if + * this source is empty. + * + *

Like {@link BufferedReader#readLine()}, this method considers a line to be a sequence of + * text that is terminated by (but does not include) one of {@code \r\n}, {@code \r} or {@code + * \n}. If the source's content does not end in a line termination sequence, it is treated as if + * it does. + * + * @throws IOException if an I/O error occurs while reading from this source + */ + public ImmutableList readLines() throws IOException { + Closer closer = Closer.create(); + try { + BufferedReader reader = closer.register(openBufferedStream()); + List result = Lists.newArrayList(); + String line; + while ((line = reader.readLine()) != null) { + result.add(line); + } + return ImmutableList.copyOf(result); + } catch (Throwable e) { + throw closer.rethrow(e); + } finally { + closer.close(); + } + } + + /** + * Reads lines of text from this source, processing each line as it is read using the given {@link + * LineProcessor processor}. Stops when all lines have been processed or the processor returns + * {@code false} and returns the result produced by the processor. + * + *

Like {@link BufferedReader#readLine()}, this method considers a line to be a sequence of + * text that is terminated by (but does not include) one of {@code \r\n}, {@code \r} or {@code + * \n}. If the source's content does not end in a line termination sequence, it is treated as if + * it does. + * + * @throws IOException if an I/O error occurs while reading from this source or if {@code + * processor} throws an {@code IOException} + * @since 16.0 + */ + @Beta + // some processors won't return a useful result + public T readLines(LineProcessor processor) throws IOException { + checkNotNull(processor); + + Closer closer = Closer.create(); + try { + Reader reader = closer.register(openStream()); + return CharStreams.readLines(reader, processor); + } catch (Throwable e) { + throw closer.rethrow(e); + } finally { + closer.close(); + } + } + + /** + * Reads all lines of text from this source, running the given {@code action} for each line as it + * is read. + * + *

Like {@link BufferedReader#readLine()}, this method considers a line to be a sequence of + * text that is terminated by (but does not include) one of {@code \r\n}, {@code \r} or {@code + * \n}. If the source's content does not end in a line termination sequence, it is treated as if + * it does. + * + * @throws IOException if an I/O error occurs while reading from this source or if {@code action} + * throws an {@code UncheckedIOException} + * @since 22.0 + */ + @Beta + public void forEachLine(Consumer action) throws IOException { + try (Stream lines = lines()) { + // The lines should be ordered regardless in most cases, but use forEachOrdered to be sure + lines.forEachOrdered(action); + } catch (UncheckedIOException e) { + throw e.getCause(); + } + } + + /** + * Returns whether the source has zero chars. The default implementation first checks {@link + * #lengthIfKnown}, returning true if it's known to be zero and false if it's known to be + * non-zero. If the length is not known, it falls back to opening a stream and checking for EOF. + * + *

Note that, in cases where {@code lengthIfKnown} returns zero, it is possible that + * chars are actually available for reading. This means that a source may return {@code true} from + * {@code isEmpty()} despite having readable content. + * + * @throws IOException if an I/O error occurs + * @since 15.0 + */ + public boolean isEmpty() throws IOException { + Optional lengthIfKnown = lengthIfKnown(); + if (lengthIfKnown.isPresent()) { + return lengthIfKnown.get() == 0L; + } + Closer closer = Closer.create(); + try { + Reader reader = closer.register(openStream()); + return reader.read() == -1; + } catch (Throwable e) { + throw closer.rethrow(e); + } finally { + closer.close(); + } + } + + /** + * Concatenates multiple {@link CharSource} instances into a single source. Streams returned from + * the source will contain the concatenated data from the streams of the underlying sources. + * + *

Only one underlying stream will be open at a time. Closing the concatenated stream will + * close the open underlying stream. + * + * @param sources the sources to concatenate + * @return a {@code CharSource} containing the concatenated data + * @since 15.0 + */ + public static CharSource concat(Iterable sources) { + return new ConcatenatedCharSource(sources); + } + + /** + * Concatenates multiple {@link CharSource} instances into a single source. Streams returned from + * the source will contain the concatenated data from the streams of the underlying sources. + * + *

Only one underlying stream will be open at a time. Closing the concatenated stream will + * close the open underlying stream. + * + *

Note: The input {@code Iterator} will be copied to an {@code ImmutableList} when this method + * is called. This will fail if the iterator is infinite and may cause problems if the iterator + * eagerly fetches data for each source when iterated (rather than producing sources that only + * load data through their streams). Prefer using the {@link #concat(Iterable)} overload if + * possible. + * + * @param sources the sources to concatenate + * @return a {@code CharSource} containing the concatenated data + * @throws NullPointerException if any of {@code sources} is {@code null} + * @since 15.0 + */ + public static CharSource concat(Iterator sources) { + return concat(ImmutableList.copyOf(sources)); + } + + /** + * Concatenates multiple {@link CharSource} instances into a single source. Streams returned from + * the source will contain the concatenated data from the streams of the underlying sources. + * + *

Only one underlying stream will be open at a time. Closing the concatenated stream will + * close the open underlying stream. + * + * @param sources the sources to concatenate + * @return a {@code CharSource} containing the concatenated data + * @throws NullPointerException if any of {@code sources} is {@code null} + * @since 15.0 + */ + public static CharSource concat(CharSource... sources) { + return concat(ImmutableList.copyOf(sources)); + } + + /** + * Returns a view of the given character sequence as a {@link CharSource}. The behavior of the + * returned {@code CharSource} and any {@code Reader} instances created by it is unspecified if + * the {@code charSequence} is mutated while it is being read, so don't do that. + * + * @since 15.0 (since 14.0 as {@code CharStreams.asCharSource(String)}) + */ + public static CharSource wrap(CharSequence charSequence) { + return charSequence instanceof String + ? new StringCharSource((String) charSequence) + : new CharSequenceCharSource(charSequence); + } + + /** + * Returns an immutable {@link CharSource} that contains no characters. + * + * @since 15.0 + */ + public static CharSource empty() { + return EmptyCharSource.INSTANCE; + } + + /** A byte source that reads chars from this source and encodes them as bytes using a charset. */ + private final class AsByteSource extends ByteSource { + + final Charset charset; + + AsByteSource(Charset charset) { + this.charset = checkNotNull(charset); + } + + @Override + public CharSource asCharSource(Charset charset) { + if (charset.equals(this.charset)) { + return CharSource.this; + } + return super.asCharSource(charset); + } + + @Override + public InputStream openStream() throws IOException { + return new ReaderInputStream(CharSource.this.openStream(), charset, 8192); + } + + @Override + public String toString() { + return CharSource.this.toString() + ".asByteSource(" + charset + ")"; + } + } + + private static class CharSequenceCharSource extends CharSource { + + private static final Splitter LINE_SPLITTER = Splitter.onPattern("\r\n|\n|\r"); + + protected final CharSequence seq; + + protected CharSequenceCharSource(CharSequence seq) { + this.seq = checkNotNull(seq); + } + + @Override + public Reader openStream() { + return new CharSequenceReader(seq); + } + + @Override + public String read() { + return seq.toString(); + } + + @Override + public boolean isEmpty() { + return seq.length() == 0; + } + + @Override + public long length() { + return seq.length(); + } + + @Override + public Optional lengthIfKnown() { + return Optional.of((long) seq.length()); + } + + /** + * Returns an iterator over the lines in the string. If the string ends in a newline, a final + * empty string is not included, to match the behavior of BufferedReader/LineReader.readLine(). + */ + private Iterator linesIterator() { + return new AbstractIterator() { + Iterator lines = LINE_SPLITTER.split(seq).iterator(); + + @Override + protected String computeNext() { + if (lines.hasNext()) { + String next = lines.next(); + // skip last line if it's empty + if (lines.hasNext() || !next.isEmpty()) { + return next; + } + } + return endOfData(); + } + }; + } + + @Override + public Stream lines() { + return Streams.stream(linesIterator()); + } + + @Override + public String readFirstLine() { + Iterator lines = linesIterator(); + return lines.hasNext() ? lines.next() : null; + } + + @Override + public ImmutableList readLines() { + return ImmutableList.copyOf(linesIterator()); + } + + @Override + public T readLines(LineProcessor processor) throws IOException { + Iterator lines = linesIterator(); + while (lines.hasNext()) { + if (!processor.processLine(lines.next())) { + break; + } + } + return processor.getResult(); + } + + @Override + public String toString() { + return "CharSource.wrap(" + Ascii.truncate(seq, 30, "...") + ")"; + } + } + + /** + * Subclass specialized for string instances. + * + *

Since Strings are immutable and built into the jdk we can optimize some operations + * + *

    + *
  • use {@link StringReader} instead of {@link CharSequenceReader}. It is faster since it can + * use {@link String#getChars(int, int, char[], int)} instead of copying characters one by + * one with {@link CharSequence#charAt(int)}. + *
  • use {@link Appendable#append(CharSequence)} in {@link #copyTo(Appendable)} and {@link + * #copyTo(CharSink)}. We know this is correct since strings are immutable and so the length + * can't change, and it is faster because many writers and appendables are optimized for + * appending string instances. + *
+ */ + private static class StringCharSource extends CharSequenceCharSource { + protected StringCharSource(String seq) { + super(seq); + } + + @Override + public Reader openStream() { + return new StringReader((String) seq); + } + + @Override + public long copyTo(Appendable appendable) throws IOException { + appendable.append(seq); + return seq.length(); + } + + @Override + public long copyTo(CharSink sink) throws IOException { + checkNotNull(sink); + Closer closer = Closer.create(); + try { + Writer writer = closer.register(sink.openStream()); + writer.write((String) seq); + return seq.length(); + } catch (Throwable e) { + throw closer.rethrow(e); + } finally { + closer.close(); + } + } + } + + private static final class EmptyCharSource extends StringCharSource { + + private static final EmptyCharSource INSTANCE = new EmptyCharSource(); + + private EmptyCharSource() { + super(""); + } + + @Override + public String toString() { + return "CharSource.empty()"; + } + } + + private static final class ConcatenatedCharSource extends CharSource { + + private final Iterable sources; + + ConcatenatedCharSource(Iterable sources) { + this.sources = checkNotNull(sources); + } + + @Override + public Reader openStream() throws IOException { + return new MultiReader(sources.iterator()); + } + + @Override + public boolean isEmpty() throws IOException { + for (CharSource source : sources) { + if (!source.isEmpty()) { + return false; + } + } + return true; + } + + @Override + public Optional lengthIfKnown() { + long result = 0L; + for (CharSource source : sources) { + Optional lengthIfKnown = source.lengthIfKnown(); + if (!lengthIfKnown.isPresent()) { + return Optional.absent(); + } + result += lengthIfKnown.get(); + } + return Optional.of(result); + } + + @Override + public long length() throws IOException { + long result = 0L; + for (CharSource source : sources) { + result += source.length(); + } + return result; + } + + @Override + public String toString() { + return "CharSource.concat(" + sources + ")"; + } + } +} diff --git a/src/main/java/com/google/common/io/CharStreams.java b/src/main/java/com/google/common/io/CharStreams.java new file mode 100644 index 0000000..d1821e5 --- /dev/null +++ b/src/main/java/com/google/common/io/CharStreams.java @@ -0,0 +1,352 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.io; + +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.base.Preconditions.checkPositionIndexes; + +import com.google.common.annotations.Beta; +import com.google.common.annotations.GwtIncompatible; + +import java.io.Closeable; +import java.io.EOFException; +import java.io.IOException; +import java.io.Reader; +import java.io.Writer; +import java.nio.CharBuffer; +import java.util.ArrayList; +import java.util.List; + +/** + * Provides utility methods for working with character streams. + * + *

All method parameters must be non-null unless documented otherwise. + * + *

Some of the methods in this class take arguments with a generic type of {@code Readable & + * Closeable}. A {@link java.io.Reader} implements both of those interfaces. Similarly for {@code + * Appendable & Closeable} and {@link java.io.Writer}. + * + * @author Chris Nokleberg + * @author Bin Zhu + * @author Colin Decker + * @since 1.0 + */ +@GwtIncompatible +public final class CharStreams { + + // 2K chars (4K bytes) + private static final int DEFAULT_BUF_SIZE = 0x800; + + /** Creates a new {@code CharBuffer} for buffering reads or writes. */ + static CharBuffer createBuffer() { + return CharBuffer.allocate(DEFAULT_BUF_SIZE); + } + + private CharStreams() {} + + /** + * Copies all characters between the {@link Readable} and {@link Appendable} objects. Does not + * close or flush either object. + * + * @param from the object to read from + * @param to the object to write to + * @return the number of characters copied + * @throws IOException if an I/O error occurs + */ + + public static long copy(Readable from, Appendable to) throws IOException { + // The most common case is that from is a Reader (like InputStreamReader or StringReader) so + // take advantage of that. + if (from instanceof Reader) { + // optimize for common output types which are optimized to deal with char[] + if (to instanceof StringBuilder) { + return copyReaderToBuilder((Reader) from, (StringBuilder) to); + } else { + return copyReaderToWriter((Reader) from, asWriter(to)); + } + } else { + checkNotNull(from); + checkNotNull(to); + long total = 0; + CharBuffer buf = createBuffer(); + while (from.read(buf) != -1) { + buf.flip(); + to.append(buf); + total += buf.remaining(); + buf.clear(); + } + return total; + } + } + + // TODO(lukes): consider allowing callers to pass in a buffer to use, some callers would be able + // to reuse buffers, others would be able to size them more appropriately than the constant + // defaults + + /** + * Copies all characters between the {@link Reader} and {@link StringBuilder} objects. Does not + * close or flush the reader. + * + *

This is identical to {@link #copy(Readable, Appendable)} but optimized for these specific + * types. CharBuffer has poor performance when being written into or read out of so round tripping + * all the bytes through the buffer takes a long time. With these specialized types we can just + * use a char array. + * + * @param from the object to read from + * @param to the object to write to + * @return the number of characters copied + * @throws IOException if an I/O error occurs + */ + + static long copyReaderToBuilder(Reader from, StringBuilder to) throws IOException { + checkNotNull(from); + checkNotNull(to); + char[] buf = new char[DEFAULT_BUF_SIZE]; + int nRead; + long total = 0; + while ((nRead = from.read(buf)) != -1) { + to.append(buf, 0, nRead); + total += nRead; + } + return total; + } + + /** + * Copies all characters between the {@link Reader} and {@link Writer} objects. Does not close or + * flush the reader or writer. + * + *

This is identical to {@link #copy(Readable, Appendable)} but optimized for these specific + * types. CharBuffer has poor performance when being written into or read out of so round tripping + * all the bytes through the buffer takes a long time. With these specialized types we can just + * use a char array. + * + * @param from the object to read from + * @param to the object to write to + * @return the number of characters copied + * @throws IOException if an I/O error occurs + */ + + static long copyReaderToWriter(Reader from, Writer to) throws IOException { + checkNotNull(from); + checkNotNull(to); + char[] buf = new char[DEFAULT_BUF_SIZE]; + int nRead; + long total = 0; + while ((nRead = from.read(buf)) != -1) { + to.write(buf, 0, nRead); + total += nRead; + } + return total; + } + + /** + * Reads all characters from a {@link Readable} object into a {@link String}. Does not close the + * {@code Readable}. + * + * @param r the object to read from + * @return a string containing all the characters + * @throws IOException if an I/O error occurs + */ + public static String toString(Readable r) throws IOException { + return toStringBuilder(r).toString(); + } + + /** + * Reads all characters from a {@link Readable} object into a new {@link StringBuilder} instance. + * Does not close the {@code Readable}. + * + * @param r the object to read from + * @return a {@link StringBuilder} containing all the characters + * @throws IOException if an I/O error occurs + */ + private static StringBuilder toStringBuilder(Readable r) throws IOException { + StringBuilder sb = new StringBuilder(); + if (r instanceof Reader) { + copyReaderToBuilder((Reader) r, sb); + } else { + copy(r, sb); + } + return sb; + } + + /** + * Reads all of the lines from a {@link Readable} object. The lines do not include + * line-termination characters, but do include other leading and trailing whitespace. + * + *

Does not close the {@code Readable}. If reading files or resources you should use the {@link + * Files#readLines} and {@link Resources#readLines} methods. + * + * @param r the object to read from + * @return a mutable {@link List} containing all the lines + * @throws IOException if an I/O error occurs + */ + @Beta + public static List readLines(Readable r) throws IOException { + List result = new ArrayList<>(); + LineReader lineReader = new LineReader(r); + String line; + while ((line = lineReader.readLine()) != null) { + result.add(line); + } + return result; + } + + /** + * Streams lines from a {@link Readable} object, stopping when the processor returns {@code false} + * or all lines have been read and returning the result produced by the processor. Does not close + * {@code readable}. Note that this method may not fully consume the contents of {@code readable} + * if the processor stops processing early. + * + * @throws IOException if an I/O error occurs + * @since 14.0 + */ + @Beta + // some processors won't return a useful result + public static T readLines(Readable readable, LineProcessor processor) throws IOException { + checkNotNull(readable); + checkNotNull(processor); + + LineReader lineReader = new LineReader(readable); + String line; + while ((line = lineReader.readLine()) != null) { + if (!processor.processLine(line)) { + break; + } + } + return processor.getResult(); + } + + /** + * Reads and discards data from the given {@code Readable} until the end of the stream is reached. + * Returns the total number of chars read. Does not close the stream. + * + * @since 20.0 + */ + @Beta + + public static long exhaust(Readable readable) throws IOException { + long total = 0; + long read; + CharBuffer buf = createBuffer(); + while ((read = readable.read(buf)) != -1) { + total += read; + buf.clear(); + } + return total; + } + + /** + * Discards {@code n} characters of data from the reader. This method will block until the full + * amount has been skipped. Does not close the reader. + * + * @param reader the reader to read from + * @param n the number of characters to skip + * @throws EOFException if this stream reaches the end before skipping all the characters + * @throws IOException if an I/O error occurs + */ + @Beta + public static void skipFully(Reader reader, long n) throws IOException { + checkNotNull(reader); + while (n > 0) { + long amt = reader.skip(n); + if (amt == 0) { + throw new EOFException(); + } + n -= amt; + } + } + + /** + * Returns a {@link Writer} that simply discards written chars. + * + * @since 15.0 + */ + @Beta + public static Writer nullWriter() { + return NullWriter.INSTANCE; + } + + private static final class NullWriter extends Writer { + + private static final NullWriter INSTANCE = new NullWriter(); + + @Override + public void write(int c) {} + + @Override + public void write(char[] cbuf) { + checkNotNull(cbuf); + } + + @Override + public void write(char[] cbuf, int off, int len) { + checkPositionIndexes(off, off + len, cbuf.length); + } + + @Override + public void write(String str) { + checkNotNull(str); + } + + @Override + public void write(String str, int off, int len) { + checkPositionIndexes(off, off + len, str.length()); + } + + @Override + public Writer append(CharSequence csq) { + checkNotNull(csq); + return this; + } + + @Override + public Writer append(CharSequence csq, int start, int end) { + checkPositionIndexes(start, end, csq.length()); + return this; + } + + @Override + public Writer append(char c) { + return this; + } + + @Override + public void flush() {} + + @Override + public void close() {} + + @Override + public String toString() { + return "CharStreams.nullWriter()"; + } + } + + /** + * Returns a Writer that sends all output to the given {@link Appendable} target. Closing the + * writer will close the target if it is {@link Closeable}, and flushing the writer will flush the + * target if it is {@link java.io.Flushable}. + * + * @param target the object to which output will be sent + * @return a new Writer object, unless target is a Writer, in which case the target is returned + */ + @Beta + public static Writer asWriter(Appendable target) { + if (target instanceof Writer) { + return (Writer) target; + } + return new AppendableWriter(target); + } +} diff --git a/src/main/java/com/google/common/io/Closeables.java b/src/main/java/com/google/common/io/Closeables.java new file mode 100644 index 0000000..85774a5 --- /dev/null +++ b/src/main/java/com/google/common/io/Closeables.java @@ -0,0 +1,130 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.io; + +import com.google.common.annotations.Beta; +import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.VisibleForTesting; +import java.io.Closeable; +import java.io.IOException; +import java.io.InputStream; +import java.io.Reader; +import java.util.logging.Level; +import java.util.logging.Logger; + + +/** + * Utility methods for working with {@link Closeable} objects. + * + * @author Michael Lancaster + * @since 1.0 + */ +@Beta +@GwtIncompatible +public final class Closeables { + @VisibleForTesting static final Logger logger = Logger.getLogger(Closeables.class.getName()); + + private Closeables() {} + + /** + * Closes a {@link Closeable}, with control over whether an {@code IOException} may be thrown. + * This is primarily useful in a finally block, where a thrown exception needs to be logged but + * not propagated (otherwise the original exception will be lost). + * + *

If {@code swallowIOException} is true then we never throw {@code IOException} but merely log + * it. + * + *

Example: + * + *

{@code
+   * public void useStreamNicely() throws IOException {
+   *   SomeStream stream = new SomeStream("foo");
+   *   boolean threw = true;
+   *   try {
+   *     // ... code which does something with the stream ...
+   *     threw = false;
+   *   } finally {
+   *     // If an exception occurs, rethrow it only if threw==false:
+   *     Closeables.close(stream, threw);
+   *   }
+   * }
+   * }
+ * + * @param closeable the {@code Closeable} object to be closed, or null, in which case this method + * does nothing + * @param swallowIOException if true, don't propagate IO exceptions thrown by the {@code close} + * methods + * @throws IOException if {@code swallowIOException} is false and {@code close} throws an {@code + * IOException}. + */ + public static void close(Closeable closeable, boolean swallowIOException) + throws IOException { + if (closeable == null) { + return; + } + try { + closeable.close(); + } catch (IOException e) { + if (swallowIOException) { + logger.log(Level.WARNING, "IOException thrown while closing Closeable.", e); + } else { + throw e; + } + } + } + + /** + * Closes the given {@link InputStream}, logging any {@code IOException} that's thrown rather than + * propagating it. + * + *

While it's not safe in the general case to ignore exceptions that are thrown when closing an + * I/O resource, it should generally be safe in the case of a resource that's being used only for + * reading, such as an {@code InputStream}. Unlike with writable resources, there's no chance that + * a failure that occurs when closing the stream indicates a meaningful problem such as a failure + * to flush all bytes to the underlying resource. + * + * @param inputStream the input stream to be closed, or {@code null} in which case this method + * does nothing + * @since 17.0 + */ + public static void closeQuietly(InputStream inputStream) { + try { + close(inputStream, true); + } catch (IOException impossible) { + throw new AssertionError(impossible); + } + } + + /** + * Closes the given {@link Reader}, logging any {@code IOException} that's thrown rather than + * propagating it. + * + *

While it's not safe in the general case to ignore exceptions that are thrown when closing an + * I/O resource, it should generally be safe in the case of a resource that's being used only for + * reading, such as a {@code Reader}. Unlike with writable resources, there's no chance that a + * failure that occurs when closing the reader indicates a meaningful problem such as a failure to + * flush all bytes to the underlying resource. + * + * @param reader the reader to be closed, or {@code null} in which case this method does nothing + * @since 17.0 + */ + public static void closeQuietly(Reader reader) { + try { + close(reader, true); + } catch (IOException impossible) { + throw new AssertionError(impossible); + } + } +} diff --git a/src/main/java/com/google/common/io/Closer.java b/src/main/java/com/google/common/io/Closer.java new file mode 100644 index 0000000..f4a477e --- /dev/null +++ b/src/main/java/com/google/common/io/Closer.java @@ -0,0 +1,292 @@ +/* + * Copyright (C) 2012 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.io; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.annotations.Beta; +import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.Throwables; + +import java.io.Closeable; +import java.io.IOException; +import java.lang.reflect.Method; +import java.util.ArrayDeque; +import java.util.Deque; +import java.util.logging.Level; + + + +/** + * A {@link Closeable} that collects {@code Closeable} resources and closes them all when it is + * {@linkplain #close closed}. This is intended to approximately emulate the behavior of Java 7's try-with-resources statement in JDK6-compatible code. Running on Java 7, code using this + * should be approximately equivalent in behavior to the same code written with try-with-resources. + * Running on Java 6, exceptions that cannot be thrown must be logged rather than being added to the + * thrown exception as a suppressed exception. + * + *

This class is intended to be used in the following pattern: + * + *

{@code
+ * Closer closer = Closer.create();
+ * try {
+ *   InputStream in = closer.register(openInputStream());
+ *   OutputStream out = closer.register(openOutputStream());
+ *   // do stuff
+ * } catch (Throwable e) {
+ *   // ensure that any checked exception types other than IOException that could be thrown are
+ *   // provided here, e.g. throw closer.rethrow(e, CheckedException.class);
+ *   throw closer.rethrow(e);
+ * } finally {
+ *   closer.close();
+ * }
+ * }
+ * + *

Note that this try-catch-finally block is not equivalent to a try-catch-finally block using + * try-with-resources. To get the equivalent of that, you must wrap the above code in another + * try block in order to catch any exception that may be thrown (including from the call to {@code + * close()}). + * + *

This pattern ensures the following: + * + *

    + *
  • Each {@code Closeable} resource that is successfully registered will be closed later. + *
  • If a {@code Throwable} is thrown in the try block, no exceptions that occur when attempting + * to close resources will be thrown from the finally block. The throwable from the try block + * will be thrown. + *
  • If no exceptions or errors were thrown in the try block, the first exception thrown + * by an attempt to close a resource will be thrown. + *
  • Any exception caught when attempting to close a resource that is not thrown (because + * another exception is already being thrown) is suppressed. + *
+ * + *

An exception that is suppressed is not thrown. The method of suppression used depends on the + * version of Java the code is running on: + * + *

    + *
  • Java 7+: Exceptions are suppressed by adding them to the exception that will + * be thrown using {@code Throwable.addSuppressed(Throwable)}. + *
  • Java 6: Exceptions are suppressed by logging them instead. + *
+ * + * @author Colin Decker + * @since 14.0 + */ +// Coffee's for {@link Closer closers} only. +@Beta +@GwtIncompatible +public final class Closer implements Closeable { + + /** The suppressor implementation to use for the current Java version. */ + private static final Suppressor SUPPRESSOR = + SuppressingSuppressor.isAvailable() + ? SuppressingSuppressor.INSTANCE + : LoggingSuppressor.INSTANCE; + + /** Creates a new {@link Closer}. */ + public static Closer create() { + return new Closer(SUPPRESSOR); + } + + @VisibleForTesting final Suppressor suppressor; + + // only need space for 2 elements in most cases, so try to use the smallest array possible + private final Deque stack = new ArrayDeque<>(4); + private Throwable thrown; + + @VisibleForTesting + Closer(Suppressor suppressor) { + this.suppressor = checkNotNull(suppressor); // checkNotNull to satisfy null tests + } + + /** + * Registers the given {@code closeable} to be closed when this {@code Closer} is {@linkplain + * #close closed}. + * + * @return the given {@code closeable} + */ + // close. this word no longer has any meaning to me. + + public C register(C closeable) { + if (closeable != null) { + stack.addFirst(closeable); + } + + return closeable; + } + + /** + * Stores the given throwable and rethrows it. It will be rethrown as is if it is an {@code + * IOException}, {@code RuntimeException} or {@code Error}. Otherwise, it will be rethrown wrapped + * in a {@code RuntimeException}. Note: Be sure to declare all of the checked exception + * types your try block can throw when calling an overload of this method so as to avoid losing + * the original exception type. + * + *

This method always throws, and as such should be called as {@code throw closer.rethrow(e);} + * to ensure the compiler knows that it will throw. + * + * @return this method does not return; it always throws + * @throws IOException when the given throwable is an IOException + */ + public RuntimeException rethrow(Throwable e) throws IOException { + checkNotNull(e); + thrown = e; + Throwables.propagateIfPossible(e, IOException.class); + throw new RuntimeException(e); + } + + /** + * Stores the given throwable and rethrows it. It will be rethrown as is if it is an {@code + * IOException}, {@code RuntimeException}, {@code Error} or a checked exception of the given type. + * Otherwise, it will be rethrown wrapped in a {@code RuntimeException}. Note: Be sure to + * declare all of the checked exception types your try block can throw when calling an overload of + * this method so as to avoid losing the original exception type. + * + *

This method always throws, and as such should be called as {@code throw closer.rethrow(e, + * ...);} to ensure the compiler knows that it will throw. + * + * @return this method does not return; it always throws + * @throws IOException when the given throwable is an IOException + * @throws X when the given throwable is of the declared type X + */ + public RuntimeException rethrow(Throwable e, Class declaredType) + throws IOException, X { + checkNotNull(e); + thrown = e; + Throwables.propagateIfPossible(e, IOException.class); + Throwables.propagateIfPossible(e, declaredType); + throw new RuntimeException(e); + } + + /** + * Stores the given throwable and rethrows it. It will be rethrown as is if it is an {@code + * IOException}, {@code RuntimeException}, {@code Error} or a checked exception of either of the + * given types. Otherwise, it will be rethrown wrapped in a {@code RuntimeException}. Note: + * Be sure to declare all of the checked exception types your try block can throw when calling an + * overload of this method so as to avoid losing the original exception type. + * + *

This method always throws, and as such should be called as {@code throw closer.rethrow(e, + * ...);} to ensure the compiler knows that it will throw. + * + * @return this method does not return; it always throws + * @throws IOException when the given throwable is an IOException + * @throws X1 when the given throwable is of the declared type X1 + * @throws X2 when the given throwable is of the declared type X2 + */ + public RuntimeException rethrow( + Throwable e, Class declaredType1, Class declaredType2) throws IOException, X1, X2 { + checkNotNull(e); + thrown = e; + Throwables.propagateIfPossible(e, IOException.class); + Throwables.propagateIfPossible(e, declaredType1, declaredType2); + throw new RuntimeException(e); + } + + /** + * Closes all {@code Closeable} instances that have been added to this {@code Closer}. If an + * exception was thrown in the try block and passed to one of the {@code exceptionThrown} methods, + * any exceptions thrown when attempting to close a closeable will be suppressed. Otherwise, the + * first exception to be thrown from an attempt to close a closeable will be thrown and any + * additional exceptions that are thrown after that will be suppressed. + */ + @Override + public void close() throws IOException { + Throwable throwable = thrown; + + // close closeables in LIFO order + while (!stack.isEmpty()) { + Closeable closeable = stack.removeFirst(); + try { + closeable.close(); + } catch (Throwable e) { + if (throwable == null) { + throwable = e; + } else { + suppressor.suppress(closeable, throwable, e); + } + } + } + + if (thrown == null && throwable != null) { + Throwables.propagateIfPossible(throwable, IOException.class); + throw new AssertionError(throwable); // not possible + } + } + + /** Suppression strategy interface. */ + @VisibleForTesting + interface Suppressor { + /** + * Suppresses the given exception ({@code suppressed}) which was thrown when attempting to close + * the given closeable. {@code thrown} is the exception that is actually being thrown from the + * method. Implementations of this method should not throw under any circumstances. + */ + void suppress(Closeable closeable, Throwable thrown, Throwable suppressed); + } + + /** Suppresses exceptions by logging them. */ + @VisibleForTesting + static final class LoggingSuppressor implements Suppressor { + + static final LoggingSuppressor INSTANCE = new LoggingSuppressor(); + + @Override + public void suppress(Closeable closeable, Throwable thrown, Throwable suppressed) { + // log to the same place as Closeables + Closeables.logger.log( + Level.WARNING, "Suppressing exception thrown when closing " + closeable, suppressed); + } + } + + /** + * Suppresses exceptions by adding them to the exception that will be thrown using JDK7's + * addSuppressed(Throwable) mechanism. + */ + @VisibleForTesting + static final class SuppressingSuppressor implements Suppressor { + + static final SuppressingSuppressor INSTANCE = new SuppressingSuppressor(); + + static boolean isAvailable() { + return addSuppressed != null; + } + + static final Method addSuppressed = addSuppressedMethodOrNull(); + + private static Method addSuppressedMethodOrNull() { + try { + return Throwable.class.getMethod("addSuppressed", Throwable.class); + } catch (Throwable e) { + return null; + } + } + + @Override + public void suppress(Closeable closeable, Throwable thrown, Throwable suppressed) { + // ensure no exceptions from addSuppressed + if (thrown == suppressed) { + return; + } + try { + addSuppressed.invoke(thrown, suppressed); + } catch (Throwable e) { + // if, somehow, IllegalAccessException or another exception is thrown, fall back to logging + LoggingSuppressor.INSTANCE.suppress(closeable, thrown, suppressed); + } + } + } +} diff --git a/src/main/java/com/google/common/io/CountingInputStream.java b/src/main/java/com/google/common/io/CountingInputStream.java new file mode 100644 index 0000000..b015aca --- /dev/null +++ b/src/main/java/com/google/common/io/CountingInputStream.java @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.io; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.annotations.Beta; +import com.google.common.annotations.GwtIncompatible; +import java.io.FilterInputStream; +import java.io.IOException; +import java.io.InputStream; + +/** + * An {@link InputStream} that counts the number of bytes read. + * + * @author Chris Nokleberg + * @since 1.0 + */ +@Beta +@GwtIncompatible +public final class CountingInputStream extends FilterInputStream { + + private long count; + private long mark = -1; + + /** + * Wraps another input stream, counting the number of bytes read. + * + * @param in the input stream to be wrapped + */ + public CountingInputStream(InputStream in) { + super(checkNotNull(in)); + } + + /** Returns the number of bytes read. */ + public long getCount() { + return count; + } + + @Override + public int read() throws IOException { + int result = in.read(); + if (result != -1) { + count++; + } + return result; + } + + @Override + public int read(byte[] b, int off, int len) throws IOException { + int result = in.read(b, off, len); + if (result != -1) { + count += result; + } + return result; + } + + @Override + public long skip(long n) throws IOException { + long result = in.skip(n); + count += result; + return result; + } + + @Override + public synchronized void mark(int readlimit) { + in.mark(readlimit); + mark = count; + // it's okay to mark even if mark isn't supported, as reset won't work + } + + @Override + public synchronized void reset() throws IOException { + if (!in.markSupported()) { + throw new IOException("Mark not supported"); + } + if (mark == -1) { + throw new IOException("Mark not set"); + } + + in.reset(); + count = mark; + } +} diff --git a/src/main/java/com/google/common/io/CountingOutputStream.java b/src/main/java/com/google/common/io/CountingOutputStream.java new file mode 100644 index 0000000..8a3d170 --- /dev/null +++ b/src/main/java/com/google/common/io/CountingOutputStream.java @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.io; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.annotations.Beta; +import com.google.common.annotations.GwtIncompatible; +import java.io.FilterOutputStream; +import java.io.IOException; +import java.io.OutputStream; + +/** + * An OutputStream that counts the number of bytes written. + * + * @author Chris Nokleberg + * @since 1.0 + */ +@Beta +@GwtIncompatible +public final class CountingOutputStream extends FilterOutputStream { + + private long count; + + /** + * Wraps another output stream, counting the number of bytes written. + * + * @param out the output stream to be wrapped + */ + public CountingOutputStream(OutputStream out) { + super(checkNotNull(out)); + } + + /** Returns the number of bytes written. */ + public long getCount() { + return count; + } + + @Override + public void write(byte[] b, int off, int len) throws IOException { + out.write(b, off, len); + count += len; + } + + @Override + public void write(int b) throws IOException { + out.write(b); + count++; + } + + // Overriding close() because FilterOutputStream's close() method pre-JDK8 has bad behavior: + // it silently ignores any exception thrown by flush(). Instead, just close the delegate stream. + // It should flush itself if necessary. + @Override + public void close() throws IOException { + out.close(); + } +} diff --git a/src/main/java/com/google/common/io/FileBackedOutputStream.java b/src/main/java/com/google/common/io/FileBackedOutputStream.java new file mode 100644 index 0000000..b002561 --- /dev/null +++ b/src/main/java/com/google/common/io/FileBackedOutputStream.java @@ -0,0 +1,212 @@ +/* + * Copyright (C) 2008 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.io; + +import com.google.common.annotations.Beta; +import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.VisibleForTesting; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + + +/** + * An {@link OutputStream} that starts buffering to a byte array, but switches to file buffering + * once the data reaches a configurable size. + * + *

This class is thread-safe. + * + * @author Chris Nokleberg + * @since 1.0 + */ +@Beta +@GwtIncompatible +public final class FileBackedOutputStream extends OutputStream { + + private final int fileThreshold; + private final boolean resetOnFinalize; + private final ByteSource source; + + private OutputStream out; + private MemoryOutput memory; + private File file; + + /** ByteArrayOutputStream that exposes its internals. */ + private static class MemoryOutput extends ByteArrayOutputStream { + byte[] getBuffer() { + return buf; + } + + int getCount() { + return count; + } + } + + /** Returns the file holding the data (possibly null). */ + @VisibleForTesting + synchronized File getFile() { + return file; + } + + /** + * Creates a new instance that uses the given file threshold, and does not reset the data when the + * {@link ByteSource} returned by {@link #asByteSource} is finalized. + * + * @param fileThreshold the number of bytes before the stream should switch to buffering to a file + */ + public FileBackedOutputStream(int fileThreshold) { + this(fileThreshold, false); + } + + /** + * Creates a new instance that uses the given file threshold, and optionally resets the data when + * the {@link ByteSource} returned by {@link #asByteSource} is finalized. + * + * @param fileThreshold the number of bytes before the stream should switch to buffering to a file + * @param resetOnFinalize if true, the {@link #reset} method will be called when the {@link + * ByteSource} returned by {@link #asByteSource} is finalized + */ + public FileBackedOutputStream(int fileThreshold, boolean resetOnFinalize) { + this.fileThreshold = fileThreshold; + this.resetOnFinalize = resetOnFinalize; + memory = new MemoryOutput(); + out = memory; + + if (resetOnFinalize) { + source = + new ByteSource() { + @Override + public InputStream openStream() throws IOException { + return openInputStream(); + } + + @Override + protected void finalize() { + try { + reset(); + } catch (Throwable t) { + t.printStackTrace(System.err); + } + } + }; + } else { + source = + new ByteSource() { + @Override + public InputStream openStream() throws IOException { + return openInputStream(); + } + }; + } + } + + /** + * Returns a readable {@link ByteSource} view of the data that has been written to this stream. + * + * @since 15.0 + */ + public ByteSource asByteSource() { + return source; + } + + private synchronized InputStream openInputStream() throws IOException { + if (file != null) { + return new FileInputStream(file); + } else { + return new ByteArrayInputStream(memory.getBuffer(), 0, memory.getCount()); + } + } + + /** + * Calls {@link #close} if not already closed, and then resets this object back to its initial + * state, for reuse. If data was buffered to a file, it will be deleted. + * + * @throws IOException if an I/O error occurred while deleting the file buffer + */ + public synchronized void reset() throws IOException { + try { + close(); + } finally { + if (memory == null) { + memory = new MemoryOutput(); + } else { + memory.reset(); + } + out = memory; + if (file != null) { + File deleteMe = file; + file = null; + if (!deleteMe.delete()) { + throw new IOException("Could not delete: " + deleteMe); + } + } + } + } + + @Override + public synchronized void write(int b) throws IOException { + update(1); + out.write(b); + } + + @Override + public synchronized void write(byte[] b) throws IOException { + write(b, 0, b.length); + } + + @Override + public synchronized void write(byte[] b, int off, int len) throws IOException { + update(len); + out.write(b, off, len); + } + + @Override + public synchronized void close() throws IOException { + out.close(); + } + + @Override + public synchronized void flush() throws IOException { + out.flush(); + } + + /** + * Checks if writing {@code len} bytes would go over threshold, and switches to file buffering if + * so. + */ + private void update(int len) throws IOException { + if (file == null && (memory.getCount() + len > fileThreshold)) { + File temp = File.createTempFile("FileBackedOutputStream", null); + if (resetOnFinalize) { + // Finalizers are not guaranteed to be called on system shutdown; + // this is insurance. + temp.deleteOnExit(); + } + FileOutputStream transfer = new FileOutputStream(temp); + transfer.write(memory.getBuffer(), 0, memory.getCount()); + transfer.flush(); + + // We've successfully transferred the data; switch to writing to file + out = transfer; + file = temp; + memory = null; + } + } +} diff --git a/src/main/java/com/google/common/io/FileWriteMode.java b/src/main/java/com/google/common/io/FileWriteMode.java new file mode 100644 index 0000000..2c69a2e --- /dev/null +++ b/src/main/java/com/google/common/io/FileWriteMode.java @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2012 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.io; + +import com.google.common.annotations.GwtIncompatible; + +/** + * Modes for opening a file for writing. The default when mode when none is specified is to truncate + * the file before writing. + * + * @author Colin Decker + */ +@GwtIncompatible +public enum FileWriteMode { + /** Specifies that writes to the opened file should append to the end of the file. */ + APPEND +} diff --git a/src/main/java/com/google/common/io/Files.java b/src/main/java/com/google/common/io/Files.java new file mode 100644 index 0000000..f0d6df1 --- /dev/null +++ b/src/main/java/com/google/common/io/Files.java @@ -0,0 +1,937 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.io; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.io.FileWriteMode.APPEND; + +import com.google.common.annotations.Beta; +import com.google.common.annotations.GwtIncompatible; +import com.google.common.base.Joiner; +import com.google.common.base.Optional; +import com.google.common.base.Predicate; +import com.google.common.base.Splitter; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Lists; +import com.google.common.collect.TreeTraverser; +import com.google.common.graph.SuccessorsFunction; +import com.google.common.graph.Traverser; +import com.google.common.hash.HashCode; +import com.google.common.hash.HashFunction; + +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.io.RandomAccessFile; +import java.nio.MappedByteBuffer; +import java.nio.channels.FileChannel; +import java.nio.channels.FileChannel.MapMode; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +/** + * Provides utility methods for working with {@linkplain File files}. + * + *

{@link java.nio.file.Path} users will find similar utilities in {@link MoreFiles} and the + * JDK's {@link java.nio.file.Files} class. + * + * @author Chris Nokleberg + * @author Colin Decker + * @since 1.0 + */ +@GwtIncompatible +public final class Files { + + /** Maximum loop count when creating temp directories. */ + private static final int TEMP_DIR_ATTEMPTS = 10000; + + private Files() {} + + /** + * Returns a buffered reader that reads from a file using the given character set. + * + *

{@link java.nio.file.Path} equivalent: {@link + * java.nio.file.Files#newBufferedReader(java.nio.file.Path, Charset)}. + * + * @param file the file to read from + * @param charset the charset used to decode the input stream; see {@link StandardCharsets} for + * helpful predefined constants + * @return the buffered reader + */ + @Beta + public static BufferedReader newReader(File file, Charset charset) throws FileNotFoundException { + checkNotNull(file); + checkNotNull(charset); + return new BufferedReader(new InputStreamReader(new FileInputStream(file), charset)); + } + + /** + * Returns a buffered writer that writes to a file using the given character set. + * + *

{@link java.nio.file.Path} equivalent: {@link + * java.nio.file.Files#newBufferedWriter(java.nio.file.Path, Charset, + * java.nio.file.OpenOption...)}. + * + * @param file the file to write to + * @param charset the charset used to encode the output stream; see {@link StandardCharsets} for + * helpful predefined constants + * @return the buffered writer + */ + @Beta + public static BufferedWriter newWriter(File file, Charset charset) throws FileNotFoundException { + checkNotNull(file); + checkNotNull(charset); + return new BufferedWriter(new OutputStreamWriter(new FileOutputStream(file), charset)); + } + + /** + * Returns a new {@link ByteSource} for reading bytes from the given file. + * + * @since 14.0 + */ + public static ByteSource asByteSource(File file) { + return new FileByteSource(file); + } + + private static final class FileByteSource extends ByteSource { + + private final File file; + + private FileByteSource(File file) { + this.file = checkNotNull(file); + } + + @Override + public FileInputStream openStream() throws IOException { + return new FileInputStream(file); + } + + @Override + public Optional sizeIfKnown() { + if (file.isFile()) { + return Optional.of(file.length()); + } else { + return Optional.absent(); + } + } + + @Override + public long size() throws IOException { + if (!file.isFile()) { + throw new FileNotFoundException(file.toString()); + } + return file.length(); + } + + @Override + public byte[] read() throws IOException { + Closer closer = Closer.create(); + try { + FileInputStream in = closer.register(openStream()); + return ByteStreams.toByteArray(in, in.getChannel().size()); + } catch (Throwable e) { + throw closer.rethrow(e); + } finally { + closer.close(); + } + } + + @Override + public String toString() { + return "Files.asByteSource(" + file + ")"; + } + } + + /** + * Returns a new {@link ByteSink} for writing bytes to the given file. The given {@code modes} + * control how the file is opened for writing. When no mode is provided, the file will be + * truncated before writing. When the {@link FileWriteMode#APPEND APPEND} mode is provided, writes + * will append to the end of the file without truncating it. + * + * @since 14.0 + */ + public static ByteSink asByteSink(File file, FileWriteMode... modes) { + return new FileByteSink(file, modes); + } + + private static final class FileByteSink extends ByteSink { + + private final File file; + private final ImmutableSet modes; + + private FileByteSink(File file, FileWriteMode... modes) { + this.file = checkNotNull(file); + this.modes = ImmutableSet.copyOf(modes); + } + + @Override + public FileOutputStream openStream() throws IOException { + return new FileOutputStream(file, modes.contains(APPEND)); + } + + @Override + public String toString() { + return "Files.asByteSink(" + file + ", " + modes + ")"; + } + } + + /** + * Returns a new {@link CharSource} for reading character data from the given file using the given + * character set. + * + * @since 14.0 + */ + public static CharSource asCharSource(File file, Charset charset) { + return asByteSource(file).asCharSource(charset); + } + + /** + * Returns a new {@link CharSink} for writing character data to the given file using the given + * character set. The given {@code modes} control how the file is opened for writing. When no mode + * is provided, the file will be truncated before writing. When the {@link FileWriteMode#APPEND + * APPEND} mode is provided, writes will append to the end of the file without truncating it. + * + * @since 14.0 + */ + public static CharSink asCharSink(File file, Charset charset, FileWriteMode... modes) { + return asByteSink(file, modes).asCharSink(charset); + } + + /** + * Reads all bytes from a file into a byte array. + * + *

{@link java.nio.file.Path} equivalent: {@link java.nio.file.Files#readAllBytes}. + * + * @param file the file to read from + * @return a byte array containing all the bytes from file + * @throws IllegalArgumentException if the file is bigger than the largest possible byte array + * (2^31 - 1) + * @throws IOException if an I/O error occurs + */ + @Beta + public static byte[] toByteArray(File file) throws IOException { + return asByteSource(file).read(); + } + + /** + * Reads all characters from a file into a {@link String}, using the given character set. + * + * @param file the file to read from + * @param charset the charset used to decode the input stream; see {@link StandardCharsets} for + * helpful predefined constants + * @return a string containing all the characters from the file + * @throws IOException if an I/O error occurs + * @deprecated Prefer {@code asCharSource(file, charset).read()}. This method is scheduled to be + * removed in October 2019. + */ + @Beta + @Deprecated + public static String toString(File file, Charset charset) throws IOException { + return asCharSource(file, charset).read(); + } + + /** + * Overwrites a file with the contents of a byte array. + * + *

{@link java.nio.file.Path} equivalent: {@link + * java.nio.file.Files#write(java.nio.file.Path, byte[], java.nio.file.OpenOption...)}. + * + * @param from the bytes to write + * @param to the destination file + * @throws IOException if an I/O error occurs + */ + @Beta + public static void write(byte[] from, File to) throws IOException { + asByteSink(to).write(from); + } + + /** + * Writes a character sequence (such as a string) to a file using the given character set. + * + * @param from the character sequence to write + * @param to the destination file + * @param charset the charset used to encode the output stream; see {@link StandardCharsets} for + * helpful predefined constants + * @throws IOException if an I/O error occurs + * @deprecated Prefer {@code asCharSink(to, charset).write(from)}. This method is scheduled to be + * removed in October 2019. + */ + @Beta + @Deprecated + public static void write(CharSequence from, File to, Charset charset) throws IOException { + asCharSink(to, charset).write(from); + } + + /** + * Copies all bytes from a file to an output stream. + * + *

{@link java.nio.file.Path} equivalent: {@link + * java.nio.file.Files#copy(java.nio.file.Path, OutputStream)}. + * + * @param from the source file + * @param to the output stream + * @throws IOException if an I/O error occurs + */ + @Beta + public static void copy(File from, OutputStream to) throws IOException { + asByteSource(from).copyTo(to); + } + + /** + * Copies all the bytes from one file to another. + * + *

Copying is not an atomic operation - in the case of an I/O error, power loss, process + * termination, or other problems, {@code to} may not be a complete copy of {@code from}. If you + * need to guard against those conditions, you should employ other file-level synchronization. + * + *

Warning: If {@code to} represents an existing file, that file will be overwritten + * with the contents of {@code from}. If {@code to} and {@code from} refer to the same + * file, the contents of that file will be deleted. + * + *

{@link java.nio.file.Path} equivalent: {@link + * java.nio.file.Files#copy(java.nio.file.Path, java.nio.file.Path, java.nio.file.CopyOption...)}. + * + * @param from the source file + * @param to the destination file + * @throws IOException if an I/O error occurs + * @throws IllegalArgumentException if {@code from.equals(to)} + */ + @Beta + public static void copy(File from, File to) throws IOException { + checkArgument(!from.equals(to), "Source %s and destination %s must be different", from, to); + asByteSource(from).copyTo(asByteSink(to)); + } + + /** + * Copies all characters from a file to an appendable object, using the given character set. + * + * @param from the source file + * @param charset the charset used to decode the input stream; see {@link StandardCharsets} for + * helpful predefined constants + * @param to the appendable object + * @throws IOException if an I/O error occurs + * @deprecated Prefer {@code asCharSource(from, charset).copyTo(to)}. This method is scheduled to + * be removed in October 2019. + */ + @Beta + @Deprecated + public + static void copy(File from, Charset charset, Appendable to) throws IOException { + asCharSource(from, charset).copyTo(to); + } + + /** + * Appends a character sequence (such as a string) to a file using the given character set. + * + * @param from the character sequence to append + * @param to the destination file + * @param charset the charset used to encode the output stream; see {@link StandardCharsets} for + * helpful predefined constants + * @throws IOException if an I/O error occurs + * @deprecated Prefer {@code asCharSink(to, charset, FileWriteMode.APPEND).write(from)}. This + * method is scheduled to be removed in October 2019. + */ + @Beta + @Deprecated + public + static void append(CharSequence from, File to, Charset charset) throws IOException { + asCharSink(to, charset, FileWriteMode.APPEND).write(from); + } + + /** + * Returns true if the given files exist, are not directories, and contain the same bytes. + * + * @throws IOException if an I/O error occurs + */ + @Beta + public static boolean equal(File file1, File file2) throws IOException { + checkNotNull(file1); + checkNotNull(file2); + if (file1 == file2 || file1.equals(file2)) { + return true; + } + + /* + * Some operating systems may return zero as the length for files denoting system-dependent + * entities such as devices or pipes, in which case we must fall back on comparing the bytes + * directly. + */ + long len1 = file1.length(); + long len2 = file2.length(); + if (len1 != 0 && len2 != 0 && len1 != len2) { + return false; + } + return asByteSource(file1).contentEquals(asByteSource(file2)); + } + + /** + * Atomically creates a new directory somewhere beneath the system's temporary directory (as + * defined by the {@code java.io.tmpdir} system property), and returns its name. + * + *

Use this method instead of {@link File#createTempFile(String, String)} when you wish to + * create a directory, not a regular file. A common pitfall is to call {@code createTempFile}, + * delete the file and create a directory in its place, but this leads a race condition which can + * be exploited to create security vulnerabilities, especially when executable files are to be + * written into the directory. + * + *

This method assumes that the temporary volume is writable, has free inodes and free blocks, + * and that it will not be called thousands of times per second. + * + *

{@link java.nio.file.Path} equivalent: {@link + * java.nio.file.Files#createTempDirectory}. + * + * @return the newly-created directory + * @throws IllegalStateException if the directory could not be created + */ + @Beta + public static File createTempDir() { + File baseDir = new File(System.getProperty("java.io.tmpdir")); + @SuppressWarnings("GoodTime") // reading system time without TimeSource + String baseName = System.currentTimeMillis() + "-"; + + for (int counter = 0; counter < TEMP_DIR_ATTEMPTS; counter++) { + File tempDir = new File(baseDir, baseName + counter); + if (tempDir.mkdir()) { + return tempDir; + } + } + throw new IllegalStateException( + "Failed to create directory within " + + TEMP_DIR_ATTEMPTS + + " attempts (tried " + + baseName + + "0 to " + + baseName + + (TEMP_DIR_ATTEMPTS - 1) + + ')'); + } + + /** + * Creates an empty file or updates the last updated timestamp on the same as the unix command of + * the same name. + * + * @param file the file to create or update + * @throws IOException if an I/O error occurs + */ + @Beta + @SuppressWarnings("GoodTime") // reading system time without TimeSource + public static void touch(File file) throws IOException { + checkNotNull(file); + if (!file.createNewFile() && !file.setLastModified(System.currentTimeMillis())) { + throw new IOException("Unable to update modification time of " + file); + } + } + + /** + * Creates any necessary but nonexistent parent directories of the specified file. Note that if + * this operation fails it may have succeeded in creating some (but not all) of the necessary + * parent directories. + * + * @throws IOException if an I/O error occurs, or if any necessary but nonexistent parent + * directories of the specified file could not be created. + * @since 4.0 + */ + @Beta + public static void createParentDirs(File file) throws IOException { + checkNotNull(file); + File parent = file.getCanonicalFile().getParentFile(); + if (parent == null) { + /* + * The given directory is a filesystem root. All zero of its ancestors exist. This doesn't + * mean that the root itself exists -- consider x:\ on a Windows machine without such a drive + * -- or even that the caller can create it, but this method makes no such guarantees even for + * non-root files. + */ + return; + } + parent.mkdirs(); + if (!parent.isDirectory()) { + throw new IOException("Unable to create parent directories of " + file); + } + } + + /** + * Moves a file from one path to another. This method can rename a file and/or move it to a + * different directory. In either case {@code to} must be the target path for the file itself; not + * just the new name for the file or the path to the new parent directory. + * + *

{@link java.nio.file.Path} equivalent: {@link java.nio.file.Files#move}. + * + * @param from the source file + * @param to the destination file + * @throws IOException if an I/O error occurs + * @throws IllegalArgumentException if {@code from.equals(to)} + */ + @Beta + public static void move(File from, File to) throws IOException { + checkNotNull(from); + checkNotNull(to); + checkArgument(!from.equals(to), "Source %s and destination %s must be different", from, to); + + if (!from.renameTo(to)) { + copy(from, to); + if (!from.delete()) { + if (!to.delete()) { + throw new IOException("Unable to delete " + to); + } + throw new IOException("Unable to delete " + from); + } + } + } + + /** + * Reads the first line from a file. The line does not include line-termination characters, but + * does include other leading and trailing whitespace. + * + * @param file the file to read from + * @param charset the charset used to decode the input stream; see {@link StandardCharsets} for + * helpful predefined constants + * @return the first line, or null if the file is empty + * @throws IOException if an I/O error occurs + * @deprecated Prefer {@code asCharSource(file, charset).readFirstLine()}. This method is + * scheduled to be removed in October 2019. + */ + @Beta + @Deprecated + public + static String readFirstLine(File file, Charset charset) throws IOException { + return asCharSource(file, charset).readFirstLine(); + } + + /** + * Reads all of the lines from a file. The lines do not include line-termination characters, but + * do include other leading and trailing whitespace. + * + *

This method returns a mutable {@code List}. For an {@code ImmutableList}, use {@code + * Files.asCharSource(file, charset).readLines()}. + * + *

{@link java.nio.file.Path} equivalent: {@link + * java.nio.file.Files#readAllLines(java.nio.file.Path, Charset)}. + * + * @param file the file to read from + * @param charset the charset used to decode the input stream; see {@link StandardCharsets} for + * helpful predefined constants + * @return a mutable {@link List} containing all the lines + * @throws IOException if an I/O error occurs + */ + @Beta + public static List readLines(File file, Charset charset) throws IOException { + // don't use asCharSource(file, charset).readLines() because that returns + // an immutable list, which would change the behavior of this method + return asCharSource(file, charset) + .readLines( + new LineProcessor>() { + final List result = Lists.newArrayList(); + + @Override + public boolean processLine(String line) { + result.add(line); + return true; + } + + @Override + public List getResult() { + return result; + } + }); + } + + /** + * Streams lines from a {@link File}, stopping when our callback returns false, or we have read + * all of the lines. + * + * @param file the file to read from + * @param charset the charset used to decode the input stream; see {@link StandardCharsets} for + * helpful predefined constants + * @param callback the {@link LineProcessor} to use to handle the lines + * @return the output of processing the lines + * @throws IOException if an I/O error occurs + * @deprecated Prefer {@code asCharSource(file, charset).readLines(callback)}. This method is + * scheduled to be removed in October 2019. + */ + @Beta + @Deprecated + // some processors won't return a useful result + public + static T readLines(File file, Charset charset, LineProcessor callback) throws IOException { + return asCharSource(file, charset).readLines(callback); + } + + /** + * Process the bytes of a file. + * + *

(If this seems too complicated, maybe you're looking for {@link #toByteArray}.) + * + * @param file the file to read + * @param processor the object to which the bytes of the file are passed. + * @return the result of the byte processor + * @throws IOException if an I/O error occurs + * @deprecated Prefer {@code asByteSource(file).read(processor)}. This method is scheduled to be + * removed in October 2019. + */ + @Beta + @Deprecated + // some processors won't return a useful result + public + static T readBytes(File file, ByteProcessor processor) throws IOException { + return asByteSource(file).read(processor); + } + + /** + * Computes the hash code of the {@code file} using {@code hashFunction}. + * + * @param file the file to read + * @param hashFunction the hash function to use to hash the data + * @return the {@link HashCode} of all of the bytes in the file + * @throws IOException if an I/O error occurs + * @since 12.0 + * @deprecated Prefer {@code asByteSource(file).hash(hashFunction)}. This method is scheduled to + * be removed in October 2019. + */ + @Beta + @Deprecated + public + static HashCode hash(File file, HashFunction hashFunction) throws IOException { + return asByteSource(file).hash(hashFunction); + } + + /** + * Fully maps a file read-only in to memory as per {@link + * FileChannel#map(java.nio.channels.FileChannel.MapMode, long, long)}. + * + *

Files are mapped from offset 0 to its length. + * + *

This only works for files ≤ {@link Integer#MAX_VALUE} bytes. + * + * @param file the file to map + * @return a read-only buffer reflecting {@code file} + * @throws FileNotFoundException if the {@code file} does not exist + * @throws IOException if an I/O error occurs + * @see FileChannel#map(MapMode, long, long) + * @since 2.0 + */ + @Beta + public static MappedByteBuffer map(File file) throws IOException { + checkNotNull(file); + return map(file, MapMode.READ_ONLY); + } + + /** + * Fully maps a file in to memory as per {@link + * FileChannel#map(java.nio.channels.FileChannel.MapMode, long, long)} using the requested {@link + * MapMode}. + * + *

Files are mapped from offset 0 to its length. + * + *

This only works for files ≤ {@link Integer#MAX_VALUE} bytes. + * + * @param file the file to map + * @param mode the mode to use when mapping {@code file} + * @return a buffer reflecting {@code file} + * @throws FileNotFoundException if the {@code file} does not exist + * @throws IOException if an I/O error occurs + * @see FileChannel#map(MapMode, long, long) + * @since 2.0 + */ + @Beta + public static MappedByteBuffer map(File file, MapMode mode) throws IOException { + return mapInternal(file, mode, -1); + } + + /** + * Maps a file in to memory as per {@link FileChannel#map(java.nio.channels.FileChannel.MapMode, + * long, long)} using the requested {@link MapMode}. + * + *

Files are mapped from offset 0 to {@code size}. + * + *

If the mode is {@link MapMode#READ_WRITE} and the file does not exist, it will be created + * with the requested {@code size}. Thus this method is useful for creating memory mapped files + * which do not yet exist. + * + *

This only works for files ≤ {@link Integer#MAX_VALUE} bytes. + * + * @param file the file to map + * @param mode the mode to use when mapping {@code file} + * @return a buffer reflecting {@code file} + * @throws IOException if an I/O error occurs + * @see FileChannel#map(MapMode, long, long) + * @since 2.0 + */ + @Beta + public static MappedByteBuffer map(File file, MapMode mode, long size) throws IOException { + checkArgument(size >= 0, "size (%s) may not be negative", size); + return mapInternal(file, mode, size); + } + + private static MappedByteBuffer mapInternal(File file, MapMode mode, long size) + throws IOException { + checkNotNull(file); + checkNotNull(mode); + + Closer closer = Closer.create(); + try { + RandomAccessFile raf = + closer.register(new RandomAccessFile(file, mode == MapMode.READ_ONLY ? "r" : "rw")); + FileChannel channel = closer.register(raf.getChannel()); + return channel.map(mode, 0, size == -1 ? channel.size() : size); + } catch (Throwable e) { + throw closer.rethrow(e); + } finally { + closer.close(); + } + } + + /** + * Returns the lexically cleaned form of the path name, usually (but not always) equivalent + * to the original. The following heuristics are used: + * + *

    + *
  • empty string becomes . + *
  • . stays as . + *
  • fold out ./ + *
  • fold out ../ when possible + *
  • collapse multiple slashes + *
  • delete trailing slashes (unless the path is just "/") + *
+ * + *

These heuristics do not always match the behavior of the filesystem. In particular, consider + * the path {@code a/../b}, which {@code simplifyPath} will change to {@code b}. If {@code a} is a + * symlink to {@code x}, {@code a/../b} may refer to a sibling of {@code x}, rather than the + * sibling of {@code a} referred to by {@code b}. + * + * @since 11.0 + */ + @Beta + public static String simplifyPath(String pathname) { + checkNotNull(pathname); + if (pathname.length() == 0) { + return "."; + } + + // split the path apart + Iterable components = Splitter.on('/').omitEmptyStrings().split(pathname); + List path = new ArrayList<>(); + + // resolve ., .., and // + for (String component : components) { + switch (component) { + case ".": + continue; + case "..": + if (path.size() > 0 && !path.get(path.size() - 1).equals("..")) { + path.remove(path.size() - 1); + } else { + path.add(".."); + } + break; + default: + path.add(component); + break; + } + } + + // put it back together + String result = Joiner.on('/').join(path); + if (pathname.charAt(0) == '/') { + result = "/" + result; + } + + while (result.startsWith("/../")) { + result = result.substring(3); + } + if (result.equals("/..")) { + result = "/"; + } else if ("".equals(result)) { + result = "."; + } + + return result; + } + + /** + * Returns the file extension for + * the given file name, or the empty string if the file has no extension. The result does not + * include the '{@code .}'. + * + *

Note: This method simply returns everything after the last '{@code .}' in the file's + * name as determined by {@link File#getName}. It does not account for any filesystem-specific + * behavior that the {@link File} API does not already account for. For example, on NTFS it will + * report {@code "txt"} as the extension for the filename {@code "foo.exe:.txt"} even though NTFS + * will drop the {@code ":.txt"} part of the name when the file is actually created on the + * filesystem due to NTFS's Alternate Data Streams. + * + * @since 11.0 + */ + @Beta + public static String getFileExtension(String fullName) { + checkNotNull(fullName); + String fileName = new File(fullName).getName(); + int dotIndex = fileName.lastIndexOf('.'); + return (dotIndex == -1) ? "" : fileName.substring(dotIndex + 1); + } + + /** + * Returns the file name without its file extension or path. This is + * similar to the {@code basename} unix command. The result does not include the '{@code .}'. + * + * @param file The name of the file to trim the extension from. This can be either a fully + * qualified file name (including a path) or just a file name. + * @return The file name without its path or extension. + * @since 14.0 + */ + @Beta + public static String getNameWithoutExtension(String file) { + checkNotNull(file); + String fileName = new File(file).getName(); + int dotIndex = fileName.lastIndexOf('.'); + return (dotIndex == -1) ? fileName : fileName.substring(0, dotIndex); + } + + /** + * Returns a {@link TreeTraverser} instance for {@link File} trees. + * + *

Warning: {@code File} provides no support for symbolic links, and as such there is no + * way to ensure that a symbolic link to a directory is not followed when traversing the tree. In + * this case, iterables created by this traverser could contain files that are outside of the + * given directory or even be infinite if there is a symbolic link loop. + * + * @since 15.0 + * @deprecated The returned {@link TreeTraverser} type is deprecated. Use the replacement method + * {@link #fileTraverser()} instead with the same semantics as this method. + */ + @Deprecated + static TreeTraverser fileTreeTraverser() { + return FILE_TREE_TRAVERSER; + } + + private static final TreeTraverser FILE_TREE_TRAVERSER = + new TreeTraverser() { + @Override + public Iterable children(File file) { + return fileTreeChildren(file); + } + + @Override + public String toString() { + return "Files.fileTreeTraverser()"; + } + }; + + /** + * Returns a {@link Traverser} instance for the file and directory tree. The returned traverser + * starts from a {@link File} and will return all files and directories it encounters. + * + *

Warning: {@code File} provides no support for symbolic links, and as such there is no + * way to ensure that a symbolic link to a directory is not followed when traversing the tree. In + * this case, iterables created by this traverser could contain files that are outside of the + * given directory or even be infinite if there is a symbolic link loop. + * + *

If available, consider using {@link MoreFiles#fileTraverser()} instead. It behaves the same + * except that it doesn't follow symbolic links and returns {@code Path} instances. + * + *

If the {@link File} passed to one of the {@link Traverser} methods does not exist or is not + * a directory, no exception will be thrown and the returned {@link Iterable} will contain a + * single element: that file. + * + *

Example: {@code Files.fileTraverser().depthFirstPreOrder(new File("/"))} may return files + * with the following paths: {@code ["/", "/etc", "/etc/config.txt", "/etc/fonts", "/home", + * "/home/alice", ...]} + * + * @since 23.5 + */ + @Beta + public static Traverser fileTraverser() { + return Traverser.forTree(FILE_TREE); + } + + private static final SuccessorsFunction FILE_TREE = + new SuccessorsFunction() { + @Override + public Iterable successors(File file) { + return fileTreeChildren(file); + } + }; + + private static Iterable fileTreeChildren(File file) { + // check isDirectory() just because it may be faster than listFiles() on a non-directory + if (file.isDirectory()) { + File[] files = file.listFiles(); + if (files != null) { + return Collections.unmodifiableList(Arrays.asList(files)); + } + } + + return Collections.emptyList(); + } + + /** + * Returns a predicate that returns the result of {@link File#isDirectory} on input files. + * + * @since 15.0 + */ + @Beta + public static Predicate isDirectory() { + return FilePredicate.IS_DIRECTORY; + } + + /** + * Returns a predicate that returns the result of {@link File#isFile} on input files. + * + * @since 15.0 + */ + @Beta + public static Predicate isFile() { + return FilePredicate.IS_FILE; + } + + private enum FilePredicate implements Predicate { + IS_DIRECTORY { + @Override + public boolean apply(File file) { + return file.isDirectory(); + } + + @Override + public String toString() { + return "Files.isDirectory()"; + } + }, + + IS_FILE { + @Override + public boolean apply(File file) { + return file.isFile(); + } + + @Override + public String toString() { + return "Files.isFile()"; + } + } + } +} diff --git a/src/main/java/com/google/common/io/Flushables.java b/src/main/java/com/google/common/io/Flushables.java new file mode 100644 index 0000000..9b1d6a0 --- /dev/null +++ b/src/main/java/com/google/common/io/Flushables.java @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.io; + +import com.google.common.annotations.Beta; +import com.google.common.annotations.GwtIncompatible; +import java.io.Flushable; +import java.io.IOException; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * Utility methods for working with {@link Flushable} objects. + * + * @author Michael Lancaster + * @since 1.0 + */ +@Beta +@GwtIncompatible +public final class Flushables { + private static final Logger logger = Logger.getLogger(Flushables.class.getName()); + + private Flushables() {} + + /** + * Flush a {@link Flushable}, with control over whether an {@code IOException} may be thrown. + * + *

If {@code swallowIOException} is true, then we don't rethrow {@code IOException}, but merely + * log it. + * + * @param flushable the {@code Flushable} object to be flushed. + * @param swallowIOException if true, don't propagate IO exceptions thrown by the {@code flush} + * method + * @throws IOException if {@code swallowIOException} is false and {@link Flushable#flush} throws + * an {@code IOException}. + * @see Closeables#close + */ + public static void flush(Flushable flushable, boolean swallowIOException) throws IOException { + try { + flushable.flush(); + } catch (IOException e) { + if (swallowIOException) { + logger.log(Level.WARNING, "IOException thrown while flushing Flushable.", e); + } else { + throw e; + } + } + } + + /** + * Equivalent to calling {@code flush(flushable, true)}, but with no {@code IOException} in the + * signature. + * + * @param flushable the {@code Flushable} object to be flushed. + */ + public static void flushQuietly(Flushable flushable) { + try { + flush(flushable, true); + } catch (IOException e) { + logger.log(Level.SEVERE, "IOException should not have been thrown.", e); + } + } +} diff --git a/src/main/java/com/google/common/io/InsecureRecursiveDeleteException.java b/src/main/java/com/google/common/io/InsecureRecursiveDeleteException.java new file mode 100644 index 0000000..c3ca60c --- /dev/null +++ b/src/main/java/com/google/common/io/InsecureRecursiveDeleteException.java @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2014 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.io; + +import com.google.common.annotations.Beta; +import com.google.common.annotations.GwtIncompatible; + +import java.nio.file.FileSystemException; +import java.nio.file.SecureDirectoryStream; + + +/** + * Exception indicating that a recursive delete can't be performed because the file system does not + * have the support necessary to guarantee that it is not vulnerable to race conditions that would + * allow it to delete files and directories outside of the directory being deleted (i.e., {@link + * SecureDirectoryStream} is not supported). + * + *

{@link RecursiveDeleteOption#ALLOW_INSECURE} can be used to force the recursive delete method + * to proceed anyway. + * + * @since 21.0 + * @author Colin Decker + */ +@Beta +@GwtIncompatible + // java.nio.file +public final class InsecureRecursiveDeleteException extends FileSystemException { + + public InsecureRecursiveDeleteException(String file) { + super(file, null, "unable to guarantee security of recursive delete"); + } +} diff --git a/src/main/java/com/google/common/io/LineBuffer.java b/src/main/java/com/google/common/io/LineBuffer.java new file mode 100644 index 0000000..052a4a5 --- /dev/null +++ b/src/main/java/com/google/common/io/LineBuffer.java @@ -0,0 +1,115 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.io; + +import com.google.common.annotations.GwtIncompatible; + +import java.io.IOException; + +/** + * Package-protected abstract class that implements the line reading algorithm used by {@link + * LineReader}. Line separators are per {@link java.io.BufferedReader}: line feed, carriage return, + * or carriage return followed immediately by a linefeed. + * + *

Subclasses must implement {@link #handleLine}, call {@link #add} to pass character data, and + * call {@link #finish} at the end of stream. + * + * @author Chris Nokleberg + * @since 1.0 + */ +@GwtIncompatible +abstract class LineBuffer { + /** Holds partial line contents. */ + private StringBuilder line = new StringBuilder(); + /** Whether a line ending with a CR is pending processing. */ + private boolean sawReturn; + + /** + * Process additional characters from the stream. When a line separator is found the contents of + * the line and the line separator itself are passed to the abstract {@link #handleLine} method. + * + * @param cbuf the character buffer to process + * @param off the offset into the buffer + * @param len the number of characters to process + * @throws IOException if an I/O error occurs + * @see #finish + */ + protected void add(char[] cbuf, int off, int len) throws IOException { + int pos = off; + if (sawReturn && len > 0) { + // Last call to add ended with a CR; we can handle the line now. + if (finishLine(cbuf[pos] == '\n')) { + pos++; + } + } + + int start = pos; + for (int end = off + len; pos < end; pos++) { + switch (cbuf[pos]) { + case '\r': + line.append(cbuf, start, pos - start); + sawReturn = true; + if (pos + 1 < end) { + if (finishLine(cbuf[pos + 1] == '\n')) { + pos++; + } + } + start = pos + 1; + break; + + case '\n': + line.append(cbuf, start, pos - start); + finishLine(true); + start = pos + 1; + break; + + default: + // do nothing + } + } + line.append(cbuf, start, off + len - start); + } + + /** Called when a line is complete. */ + + private boolean finishLine(boolean sawNewline) throws IOException { + String separator = sawReturn ? (sawNewline ? "\r\n" : "\r") : (sawNewline ? "\n" : ""); + handleLine(line.toString(), separator); + line = new StringBuilder(); + sawReturn = false; + return sawNewline; + } + + /** + * Subclasses must call this method after finishing character processing, in order to ensure that + * any unterminated line in the buffer is passed to {@link #handleLine}. + * + * @throws IOException if an I/O error occurs + */ + protected void finish() throws IOException { + if (sawReturn || line.length() > 0) { + finishLine(false); + } + } + + /** + * Called for each line found in the character data passed to {@link #add}. + * + * @param line a line of text (possibly empty), without any line separators + * @param end the line separator; one of {@code "\r"}, {@code "\n"}, {@code "\r\n"}, or {@code ""} + * @throws IOException if an I/O error occurs + */ + protected abstract void handleLine(String line, String end) throws IOException; +} diff --git a/src/main/java/com/google/common/io/LineProcessor.java b/src/main/java/com/google/common/io/LineProcessor.java new file mode 100644 index 0000000..02df586 --- /dev/null +++ b/src/main/java/com/google/common/io/LineProcessor.java @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2009 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.io; + +import com.google.common.annotations.Beta; +import com.google.common.annotations.GwtIncompatible; + +import java.io.IOException; + +/** + * A callback to be used with the streaming {@code readLines} methods. + * + *

{@link #processLine} will be called for each line that is read, and should return {@code + * false} when you want to stop processing. + * + * @author Miles Barr + * @since 1.0 + */ +@Beta +@GwtIncompatible +public interface LineProcessor { + + /** + * This method will be called once for each line. + * + * @param line the line read from the input, without delimiter + * @return true to continue processing, false to stop + */ + // some uses know that their processor never returns false + boolean processLine(String line) throws IOException; + + /** Return the result of processing all the lines. */ + T getResult(); +} diff --git a/src/main/java/com/google/common/io/LineReader.java b/src/main/java/com/google/common/io/LineReader.java new file mode 100644 index 0000000..2b36471 --- /dev/null +++ b/src/main/java/com/google/common/io/LineReader.java @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.io; + +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.io.CharStreams.createBuffer; + +import com.google.common.annotations.Beta; +import com.google.common.annotations.GwtIncompatible; + +import java.io.IOException; +import java.io.Reader; +import java.nio.CharBuffer; +import java.util.LinkedList; +import java.util.Queue; + + +/** + * A class for reading lines of text. Provides the same functionality as {@link + * java.io.BufferedReader#readLine()} but for all {@link Readable} objects, not just instances of + * {@link Reader}. + * + * @author Chris Nokleberg + * @since 1.0 + */ +@Beta +@GwtIncompatible +public final class LineReader { + private final Readable readable; + private final Reader reader; + private final CharBuffer cbuf = createBuffer(); + private final char[] buf = cbuf.array(); + + private final Queue lines = new LinkedList<>(); + private final LineBuffer lineBuf = + new LineBuffer() { + @Override + protected void handleLine(String line, String end) { + lines.add(line); + } + }; + + /** Creates a new instance that will read lines from the given {@code Readable} object. */ + public LineReader(Readable readable) { + this.readable = checkNotNull(readable); + this.reader = (readable instanceof Reader) ? (Reader) readable : null; + } + + /** + * Reads a line of text. A line is considered to be terminated by any one of a line feed ({@code + * '\n'}), a carriage return ({@code '\r'}), or a carriage return followed immediately by a + * linefeed ({@code "\r\n"}). + * + * @return a {@code String} containing the contents of the line, not including any + * line-termination characters, or {@code null} if the end of the stream has been reached. + * @throws IOException if an I/O error occurs + */ + // to skip a line + public String readLine() throws IOException { + while (lines.peek() == null) { + cbuf.clear(); + // The default implementation of Reader#read(CharBuffer) allocates a + // temporary char[], so we call Reader#read(char[], int, int) instead. + int read = (reader != null) ? reader.read(buf, 0, buf.length) : readable.read(cbuf); + if (read == -1) { + lineBuf.finish(); + break; + } + lineBuf.add(buf, 0, read); + } + return lines.poll(); + } +} diff --git a/src/main/java/com/google/common/io/LittleEndianDataInputStream.java b/src/main/java/com/google/common/io/LittleEndianDataInputStream.java new file mode 100644 index 0000000..293f502 --- /dev/null +++ b/src/main/java/com/google/common/io/LittleEndianDataInputStream.java @@ -0,0 +1,237 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.io; + +import com.google.common.annotations.Beta; +import com.google.common.annotations.GwtIncompatible; +import com.google.common.base.Preconditions; +import com.google.common.primitives.Ints; +import com.google.common.primitives.Longs; + +import java.io.DataInput; +import java.io.DataInputStream; +import java.io.EOFException; +import java.io.FilterInputStream; +import java.io.IOException; +import java.io.InputStream; + +/** + * An implementation of {@link DataInput} that uses little-endian byte ordering for reading {@code + * short}, {@code int}, {@code float}, {@code double}, and {@code long} values. + * + *

Note: This class intentionally violates the specification of its supertype {@code + * DataInput}, which explicitly requires big-endian byte order. + * + * @author Chris Nokleberg + * @author Keith Bottner + * @since 8.0 + */ +@Beta +@GwtIncompatible +public final class LittleEndianDataInputStream extends FilterInputStream implements DataInput { + + /** + * Creates a {@code LittleEndianDataInputStream} that wraps the given stream. + * + * @param in the stream to delegate to + */ + public LittleEndianDataInputStream(InputStream in) { + super(Preconditions.checkNotNull(in)); + } + + /** This method will throw an {@link UnsupportedOperationException}. */ + // to skip a line + @Override + public String readLine() { + throw new UnsupportedOperationException("readLine is not supported"); + } + + @Override + public void readFully(byte[] b) throws IOException { + ByteStreams.readFully(this, b); + } + + @Override + public void readFully(byte[] b, int off, int len) throws IOException { + ByteStreams.readFully(this, b, off, len); + } + + @Override + public int skipBytes(int n) throws IOException { + return (int) in.skip(n); + } + + // to skip a byte + @Override + public int readUnsignedByte() throws IOException { + int b1 = in.read(); + if (0 > b1) { + throw new EOFException(); + } + + return b1; + } + + /** + * Reads an unsigned {@code short} as specified by {@link DataInputStream#readUnsignedShort()}, + * except using little-endian byte order. + * + * @return the next two bytes of the input stream, interpreted as an unsigned 16-bit integer in + * little-endian byte order + * @throws IOException if an I/O error occurs + */ + // to skip some bytes + @Override + public int readUnsignedShort() throws IOException { + byte b1 = readAndCheckByte(); + byte b2 = readAndCheckByte(); + + return Ints.fromBytes((byte) 0, (byte) 0, b2, b1); + } + + /** + * Reads an integer as specified by {@link DataInputStream#readInt()}, except using little-endian + * byte order. + * + * @return the next four bytes of the input stream, interpreted as an {@code int} in little-endian + * byte order + * @throws IOException if an I/O error occurs + */ + // to skip some bytes + @Override + public int readInt() throws IOException { + byte b1 = readAndCheckByte(); + byte b2 = readAndCheckByte(); + byte b3 = readAndCheckByte(); + byte b4 = readAndCheckByte(); + + return Ints.fromBytes(b4, b3, b2, b1); + } + + /** + * Reads a {@code long} as specified by {@link DataInputStream#readLong()}, except using + * little-endian byte order. + * + * @return the next eight bytes of the input stream, interpreted as a {@code long} in + * little-endian byte order + * @throws IOException if an I/O error occurs + */ + // to skip some bytes + @Override + public long readLong() throws IOException { + byte b1 = readAndCheckByte(); + byte b2 = readAndCheckByte(); + byte b3 = readAndCheckByte(); + byte b4 = readAndCheckByte(); + byte b5 = readAndCheckByte(); + byte b6 = readAndCheckByte(); + byte b7 = readAndCheckByte(); + byte b8 = readAndCheckByte(); + + return Longs.fromBytes(b8, b7, b6, b5, b4, b3, b2, b1); + } + + /** + * Reads a {@code float} as specified by {@link DataInputStream#readFloat()}, except using + * little-endian byte order. + * + * @return the next four bytes of the input stream, interpreted as a {@code float} in + * little-endian byte order + * @throws IOException if an I/O error occurs + */ + // to skip some bytes + @Override + public float readFloat() throws IOException { + return Float.intBitsToFloat(readInt()); + } + + /** + * Reads a {@code double} as specified by {@link DataInputStream#readDouble()}, except using + * little-endian byte order. + * + * @return the next eight bytes of the input stream, interpreted as a {@code double} in + * little-endian byte order + * @throws IOException if an I/O error occurs + */ + // to skip some bytes + @Override + public double readDouble() throws IOException { + return Double.longBitsToDouble(readLong()); + } + + // to skip a field + @Override + public String readUTF() throws IOException { + return new DataInputStream(in).readUTF(); + } + + /** + * Reads a {@code short} as specified by {@link DataInputStream#readShort()}, except using + * little-endian byte order. + * + * @return the next two bytes of the input stream, interpreted as a {@code short} in little-endian + * byte order. + * @throws IOException if an I/O error occurs. + */ + // to skip some bytes + @Override + public short readShort() throws IOException { + return (short) readUnsignedShort(); + } + + /** + * Reads a char as specified by {@link DataInputStream#readChar()}, except using little-endian + * byte order. + * + * @return the next two bytes of the input stream, interpreted as a {@code char} in little-endian + * byte order + * @throws IOException if an I/O error occurs + */ + // to skip some bytes + @Override + public char readChar() throws IOException { + return (char) readUnsignedShort(); + } + + // to skip a byte + @Override + public byte readByte() throws IOException { + return (byte) readUnsignedByte(); + } + + // to skip a byte + @Override + public boolean readBoolean() throws IOException { + return readUnsignedByte() != 0; + } + + /** + * Reads a byte from the input stream checking that the end of file (EOF) has not been + * encountered. + * + * @return byte read from input + * @throws IOException if an error is encountered while reading + * @throws EOFException if the end of file (EOF) is encountered. + */ + private byte readAndCheckByte() throws IOException, EOFException { + int b1 = in.read(); + + if (-1 == b1) { + throw new EOFException(); + } + + return (byte) b1; + } +} diff --git a/src/main/java/com/google/common/io/LittleEndianDataOutputStream.java b/src/main/java/com/google/common/io/LittleEndianDataOutputStream.java new file mode 100644 index 0000000..e5e398f --- /dev/null +++ b/src/main/java/com/google/common/io/LittleEndianDataOutputStream.java @@ -0,0 +1,173 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.io; + +import com.google.common.annotations.Beta; +import com.google.common.annotations.GwtIncompatible; +import com.google.common.base.Preconditions; +import com.google.common.primitives.Longs; +import java.io.DataOutput; +import java.io.DataOutputStream; +import java.io.FilterOutputStream; +import java.io.IOException; +import java.io.OutputStream; + +/** + * An implementation of {@link DataOutput} that uses little-endian byte ordering for writing {@code + * char}, {@code short}, {@code int}, {@code float}, {@code double}, and {@code long} values. + * + *

Note: This class intentionally violates the specification of its supertype {@code + * DataOutput}, which explicitly requires big-endian byte order. + * + * @author Chris Nokleberg + * @author Keith Bottner + * @since 8.0 + */ +@Beta +@GwtIncompatible +public final class LittleEndianDataOutputStream extends FilterOutputStream implements DataOutput { + + /** + * Creates a {@code LittleEndianDataOutputStream} that wraps the given stream. + * + * @param out the stream to delegate to + */ + public LittleEndianDataOutputStream(OutputStream out) { + super(new DataOutputStream(Preconditions.checkNotNull(out))); + } + + @Override + public void write(byte[] b, int off, int len) throws IOException { + // Override slow FilterOutputStream impl + out.write(b, off, len); + } + + @Override + public void writeBoolean(boolean v) throws IOException { + ((DataOutputStream) out).writeBoolean(v); + } + + @Override + public void writeByte(int v) throws IOException { + ((DataOutputStream) out).writeByte(v); + } + + /** + * @deprecated The semantics of {@code writeBytes(String s)} are considered dangerous. Please use + * {@link #writeUTF(String s)}, {@link #writeChars(String s)} or another write method instead. + */ + @Deprecated + @Override + public void writeBytes(String s) throws IOException { + ((DataOutputStream) out).writeBytes(s); + } + + /** + * Writes a char as specified by {@link DataOutputStream#writeChar(int)}, except using + * little-endian byte order. + * + * @throws IOException if an I/O error occurs + */ + @Override + public void writeChar(int v) throws IOException { + writeShort(v); + } + + /** + * Writes a {@code String} as specified by {@link DataOutputStream#writeChars(String)}, except + * each character is written using little-endian byte order. + * + * @throws IOException if an I/O error occurs + */ + @Override + public void writeChars(String s) throws IOException { + for (int i = 0; i < s.length(); i++) { + writeChar(s.charAt(i)); + } + } + + /** + * Writes a {@code double} as specified by {@link DataOutputStream#writeDouble(double)}, except + * using little-endian byte order. + * + * @throws IOException if an I/O error occurs + */ + @Override + public void writeDouble(double v) throws IOException { + writeLong(Double.doubleToLongBits(v)); + } + + /** + * Writes a {@code float} as specified by {@link DataOutputStream#writeFloat(float)}, except using + * little-endian byte order. + * + * @throws IOException if an I/O error occurs + */ + @Override + public void writeFloat(float v) throws IOException { + writeInt(Float.floatToIntBits(v)); + } + + /** + * Writes an {@code int} as specified by {@link DataOutputStream#writeInt(int)}, except using + * little-endian byte order. + * + * @throws IOException if an I/O error occurs + */ + @Override + public void writeInt(int v) throws IOException { + out.write(0xFF & v); + out.write(0xFF & (v >> 8)); + out.write(0xFF & (v >> 16)); + out.write(0xFF & (v >> 24)); + } + + /** + * Writes a {@code long} as specified by {@link DataOutputStream#writeLong(long)}, except using + * little-endian byte order. + * + * @throws IOException if an I/O error occurs + */ + @Override + public void writeLong(long v) throws IOException { + byte[] bytes = Longs.toByteArray(Long.reverseBytes(v)); + write(bytes, 0, bytes.length); + } + + /** + * Writes a {@code short} as specified by {@link DataOutputStream#writeShort(int)}, except using + * little-endian byte order. + * + * @throws IOException if an I/O error occurs + */ + @Override + public void writeShort(int v) throws IOException { + out.write(0xFF & v); + out.write(0xFF & (v >> 8)); + } + + @Override + public void writeUTF(String str) throws IOException { + ((DataOutputStream) out).writeUTF(str); + } + + // Overriding close() because FilterOutputStream's close() method pre-JDK8 has bad behavior: + // it silently ignores any exception thrown by flush(). Instead, just close the delegate stream. + // It should flush itself if necessary. + @Override + public void close() throws IOException { + out.close(); + } +} diff --git a/src/main/java/com/google/common/io/MoreFiles.java b/src/main/java/com/google/common/io/MoreFiles.java new file mode 100644 index 0000000..cbd92e7 --- /dev/null +++ b/src/main/java/com/google/common/io/MoreFiles.java @@ -0,0 +1,794 @@ +/* + * Copyright (C) 2013 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.io; + +import static com.google.common.base.Preconditions.checkNotNull; +import static java.nio.file.LinkOption.NOFOLLOW_LINKS; + +import com.google.common.annotations.Beta; +import com.google.common.annotations.GwtIncompatible; +import com.google.common.base.Optional; +import com.google.common.base.Predicate; +import com.google.common.collect.ImmutableList; +import com.google.common.graph.SuccessorsFunction; +import com.google.common.graph.Traverser; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.channels.Channels; +import java.nio.channels.SeekableByteChannel; +import java.nio.charset.Charset; +import java.nio.file.DirectoryIteratorException; +import java.nio.file.DirectoryStream; +import java.nio.file.FileAlreadyExistsException; +import java.nio.file.FileSystemException; +import java.nio.file.Files; +import java.nio.file.LinkOption; +import java.nio.file.NoSuchFileException; +import java.nio.file.NotDirectoryException; +import java.nio.file.OpenOption; +import java.nio.file.Path; +import java.nio.file.SecureDirectoryStream; +import java.nio.file.StandardOpenOption; +import java.nio.file.attribute.BasicFileAttributeView; +import java.nio.file.attribute.BasicFileAttributes; +import java.nio.file.attribute.FileAttribute; +import java.nio.file.attribute.FileTime; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.stream.Stream; + + +/** + * Static utilities for use with {@link Path} instances, intended to complement {@link Files}. + * + *

Many methods provided by Guava's {@code Files} class for {@link java.io.File} instances are + * now available via the JDK's {@link java.nio.file.Files} class for {@code Path} - check the JDK's + * class if a sibling method from {@code Files} appears to be missing from this class. + * + * @since 21.0 + * @author Colin Decker + */ +@Beta +@GwtIncompatible + // java.nio.file +public final class MoreFiles { + + private MoreFiles() {} + + /** + * Returns a view of the given {@code path} as a {@link ByteSource}. + * + *

Any {@linkplain OpenOption open options} provided are used when opening streams to the file + * and may affect the behavior of the returned source and the streams it provides. See {@link + * StandardOpenOption} for the standard options that may be provided. Providing no options is + * equivalent to providing the {@link StandardOpenOption#READ READ} option. + */ + public static ByteSource asByteSource(Path path, OpenOption... options) { + return new PathByteSource(path, options); + } + + private static final class PathByteSource extends ByteSource { + + private static final LinkOption[] FOLLOW_LINKS = {}; + + private final Path path; + private final OpenOption[] options; + private final boolean followLinks; + + private PathByteSource(Path path, OpenOption... options) { + this.path = checkNotNull(path); + this.options = options.clone(); + this.followLinks = followLinks(this.options); + // TODO(cgdecker): validate the provided options... for example, just WRITE seems wrong + } + + private static boolean followLinks(OpenOption[] options) { + for (OpenOption option : options) { + if (option == NOFOLLOW_LINKS) { + return false; + } + } + return true; + } + + @Override + public InputStream openStream() throws IOException { + return Files.newInputStream(path, options); + } + + private BasicFileAttributes readAttributes() throws IOException { + return Files.readAttributes( + path, + BasicFileAttributes.class, + followLinks ? FOLLOW_LINKS : new LinkOption[] {NOFOLLOW_LINKS}); + } + + @Override + public Optional sizeIfKnown() { + BasicFileAttributes attrs; + try { + attrs = readAttributes(); + } catch (IOException e) { + // Failed to get attributes; we don't know the size. + return Optional.absent(); + } + + // Don't return a size for directories or symbolic links; their sizes are implementation + // specific and they can't be read as bytes using the read methods anyway. + if (attrs.isDirectory() || attrs.isSymbolicLink()) { + return Optional.absent(); + } + + return Optional.of(attrs.size()); + } + + @Override + public long size() throws IOException { + BasicFileAttributes attrs = readAttributes(); + + // Don't return a size for directories or symbolic links; their sizes are implementation + // specific and they can't be read as bytes using the read methods anyway. + if (attrs.isDirectory()) { + throw new IOException("can't read: is a directory"); + } else if (attrs.isSymbolicLink()) { + throw new IOException("can't read: is a symbolic link"); + } + + return attrs.size(); + } + + @Override + public byte[] read() throws IOException { + try (SeekableByteChannel channel = Files.newByteChannel(path, options)) { + return ByteStreams.toByteArray(Channels.newInputStream(channel), channel.size()); + } + } + + @Override + public CharSource asCharSource(Charset charset) { + if (options.length == 0) { + // If no OpenOptions were passed, delegate to Files.lines, which could have performance + // advantages. (If OpenOptions were passed we can't, because Files.lines doesn't have an + // overload taking OpenOptions, meaning we can't guarantee the same behavior w.r.t. things + // like following/not following symlinks. + return new AsCharSource(charset) { + @SuppressWarnings("FilesLinesLeak") // the user needs to close it in this case + @Override + public Stream lines() throws IOException { + return Files.lines(path, charset); + } + }; + } + + return super.asCharSource(charset); + } + + @Override + public String toString() { + return "MoreFiles.asByteSource(" + path + ", " + Arrays.toString(options) + ")"; + } + } + + /** + * Returns a view of the given {@code path} as a {@link ByteSink}. + * + *

Any {@linkplain OpenOption open options} provided are used when opening streams to the file + * and may affect the behavior of the returned sink and the streams it provides. See {@link + * StandardOpenOption} for the standard options that may be provided. Providing no options is + * equivalent to providing the {@link StandardOpenOption#CREATE CREATE}, {@link + * StandardOpenOption#TRUNCATE_EXISTING TRUNCATE_EXISTING} and {@link StandardOpenOption#WRITE + * WRITE} options. + */ + public static ByteSink asByteSink(Path path, OpenOption... options) { + return new PathByteSink(path, options); + } + + private static final class PathByteSink extends ByteSink { + + private final Path path; + private final OpenOption[] options; + + private PathByteSink(Path path, OpenOption... options) { + this.path = checkNotNull(path); + this.options = options.clone(); + // TODO(cgdecker): validate the provided options... for example, just READ seems wrong + } + + @Override + public OutputStream openStream() throws IOException { + return Files.newOutputStream(path, options); + } + + @Override + public String toString() { + return "MoreFiles.asByteSink(" + path + ", " + Arrays.toString(options) + ")"; + } + } + + /** + * Returns a view of the given {@code path} as a {@link CharSource} using the given {@code + * charset}. + * + *

Any {@linkplain OpenOption open options} provided are used when opening streams to the file + * and may affect the behavior of the returned source and the streams it provides. See {@link + * StandardOpenOption} for the standard options that may be provided. Providing no options is + * equivalent to providing the {@link StandardOpenOption#READ READ} option. + */ + public static CharSource asCharSource(Path path, Charset charset, OpenOption... options) { + return asByteSource(path, options).asCharSource(charset); + } + + /** + * Returns a view of the given {@code path} as a {@link CharSink} using the given {@code charset}. + * + *

Any {@linkplain OpenOption open options} provided are used when opening streams to the file + * and may affect the behavior of the returned sink and the streams it provides. See {@link + * StandardOpenOption} for the standard options that may be provided. Providing no options is + * equivalent to providing the {@link StandardOpenOption#CREATE CREATE}, {@link + * StandardOpenOption#TRUNCATE_EXISTING TRUNCATE_EXISTING} and {@link StandardOpenOption#WRITE + * WRITE} options. + */ + public static CharSink asCharSink(Path path, Charset charset, OpenOption... options) { + return asByteSink(path, options).asCharSink(charset); + } + + /** + * Returns an immutable list of paths to the files contained in the given directory. + * + * @throws NoSuchFileException if the file does not exist (optional specific exception) + * @throws NotDirectoryException if the file could not be opened because it is not a directory + * (optional specific exception) + * @throws IOException if an I/O error occurs + */ + public static ImmutableList listFiles(Path dir) throws IOException { + try (DirectoryStream stream = Files.newDirectoryStream(dir)) { + return ImmutableList.copyOf(stream); + } catch (DirectoryIteratorException e) { + throw e.getCause(); + } + } + + /** + * Returns a {@link Traverser} instance for the file and directory tree. The returned traverser + * starts from a {@link Path} and will return all files and directories it encounters. + * + *

The returned traverser attempts to avoid following symbolic links to directories. However, + * the traverser cannot guarantee that it will not follow symbolic links to directories as it is + * possible for a directory to be replaced with a symbolic link between checking if the file is a + * directory and actually reading the contents of that directory. + * + *

If the {@link Path} passed to one of the traversal methods does not exist or is not a + * directory, no exception will be thrown and the returned {@link Iterable} will contain a single + * element: that path. + * + *

{@link DirectoryIteratorException} may be thrown when iterating {@link Iterable} instances + * created by this traverser if an {@link IOException} is thrown by a call to {@link + * #listFiles(Path)}. + * + *

Example: {@code MoreFiles.fileTraverser().depthFirstPreOrder(Paths.get("/"))} may return the + * following paths: {@code ["/", "/etc", "/etc/config.txt", "/etc/fonts", "/home", "/home/alice", + * ...]} + * + * @since 23.5 + */ + public static Traverser fileTraverser() { + return Traverser.forTree(FILE_TREE); + } + + private static final SuccessorsFunction FILE_TREE = + new SuccessorsFunction() { + @Override + public Iterable successors(Path path) { + return fileTreeChildren(path); + } + }; + + private static Iterable fileTreeChildren(Path dir) { + if (Files.isDirectory(dir, NOFOLLOW_LINKS)) { + try { + return listFiles(dir); + } catch (IOException e) { + // the exception thrown when iterating a DirectoryStream if an I/O exception occurs + throw new DirectoryIteratorException(e); + } + } + return ImmutableList.of(); + } + + /** + * Returns a predicate that returns the result of {@link java.nio.file.Files#isDirectory(Path, + * LinkOption...)} on input paths with the given link options. + */ + public static Predicate isDirectory(LinkOption... options) { + final LinkOption[] optionsCopy = options.clone(); + return new Predicate() { + @Override + public boolean apply(Path input) { + return Files.isDirectory(input, optionsCopy); + } + + @Override + public String toString() { + return "MoreFiles.isDirectory(" + Arrays.toString(optionsCopy) + ")"; + } + }; + } + + /** Returns whether or not the file with the given name in the given dir is a directory. */ + private static boolean isDirectory( + SecureDirectoryStream dir, Path name, LinkOption... options) throws IOException { + return dir.getFileAttributeView(name, BasicFileAttributeView.class, options) + .readAttributes() + .isDirectory(); + } + + /** + * Returns a predicate that returns the result of {@link java.nio.file.Files#isRegularFile(Path, + * LinkOption...)} on input paths with the given link options. + */ + public static Predicate isRegularFile(LinkOption... options) { + final LinkOption[] optionsCopy = options.clone(); + return new Predicate() { + @Override + public boolean apply(Path input) { + return Files.isRegularFile(input, optionsCopy); + } + + @Override + public String toString() { + return "MoreFiles.isRegularFile(" + Arrays.toString(optionsCopy) + ")"; + } + }; + } + + /** + * Returns true if the files located by the given paths exist, are not directories, and contain + * the same bytes. + * + * @throws IOException if an I/O error occurs + * @since 22.0 + */ + public static boolean equal(Path path1, Path path2) throws IOException { + checkNotNull(path1); + checkNotNull(path2); + if (Files.isSameFile(path1, path2)) { + return true; + } + + /* + * Some operating systems may return zero as the length for files denoting system-dependent + * entities such as devices or pipes, in which case we must fall back on comparing the bytes + * directly. + */ + ByteSource source1 = asByteSource(path1); + ByteSource source2 = asByteSource(path2); + long len1 = source1.sizeIfKnown().or(0L); + long len2 = source2.sizeIfKnown().or(0L); + if (len1 != 0 && len2 != 0 && len1 != len2) { + return false; + } + return source1.contentEquals(source2); + } + + /** + * Like the unix command of the same name, creates an empty file or updates the last modified + * timestamp of the existing file at the given path to the current system time. + */ + @SuppressWarnings("GoodTime") // reading system time without TimeSource + public static void touch(Path path) throws IOException { + checkNotNull(path); + + try { + Files.setLastModifiedTime(path, FileTime.fromMillis(System.currentTimeMillis())); + } catch (NoSuchFileException e) { + try { + Files.createFile(path); + } catch (FileAlreadyExistsException ignore) { + // The file didn't exist when we called setLastModifiedTime, but it did when we called + // createFile, so something else created the file in between. The end result is + // what we wanted: a new file that probably has its last modified time set to approximately + // now. Or it could have an arbitrary last modified time set by the creator, but that's no + // different than if another process set its last modified time to something else after we + // created it here. + } + } + } + + /** + * Creates any necessary but nonexistent parent directories of the specified path. Note that if + * this operation fails, it may have succeeded in creating some (but not all) of the necessary + * parent directories. The parent directory is created with the given {@code attrs}. + * + * @throws IOException if an I/O error occurs, or if any necessary but nonexistent parent + * directories of the specified file could not be created. + */ + public static void createParentDirectories(Path path, FileAttribute... attrs) + throws IOException { + // Interestingly, unlike File.getCanonicalFile(), Path/Files provides no way of getting the + // canonical (absolute, normalized, symlinks resolved, etc.) form of a path to a nonexistent + // file. getCanonicalFile() can at least get the canonical form of the part of the path which + // actually exists and then append the normalized remainder of the path to that. + Path normalizedAbsolutePath = path.toAbsolutePath().normalize(); + Path parent = normalizedAbsolutePath.getParent(); + if (parent == null) { + // The given directory is a filesystem root. All zero of its ancestors exist. This doesn't + // mean that the root itself exists -- consider x:\ on a Windows machine without such a + // drive -- or even that the caller can create it, but this method makes no such guarantees + // even for non-root files. + return; + } + + // Check if the parent is a directory first because createDirectories will fail if the parent + // exists and is a symlink to a directory... we'd like for this to succeed in that case. + // (I'm kind of surprised that createDirectories would fail in that case; doesn't seem like + // what you'd want to happen.) + if (!Files.isDirectory(parent)) { + Files.createDirectories(parent, attrs); + if (!Files.isDirectory(parent)) { + throw new IOException("Unable to create parent directories of " + path); + } + } + } + + /** + * Returns the file extension for + * the file at the given path, or the empty string if the file has no extension. The result does + * not include the '{@code .}'. + * + *

Note: This method simply returns everything after the last '{@code .}' in the file's + * name as determined by {@link Path#getFileName}. It does not account for any filesystem-specific + * behavior that the {@link Path} API does not already account for. For example, on NTFS it will + * report {@code "txt"} as the extension for the filename {@code "foo.exe:.txt"} even though NTFS + * will drop the {@code ":.txt"} part of the name when the file is actually created on the + * filesystem due to NTFS's Alternate Data Streams. + */ + public static String getFileExtension(Path path) { + Path name = path.getFileName(); + + // null for empty paths and root-only paths + if (name == null) { + return ""; + } + + String fileName = name.toString(); + int dotIndex = fileName.lastIndexOf('.'); + return dotIndex == -1 ? "" : fileName.substring(dotIndex + 1); + } + + /** + * Returns the file name without its file extension or path. This is + * similar to the {@code basename} unix command. The result does not include the '{@code .}'. + */ + public static String getNameWithoutExtension(Path path) { + Path name = path.getFileName(); + + // null for empty paths and root-only paths + if (name == null) { + return ""; + } + + String fileName = name.toString(); + int dotIndex = fileName.lastIndexOf('.'); + return dotIndex == -1 ? fileName : fileName.substring(0, dotIndex); + } + + /** + * Deletes the file or directory at the given {@code path} recursively. Deletes symbolic links, + * not their targets (subject to the caveat below). + * + *

If an I/O exception occurs attempting to read, open or delete any file under the given + * directory, this method skips that file and continues. All such exceptions are collected and, + * after attempting to delete all files, an {@code IOException} is thrown containing those + * exceptions as {@linkplain Throwable#getSuppressed() suppressed exceptions}. + * + *

Warning: Security of recursive deletes

+ * + *

On a file system that supports symbolic links and does not support {@link + * SecureDirectoryStream}, it is possible for a recursive delete to delete files and directories + * that are outside the directory being deleted. This can happen if, after checking that a + * file is a directory (and not a symbolic link), that directory is replaced by a symbolic link to + * an outside directory before the call that opens the directory to read its entries. + * + *

By default, this method throws {@link InsecureRecursiveDeleteException} if it can't + * guarantee the security of recursive deletes. If you wish to allow the recursive deletes anyway, + * pass {@link RecursiveDeleteOption#ALLOW_INSECURE} to this method to override that behavior. + * + * @throws NoSuchFileException if {@code path} does not exist (optional specific exception) + * @throws InsecureRecursiveDeleteException if the security of recursive deletes can't be + * guaranteed for the file system and {@link RecursiveDeleteOption#ALLOW_INSECURE} was not + * specified + * @throws IOException if {@code path} or any file in the subtree rooted at it can't be deleted + * for any reason + */ + public static void deleteRecursively(Path path, RecursiveDeleteOption... options) + throws IOException { + Path parentPath = getParentPath(path); + if (parentPath == null) { + throw new FileSystemException(path.toString(), null, "can't delete recursively"); + } + + Collection exceptions = null; // created lazily if needed + try { + boolean sdsSupported = false; + try (DirectoryStream parent = Files.newDirectoryStream(parentPath)) { + if (parent instanceof SecureDirectoryStream) { + sdsSupported = true; + exceptions = + deleteRecursivelySecure((SecureDirectoryStream) parent, path.getFileName()); + } + } + + if (!sdsSupported) { + checkAllowsInsecure(path, options); + exceptions = deleteRecursivelyInsecure(path); + } + } catch (IOException e) { + if (exceptions == null) { + throw e; + } else { + exceptions.add(e); + } + } + + if (exceptions != null) { + throwDeleteFailed(path, exceptions); + } + } + + /** + * Deletes all files within the directory at the given {@code path} {@linkplain #deleteRecursively + * recursively}. Does not delete the directory itself. Deletes symbolic links, not their targets + * (subject to the caveat below). If {@code path} itself is a symbolic link to a directory, that + * link is followed and the contents of the directory it targets are deleted. + * + *

If an I/O exception occurs attempting to read, open or delete any file under the given + * directory, this method skips that file and continues. All such exceptions are collected and, + * after attempting to delete all files, an {@code IOException} is thrown containing those + * exceptions as {@linkplain Throwable#getSuppressed() suppressed exceptions}. + * + *

Warning: Security of recursive deletes

+ * + *

On a file system that supports symbolic links and does not support {@link + * SecureDirectoryStream}, it is possible for a recursive delete to delete files and directories + * that are outside the directory being deleted. This can happen if, after checking that a + * file is a directory (and not a symbolic link), that directory is replaced by a symbolic link to + * an outside directory before the call that opens the directory to read its entries. + * + *

By default, this method throws {@link InsecureRecursiveDeleteException} if it can't + * guarantee the security of recursive deletes. If you wish to allow the recursive deletes anyway, + * pass {@link RecursiveDeleteOption#ALLOW_INSECURE} to this method to override that behavior. + * + * @throws NoSuchFileException if {@code path} does not exist (optional specific exception) + * @throws NotDirectoryException if the file at {@code path} is not a directory (optional + * specific exception) + * @throws InsecureRecursiveDeleteException if the security of recursive deletes can't be + * guaranteed for the file system and {@link RecursiveDeleteOption#ALLOW_INSECURE} was not + * specified + * @throws IOException if one or more files can't be deleted for any reason + */ + public static void deleteDirectoryContents(Path path, RecursiveDeleteOption... options) + throws IOException { + Collection exceptions = null; // created lazily if needed + try (DirectoryStream stream = Files.newDirectoryStream(path)) { + if (stream instanceof SecureDirectoryStream) { + SecureDirectoryStream sds = (SecureDirectoryStream) stream; + exceptions = deleteDirectoryContentsSecure(sds); + } else { + checkAllowsInsecure(path, options); + exceptions = deleteDirectoryContentsInsecure(stream); + } + } catch (IOException e) { + if (exceptions == null) { + throw e; + } else { + exceptions.add(e); + } + } + + if (exceptions != null) { + throwDeleteFailed(path, exceptions); + } + } + + /** + * Secure recursive delete using {@code SecureDirectoryStream}. Returns a collection of exceptions + * that occurred or null if no exceptions were thrown. + */ + private static Collection deleteRecursivelySecure( + SecureDirectoryStream dir, Path path) { + Collection exceptions = null; + try { + if (isDirectory(dir, path, NOFOLLOW_LINKS)) { + try (SecureDirectoryStream childDir = dir.newDirectoryStream(path, NOFOLLOW_LINKS)) { + exceptions = deleteDirectoryContentsSecure(childDir); + } + + // If exceptions is not null, something went wrong trying to delete the contents of the + // directory, so we shouldn't try to delete the directory as it will probably fail. + if (exceptions == null) { + dir.deleteDirectory(path); + } + } else { + dir.deleteFile(path); + } + + return exceptions; + } catch (IOException e) { + return addException(exceptions, e); + } + } + + /** + * Secure method for deleting the contents of a directory using {@code SecureDirectoryStream}. + * Returns a collection of exceptions that occurred or null if no exceptions were thrown. + */ + private static Collection deleteDirectoryContentsSecure( + SecureDirectoryStream dir) { + Collection exceptions = null; + try { + for (Path path : dir) { + exceptions = concat(exceptions, deleteRecursivelySecure(dir, path.getFileName())); + } + + return exceptions; + } catch (DirectoryIteratorException e) { + return addException(exceptions, e.getCause()); + } + } + + /** + * Insecure recursive delete for file systems that don't support {@code SecureDirectoryStream}. + * Returns a collection of exceptions that occurred or null if no exceptions were thrown. + */ + private static Collection deleteRecursivelyInsecure(Path path) { + Collection exceptions = null; + try { + if (Files.isDirectory(path, NOFOLLOW_LINKS)) { + try (DirectoryStream stream = Files.newDirectoryStream(path)) { + exceptions = deleteDirectoryContentsInsecure(stream); + } + } + + // If exceptions is not null, something went wrong trying to delete the contents of the + // directory, so we shouldn't try to delete the directory as it will probably fail. + if (exceptions == null) { + Files.delete(path); + } + + return exceptions; + } catch (IOException e) { + return addException(exceptions, e); + } + } + + /** + * Simple, insecure method for deleting the contents of a directory for file systems that don't + * support {@code SecureDirectoryStream}. Returns a collection of exceptions that occurred or null + * if no exceptions were thrown. + */ + private static Collection deleteDirectoryContentsInsecure( + DirectoryStream dir) { + Collection exceptions = null; + try { + for (Path entry : dir) { + exceptions = concat(exceptions, deleteRecursivelyInsecure(entry)); + } + + return exceptions; + } catch (DirectoryIteratorException e) { + return addException(exceptions, e.getCause()); + } + } + + /** + * Returns a path to the parent directory of the given path. If the path actually has a parent + * path, this is simple. Otherwise, we need to do some trickier things. Returns null if the path + * is a root or is the empty path. + */ + private static Path getParentPath(Path path) { + Path parent = path.getParent(); + + // Paths that have a parent: + if (parent != null) { + // "/foo" ("/") + // "foo/bar" ("foo") + // "C:\foo" ("C:\") + // "\foo" ("\" - current drive for process on Windows) + // "C:foo" ("C:" - working dir of drive C on Windows) + return parent; + } + + // Paths that don't have a parent: + if (path.getNameCount() == 0) { + // "/", "C:\", "\" (no parent) + // "" (undefined, though typically parent of working dir) + // "C:" (parent of working dir of drive C on Windows) + // + // For working dir paths ("" and "C:"), return null because: + // A) it's not specified that "" is the path to the working directory. + // B) if we're getting this path for recursive delete, it's typically not possible to + // delete the working dir with a relative path anyway, so it's ok to fail. + // C) if we're getting it for opening a new SecureDirectoryStream, there's no need to get + // the parent path anyway since we can safely open a DirectoryStream to the path without + // worrying about a symlink. + return null; + } else { + // "foo" (working dir) + return path.getFileSystem().getPath("."); + } + } + + /** Checks that the given options allow an insecure delete, throwing an exception if not. */ + private static void checkAllowsInsecure(Path path, RecursiveDeleteOption[] options) + throws InsecureRecursiveDeleteException { + if (!Arrays.asList(options).contains(RecursiveDeleteOption.ALLOW_INSECURE)) { + throw new InsecureRecursiveDeleteException(path.toString()); + } + } + + /** + * Adds the given exception to the given collection, creating the collection if it's null. Returns + * the collection. + */ + private static Collection addException( + Collection exceptions, IOException e) { + if (exceptions == null) { + exceptions = new ArrayList<>(); // don't need Set semantics + } + exceptions.add(e); + return exceptions; + } + + /** + * Concatenates the contents of the two given collections of exceptions. If either collection is + * null, the other collection is returned. Otherwise, the elements of {@code other} are added to + * {@code exceptions} and {@code exceptions} is returned. + */ + private static Collection concat( + Collection exceptions, Collection other) { + if (exceptions == null) { + return other; + } else if (other != null) { + exceptions.addAll(other); + } + return exceptions; + } + + /** + * Throws an exception indicating that one or more files couldn't be deleted. The thrown exception + * contains all the exceptions in the given collection as suppressed exceptions. + */ + private static void throwDeleteFailed(Path path, Collection exceptions) + throws FileSystemException { + // TODO(cgdecker): Should there be a custom exception type for this? + // Also, should we try to include the Path of each file we may have failed to delete rather + // than just the exceptions that occurred? + FileSystemException deleteFailed = + new FileSystemException( + path.toString(), + null, + "failed to delete one or more files; see suppressed exceptions for details"); + for (IOException e : exceptions) { + deleteFailed.addSuppressed(e); + } + throw deleteFailed; + } +} diff --git a/src/main/java/com/google/common/io/MultiInputStream.java b/src/main/java/com/google/common/io/MultiInputStream.java new file mode 100644 index 0000000..5b0a039 --- /dev/null +++ b/src/main/java/com/google/common/io/MultiInputStream.java @@ -0,0 +1,118 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.io; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.annotations.GwtIncompatible; +import java.io.IOException; +import java.io.InputStream; +import java.util.Iterator; + + +/** + * An {@link InputStream} that concatenates multiple substreams. At most one stream will be open at + * a time. + * + * @author Chris Nokleberg + * @since 1.0 + */ +@GwtIncompatible +final class MultiInputStream extends InputStream { + + private Iterator it; + private InputStream in; + + /** + * Creates a new instance. + * + * @param it an iterator of I/O suppliers that will provide each substream + */ + public MultiInputStream(Iterator it) throws IOException { + this.it = checkNotNull(it); + advance(); + } + + @Override + public void close() throws IOException { + if (in != null) { + try { + in.close(); + } finally { + in = null; + } + } + } + + /** Closes the current input stream and opens the next one, if any. */ + private void advance() throws IOException { + close(); + if (it.hasNext()) { + in = it.next().openStream(); + } + } + + @Override + public int available() throws IOException { + if (in == null) { + return 0; + } + return in.available(); + } + + @Override + public boolean markSupported() { + return false; + } + + @Override + public int read() throws IOException { + while (in != null) { + int result = in.read(); + if (result != -1) { + return result; + } + advance(); + } + return -1; + } + + @Override + public int read(byte [] b, int off, int len) throws IOException { + while (in != null) { + int result = in.read(b, off, len); + if (result != -1) { + return result; + } + advance(); + } + return -1; + } + + @Override + public long skip(long n) throws IOException { + if (in == null || n <= 0) { + return 0; + } + long result = in.skip(n); + if (result != 0) { + return result; + } + if (read() == -1) { + return 0; + } + return 1 + in.skip(n - 1); + } +} diff --git a/src/main/java/com/google/common/io/MultiReader.java b/src/main/java/com/google/common/io/MultiReader.java new file mode 100644 index 0000000..37d5cd6 --- /dev/null +++ b/src/main/java/com/google/common/io/MultiReader.java @@ -0,0 +1,91 @@ +/* + * Copyright (C) 2008 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.io; + +import com.google.common.annotations.GwtIncompatible; +import com.google.common.base.Preconditions; +import java.io.IOException; +import java.io.Reader; +import java.util.Iterator; + + +/** + * A {@link Reader} that concatenates multiple readers. + * + * @author Bin Zhu + * @since 1.0 + */ +@GwtIncompatible +class MultiReader extends Reader { + private final Iterator it; + private Reader current; + + MultiReader(Iterator readers) throws IOException { + this.it = readers; + advance(); + } + + /** Closes the current reader and opens the next one, if any. */ + private void advance() throws IOException { + close(); + if (it.hasNext()) { + current = it.next().openStream(); + } + } + + @Override + public int read(char [] cbuf, int off, int len) throws IOException { + if (current == null) { + return -1; + } + int result = current.read(cbuf, off, len); + if (result == -1) { + advance(); + return read(cbuf, off, len); + } + return result; + } + + @Override + public long skip(long n) throws IOException { + Preconditions.checkArgument(n >= 0, "n is negative"); + if (n > 0) { + while (current != null) { + long result = current.skip(n); + if (result > 0) { + return result; + } + advance(); + } + } + return 0; + } + + @Override + public boolean ready() throws IOException { + return (current != null) && current.ready(); + } + + @Override + public void close() throws IOException { + if (current != null) { + try { + current.close(); + } finally { + current = null; + } + } + } +} diff --git a/src/main/java/com/google/common/io/PatternFilenameFilter.java b/src/main/java/com/google/common/io/PatternFilenameFilter.java new file mode 100644 index 0000000..2abb2ed --- /dev/null +++ b/src/main/java/com/google/common/io/PatternFilenameFilter.java @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2006 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.io; + +import com.google.common.annotations.Beta; +import com.google.common.annotations.GwtIncompatible; +import com.google.common.base.Preconditions; +import java.io.File; +import java.io.FilenameFilter; +import java.util.regex.Pattern; +import java.util.regex.PatternSyntaxException; + + +/** + * File name filter that only accepts files matching a regular expression. This class is thread-safe + * and immutable. + * + * @author Apple Chow + * @since 1.0 + */ +@Beta +@GwtIncompatible +public final class PatternFilenameFilter implements FilenameFilter { + + private final Pattern pattern; + + /** + * Constructs a pattern file name filter object. + * + * @param patternStr the pattern string on which to filter file names + * @throws PatternSyntaxException if pattern compilation fails (runtime) + */ + public PatternFilenameFilter(String patternStr) { + this(Pattern.compile(patternStr)); + } + + /** + * Constructs a pattern file name filter object. + * + * @param pattern the pattern on which to filter file names + */ + public PatternFilenameFilter(Pattern pattern) { + this.pattern = Preconditions.checkNotNull(pattern); + } + + @Override + public boolean accept(File dir, String fileName) { + return pattern.matcher(fileName).matches(); + } +} diff --git a/src/main/java/com/google/common/io/ReaderInputStream.java b/src/main/java/com/google/common/io/ReaderInputStream.java new file mode 100644 index 0000000..9cbca93 --- /dev/null +++ b/src/main/java/com/google/common/io/ReaderInputStream.java @@ -0,0 +1,255 @@ +/* + * Copyright (C) 2015 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.io; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.base.Preconditions.checkPositionIndexes; + +import com.google.common.annotations.GwtIncompatible; +import com.google.common.primitives.UnsignedBytes; +import java.io.IOException; +import java.io.InputStream; +import java.io.Reader; +import java.nio.Buffer; +import java.nio.ByteBuffer; +import java.nio.CharBuffer; +import java.nio.charset.Charset; +import java.nio.charset.CharsetEncoder; +import java.nio.charset.CoderResult; +import java.nio.charset.CodingErrorAction; +import java.util.Arrays; + +/** + * An {@link InputStream} that converts characters from a {@link Reader} into bytes using an + * arbitrary Charset. + * + *

This is an alternative to copying the data to an {@code OutputStream} via a {@code Writer}, + * which is necessarily blocking. By implementing an {@code InputStream} it allows consumers to + * "pull" as much data as they can handle, which is more convenient when dealing with flow + * controlled, async APIs. + * + * @author Chris Nokleberg + */ +@GwtIncompatible +final class ReaderInputStream extends InputStream { + private final Reader reader; + private final CharsetEncoder encoder; + private final byte[] singleByte = new byte[1]; + + /** + * charBuffer holds characters that have been read from the Reader but not encoded yet. The buffer + * is perpetually "flipped" (unencoded characters between position and limit). + */ + private CharBuffer charBuffer; + + /** + * byteBuffer holds encoded characters that have not yet been sent to the caller of the input + * stream. When encoding it is "unflipped" (encoded bytes between 0 and position) and when + * draining it is flipped (undrained bytes between position and limit). + */ + private ByteBuffer byteBuffer; + + /** Whether we've finished reading the reader. */ + private boolean endOfInput; + /** Whether we're copying encoded bytes to the caller's buffer. */ + private boolean draining; + /** Whether we've successfully flushed the encoder. */ + private boolean doneFlushing; + + /** + * Creates a new input stream that will encode the characters from {@code reader} into bytes using + * the given character set. Malformed input and unmappable characters will be replaced. + * + * @param reader input source + * @param charset character set used for encoding chars to bytes + * @param bufferSize size of internal input and output buffers + * @throws IllegalArgumentException if bufferSize is non-positive + */ + ReaderInputStream(Reader reader, Charset charset, int bufferSize) { + this( + reader, + charset + .newEncoder() + .onMalformedInput(CodingErrorAction.REPLACE) + .onUnmappableCharacter(CodingErrorAction.REPLACE), + bufferSize); + } + + /** + * Creates a new input stream that will encode the characters from {@code reader} into bytes using + * the given character set encoder. + * + * @param reader input source + * @param encoder character set encoder used for encoding chars to bytes + * @param bufferSize size of internal input and output buffers + * @throws IllegalArgumentException if bufferSize is non-positive + */ + ReaderInputStream(Reader reader, CharsetEncoder encoder, int bufferSize) { + this.reader = checkNotNull(reader); + this.encoder = checkNotNull(encoder); + checkArgument(bufferSize > 0, "bufferSize must be positive: %s", bufferSize); + encoder.reset(); + + charBuffer = CharBuffer.allocate(bufferSize); + charBuffer.flip(); + + byteBuffer = ByteBuffer.allocate(bufferSize); + } + + @Override + public void close() throws IOException { + reader.close(); + } + + @Override + public int read() throws IOException { + return (read(singleByte) == 1) ? UnsignedBytes.toInt(singleByte[0]) : -1; + } + + // TODO(chrisn): Consider trying to encode/flush directly to the argument byte + // buffer when possible. + @Override + public int read(byte[] b, int off, int len) throws IOException { + // Obey InputStream contract. + checkPositionIndexes(off, off + len, b.length); + if (len == 0) { + return 0; + } + + // The rest of this method implements the process described by the CharsetEncoder javadoc. + int totalBytesRead = 0; + boolean doneEncoding = endOfInput; + + DRAINING: + while (true) { + // We stay in draining mode until there are no bytes left in the output buffer. Then we go + // back to encoding/flushing. + if (draining) { + totalBytesRead += drain(b, off + totalBytesRead, len - totalBytesRead); + if (totalBytesRead == len || doneFlushing) { + return (totalBytesRead > 0) ? totalBytesRead : -1; + } + draining = false; + byteBuffer.clear(); + } + + while (true) { + // We call encode until there is no more input. The last call to encode will have endOfInput + // == true. Then there is a final call to flush. + CoderResult result; + if (doneFlushing) { + result = CoderResult.UNDERFLOW; + } else if (doneEncoding) { + result = encoder.flush(byteBuffer); + } else { + result = encoder.encode(charBuffer, byteBuffer, endOfInput); + } + + if (result.isOverflow()) { + // Not enough room in output buffer--drain it, creating a bigger buffer if necessary. + startDraining(true); + continue DRAINING; + } else if (result.isUnderflow()) { + // If encoder underflows, it means either: + // a) the final flush() succeeded; next drain (then done) + // b) we encoded all of the input; next flush + // c) we ran of out input to encode; next read more input + if (doneEncoding) { // (a) + doneFlushing = true; + startDraining(false); + continue DRAINING; + } else if (endOfInput) { // (b) + doneEncoding = true; + } else { // (c) + readMoreChars(); + } + } else if (result.isError()) { + // Only reach here if a CharsetEncoder with non-REPLACE settings is used. + result.throwException(); + return 0; // Not called. + } + } + } + } + + /** Returns a new CharBuffer identical to buf, except twice the capacity. */ + private static CharBuffer grow(CharBuffer buf) { + char[] copy = Arrays.copyOf(buf.array(), buf.capacity() * 2); + CharBuffer bigger = CharBuffer.wrap(copy); + bigger.position(buf.position()); + bigger.limit(buf.limit()); + return bigger; + } + + /** Handle the case of underflow caused by needing more input characters. */ + private void readMoreChars() throws IOException { + // Possibilities: + // 1) array has space available on right hand side (between limit and capacity) + // 2) array has space available on left hand side (before position) + // 3) array has no space available + // + // In case 2 we shift the existing chars to the left, and in case 3 we create a bigger + // array, then they both become case 1. + + if (availableCapacity(charBuffer) == 0) { + if (charBuffer.position() > 0) { + // (2) There is room in the buffer. Move existing bytes to the beginning. + charBuffer.compact().flip(); + } else { + // (3) Entire buffer is full, need bigger buffer. + charBuffer = grow(charBuffer); + } + } + + // (1) Read more characters into free space at end of array. + int limit = charBuffer.limit(); + int numChars = reader.read(charBuffer.array(), limit, availableCapacity(charBuffer)); + if (numChars == -1) { + endOfInput = true; + } else { + charBuffer.limit(limit + numChars); + } + } + + /** Returns the number of elements between the limit and capacity. */ + private static int availableCapacity(Buffer buffer) { + return buffer.capacity() - buffer.limit(); + } + + /** + * Flips the buffer output buffer so we can start reading bytes from it. If we are starting to + * drain because there was overflow, and there aren't actually any characters to drain, then the + * overflow must be due to a small output buffer. + */ + private void startDraining(boolean overflow) { + byteBuffer.flip(); + if (overflow && byteBuffer.remaining() == 0) { + byteBuffer = ByteBuffer.allocate(byteBuffer.capacity() * 2); + } else { + draining = true; + } + } + + /** + * Copy as much of the byte buffer into the output array as possible, returning the (positive) + * number of characters copied. + */ + private int drain(byte[] b, int off, int len) { + int remaining = Math.min(len, byteBuffer.remaining()); + byteBuffer.get(b, off, remaining); + return remaining; + } +} diff --git a/src/main/java/com/google/common/io/RecursiveDeleteOption.java b/src/main/java/com/google/common/io/RecursiveDeleteOption.java new file mode 100644 index 0000000..43e7e5b --- /dev/null +++ b/src/main/java/com/google/common/io/RecursiveDeleteOption.java @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2014 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.io; + +import com.google.common.annotations.Beta; +import com.google.common.annotations.GwtIncompatible; + +import java.nio.file.SecureDirectoryStream; + +/** + * Options for use with recursive delete methods ({@link MoreFiles#deleteRecursively} and {@link + * MoreFiles#deleteDirectoryContents}). + * + * @since 21.0 + * @author Colin Decker + */ +@Beta +@GwtIncompatible + // java.nio.file +public enum RecursiveDeleteOption { + /** + * Specifies that the recursive delete should not throw an exception when it can't be guaranteed + * that it can be done securely, without vulnerability to race conditions (i.e. when the file + * system does not support {@link SecureDirectoryStream}). + * + *

Warning: On a file system that supports symbolic links, it is possible for an + * insecure recursive delete to delete files and directories that are outside the directory + * being deleted. This can happen if, after checking that a file is a directory (and not a + * symbolic link), that directory is deleted and replaced by a symbolic link to an outside + * directory before the call that opens the directory to read its entries. File systems that + * support {@code SecureDirectoryStream} do not have this vulnerability. + */ + ALLOW_INSECURE +} diff --git a/src/main/java/com/google/common/io/Resources.java b/src/main/java/com/google/common/io/Resources.java new file mode 100644 index 0000000..4e67203 --- /dev/null +++ b/src/main/java/com/google/common/io/Resources.java @@ -0,0 +1,211 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.io; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.annotations.Beta; +import com.google.common.annotations.GwtIncompatible; +import com.google.common.base.Charsets; +import com.google.common.base.MoreObjects; +import com.google.common.collect.Lists; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.URL; +import java.nio.charset.Charset; +import java.util.List; + +/** + * Provides utility methods for working with resources in the classpath. Note that even though these + * methods use {@link URL} parameters, they are usually not appropriate for HTTP or other + * non-classpath resources. + * + *

All method parameters must be non-null unless documented otherwise. + * + * @author Chris Nokleberg + * @author Ben Yu + * @author Colin Decker + * @since 1.0 + */ +@Beta +@GwtIncompatible +public final class Resources { + private Resources() {} + + /** + * Returns a {@link ByteSource} that reads from the given URL. + * + * @since 14.0 + */ + public static ByteSource asByteSource(URL url) { + return new UrlByteSource(url); + } + + /** A byte source that reads from a URL using {@link URL#openStream()}. */ + private static final class UrlByteSource extends ByteSource { + + private final URL url; + + private UrlByteSource(URL url) { + this.url = checkNotNull(url); + } + + @Override + public InputStream openStream() throws IOException { + return url.openStream(); + } + + @Override + public String toString() { + return "Resources.asByteSource(" + url + ")"; + } + } + + /** + * Returns a {@link CharSource} that reads from the given URL using the given character set. + * + * @since 14.0 + */ + public static CharSource asCharSource(URL url, Charset charset) { + return asByteSource(url).asCharSource(charset); + } + + /** + * Reads all bytes from a URL into a byte array. + * + * @param url the URL to read from + * @return a byte array containing all the bytes from the URL + * @throws IOException if an I/O error occurs + */ + public static byte[] toByteArray(URL url) throws IOException { + return asByteSource(url).read(); + } + + /** + * Reads all characters from a URL into a {@link String}, using the given character set. + * + * @param url the URL to read from + * @param charset the charset used to decode the input stream; see {@link Charsets} for helpful + * predefined constants + * @return a string containing all the characters from the URL + * @throws IOException if an I/O error occurs. + */ + public static String toString(URL url, Charset charset) throws IOException { + return asCharSource(url, charset).read(); + } + + /** + * Streams lines from a URL, stopping when our callback returns false, or we have read all of the + * lines. + * + * @param url the URL to read from + * @param charset the charset used to decode the input stream; see {@link Charsets} for helpful + * predefined constants + * @param callback the LineProcessor to use to handle the lines + * @return the output of processing the lines + * @throws IOException if an I/O error occurs + */ + // some processors won't return a useful result + public static T readLines(URL url, Charset charset, LineProcessor callback) + throws IOException { + return asCharSource(url, charset).readLines(callback); + } + + /** + * Reads all of the lines from a URL. The lines do not include line-termination characters, but do + * include other leading and trailing whitespace. + * + *

This method returns a mutable {@code List}. For an {@code ImmutableList}, use {@code + * Resources.asCharSource(url, charset).readLines()}. + * + * @param url the URL to read from + * @param charset the charset used to decode the input stream; see {@link Charsets} for helpful + * predefined constants + * @return a mutable {@link List} containing all the lines + * @throws IOException if an I/O error occurs + */ + public static List readLines(URL url, Charset charset) throws IOException { + // don't use asCharSource(url, charset).readLines() because that returns + // an immutable list, which would change the behavior of this method + return readLines( + url, + charset, + new LineProcessor>() { + final List result = Lists.newArrayList(); + + @Override + public boolean processLine(String line) { + result.add(line); + return true; + } + + @Override + public List getResult() { + return result; + } + }); + } + + /** + * Copies all bytes from a URL to an output stream. + * + * @param from the URL to read from + * @param to the output stream + * @throws IOException if an I/O error occurs + */ + public static void copy(URL from, OutputStream to) throws IOException { + asByteSource(from).copyTo(to); + } + + /** + * Returns a {@code URL} pointing to {@code resourceName} if the resource is found using the + * {@linkplain Thread#getContextClassLoader() context class loader}. In simple environments, the + * context class loader will find resources from the class path. In environments where different + * threads can have different class loaders, for example app servers, the context class loader + * will typically have been set to an appropriate loader for the current thread. + * + *

In the unusual case where the context class loader is null, the class loader that loaded + * this class ({@code Resources}) will be used instead. + * + * @throws IllegalArgumentException if the resource is not found + */ + // being used to check if a resource exists + // TODO(cgdecker): maybe add a better way to check if a resource exists + // e.g. Optional tryGetResource or boolean resourceExists + public static URL getResource(String resourceName) { + ClassLoader loader = + MoreObjects.firstNonNull( + Thread.currentThread().getContextClassLoader(), Resources.class.getClassLoader()); + URL url = loader.getResource(resourceName); + checkArgument(url != null, "resource %s not found.", resourceName); + return url; + } + + /** + * Given a {@code resourceName} that is relative to {@code contextClass}, returns a {@code URL} + * pointing to the named resource. + * + * @throws IllegalArgumentException if the resource is not found + */ + public static URL getResource(Class contextClass, String resourceName) { + URL url = contextClass.getResource(resourceName); + checkArgument( + url != null, "resource %s relative to %s not found.", resourceName, contextClass.getName()); + return url; + } +} diff --git a/src/main/java/com/google/common/math/BigIntegerMath.java b/src/main/java/com/google/common/math/BigIntegerMath.java new file mode 100644 index 0000000..b0c0766 --- /dev/null +++ b/src/main/java/com/google/common/math/BigIntegerMath.java @@ -0,0 +1,474 @@ +/* + * Copyright (C) 2011 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.math; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.math.MathPreconditions.checkNonNegative; +import static com.google.common.math.MathPreconditions.checkPositive; +import static com.google.common.math.MathPreconditions.checkRoundingUnnecessary; +import static java.math.RoundingMode.CEILING; +import static java.math.RoundingMode.FLOOR; +import static java.math.RoundingMode.HALF_EVEN; + +import com.google.common.annotations.Beta; +import com.google.common.annotations.GwtCompatible; +import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.VisibleForTesting; +import java.math.BigDecimal; +import java.math.BigInteger; +import java.math.RoundingMode; +import java.util.ArrayList; +import java.util.List; + +/** + * A class for arithmetic on values of type {@code BigInteger}. + * + *

The implementations of many methods in this class are based on material from Henry S. Warren, + * Jr.'s Hacker's Delight, (Addison Wesley, 2002). + * + *

Similar functionality for {@code int} and for {@code long} can be found in {@link IntMath} and + * {@link LongMath} respectively. + * + * @author Louis Wasserman + * @since 11.0 + */ +@GwtCompatible(emulated = true) +public final class BigIntegerMath { + /** + * Returns the smallest power of two greater than or equal to {@code x}. This is equivalent to + * {@code BigInteger.valueOf(2).pow(log2(x, CEILING))}. + * + * @throws IllegalArgumentException if {@code x <= 0} + * @since 20.0 + */ + @Beta + public static BigInteger ceilingPowerOfTwo(BigInteger x) { + return BigInteger.ZERO.setBit(log2(x, RoundingMode.CEILING)); + } + + /** + * Returns the largest power of two less than or equal to {@code x}. This is equivalent to {@code + * BigInteger.valueOf(2).pow(log2(x, FLOOR))}. + * + * @throws IllegalArgumentException if {@code x <= 0} + * @since 20.0 + */ + @Beta + public static BigInteger floorPowerOfTwo(BigInteger x) { + return BigInteger.ZERO.setBit(log2(x, RoundingMode.FLOOR)); + } + + /** Returns {@code true} if {@code x} represents a power of two. */ + public static boolean isPowerOfTwo(BigInteger x) { + checkNotNull(x); + return x.signum() > 0 && x.getLowestSetBit() == x.bitLength() - 1; + } + + /** + * Returns the base-2 logarithm of {@code x}, rounded according to the specified rounding mode. + * + * @throws IllegalArgumentException if {@code x <= 0} + * @throws ArithmeticException if {@code mode} is {@link RoundingMode#UNNECESSARY} and {@code x} + * is not a power of two + */ + @SuppressWarnings("fallthrough") + // TODO(kevinb): remove after this warning is disabled globally + public static int log2(BigInteger x, RoundingMode mode) { + checkPositive("x", checkNotNull(x)); + int logFloor = x.bitLength() - 1; + switch (mode) { + case UNNECESSARY: + checkRoundingUnnecessary(isPowerOfTwo(x)); // fall through + case DOWN: + case FLOOR: + return logFloor; + + case UP: + case CEILING: + return isPowerOfTwo(x) ? logFloor : logFloor + 1; + + case HALF_DOWN: + case HALF_UP: + case HALF_EVEN: + if (logFloor < SQRT2_PRECOMPUTE_THRESHOLD) { + BigInteger halfPower = + SQRT2_PRECOMPUTED_BITS.shiftRight(SQRT2_PRECOMPUTE_THRESHOLD - logFloor); + if (x.compareTo(halfPower) <= 0) { + return logFloor; + } else { + return logFloor + 1; + } + } + // Since sqrt(2) is irrational, log2(x) - logFloor cannot be exactly 0.5 + // + // To determine which side of logFloor.5 the logarithm is, + // we compare x^2 to 2^(2 * logFloor + 1). + BigInteger x2 = x.pow(2); + int logX2Floor = x2.bitLength() - 1; + return (logX2Floor < 2 * logFloor + 1) ? logFloor : logFloor + 1; + + default: + throw new AssertionError(); + } + } + + /* + * The maximum number of bits in a square root for which we'll precompute an explicit half power + * of two. This can be any value, but higher values incur more class load time and linearly + * increasing memory consumption. + */ + @VisibleForTesting static final int SQRT2_PRECOMPUTE_THRESHOLD = 256; + + @VisibleForTesting + static final BigInteger SQRT2_PRECOMPUTED_BITS = + new BigInteger("16a09e667f3bcc908b2fb1366ea957d3e3adec17512775099da2f590b0667322a", 16); + + /** + * Returns the base-10 logarithm of {@code x}, rounded according to the specified rounding mode. + * + * @throws IllegalArgumentException if {@code x <= 0} + * @throws ArithmeticException if {@code mode} is {@link RoundingMode#UNNECESSARY} and {@code x} + * is not a power of ten + */ + @GwtIncompatible // TODO + @SuppressWarnings("fallthrough") + public static int log10(BigInteger x, RoundingMode mode) { + checkPositive("x", x); + if (fitsInLong(x)) { + return LongMath.log10(x.longValue(), mode); + } + + int approxLog10 = (int) (log2(x, FLOOR) * LN_2 / LN_10); + BigInteger approxPow = BigInteger.TEN.pow(approxLog10); + int approxCmp = approxPow.compareTo(x); + + /* + * We adjust approxLog10 and approxPow until they're equal to floor(log10(x)) and + * 10^floor(log10(x)). + */ + + if (approxCmp > 0) { + /* + * The code is written so that even completely incorrect approximations will still yield the + * correct answer eventually, but in practice this branch should almost never be entered, and + * even then the loop should not run more than once. + */ + do { + approxLog10--; + approxPow = approxPow.divide(BigInteger.TEN); + approxCmp = approxPow.compareTo(x); + } while (approxCmp > 0); + } else { + BigInteger nextPow = BigInteger.TEN.multiply(approxPow); + int nextCmp = nextPow.compareTo(x); + while (nextCmp <= 0) { + approxLog10++; + approxPow = nextPow; + approxCmp = nextCmp; + nextPow = BigInteger.TEN.multiply(approxPow); + nextCmp = nextPow.compareTo(x); + } + } + + int floorLog = approxLog10; + BigInteger floorPow = approxPow; + int floorCmp = approxCmp; + + switch (mode) { + case UNNECESSARY: + checkRoundingUnnecessary(floorCmp == 0); + // fall through + case FLOOR: + case DOWN: + return floorLog; + + case CEILING: + case UP: + return floorPow.equals(x) ? floorLog : floorLog + 1; + + case HALF_DOWN: + case HALF_UP: + case HALF_EVEN: + // Since sqrt(10) is irrational, log10(x) - floorLog can never be exactly 0.5 + BigInteger x2 = x.pow(2); + BigInteger halfPowerSquared = floorPow.pow(2).multiply(BigInteger.TEN); + return (x2.compareTo(halfPowerSquared) <= 0) ? floorLog : floorLog + 1; + default: + throw new AssertionError(); + } + } + + private static final double LN_10 = Math.log(10); + private static final double LN_2 = Math.log(2); + + /** + * Returns the square root of {@code x}, rounded with the specified rounding mode. + * + * @throws IllegalArgumentException if {@code x < 0} + * @throws ArithmeticException if {@code mode} is {@link RoundingMode#UNNECESSARY} and {@code + * sqrt(x)} is not an integer + */ + @GwtIncompatible // TODO + @SuppressWarnings("fallthrough") + public static BigInteger sqrt(BigInteger x, RoundingMode mode) { + checkNonNegative("x", x); + if (fitsInLong(x)) { + return BigInteger.valueOf(LongMath.sqrt(x.longValue(), mode)); + } + BigInteger sqrtFloor = sqrtFloor(x); + switch (mode) { + case UNNECESSARY: + checkRoundingUnnecessary(sqrtFloor.pow(2).equals(x)); // fall through + case FLOOR: + case DOWN: + return sqrtFloor; + case CEILING: + case UP: + int sqrtFloorInt = sqrtFloor.intValue(); + boolean sqrtFloorIsExact = + (sqrtFloorInt * sqrtFloorInt == x.intValue()) // fast check mod 2^32 + && sqrtFloor.pow(2).equals(x); // slow exact check + return sqrtFloorIsExact ? sqrtFloor : sqrtFloor.add(BigInteger.ONE); + case HALF_DOWN: + case HALF_UP: + case HALF_EVEN: + BigInteger halfSquare = sqrtFloor.pow(2).add(sqrtFloor); + /* + * We wish to test whether or not x <= (sqrtFloor + 0.5)^2 = halfSquare + 0.25. Since both x + * and halfSquare are integers, this is equivalent to testing whether or not x <= + * halfSquare. + */ + return (halfSquare.compareTo(x) >= 0) ? sqrtFloor : sqrtFloor.add(BigInteger.ONE); + default: + throw new AssertionError(); + } + } + + @GwtIncompatible // TODO + private static BigInteger sqrtFloor(BigInteger x) { + /* + * Adapted from Hacker's Delight, Figure 11-1. + * + * Using DoubleUtils.bigToDouble, getting a double approximation of x is extremely fast, and + * then we can get a double approximation of the square root. Then, we iteratively improve this + * guess with an application of Newton's method, which sets guess := (guess + (x / guess)) / 2. + * This iteration has the following two properties: + * + * a) every iteration (except potentially the first) has guess >= floor(sqrt(x)). This is + * because guess' is the arithmetic mean of guess and x / guess, sqrt(x) is the geometric mean, + * and the arithmetic mean is always higher than the geometric mean. + * + * b) this iteration converges to floor(sqrt(x)). In fact, the number of correct digits doubles + * with each iteration, so this algorithm takes O(log(digits)) iterations. + * + * We start out with a double-precision approximation, which may be higher or lower than the + * true value. Therefore, we perform at least one Newton iteration to get a guess that's + * definitely >= floor(sqrt(x)), and then continue the iteration until we reach a fixed point. + */ + BigInteger sqrt0; + int log2 = log2(x, FLOOR); + if (log2 < Double.MAX_EXPONENT) { + sqrt0 = sqrtApproxWithDoubles(x); + } else { + int shift = (log2 - DoubleUtils.SIGNIFICAND_BITS) & ~1; // even! + /* + * We have that x / 2^shift < 2^54. Our initial approximation to sqrtFloor(x) will be + * 2^(shift/2) * sqrtApproxWithDoubles(x / 2^shift). + */ + sqrt0 = sqrtApproxWithDoubles(x.shiftRight(shift)).shiftLeft(shift >> 1); + } + BigInteger sqrt1 = sqrt0.add(x.divide(sqrt0)).shiftRight(1); + if (sqrt0.equals(sqrt1)) { + return sqrt0; + } + do { + sqrt0 = sqrt1; + sqrt1 = sqrt0.add(x.divide(sqrt0)).shiftRight(1); + } while (sqrt1.compareTo(sqrt0) < 0); + return sqrt0; + } + + @GwtIncompatible // TODO + private static BigInteger sqrtApproxWithDoubles(BigInteger x) { + return DoubleMath.roundToBigInteger(Math.sqrt(DoubleUtils.bigToDouble(x)), HALF_EVEN); + } + + /** + * Returns the result of dividing {@code p} by {@code q}, rounding using the specified {@code + * RoundingMode}. + * + * @throws ArithmeticException if {@code q == 0}, or if {@code mode == UNNECESSARY} and {@code a} + * is not an integer multiple of {@code b} + */ + @GwtIncompatible // TODO + public static BigInteger divide(BigInteger p, BigInteger q, RoundingMode mode) { + BigDecimal pDec = new BigDecimal(p); + BigDecimal qDec = new BigDecimal(q); + return pDec.divide(qDec, 0, mode).toBigIntegerExact(); + } + + /** + * Returns {@code n!}, that is, the product of the first {@code n} positive integers, or {@code 1} + * if {@code n == 0}. + * + *

Warning: the result takes O(n log n) space, so use cautiously. + * + *

This uses an efficient binary recursive algorithm to compute the factorial with balanced + * multiplies. It also removes all the 2s from the intermediate products (shifting them back in at + * the end). + * + * @throws IllegalArgumentException if {@code n < 0} + */ + public static BigInteger factorial(int n) { + checkNonNegative("n", n); + + // If the factorial is small enough, just use LongMath to do it. + if (n < LongMath.factorials.length) { + return BigInteger.valueOf(LongMath.factorials[n]); + } + + // Pre-allocate space for our list of intermediate BigIntegers. + int approxSize = IntMath.divide(n * IntMath.log2(n, CEILING), Long.SIZE, CEILING); + ArrayList bignums = new ArrayList<>(approxSize); + + // Start from the pre-computed maximum long factorial. + int startingNumber = LongMath.factorials.length; + long product = LongMath.factorials[startingNumber - 1]; + // Strip off 2s from this value. + int shift = Long.numberOfTrailingZeros(product); + product >>= shift; + + // Use floor(log2(num)) + 1 to prevent overflow of multiplication. + int productBits = LongMath.log2(product, FLOOR) + 1; + int bits = LongMath.log2(startingNumber, FLOOR) + 1; + // Check for the next power of two boundary, to save us a CLZ operation. + int nextPowerOfTwo = 1 << (bits - 1); + + // Iteratively multiply the longs as big as they can go. + for (long num = startingNumber; num <= n; num++) { + // Check to see if the floor(log2(num)) + 1 has changed. + if ((num & nextPowerOfTwo) != 0) { + nextPowerOfTwo <<= 1; + bits++; + } + // Get rid of the 2s in num. + int tz = Long.numberOfTrailingZeros(num); + long normalizedNum = num >> tz; + shift += tz; + // Adjust floor(log2(num)) + 1. + int normalizedBits = bits - tz; + // If it won't fit in a long, then we store off the intermediate product. + if (normalizedBits + productBits >= Long.SIZE) { + bignums.add(BigInteger.valueOf(product)); + product = 1; + productBits = 0; + } + product *= normalizedNum; + productBits = LongMath.log2(product, FLOOR) + 1; + } + // Check for leftovers. + if (product > 1) { + bignums.add(BigInteger.valueOf(product)); + } + // Efficiently multiply all the intermediate products together. + return listProduct(bignums).shiftLeft(shift); + } + + static BigInteger listProduct(List nums) { + return listProduct(nums, 0, nums.size()); + } + + static BigInteger listProduct(List nums, int start, int end) { + switch (end - start) { + case 0: + return BigInteger.ONE; + case 1: + return nums.get(start); + case 2: + return nums.get(start).multiply(nums.get(start + 1)); + case 3: + return nums.get(start).multiply(nums.get(start + 1)).multiply(nums.get(start + 2)); + default: + // Otherwise, split the list in half and recursively do this. + int m = (end + start) >>> 1; + return listProduct(nums, start, m).multiply(listProduct(nums, m, end)); + } + } + + /** + * Returns {@code n} choose {@code k}, also known as the binomial coefficient of {@code n} and + * {@code k}, that is, {@code n! / (k! (n - k)!)}. + * + *

Warning: the result can take as much as O(k log n) space. + * + * @throws IllegalArgumentException if {@code n < 0}, {@code k < 0}, or {@code k > n} + */ + public static BigInteger binomial(int n, int k) { + checkNonNegative("n", n); + checkNonNegative("k", k); + checkArgument(k <= n, "k (%s) > n (%s)", k, n); + if (k > (n >> 1)) { + k = n - k; + } + if (k < LongMath.biggestBinomials.length && n <= LongMath.biggestBinomials[k]) { + return BigInteger.valueOf(LongMath.binomial(n, k)); + } + + BigInteger accum = BigInteger.ONE; + + long numeratorAccum = n; + long denominatorAccum = 1; + + int bits = LongMath.log2(n, RoundingMode.CEILING); + + int numeratorBits = bits; + + for (int i = 1; i < k; i++) { + int p = n - i; + int q = i + 1; + + // log2(p) >= bits - 1, because p >= n/2 + + if (numeratorBits + bits >= Long.SIZE - 1) { + // The numerator is as big as it can get without risking overflow. + // Multiply numeratorAccum / denominatorAccum into accum. + accum = + accum + .multiply(BigInteger.valueOf(numeratorAccum)) + .divide(BigInteger.valueOf(denominatorAccum)); + numeratorAccum = p; + denominatorAccum = q; + numeratorBits = bits; + } else { + // We can definitely multiply into the long accumulators without overflowing them. + numeratorAccum *= p; + denominatorAccum *= q; + numeratorBits += bits; + } + } + return accum + .multiply(BigInteger.valueOf(numeratorAccum)) + .divide(BigInteger.valueOf(denominatorAccum)); + } + + // Returns true if BigInteger.valueOf(x.longValue()).equals(x). + @GwtIncompatible // TODO + static boolean fitsInLong(BigInteger x) { + return x.bitLength() <= Long.SIZE - 1; + } + + private BigIntegerMath() {} +} diff --git a/src/main/java/com/google/common/math/DoubleMath.java b/src/main/java/com/google/common/math/DoubleMath.java new file mode 100644 index 0000000..1110533 --- /dev/null +++ b/src/main/java/com/google/common/math/DoubleMath.java @@ -0,0 +1,528 @@ +/* + * Copyright (C) 2011 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.math; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.math.DoubleUtils.IMPLICIT_BIT; +import static com.google.common.math.DoubleUtils.SIGNIFICAND_BITS; +import static com.google.common.math.DoubleUtils.getSignificand; +import static com.google.common.math.DoubleUtils.isFinite; +import static com.google.common.math.DoubleUtils.isNormal; +import static com.google.common.math.DoubleUtils.scaleNormalize; +import static com.google.common.math.MathPreconditions.checkInRangeForRoundingInputs; +import static com.google.common.math.MathPreconditions.checkNonNegative; +import static com.google.common.math.MathPreconditions.checkRoundingUnnecessary; +import static java.lang.Math.abs; +import static java.lang.Math.copySign; +import static java.lang.Math.getExponent; +import static java.lang.Math.log; +import static java.lang.Math.rint; + +import com.google.common.annotations.GwtCompatible; +import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.VisibleForTesting; +import com.google.common.primitives.Booleans; + +import java.math.BigInteger; +import java.math.RoundingMode; +import java.util.Iterator; + +/** + * A class for arithmetic on doubles that is not covered by {@link java.lang.Math}. + * + * @author Louis Wasserman + * @since 11.0 + */ +@GwtCompatible(emulated = true) +public final class DoubleMath { + /* + * This method returns a value y such that rounding y DOWN (towards zero) gives the same result as + * rounding x according to the specified mode. + */ + @GwtIncompatible // #isMathematicalInteger, com.google.common.math.DoubleUtils + static double roundIntermediate(double x, RoundingMode mode) { + if (!isFinite(x)) { + throw new ArithmeticException("input is infinite or NaN"); + } + switch (mode) { + case UNNECESSARY: + checkRoundingUnnecessary(isMathematicalInteger(x)); + return x; + + case FLOOR: + if (x >= 0.0 || isMathematicalInteger(x)) { + return x; + } else { + return (long) x - 1; + } + + case CEILING: + if (x <= 0.0 || isMathematicalInteger(x)) { + return x; + } else { + return (long) x + 1; + } + + case DOWN: + return x; + + case UP: + if (isMathematicalInteger(x)) { + return x; + } else { + return (long) x + (x > 0 ? 1 : -1); + } + + case HALF_EVEN: + return rint(x); + + case HALF_UP: + { + double z = rint(x); + if (abs(x - z) == 0.5) { + return x + copySign(0.5, x); + } else { + return z; + } + } + + case HALF_DOWN: + { + double z = rint(x); + if (abs(x - z) == 0.5) { + return x; + } else { + return z; + } + } + + default: + throw new AssertionError(); + } + } + + /** + * Returns the {@code int} value that is equal to {@code x} rounded with the specified rounding + * mode, if possible. + * + * @throws ArithmeticException if + *

    + *
  • {@code x} is infinite or NaN + *
  • {@code x}, after being rounded to a mathematical integer using the specified rounding + * mode, is either less than {@code Integer.MIN_VALUE} or greater than {@code + * Integer.MAX_VALUE} + *
  • {@code x} is not a mathematical integer and {@code mode} is {@link + * RoundingMode#UNNECESSARY} + *
+ */ + @GwtIncompatible // #roundIntermediate + public static int roundToInt(double x, RoundingMode mode) { + double z = roundIntermediate(x, mode); + checkInRangeForRoundingInputs( + z > MIN_INT_AS_DOUBLE - 1.0 & z < MAX_INT_AS_DOUBLE + 1.0, x, mode); + return (int) z; + } + + private static final double MIN_INT_AS_DOUBLE = -0x1p31; + private static final double MAX_INT_AS_DOUBLE = 0x1p31 - 1.0; + + /** + * Returns the {@code long} value that is equal to {@code x} rounded with the specified rounding + * mode, if possible. + * + * @throws ArithmeticException if + *
    + *
  • {@code x} is infinite or NaN + *
  • {@code x}, after being rounded to a mathematical integer using the specified rounding + * mode, is either less than {@code Long.MIN_VALUE} or greater than {@code + * Long.MAX_VALUE} + *
  • {@code x} is not a mathematical integer and {@code mode} is {@link + * RoundingMode#UNNECESSARY} + *
+ */ + @GwtIncompatible // #roundIntermediate + public static long roundToLong(double x, RoundingMode mode) { + double z = roundIntermediate(x, mode); + checkInRangeForRoundingInputs( + MIN_LONG_AS_DOUBLE - z < 1.0 & z < MAX_LONG_AS_DOUBLE_PLUS_ONE, x, mode); + return (long) z; + } + + private static final double MIN_LONG_AS_DOUBLE = -0x1p63; + /* + * We cannot store Long.MAX_VALUE as a double without losing precision. Instead, we store + * Long.MAX_VALUE + 1 == -Long.MIN_VALUE, and then offset all comparisons by 1. + */ + private static final double MAX_LONG_AS_DOUBLE_PLUS_ONE = 0x1p63; + + /** + * Returns the {@code BigInteger} value that is equal to {@code x} rounded with the specified + * rounding mode, if possible. + * + * @throws ArithmeticException if + *
    + *
  • {@code x} is infinite or NaN + *
  • {@code x} is not a mathematical integer and {@code mode} is {@link + * RoundingMode#UNNECESSARY} + *
+ */ + // #roundIntermediate, java.lang.Math.getExponent, com.google.common.math.DoubleUtils + @GwtIncompatible + public static BigInteger roundToBigInteger(double x, RoundingMode mode) { + x = roundIntermediate(x, mode); + if (MIN_LONG_AS_DOUBLE - x < 1.0 & x < MAX_LONG_AS_DOUBLE_PLUS_ONE) { + return BigInteger.valueOf((long) x); + } + int exponent = getExponent(x); + long significand = getSignificand(x); + BigInteger result = BigInteger.valueOf(significand).shiftLeft(exponent - SIGNIFICAND_BITS); + return (x < 0) ? result.negate() : result; + } + + /** + * Returns {@code true} if {@code x} is exactly equal to {@code 2^k} for some finite integer + * {@code k}. + */ + @GwtIncompatible // com.google.common.math.DoubleUtils + public static boolean isPowerOfTwo(double x) { + if (x > 0.0 && isFinite(x)) { + long significand = getSignificand(x); + return (significand & (significand - 1)) == 0; + } + return false; + } + + /** + * Returns the base 2 logarithm of a double value. + * + *

Special cases: + * + *

    + *
  • If {@code x} is NaN or less than zero, the result is NaN. + *
  • If {@code x} is positive infinity, the result is positive infinity. + *
  • If {@code x} is positive or negative zero, the result is negative infinity. + *
+ * + *

The computed result is within 1 ulp of the exact result. + * + *

If the result of this method will be immediately rounded to an {@code int}, {@link + * #log2(double, RoundingMode)} is faster. + */ + public static double log2(double x) { + return log(x) / LN_2; // surprisingly within 1 ulp according to tests + } + + /** + * Returns the base 2 logarithm of a double value, rounded with the specified rounding mode to an + * {@code int}. + * + *

Regardless of the rounding mode, this is faster than {@code (int) log2(x)}. + * + * @throws IllegalArgumentException if {@code x <= 0.0}, {@code x} is NaN, or {@code x} is + * infinite + */ + @GwtIncompatible // java.lang.Math.getExponent, com.google.common.math.DoubleUtils + @SuppressWarnings("fallthrough") + public static int log2(double x, RoundingMode mode) { + checkArgument(x > 0.0 && isFinite(x), "x must be positive and finite"); + int exponent = getExponent(x); + if (!isNormal(x)) { + return log2(x * IMPLICIT_BIT, mode) - SIGNIFICAND_BITS; + // Do the calculation on a normal value. + } + // x is positive, finite, and normal + boolean increment; + switch (mode) { + case UNNECESSARY: + checkRoundingUnnecessary(isPowerOfTwo(x)); + // fall through + case FLOOR: + increment = false; + break; + case CEILING: + increment = !isPowerOfTwo(x); + break; + case DOWN: + increment = exponent < 0 & !isPowerOfTwo(x); + break; + case UP: + increment = exponent >= 0 & !isPowerOfTwo(x); + break; + case HALF_DOWN: + case HALF_EVEN: + case HALF_UP: + double xScaled = scaleNormalize(x); + // sqrt(2) is irrational, and the spec is relative to the "exact numerical result," + // so log2(x) is never exactly exponent + 0.5. + increment = (xScaled * xScaled) > 2.0; + break; + default: + throw new AssertionError(); + } + return increment ? exponent + 1 : exponent; + } + + private static final double LN_2 = log(2); + + /** + * Returns {@code true} if {@code x} represents a mathematical integer. + * + *

This is equivalent to, but not necessarily implemented as, the expression {@code + * !Double.isNaN(x) && !Double.isInfinite(x) && x == Math.rint(x)}. + */ + @GwtIncompatible // java.lang.Math.getExponent, com.google.common.math.DoubleUtils + public static boolean isMathematicalInteger(double x) { + return isFinite(x) + && (x == 0.0 + || SIGNIFICAND_BITS - Long.numberOfTrailingZeros(getSignificand(x)) <= getExponent(x)); + } + + /** + * Returns {@code n!}, that is, the product of the first {@code n} positive integers, {@code 1} if + * {@code n == 0}, or {@code n!}, or {@link Double#POSITIVE_INFINITY} if {@code n! > + * Double.MAX_VALUE}. + * + *

The result is within 1 ulp of the true value. + * + * @throws IllegalArgumentException if {@code n < 0} + */ + public static double factorial(int n) { + checkNonNegative("n", n); + if (n > MAX_FACTORIAL) { + return Double.POSITIVE_INFINITY; + } else { + // Multiplying the last (n & 0xf) values into their own accumulator gives a more accurate + // result than multiplying by everySixteenthFactorial[n >> 4] directly. + double accum = 1.0; + for (int i = 1 + (n & ~0xf); i <= n; i++) { + accum *= i; + } + return accum * everySixteenthFactorial[n >> 4]; + } + } + + @VisibleForTesting static final int MAX_FACTORIAL = 170; + + @VisibleForTesting + static final double[] everySixteenthFactorial = { + 0x1.0p0, + 0x1.30777758p44, + 0x1.956ad0aae33a4p117, + 0x1.ee69a78d72cb6p202, + 0x1.fe478ee34844ap295, + 0x1.c619094edabffp394, + 0x1.3638dd7bd6347p498, + 0x1.7cac197cfe503p605, + 0x1.1e5dfc140e1e5p716, + 0x1.8ce85fadb707ep829, + 0x1.95d5f3d928edep945 + }; + + /** + * Returns {@code true} if {@code a} and {@code b} are within {@code tolerance} of each other. + * + *

Technically speaking, this is equivalent to {@code Math.abs(a - b) <= tolerance || + * Double.valueOf(a).equals(Double.valueOf(b))}. + * + *

Notable special cases include: + * + *

    + *
  • All NaNs are fuzzily equal. + *
  • If {@code a == b}, then {@code a} and {@code b} are always fuzzily equal. + *
  • Positive and negative zero are always fuzzily equal. + *
  • If {@code tolerance} is zero, and neither {@code a} nor {@code b} is NaN, then {@code a} + * and {@code b} are fuzzily equal if and only if {@code a == b}. + *
  • With {@link Double#POSITIVE_INFINITY} tolerance, all non-NaN values are fuzzily equal. + *
  • With finite tolerance, {@code Double.POSITIVE_INFINITY} and {@code + * Double.NEGATIVE_INFINITY} are fuzzily equal only to themselves. + *
+ * + *

This is reflexive and symmetric, but not transitive, so it is not an + * equivalence relation and not suitable for use in {@link Object#equals} + * implementations. + * + * @throws IllegalArgumentException if {@code tolerance} is {@code < 0} or NaN + * @since 13.0 + */ + public static boolean fuzzyEquals(double a, double b, double tolerance) { + MathPreconditions.checkNonNegative("tolerance", tolerance); + return Math.copySign(a - b, 1.0) <= tolerance + // copySign(x, 1.0) is a branch-free version of abs(x), but with different NaN semantics + || (a == b) // needed to ensure that infinities equal themselves + || (Double.isNaN(a) && Double.isNaN(b)); + } + + /** + * Compares {@code a} and {@code b} "fuzzily," with a tolerance for nearly-equal values. + * + *

This method is equivalent to {@code fuzzyEquals(a, b, tolerance) ? 0 : Double.compare(a, + * b)}. In particular, like {@link Double#compare(double, double)}, it treats all NaN values as + * equal and greater than all other values (including {@link Double#POSITIVE_INFINITY}). + * + *

This is not a total ordering and is not suitable for use in {@link + * Comparable#compareTo} implementations. In particular, it is not transitive. + * + * @throws IllegalArgumentException if {@code tolerance} is {@code < 0} or NaN + * @since 13.0 + */ + public static int fuzzyCompare(double a, double b, double tolerance) { + if (fuzzyEquals(a, b, tolerance)) { + return 0; + } else if (a < b) { + return -1; + } else if (a > b) { + return 1; + } else { + return Booleans.compare(Double.isNaN(a), Double.isNaN(b)); + } + } + + /** + * Returns the arithmetic mean of + * {@code values}. + * + *

If these values are a sample drawn from a population, this is also an unbiased estimator of + * the arithmetic mean of the population. + * + * @param values a nonempty series of values + * @throws IllegalArgumentException if {@code values} is empty or contains any non-finite value + * @deprecated Use {@link Stats#meanOf} instead, noting the less strict handling of non-finite + * values. + */ + @Deprecated + // com.google.common.math.DoubleUtils + @GwtIncompatible + public static double mean(double... values) { + checkArgument(values.length > 0, "Cannot take mean of 0 values"); + long count = 1; + double mean = checkFinite(values[0]); + for (int index = 1; index < values.length; ++index) { + checkFinite(values[index]); + count++; + // Art of Computer Programming vol. 2, Knuth, 4.2.2, (15) + mean += (values[index] - mean) / count; + } + return mean; + } + + /** + * Returns the arithmetic mean of + * {@code values}. + * + *

If these values are a sample drawn from a population, this is also an unbiased estimator of + * the arithmetic mean of the population. + * + * @param values a nonempty series of values + * @throws IllegalArgumentException if {@code values} is empty + * @deprecated Use {@link Stats#meanOf} instead, noting the less strict handling of non-finite + * values. + */ + @Deprecated + public static double mean(int... values) { + checkArgument(values.length > 0, "Cannot take mean of 0 values"); + // The upper bound on the the length of an array and the bounds on the int values mean that, in + // this case only, we can compute the sum as a long without risking overflow or loss of + // precision. So we do that, as it's slightly quicker than the Knuth algorithm. + long sum = 0; + for (int index = 0; index < values.length; ++index) { + sum += values[index]; + } + return (double) sum / values.length; + } + + /** + * Returns the arithmetic mean of + * {@code values}. + * + *

If these values are a sample drawn from a population, this is also an unbiased estimator of + * the arithmetic mean of the population. + * + * @param values a nonempty series of values, which will be converted to {@code double} values + * (this may cause loss of precision for longs of magnitude over 2^53 (slightly over 9e15)) + * @throws IllegalArgumentException if {@code values} is empty + * @deprecated Use {@link Stats#meanOf} instead, noting the less strict handling of non-finite + * values. + */ + @Deprecated + public static double mean(long... values) { + checkArgument(values.length > 0, "Cannot take mean of 0 values"); + long count = 1; + double mean = values[0]; + for (int index = 1; index < values.length; ++index) { + count++; + // Art of Computer Programming vol. 2, Knuth, 4.2.2, (15) + mean += (values[index] - mean) / count; + } + return mean; + } + + /** + * Returns the arithmetic mean of + * {@code values}. + * + *

If these values are a sample drawn from a population, this is also an unbiased estimator of + * the arithmetic mean of the population. + * + * @param values a nonempty series of values, which will be converted to {@code double} values + * (this may cause loss of precision) + * @throws IllegalArgumentException if {@code values} is empty or contains any non-finite value + * @deprecated Use {@link Stats#meanOf} instead, noting the less strict handling of non-finite + * values. + */ + @Deprecated + // com.google.common.math.DoubleUtils + @GwtIncompatible + public static double mean(Iterable values) { + return mean(values.iterator()); + } + + /** + * Returns the arithmetic mean of + * {@code values}. + * + *

If these values are a sample drawn from a population, this is also an unbiased estimator of + * the arithmetic mean of the population. + * + * @param values a nonempty series of values, which will be converted to {@code double} values + * (this may cause loss of precision) + * @throws IllegalArgumentException if {@code values} is empty or contains any non-finite value + * @deprecated Use {@link Stats#meanOf} instead, noting the less strict handling of non-finite + * values. + */ + @Deprecated + // com.google.common.math.DoubleUtils + @GwtIncompatible + public static double mean(Iterator values) { + checkArgument(values.hasNext(), "Cannot take mean of 0 values"); + long count = 1; + double mean = checkFinite(values.next().doubleValue()); + while (values.hasNext()) { + double value = checkFinite(values.next().doubleValue()); + count++; + // Art of Computer Programming vol. 2, Knuth, 4.2.2, (15) + mean += (value - mean) / count; + } + return mean; + } + + @GwtIncompatible // com.google.common.math.DoubleUtils + + private static double checkFinite(double argument) { + checkArgument(isFinite(argument)); + return argument; + } + + private DoubleMath() {} +} diff --git a/src/main/java/com/google/common/math/DoubleUtils.java b/src/main/java/com/google/common/math/DoubleUtils.java new file mode 100644 index 0000000..4183195 --- /dev/null +++ b/src/main/java/com/google/common/math/DoubleUtils.java @@ -0,0 +1,138 @@ +/* + * Copyright (C) 2011 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.math; + +import static com.google.common.base.Preconditions.checkArgument; +import static java.lang.Double.MAX_EXPONENT; +import static java.lang.Double.MIN_EXPONENT; +import static java.lang.Double.POSITIVE_INFINITY; +import static java.lang.Double.doubleToRawLongBits; +import static java.lang.Double.isNaN; +import static java.lang.Double.longBitsToDouble; +import static java.lang.Math.getExponent; + +import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.VisibleForTesting; +import java.math.BigInteger; + +/** + * Utilities for {@code double} primitives. + * + * @author Louis Wasserman + */ +@GwtIncompatible +final class DoubleUtils { + private DoubleUtils() {} + + static double nextDown(double d) { + return -Math.nextUp(-d); + } + + // The mask for the significand, according to the {@link + // Double#doubleToRawLongBits(double)} spec. + static final long SIGNIFICAND_MASK = 0x000fffffffffffffL; + + // The mask for the exponent, according to the {@link + // Double#doubleToRawLongBits(double)} spec. + static final long EXPONENT_MASK = 0x7ff0000000000000L; + + // The mask for the sign, according to the {@link + // Double#doubleToRawLongBits(double)} spec. + static final long SIGN_MASK = 0x8000000000000000L; + + static final int SIGNIFICAND_BITS = 52; + + static final int EXPONENT_BIAS = 1023; + + /** The implicit 1 bit that is omitted in significands of normal doubles. */ + static final long IMPLICIT_BIT = SIGNIFICAND_MASK + 1; + + static long getSignificand(double d) { + checkArgument(isFinite(d), "not a normal value"); + int exponent = getExponent(d); + long bits = doubleToRawLongBits(d); + bits &= SIGNIFICAND_MASK; + return (exponent == MIN_EXPONENT - 1) ? bits << 1 : bits | IMPLICIT_BIT; + } + + static boolean isFinite(double d) { + return getExponent(d) <= MAX_EXPONENT; + } + + static boolean isNormal(double d) { + return getExponent(d) >= MIN_EXPONENT; + } + + /* + * Returns x scaled by a power of 2 such that it is in the range [1, 2). Assumes x is positive, + * normal, and finite. + */ + static double scaleNormalize(double x) { + long significand = doubleToRawLongBits(x) & SIGNIFICAND_MASK; + return longBitsToDouble(significand | ONE_BITS); + } + + static double bigToDouble(BigInteger x) { + // This is an extremely fast implementation of BigInteger.doubleValue(). JDK patch pending. + BigInteger absX = x.abs(); + int exponent = absX.bitLength() - 1; + // exponent == floor(log2(abs(x))) + if (exponent < Long.SIZE - 1) { + return x.longValue(); + } else if (exponent > MAX_EXPONENT) { + return x.signum() * POSITIVE_INFINITY; + } + + /* + * We need the top SIGNIFICAND_BITS + 1 bits, including the "implicit" one bit. To make rounding + * easier, we pick out the top SIGNIFICAND_BITS + 2 bits, so we have one to help us round up or + * down. twiceSignifFloor will contain the top SIGNIFICAND_BITS + 2 bits, and signifFloor the + * top SIGNIFICAND_BITS + 1. + * + * It helps to consider the real number signif = absX * 2^(SIGNIFICAND_BITS - exponent). + */ + int shift = exponent - SIGNIFICAND_BITS - 1; + long twiceSignifFloor = absX.shiftRight(shift).longValue(); + long signifFloor = twiceSignifFloor >> 1; + signifFloor &= SIGNIFICAND_MASK; // remove the implied bit + + /* + * We round up if either the fractional part of signif is strictly greater than 0.5 (which is + * true if the 0.5 bit is set and any lower bit is set), or if the fractional part of signif is + * >= 0.5 and signifFloor is odd (which is true if both the 0.5 bit and the 1 bit are set). + */ + boolean increment = + (twiceSignifFloor & 1) != 0 && ((signifFloor & 1) != 0 || absX.getLowestSetBit() < shift); + long signifRounded = increment ? signifFloor + 1 : signifFloor; + long bits = (long) (exponent + EXPONENT_BIAS) << SIGNIFICAND_BITS; + bits += signifRounded; + /* + * If signifRounded == 2^53, we'd need to set all of the significand bits to zero and add 1 to + * the exponent. This is exactly the behavior we get from just adding signifRounded to bits + * directly. If the exponent is MAX_DOUBLE_EXPONENT, we round up (correctly) to + * Double.POSITIVE_INFINITY. + */ + bits |= x.signum() & SIGN_MASK; + return longBitsToDouble(bits); + } + + /** Returns its argument if it is non-negative, zero if it is negative. */ + static double ensureNonNegative(double value) { + checkArgument(!isNaN(value)); + return Math.max(value, 0.0); + } + + @VisibleForTesting static final long ONE_BITS = 0x3ff0000000000000L; +} diff --git a/src/main/java/com/google/common/math/IntMath.java b/src/main/java/com/google/common/math/IntMath.java new file mode 100644 index 0000000..78aedda --- /dev/null +++ b/src/main/java/com/google/common/math/IntMath.java @@ -0,0 +1,728 @@ +/* + * Copyright (C) 2011 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.math; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.math.MathPreconditions.checkNoOverflow; +import static com.google.common.math.MathPreconditions.checkNonNegative; +import static com.google.common.math.MathPreconditions.checkPositive; +import static com.google.common.math.MathPreconditions.checkRoundingUnnecessary; +import static java.lang.Math.abs; +import static java.lang.Math.min; +import static java.math.RoundingMode.HALF_EVEN; +import static java.math.RoundingMode.HALF_UP; + +import com.google.common.annotations.Beta; +import com.google.common.annotations.GwtCompatible; +import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.VisibleForTesting; +import com.google.common.primitives.Ints; +import java.math.BigInteger; +import java.math.RoundingMode; + +/** + * A class for arithmetic on values of type {@code int}. Where possible, methods are defined and + * named analogously to their {@code BigInteger} counterparts. + * + *

The implementations of many methods in this class are based on material from Henry S. Warren, + * Jr.'s Hacker's Delight, (Addison Wesley, 2002). + * + *

Similar functionality for {@code long} and for {@link BigInteger} can be found in {@link + * LongMath} and {@link BigIntegerMath} respectively. For other common operations on {@code int} + * values, see {@link com.google.common.primitives.Ints}. + * + * @author Louis Wasserman + * @since 11.0 + */ +@GwtCompatible(emulated = true) +public final class IntMath { + // NOTE: Whenever both tests are cheap and functional, it's faster to use &, | instead of &&, || + + @VisibleForTesting static final int MAX_SIGNED_POWER_OF_TWO = 1 << (Integer.SIZE - 2); + + /** + * Returns the smallest power of two greater than or equal to {@code x}. This is equivalent to + * {@code checkedPow(2, log2(x, CEILING))}. + * + * @throws IllegalArgumentException if {@code x <= 0} + * @throws ArithmeticException of the next-higher power of two is not representable as an {@code + * int}, i.e. when {@code x > 2^30} + * @since 20.0 + */ + @Beta + public static int ceilingPowerOfTwo(int x) { + checkPositive("x", x); + if (x > MAX_SIGNED_POWER_OF_TWO) { + throw new ArithmeticException("ceilingPowerOfTwo(" + x + ") not representable as an int"); + } + return 1 << -Integer.numberOfLeadingZeros(x - 1); + } + + /** + * Returns the largest power of two less than or equal to {@code x}. This is equivalent to {@code + * checkedPow(2, log2(x, FLOOR))}. + * + * @throws IllegalArgumentException if {@code x <= 0} + * @since 20.0 + */ + @Beta + public static int floorPowerOfTwo(int x) { + checkPositive("x", x); + return Integer.highestOneBit(x); + } + + /** + * Returns {@code true} if {@code x} represents a power of two. + * + *

This differs from {@code Integer.bitCount(x) == 1}, because {@code + * Integer.bitCount(Integer.MIN_VALUE) == 1}, but {@link Integer#MIN_VALUE} is not a power of two. + */ + public static boolean isPowerOfTwo(int x) { + return x > 0 & (x & (x - 1)) == 0; + } + + /** + * Returns 1 if {@code x < y} as unsigned integers, and 0 otherwise. Assumes that x - y fits into + * a signed int. The implementation is branch-free, and benchmarks suggest it is measurably (if + * narrowly) faster than the straightforward ternary expression. + */ + @VisibleForTesting + static int lessThanBranchFree(int x, int y) { + // The double negation is optimized away by normal Java, but is necessary for GWT + // to make sure bit twiddling works as expected. + return ~~(x - y) >>> (Integer.SIZE - 1); + } + + /** + * Returns the base-2 logarithm of {@code x}, rounded according to the specified rounding mode. + * + * @throws IllegalArgumentException if {@code x <= 0} + * @throws ArithmeticException if {@code mode} is {@link RoundingMode#UNNECESSARY} and {@code x} + * is not a power of two + */ + @SuppressWarnings("fallthrough") + // TODO(kevinb): remove after this warning is disabled globally + public static int log2(int x, RoundingMode mode) { + checkPositive("x", x); + switch (mode) { + case UNNECESSARY: + checkRoundingUnnecessary(isPowerOfTwo(x)); + // fall through + case DOWN: + case FLOOR: + return (Integer.SIZE - 1) - Integer.numberOfLeadingZeros(x); + + case UP: + case CEILING: + return Integer.SIZE - Integer.numberOfLeadingZeros(x - 1); + + case HALF_DOWN: + case HALF_UP: + case HALF_EVEN: + // Since sqrt(2) is irrational, log2(x) - logFloor cannot be exactly 0.5 + int leadingZeros = Integer.numberOfLeadingZeros(x); + int cmp = MAX_POWER_OF_SQRT2_UNSIGNED >>> leadingZeros; + // floor(2^(logFloor + 0.5)) + int logFloor = (Integer.SIZE - 1) - leadingZeros; + return logFloor + lessThanBranchFree(cmp, x); + + default: + throw new AssertionError(); + } + } + + /** The biggest half power of two that can fit in an unsigned int. */ + @VisibleForTesting static final int MAX_POWER_OF_SQRT2_UNSIGNED = 0xB504F333; + + /** + * Returns the base-10 logarithm of {@code x}, rounded according to the specified rounding mode. + * + * @throws IllegalArgumentException if {@code x <= 0} + * @throws ArithmeticException if {@code mode} is {@link RoundingMode#UNNECESSARY} and {@code x} + * is not a power of ten + */ + @GwtIncompatible // need BigIntegerMath to adequately test + @SuppressWarnings("fallthrough") + public static int log10(int x, RoundingMode mode) { + checkPositive("x", x); + int logFloor = log10Floor(x); + int floorPow = powersOf10[logFloor]; + switch (mode) { + case UNNECESSARY: + checkRoundingUnnecessary(x == floorPow); + // fall through + case FLOOR: + case DOWN: + return logFloor; + case CEILING: + case UP: + return logFloor + lessThanBranchFree(floorPow, x); + case HALF_DOWN: + case HALF_UP: + case HALF_EVEN: + // sqrt(10) is irrational, so log10(x) - logFloor is never exactly 0.5 + return logFloor + lessThanBranchFree(halfPowersOf10[logFloor], x); + default: + throw new AssertionError(); + } + } + + private static int log10Floor(int x) { + /* + * Based on Hacker's Delight Fig. 11-5, the two-table-lookup, branch-free implementation. + * + * The key idea is that based on the number of leading zeros (equivalently, floor(log2(x))), we + * can narrow the possible floor(log10(x)) values to two. For example, if floor(log2(x)) is 6, + * then 64 <= x < 128, so floor(log10(x)) is either 1 or 2. + */ + int y = maxLog10ForLeadingZeros[Integer.numberOfLeadingZeros(x)]; + /* + * y is the higher of the two possible values of floor(log10(x)). If x < 10^y, then we want the + * lower of the two possible values, or y - 1, otherwise, we want y. + */ + return y - lessThanBranchFree(x, powersOf10[y]); + } + + // maxLog10ForLeadingZeros[i] == floor(log10(2^(Long.SIZE - i))) + @VisibleForTesting + static final byte[] maxLog10ForLeadingZeros = { + 9, 9, 9, 8, 8, 8, 7, 7, 7, 6, 6, 6, 6, 5, 5, 5, 4, 4, 4, 3, 3, 3, 3, 2, 2, 2, 1, 1, 1, 0, 0, 0, + 0 + }; + + @VisibleForTesting + static final int[] powersOf10 = { + 1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000, 1000000000 + }; + + // halfPowersOf10[i] = largest int less than 10^(i + 0.5) + @VisibleForTesting + static final int[] halfPowersOf10 = { + 3, 31, 316, 3162, 31622, 316227, 3162277, 31622776, 316227766, Integer.MAX_VALUE + }; + + /** + * Returns {@code b} to the {@code k}th power. Even if the result overflows, it will be equal to + * {@code BigInteger.valueOf(b).pow(k).intValue()}. This implementation runs in {@code O(log k)} + * time. + * + *

Compare {@link #checkedPow}, which throws an {@link ArithmeticException} upon overflow. + * + * @throws IllegalArgumentException if {@code k < 0} + */ + @GwtIncompatible // failing tests + public static int pow(int b, int k) { + checkNonNegative("exponent", k); + switch (b) { + case 0: + return (k == 0) ? 1 : 0; + case 1: + return 1; + case (-1): + return ((k & 1) == 0) ? 1 : -1; + case 2: + return (k < Integer.SIZE) ? (1 << k) : 0; + case (-2): + if (k < Integer.SIZE) { + return ((k & 1) == 0) ? (1 << k) : -(1 << k); + } else { + return 0; + } + default: + // continue below to handle the general case + } + for (int accum = 1; ; k >>= 1) { + switch (k) { + case 0: + return accum; + case 1: + return b * accum; + default: + accum *= ((k & 1) == 0) ? 1 : b; + b *= b; + } + } + } + + /** + * Returns the square root of {@code x}, rounded with the specified rounding mode. + * + * @throws IllegalArgumentException if {@code x < 0} + * @throws ArithmeticException if {@code mode} is {@link RoundingMode#UNNECESSARY} and {@code + * sqrt(x)} is not an integer + */ + @GwtIncompatible // need BigIntegerMath to adequately test + @SuppressWarnings("fallthrough") + public static int sqrt(int x, RoundingMode mode) { + checkNonNegative("x", x); + int sqrtFloor = sqrtFloor(x); + switch (mode) { + case UNNECESSARY: + checkRoundingUnnecessary(sqrtFloor * sqrtFloor == x); // fall through + case FLOOR: + case DOWN: + return sqrtFloor; + case CEILING: + case UP: + return sqrtFloor + lessThanBranchFree(sqrtFloor * sqrtFloor, x); + case HALF_DOWN: + case HALF_UP: + case HALF_EVEN: + int halfSquare = sqrtFloor * sqrtFloor + sqrtFloor; + /* + * We wish to test whether or not x <= (sqrtFloor + 0.5)^2 = halfSquare + 0.25. Since both x + * and halfSquare are integers, this is equivalent to testing whether or not x <= + * halfSquare. (We have to deal with overflow, though.) + * + * If we treat halfSquare as an unsigned int, we know that + * sqrtFloor^2 <= x < (sqrtFloor + 1)^2 + * halfSquare - sqrtFloor <= x < halfSquare + sqrtFloor + 1 + * so |x - halfSquare| <= sqrtFloor. Therefore, it's safe to treat x - halfSquare as a + * signed int, so lessThanBranchFree is safe for use. + */ + return sqrtFloor + lessThanBranchFree(halfSquare, x); + default: + throw new AssertionError(); + } + } + + private static int sqrtFloor(int x) { + // There is no loss of precision in converting an int to a double, according to + // http://java.sun.com/docs/books/jls/third_edition/html/conversions.html#5.1.2 + return (int) Math.sqrt(x); + } + + /** + * Returns the result of dividing {@code p} by {@code q}, rounding using the specified {@code + * RoundingMode}. + * + * @throws ArithmeticException if {@code q == 0}, or if {@code mode == UNNECESSARY} and {@code a} + * is not an integer multiple of {@code b} + */ + @SuppressWarnings("fallthrough") + public static int divide(int p, int q, RoundingMode mode) { + checkNotNull(mode); + if (q == 0) { + throw new ArithmeticException("/ by zero"); // for GWT + } + int div = p / q; + int rem = p - q * div; // equal to p % q + + if (rem == 0) { + return div; + } + + /* + * Normal Java division rounds towards 0, consistently with RoundingMode.DOWN. We just have to + * deal with the cases where rounding towards 0 is wrong, which typically depends on the sign of + * p / q. + * + * signum is 1 if p and q are both nonnegative or both negative, and -1 otherwise. + */ + int signum = 1 | ((p ^ q) >> (Integer.SIZE - 1)); + boolean increment; + switch (mode) { + case UNNECESSARY: + checkRoundingUnnecessary(rem == 0); + // fall through + case DOWN: + increment = false; + break; + case UP: + increment = true; + break; + case CEILING: + increment = signum > 0; + break; + case FLOOR: + increment = signum < 0; + break; + case HALF_EVEN: + case HALF_DOWN: + case HALF_UP: + int absRem = abs(rem); + int cmpRemToHalfDivisor = absRem - (abs(q) - absRem); + // subtracting two nonnegative ints can't overflow + // cmpRemToHalfDivisor has the same sign as compare(abs(rem), abs(q) / 2). + if (cmpRemToHalfDivisor == 0) { // exactly on the half mark + increment = (mode == HALF_UP || (mode == HALF_EVEN & (div & 1) != 0)); + } else { + increment = cmpRemToHalfDivisor > 0; // closer to the UP value + } + break; + default: + throw new AssertionError(); + } + return increment ? div + signum : div; + } + + /** + * Returns {@code x mod m}, a non-negative value less than {@code m}. This differs from {@code x % + * m}, which might be negative. + * + *

For example: + * + *

{@code
+   * mod(7, 4) == 3
+   * mod(-7, 4) == 1
+   * mod(-1, 4) == 3
+   * mod(-8, 4) == 0
+   * mod(8, 4) == 0
+   * }
+ * + * @throws ArithmeticException if {@code m <= 0} + * @see + * Remainder Operator + */ + public static int mod(int x, int m) { + if (m <= 0) { + throw new ArithmeticException("Modulus " + m + " must be > 0"); + } + int result = x % m; + return (result >= 0) ? result : result + m; + } + + /** + * Returns the greatest common divisor of {@code a, b}. Returns {@code 0} if {@code a == 0 && b == + * 0}. + * + * @throws IllegalArgumentException if {@code a < 0} or {@code b < 0} + */ + public static int gcd(int a, int b) { + /* + * The reason we require both arguments to be >= 0 is because otherwise, what do you return on + * gcd(0, Integer.MIN_VALUE)? BigInteger.gcd would return positive 2^31, but positive 2^31 isn't + * an int. + */ + checkNonNegative("a", a); + checkNonNegative("b", b); + if (a == 0) { + // 0 % b == 0, so b divides a, but the converse doesn't hold. + // BigInteger.gcd is consistent with this decision. + return b; + } else if (b == 0) { + return a; // similar logic + } + /* + * Uses the binary GCD algorithm; see http://en.wikipedia.org/wiki/Binary_GCD_algorithm. This is + * >40% faster than the Euclidean algorithm in benchmarks. + */ + int aTwos = Integer.numberOfTrailingZeros(a); + a >>= aTwos; // divide out all 2s + int bTwos = Integer.numberOfTrailingZeros(b); + b >>= bTwos; // divide out all 2s + while (a != b) { // both a, b are odd + // The key to the binary GCD algorithm is as follows: + // Both a and b are odd. Assume a > b; then gcd(a - b, b) = gcd(a, b). + // But in gcd(a - b, b), a - b is even and b is odd, so we can divide out powers of two. + + // We bend over backwards to avoid branching, adapting a technique from + // http://graphics.stanford.edu/~seander/bithacks.html#IntegerMinOrMax + + int delta = a - b; // can't overflow, since a and b are nonnegative + + int minDeltaOrZero = delta & (delta >> (Integer.SIZE - 1)); + // equivalent to Math.min(delta, 0) + + a = delta - minDeltaOrZero - minDeltaOrZero; // sets a to Math.abs(a - b) + // a is now nonnegative and even + + b += minDeltaOrZero; // sets b to min(old a, b) + a >>= Integer.numberOfTrailingZeros(a); // divide out all 2s, since 2 doesn't divide b + } + return a << min(aTwos, bTwos); + } + + /** + * Returns the sum of {@code a} and {@code b}, provided it does not overflow. + * + * @throws ArithmeticException if {@code a + b} overflows in signed {@code int} arithmetic + */ + public static int checkedAdd(int a, int b) { + long result = (long) a + b; + checkNoOverflow(result == (int) result, "checkedAdd", a, b); + return (int) result; + } + + /** + * Returns the difference of {@code a} and {@code b}, provided it does not overflow. + * + * @throws ArithmeticException if {@code a - b} overflows in signed {@code int} arithmetic + */ + public static int checkedSubtract(int a, int b) { + long result = (long) a - b; + checkNoOverflow(result == (int) result, "checkedSubtract", a, b); + return (int) result; + } + + /** + * Returns the product of {@code a} and {@code b}, provided it does not overflow. + * + * @throws ArithmeticException if {@code a * b} overflows in signed {@code int} arithmetic + */ + public static int checkedMultiply(int a, int b) { + long result = (long) a * b; + checkNoOverflow(result == (int) result, "checkedMultiply", a, b); + return (int) result; + } + + /** + * Returns the {@code b} to the {@code k}th power, provided it does not overflow. + * + *

{@link #pow} may be faster, but does not check for overflow. + * + * @throws ArithmeticException if {@code b} to the {@code k}th power overflows in signed {@code + * int} arithmetic + */ + public static int checkedPow(int b, int k) { + checkNonNegative("exponent", k); + switch (b) { + case 0: + return (k == 0) ? 1 : 0; + case 1: + return 1; + case (-1): + return ((k & 1) == 0) ? 1 : -1; + case 2: + checkNoOverflow(k < Integer.SIZE - 1, "checkedPow", b, k); + return 1 << k; + case (-2): + checkNoOverflow(k < Integer.SIZE, "checkedPow", b, k); + return ((k & 1) == 0) ? 1 << k : -1 << k; + default: + // continue below to handle the general case + } + int accum = 1; + while (true) { + switch (k) { + case 0: + return accum; + case 1: + return checkedMultiply(accum, b); + default: + if ((k & 1) != 0) { + accum = checkedMultiply(accum, b); + } + k >>= 1; + if (k > 0) { + checkNoOverflow(-FLOOR_SQRT_MAX_INT <= b & b <= FLOOR_SQRT_MAX_INT, "checkedPow", b, k); + b *= b; + } + } + } + } + + /** + * Returns the sum of {@code a} and {@code b} unless it would overflow or underflow in which case + * {@code Integer.MAX_VALUE} or {@code Integer.MIN_VALUE} is returned, respectively. + * + * @since 20.0 + */ + @Beta + public static int saturatedAdd(int a, int b) { + return Ints.saturatedCast((long) a + b); + } + + /** + * Returns the difference of {@code a} and {@code b} unless it would overflow or underflow in + * which case {@code Integer.MAX_VALUE} or {@code Integer.MIN_VALUE} is returned, respectively. + * + * @since 20.0 + */ + @Beta + public static int saturatedSubtract(int a, int b) { + return Ints.saturatedCast((long) a - b); + } + + /** + * Returns the product of {@code a} and {@code b} unless it would overflow or underflow in which + * case {@code Integer.MAX_VALUE} or {@code Integer.MIN_VALUE} is returned, respectively. + * + * @since 20.0 + */ + @Beta + public static int saturatedMultiply(int a, int b) { + return Ints.saturatedCast((long) a * b); + } + + /** + * Returns the {@code b} to the {@code k}th power, unless it would overflow or underflow in which + * case {@code Integer.MAX_VALUE} or {@code Integer.MIN_VALUE} is returned, respectively. + * + * @since 20.0 + */ + @Beta + public static int saturatedPow(int b, int k) { + checkNonNegative("exponent", k); + switch (b) { + case 0: + return (k == 0) ? 1 : 0; + case 1: + return 1; + case (-1): + return ((k & 1) == 0) ? 1 : -1; + case 2: + if (k >= Integer.SIZE - 1) { + return Integer.MAX_VALUE; + } + return 1 << k; + case (-2): + if (k >= Integer.SIZE) { + return Integer.MAX_VALUE + (k & 1); + } + return ((k & 1) == 0) ? 1 << k : -1 << k; + default: + // continue below to handle the general case + } + int accum = 1; + // if b is negative and k is odd then the limit is MIN otherwise the limit is MAX + int limit = Integer.MAX_VALUE + ((b >>> Integer.SIZE - 1) & (k & 1)); + while (true) { + switch (k) { + case 0: + return accum; + case 1: + return saturatedMultiply(accum, b); + default: + if ((k & 1) != 0) { + accum = saturatedMultiply(accum, b); + } + k >>= 1; + if (k > 0) { + if (-FLOOR_SQRT_MAX_INT > b | b > FLOOR_SQRT_MAX_INT) { + return limit; + } + b *= b; + } + } + } + } + + @VisibleForTesting static final int FLOOR_SQRT_MAX_INT = 46340; + + /** + * Returns {@code n!}, that is, the product of the first {@code n} positive integers, {@code 1} if + * {@code n == 0}, or {@link Integer#MAX_VALUE} if the result does not fit in a {@code int}. + * + * @throws IllegalArgumentException if {@code n < 0} + */ + public static int factorial(int n) { + checkNonNegative("n", n); + return (n < factorials.length) ? factorials[n] : Integer.MAX_VALUE; + } + + private static final int[] factorials = { + 1, + 1, + 1 * 2, + 1 * 2 * 3, + 1 * 2 * 3 * 4, + 1 * 2 * 3 * 4 * 5, + 1 * 2 * 3 * 4 * 5 * 6, + 1 * 2 * 3 * 4 * 5 * 6 * 7, + 1 * 2 * 3 * 4 * 5 * 6 * 7 * 8, + 1 * 2 * 3 * 4 * 5 * 6 * 7 * 8 * 9, + 1 * 2 * 3 * 4 * 5 * 6 * 7 * 8 * 9 * 10, + 1 * 2 * 3 * 4 * 5 * 6 * 7 * 8 * 9 * 10 * 11, + 1 * 2 * 3 * 4 * 5 * 6 * 7 * 8 * 9 * 10 * 11 * 12 + }; + + /** + * Returns {@code n} choose {@code k}, also known as the binomial coefficient of {@code n} and + * {@code k}, or {@link Integer#MAX_VALUE} if the result does not fit in an {@code int}. + * + * @throws IllegalArgumentException if {@code n < 0}, {@code k < 0} or {@code k > n} + */ + public static int binomial(int n, int k) { + checkNonNegative("n", n); + checkNonNegative("k", k); + checkArgument(k <= n, "k (%s) > n (%s)", k, n); + if (k > (n >> 1)) { + k = n - k; + } + if (k >= biggestBinomials.length || n > biggestBinomials[k]) { + return Integer.MAX_VALUE; + } + switch (k) { + case 0: + return 1; + case 1: + return n; + default: + long result = 1; + for (int i = 0; i < k; i++) { + result *= n - i; + result /= i + 1; + } + return (int) result; + } + } + + // binomial(biggestBinomials[k], k) fits in an int, but not binomial(biggestBinomials[k]+1,k). + @VisibleForTesting + static int[] biggestBinomials = { + Integer.MAX_VALUE, + Integer.MAX_VALUE, + 65536, + 2345, + 477, + 193, + 110, + 75, + 58, + 49, + 43, + 39, + 37, + 35, + 34, + 34, + 33 + }; + + /** + * Returns the arithmetic mean of {@code x} and {@code y}, rounded towards negative infinity. This + * method is overflow resilient. + * + * @since 14.0 + */ + public static int mean(int x, int y) { + // Efficient method for computing the arithmetic mean. + // The alternative (x + y) / 2 fails for large values. + // The alternative (x + y) >>> 1 fails for negative values. + return (x & y) + ((x ^ y) >> 1); + } + + /** + * Returns {@code true} if {@code n} is a prime number: an integer greater + * than one that cannot be factored into a product of smaller positive integers. + * Returns {@code false} if {@code n} is zero, one, or a composite number (one which can be + * factored into smaller positive integers). + * + *

To test larger numbers, use {@link LongMath#isPrime} or {@link BigInteger#isProbablePrime}. + * + * @throws IllegalArgumentException if {@code n} is negative + * @since 20.0 + */ + @GwtIncompatible // TODO + @Beta + public static boolean isPrime(int n) { + return LongMath.isPrime(n); + } + + private IntMath() {} +} diff --git a/src/main/java/com/google/common/math/LinearTransformation.java b/src/main/java/com/google/common/math/LinearTransformation.java new file mode 100644 index 0000000..7e4e5c8 --- /dev/null +++ b/src/main/java/com/google/common/math/LinearTransformation.java @@ -0,0 +1,304 @@ +/* + * Copyright (C) 2012 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.math; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.math.DoubleUtils.isFinite; +import static java.lang.Double.NaN; + +import com.google.common.annotations.Beta; +import com.google.common.annotations.GwtIncompatible; + + +/** + * The representation of a linear transformation between real numbers {@code x} and {@code y}. + * Graphically, this is the specification of a straight line on a plane. The transformation can be + * expressed as {@code y = m * x + c} for finite {@code m} and {@code c}, unless it is a vertical + * transformation in which case {@code x} has a constant value for all {@code y}. In the + * non-vertical case, {@code m} is the slope of the transformation (and a horizontal transformation + * has zero slope). + * + * @author Pete Gillin + * @since 20.0 + */ +@Beta +@GwtIncompatible +public abstract class LinearTransformation { + + /** + * Start building an instance which maps {@code x = x1} to {@code y = y1}. Both arguments must be + * finite. Call either {@link LinearTransformationBuilder#and} or {@link + * LinearTransformationBuilder#withSlope} on the returned object to finish building the instance. + */ + public static LinearTransformationBuilder mapping(double x1, double y1) { + checkArgument(isFinite(x1) && isFinite(y1)); + return new LinearTransformationBuilder(x1, y1); + } + + /** + * This is an intermediate stage in the construction process. It is returned by {@link + * LinearTransformation#mapping}. You almost certainly don't want to keep instances around, but + * instead use method chaining. This represents a single point mapping, i.e. a mapping between one + * {@code x} and {@code y} value pair. + * + * @since 20.0 + */ + public static final class LinearTransformationBuilder { + + private final double x1; + private final double y1; + + private LinearTransformationBuilder(double x1, double y1) { + this.x1 = x1; + this.y1 = y1; + } + + /** + * Finish building an instance which also maps {@code x = x2} to {@code y = y2}. These values + * must not both be identical to the values given in the first mapping. If only the {@code x} + * values are identical, the transformation is vertical. If only the {@code y} values are + * identical, the transformation is horizontal (i.e. the slope is zero). + */ + public LinearTransformation and(double x2, double y2) { + checkArgument(isFinite(x2) && isFinite(y2)); + if (x2 == x1) { + checkArgument(y2 != y1); + return new VerticalLinearTransformation(x1); + } else { + return withSlope((y2 - y1) / (x2 - x1)); + } + } + + /** + * Finish building an instance with the given slope, i.e. the rate of change of {@code y} with + * respect to {@code x}. The slope must not be {@code NaN}. It may be infinite, in which case + * the transformation is vertical. (If it is zero, the transformation is horizontal.) + */ + public LinearTransformation withSlope(double slope) { + checkArgument(!Double.isNaN(slope)); + if (isFinite(slope)) { + double yIntercept = y1 - x1 * slope; + return new RegularLinearTransformation(slope, yIntercept); + } else { + return new VerticalLinearTransformation(x1); + } + } + } + + /** + * Builds an instance representing a vertical transformation with a constant value of {@code x}. + * (The inverse of this will be a horizontal transformation.) + */ + public static LinearTransformation vertical(double x) { + checkArgument(isFinite(x)); + return new VerticalLinearTransformation(x); + } + + /** + * Builds an instance representing a horizontal transformation with a constant value of {@code y}. + * (The inverse of this will be a vertical transformation.) + */ + public static LinearTransformation horizontal(double y) { + checkArgument(isFinite(y)); + double slope = 0.0; + return new RegularLinearTransformation(slope, y); + } + + /** + * Builds an instance for datasets which contains {@link Double#NaN}. The {@link #isHorizontal} + * and {@link #isVertical} methods return {@code false} and the {@link #slope}, and {@link + * #transform} methods all return {@link Double#NaN}. The {@link #inverse} method returns the same + * instance. + */ + public static LinearTransformation forNaN() { + return NaNLinearTransformation.INSTANCE; + } + + /** Returns whether this is a vertical transformation. */ + public abstract boolean isVertical(); + + /** Returns whether this is a horizontal transformation. */ + public abstract boolean isHorizontal(); + + /** + * Returns the slope of the transformation, i.e. the rate of change of {@code y} with respect to + * {@code x}. This must not be called on a vertical transformation (i.e. when {@link + * #isVertical()} is true). + */ + public abstract double slope(); + + /** + * Returns the {@code y} corresponding to the given {@code x}. This must not be called on a + * vertical transformation (i.e. when {@link #isVertical()} is true). + */ + public abstract double transform(double x); + + /** + * Returns the inverse linear transformation. The inverse of a horizontal transformation is a + * vertical transformation, and vice versa. The inverse of the {@link #forNaN} transformation is + * itself. In all other cases, the inverse is a transformation such that applying both the + * original transformation and its inverse to a value gives you the original value give-or-take + * numerical errors. Calling this method multiple times on the same instance will always return + * the same instance. Calling this method on the result of calling this method on an instance will + * always return that original instance. + */ + public abstract LinearTransformation inverse(); + + private static final class RegularLinearTransformation extends LinearTransformation { + + final double slope; + final double yIntercept; + + LinearTransformation inverse; + + RegularLinearTransformation(double slope, double yIntercept) { + this.slope = slope; + this.yIntercept = yIntercept; + this.inverse = null; // to be lazily initialized + } + + RegularLinearTransformation(double slope, double yIntercept, LinearTransformation inverse) { + this.slope = slope; + this.yIntercept = yIntercept; + this.inverse = inverse; + } + + @Override + public boolean isVertical() { + return false; + } + + @Override + public boolean isHorizontal() { + return (slope == 0.0); + } + + @Override + public double slope() { + return slope; + } + + @Override + public double transform(double x) { + return x * slope + yIntercept; + } + + @Override + public LinearTransformation inverse() { + LinearTransformation result = inverse; + return (result == null) ? inverse = createInverse() : result; + } + + @Override + public String toString() { + return String.format("y = %g * x + %g", slope, yIntercept); + } + + private LinearTransformation createInverse() { + if (slope != 0.0) { + return new RegularLinearTransformation(1.0 / slope, -1.0 * yIntercept / slope, this); + } else { + return new VerticalLinearTransformation(yIntercept, this); + } + } + } + + private static final class VerticalLinearTransformation extends LinearTransformation { + + final double x; + + LinearTransformation inverse; + + VerticalLinearTransformation(double x) { + this.x = x; + this.inverse = null; // to be lazily initialized + } + + VerticalLinearTransformation(double x, LinearTransformation inverse) { + this.x = x; + this.inverse = inverse; + } + + @Override + public boolean isVertical() { + return true; + } + + @Override + public boolean isHorizontal() { + return false; + } + + @Override + public double slope() { + throw new IllegalStateException(); + } + + @Override + public double transform(double x) { + throw new IllegalStateException(); + } + + @Override + public LinearTransformation inverse() { + LinearTransformation result = inverse; + return (result == null) ? inverse = createInverse() : result; + } + + @Override + public String toString() { + return String.format("x = %g", x); + } + + private LinearTransformation createInverse() { + return new RegularLinearTransformation(0.0, x, this); + } + } + + private static final class NaNLinearTransformation extends LinearTransformation { + + static final NaNLinearTransformation INSTANCE = new NaNLinearTransformation(); + + @Override + public boolean isVertical() { + return false; + } + + @Override + public boolean isHorizontal() { + return false; + } + + @Override + public double slope() { + return NaN; + } + + @Override + public double transform(double x) { + return NaN; + } + + @Override + public LinearTransformation inverse() { + return this; + } + + @Override + public String toString() { + return "NaN"; + } + } +} diff --git a/src/main/java/com/google/common/math/LongMath.java b/src/main/java/com/google/common/math/LongMath.java new file mode 100644 index 0000000..b16db71 --- /dev/null +++ b/src/main/java/com/google/common/math/LongMath.java @@ -0,0 +1,1207 @@ +/* + * Copyright (C) 2011 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.math; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.math.MathPreconditions.checkNoOverflow; +import static com.google.common.math.MathPreconditions.checkNonNegative; +import static com.google.common.math.MathPreconditions.checkPositive; +import static com.google.common.math.MathPreconditions.checkRoundingUnnecessary; +import static java.lang.Math.abs; +import static java.lang.Math.min; +import static java.math.RoundingMode.HALF_EVEN; +import static java.math.RoundingMode.HALF_UP; + +import com.google.common.annotations.Beta; +import com.google.common.annotations.GwtCompatible; +import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.VisibleForTesting; +import com.google.common.primitives.UnsignedLongs; +import java.math.BigInteger; +import java.math.RoundingMode; + +/** + * A class for arithmetic on values of type {@code long}. Where possible, methods are defined and + * named analogously to their {@code BigInteger} counterparts. + * + *

The implementations of many methods in this class are based on material from Henry S. Warren, + * Jr.'s Hacker's Delight, (Addison Wesley, 2002). + * + *

Similar functionality for {@code int} and for {@link BigInteger} can be found in {@link + * IntMath} and {@link BigIntegerMath} respectively. For other common operations on {@code long} + * values, see {@link com.google.common.primitives.Longs}. + * + * @author Louis Wasserman + * @since 11.0 + */ +@GwtCompatible(emulated = true) +public final class LongMath { + // NOTE: Whenever both tests are cheap and functional, it's faster to use &, | instead of &&, || + + @VisibleForTesting static final long MAX_SIGNED_POWER_OF_TWO = 1L << (Long.SIZE - 2); + + /** + * Returns the smallest power of two greater than or equal to {@code x}. This is equivalent to + * {@code checkedPow(2, log2(x, CEILING))}. + * + * @throws IllegalArgumentException if {@code x <= 0} + * @throws ArithmeticException of the next-higher power of two is not representable as a {@code + * long}, i.e. when {@code x > 2^62} + * @since 20.0 + */ + @Beta + public static long ceilingPowerOfTwo(long x) { + checkPositive("x", x); + if (x > MAX_SIGNED_POWER_OF_TWO) { + throw new ArithmeticException("ceilingPowerOfTwo(" + x + ") is not representable as a long"); + } + return 1L << -Long.numberOfLeadingZeros(x - 1); + } + + /** + * Returns the largest power of two less than or equal to {@code x}. This is equivalent to {@code + * checkedPow(2, log2(x, FLOOR))}. + * + * @throws IllegalArgumentException if {@code x <= 0} + * @since 20.0 + */ + @Beta + public static long floorPowerOfTwo(long x) { + checkPositive("x", x); + + // Long.highestOneBit was buggy on GWT. We've fixed it, but I'm not certain when the fix will + // be released. + return 1L << ((Long.SIZE - 1) - Long.numberOfLeadingZeros(x)); + } + + /** + * Returns {@code true} if {@code x} represents a power of two. + * + *

This differs from {@code Long.bitCount(x) == 1}, because {@code + * Long.bitCount(Long.MIN_VALUE) == 1}, but {@link Long#MIN_VALUE} is not a power of two. + */ + public static boolean isPowerOfTwo(long x) { + return x > 0 & (x & (x - 1)) == 0; + } + + /** + * Returns 1 if {@code x < y} as unsigned longs, and 0 otherwise. Assumes that x - y fits into a + * signed long. The implementation is branch-free, and benchmarks suggest it is measurably faster + * than the straightforward ternary expression. + */ + @VisibleForTesting + static int lessThanBranchFree(long x, long y) { + // Returns the sign bit of x - y. + return (int) (~~(x - y) >>> (Long.SIZE - 1)); + } + + /** + * Returns the base-2 logarithm of {@code x}, rounded according to the specified rounding mode. + * + * @throws IllegalArgumentException if {@code x <= 0} + * @throws ArithmeticException if {@code mode} is {@link RoundingMode#UNNECESSARY} and {@code x} + * is not a power of two + */ + @SuppressWarnings("fallthrough") + // TODO(kevinb): remove after this warning is disabled globally + public static int log2(long x, RoundingMode mode) { + checkPositive("x", x); + switch (mode) { + case UNNECESSARY: + checkRoundingUnnecessary(isPowerOfTwo(x)); + // fall through + case DOWN: + case FLOOR: + return (Long.SIZE - 1) - Long.numberOfLeadingZeros(x); + + case UP: + case CEILING: + return Long.SIZE - Long.numberOfLeadingZeros(x - 1); + + case HALF_DOWN: + case HALF_UP: + case HALF_EVEN: + // Since sqrt(2) is irrational, log2(x) - logFloor cannot be exactly 0.5 + int leadingZeros = Long.numberOfLeadingZeros(x); + long cmp = MAX_POWER_OF_SQRT2_UNSIGNED >>> leadingZeros; + // floor(2^(logFloor + 0.5)) + int logFloor = (Long.SIZE - 1) - leadingZeros; + return logFloor + lessThanBranchFree(cmp, x); + + default: + throw new AssertionError("impossible"); + } + } + + /** The biggest half power of two that fits into an unsigned long */ + @VisibleForTesting static final long MAX_POWER_OF_SQRT2_UNSIGNED = 0xB504F333F9DE6484L; + + /** + * Returns the base-10 logarithm of {@code x}, rounded according to the specified rounding mode. + * + * @throws IllegalArgumentException if {@code x <= 0} + * @throws ArithmeticException if {@code mode} is {@link RoundingMode#UNNECESSARY} and {@code x} + * is not a power of ten + */ + @GwtIncompatible // TODO + @SuppressWarnings("fallthrough") + // TODO(kevinb): remove after this warning is disabled globally + public static int log10(long x, RoundingMode mode) { + checkPositive("x", x); + int logFloor = log10Floor(x); + long floorPow = powersOf10[logFloor]; + switch (mode) { + case UNNECESSARY: + checkRoundingUnnecessary(x == floorPow); + // fall through + case FLOOR: + case DOWN: + return logFloor; + case CEILING: + case UP: + return logFloor + lessThanBranchFree(floorPow, x); + case HALF_DOWN: + case HALF_UP: + case HALF_EVEN: + // sqrt(10) is irrational, so log10(x)-logFloor is never exactly 0.5 + return logFloor + lessThanBranchFree(halfPowersOf10[logFloor], x); + default: + throw new AssertionError(); + } + } + + @GwtIncompatible // TODO + static int log10Floor(long x) { + /* + * Based on Hacker's Delight Fig. 11-5, the two-table-lookup, branch-free implementation. + * + * The key idea is that based on the number of leading zeros (equivalently, floor(log2(x))), we + * can narrow the possible floor(log10(x)) values to two. For example, if floor(log2(x)) is 6, + * then 64 <= x < 128, so floor(log10(x)) is either 1 or 2. + */ + int y = maxLog10ForLeadingZeros[Long.numberOfLeadingZeros(x)]; + /* + * y is the higher of the two possible values of floor(log10(x)). If x < 10^y, then we want the + * lower of the two possible values, or y - 1, otherwise, we want y. + */ + return y - lessThanBranchFree(x, powersOf10[y]); + } + + // maxLog10ForLeadingZeros[i] == floor(log10(2^(Long.SIZE - i))) + @VisibleForTesting + static final byte[] maxLog10ForLeadingZeros = { + 19, 18, 18, 18, 18, 17, 17, 17, 16, 16, 16, 15, 15, 15, 15, 14, 14, 14, 13, 13, 13, 12, 12, 12, + 12, 11, 11, 11, 10, 10, 10, 9, 9, 9, 9, 8, 8, 8, 7, 7, 7, 6, 6, 6, 6, 5, 5, 5, 4, 4, 4, 3, 3, 3, + 3, 2, 2, 2, 1, 1, 1, 0, 0, 0 + }; + + @GwtIncompatible // TODO + @VisibleForTesting + static final long[] powersOf10 = { + 1L, + 10L, + 100L, + 1000L, + 10000L, + 100000L, + 1000000L, + 10000000L, + 100000000L, + 1000000000L, + 10000000000L, + 100000000000L, + 1000000000000L, + 10000000000000L, + 100000000000000L, + 1000000000000000L, + 10000000000000000L, + 100000000000000000L, + 1000000000000000000L + }; + + // halfPowersOf10[i] = largest long less than 10^(i + 0.5) + @GwtIncompatible // TODO + @VisibleForTesting + static final long[] halfPowersOf10 = { + 3L, + 31L, + 316L, + 3162L, + 31622L, + 316227L, + 3162277L, + 31622776L, + 316227766L, + 3162277660L, + 31622776601L, + 316227766016L, + 3162277660168L, + 31622776601683L, + 316227766016837L, + 3162277660168379L, + 31622776601683793L, + 316227766016837933L, + 3162277660168379331L + }; + + /** + * Returns {@code b} to the {@code k}th power. Even if the result overflows, it will be equal to + * {@code BigInteger.valueOf(b).pow(k).longValue()}. This implementation runs in {@code O(log k)} + * time. + * + * @throws IllegalArgumentException if {@code k < 0} + */ + @GwtIncompatible // TODO + public static long pow(long b, int k) { + checkNonNegative("exponent", k); + if (-2 <= b && b <= 2) { + switch ((int) b) { + case 0: + return (k == 0) ? 1 : 0; + case 1: + return 1; + case (-1): + return ((k & 1) == 0) ? 1 : -1; + case 2: + return (k < Long.SIZE) ? 1L << k : 0; + case (-2): + if (k < Long.SIZE) { + return ((k & 1) == 0) ? 1L << k : -(1L << k); + } else { + return 0; + } + default: + throw new AssertionError(); + } + } + for (long accum = 1; ; k >>= 1) { + switch (k) { + case 0: + return accum; + case 1: + return accum * b; + default: + accum *= ((k & 1) == 0) ? 1 : b; + b *= b; + } + } + } + + /** + * Returns the square root of {@code x}, rounded with the specified rounding mode. + * + * @throws IllegalArgumentException if {@code x < 0} + * @throws ArithmeticException if {@code mode} is {@link RoundingMode#UNNECESSARY} and {@code + * sqrt(x)} is not an integer + */ + @GwtIncompatible // TODO + @SuppressWarnings("fallthrough") + public static long sqrt(long x, RoundingMode mode) { + checkNonNegative("x", x); + if (fitsInInt(x)) { + return IntMath.sqrt((int) x, mode); + } + /* + * Let k be the true value of floor(sqrt(x)), so that + * + * k * k <= x < (k + 1) * (k + 1) + * (double) (k * k) <= (double) x <= (double) ((k + 1) * (k + 1)) + * since casting to double is nondecreasing. + * Note that the right-hand inequality is no longer strict. + * Math.sqrt(k * k) <= Math.sqrt(x) <= Math.sqrt((k + 1) * (k + 1)) + * since Math.sqrt is monotonic. + * (long) Math.sqrt(k * k) <= (long) Math.sqrt(x) <= (long) Math.sqrt((k + 1) * (k + 1)) + * since casting to long is monotonic + * k <= (long) Math.sqrt(x) <= k + 1 + * since (long) Math.sqrt(k * k) == k, as checked exhaustively in + * {@link LongMathTest#testSqrtOfPerfectSquareAsDoubleIsPerfect} + */ + long guess = (long) Math.sqrt(x); + // Note: guess is always <= FLOOR_SQRT_MAX_LONG. + long guessSquared = guess * guess; + // Note (2013-2-26): benchmarks indicate that, inscrutably enough, using if statements is + // faster here than using lessThanBranchFree. + switch (mode) { + case UNNECESSARY: + checkRoundingUnnecessary(guessSquared == x); + return guess; + case FLOOR: + case DOWN: + if (x < guessSquared) { + return guess - 1; + } + return guess; + case CEILING: + case UP: + if (x > guessSquared) { + return guess + 1; + } + return guess; + case HALF_DOWN: + case HALF_UP: + case HALF_EVEN: + long sqrtFloor = guess - ((x < guessSquared) ? 1 : 0); + long halfSquare = sqrtFloor * sqrtFloor + sqrtFloor; + /* + * We wish to test whether or not x <= (sqrtFloor + 0.5)^2 = halfSquare + 0.25. Since both x + * and halfSquare are integers, this is equivalent to testing whether or not x <= + * halfSquare. (We have to deal with overflow, though.) + * + * If we treat halfSquare as an unsigned long, we know that + * sqrtFloor^2 <= x < (sqrtFloor + 1)^2 + * halfSquare - sqrtFloor <= x < halfSquare + sqrtFloor + 1 + * so |x - halfSquare| <= sqrtFloor. Therefore, it's safe to treat x - halfSquare as a + * signed long, so lessThanBranchFree is safe for use. + */ + return sqrtFloor + lessThanBranchFree(halfSquare, x); + default: + throw new AssertionError(); + } + } + + /** + * Returns the result of dividing {@code p} by {@code q}, rounding using the specified {@code + * RoundingMode}. + * + * @throws ArithmeticException if {@code q == 0}, or if {@code mode == UNNECESSARY} and {@code a} + * is not an integer multiple of {@code b} + */ + @GwtIncompatible // TODO + @SuppressWarnings("fallthrough") + public static long divide(long p, long q, RoundingMode mode) { + checkNotNull(mode); + long div = p / q; // throws if q == 0 + long rem = p - q * div; // equals p % q + + if (rem == 0) { + return div; + } + + /* + * Normal Java division rounds towards 0, consistently with RoundingMode.DOWN. We just have to + * deal with the cases where rounding towards 0 is wrong, which typically depends on the sign of + * p / q. + * + * signum is 1 if p and q are both nonnegative or both negative, and -1 otherwise. + */ + int signum = 1 | (int) ((p ^ q) >> (Long.SIZE - 1)); + boolean increment; + switch (mode) { + case UNNECESSARY: + checkRoundingUnnecessary(rem == 0); + // fall through + case DOWN: + increment = false; + break; + case UP: + increment = true; + break; + case CEILING: + increment = signum > 0; + break; + case FLOOR: + increment = signum < 0; + break; + case HALF_EVEN: + case HALF_DOWN: + case HALF_UP: + long absRem = abs(rem); + long cmpRemToHalfDivisor = absRem - (abs(q) - absRem); + // subtracting two nonnegative longs can't overflow + // cmpRemToHalfDivisor has the same sign as compare(abs(rem), abs(q) / 2). + if (cmpRemToHalfDivisor == 0) { // exactly on the half mark + increment = (mode == HALF_UP | (mode == HALF_EVEN & (div & 1) != 0)); + } else { + increment = cmpRemToHalfDivisor > 0; // closer to the UP value + } + break; + default: + throw new AssertionError(); + } + return increment ? div + signum : div; + } + + /** + * Returns {@code x mod m}, a non-negative value less than {@code m}. This differs from {@code x % + * m}, which might be negative. + * + *

For example: + * + *

{@code
+   * mod(7, 4) == 3
+   * mod(-7, 4) == 1
+   * mod(-1, 4) == 3
+   * mod(-8, 4) == 0
+   * mod(8, 4) == 0
+   * }
+ * + * @throws ArithmeticException if {@code m <= 0} + * @see + * Remainder Operator + */ + @GwtIncompatible // TODO + public static int mod(long x, int m) { + // Cast is safe because the result is guaranteed in the range [0, m) + return (int) mod(x, (long) m); + } + + /** + * Returns {@code x mod m}, a non-negative value less than {@code m}. This differs from {@code x % + * m}, which might be negative. + * + *

For example: + * + *

{@code
+   * mod(7, 4) == 3
+   * mod(-7, 4) == 1
+   * mod(-1, 4) == 3
+   * mod(-8, 4) == 0
+   * mod(8, 4) == 0
+   * }
+ * + * @throws ArithmeticException if {@code m <= 0} + * @see + * Remainder Operator + */ + @GwtIncompatible // TODO + public static long mod(long x, long m) { + if (m <= 0) { + throw new ArithmeticException("Modulus must be positive"); + } + long result = x % m; + return (result >= 0) ? result : result + m; + } + + /** + * Returns the greatest common divisor of {@code a, b}. Returns {@code 0} if {@code a == 0 && b == + * 0}. + * + * @throws IllegalArgumentException if {@code a < 0} or {@code b < 0} + */ + public static long gcd(long a, long b) { + /* + * The reason we require both arguments to be >= 0 is because otherwise, what do you return on + * gcd(0, Long.MIN_VALUE)? BigInteger.gcd would return positive 2^63, but positive 2^63 isn't an + * int. + */ + checkNonNegative("a", a); + checkNonNegative("b", b); + if (a == 0) { + // 0 % b == 0, so b divides a, but the converse doesn't hold. + // BigInteger.gcd is consistent with this decision. + return b; + } else if (b == 0) { + return a; // similar logic + } + /* + * Uses the binary GCD algorithm; see http://en.wikipedia.org/wiki/Binary_GCD_algorithm. This is + * >60% faster than the Euclidean algorithm in benchmarks. + */ + int aTwos = Long.numberOfTrailingZeros(a); + a >>= aTwos; // divide out all 2s + int bTwos = Long.numberOfTrailingZeros(b); + b >>= bTwos; // divide out all 2s + while (a != b) { // both a, b are odd + // The key to the binary GCD algorithm is as follows: + // Both a and b are odd. Assume a > b; then gcd(a - b, b) = gcd(a, b). + // But in gcd(a - b, b), a - b is even and b is odd, so we can divide out powers of two. + + // We bend over backwards to avoid branching, adapting a technique from + // http://graphics.stanford.edu/~seander/bithacks.html#IntegerMinOrMax + + long delta = a - b; // can't overflow, since a and b are nonnegative + + long minDeltaOrZero = delta & (delta >> (Long.SIZE - 1)); + // equivalent to Math.min(delta, 0) + + a = delta - minDeltaOrZero - minDeltaOrZero; // sets a to Math.abs(a - b) + // a is now nonnegative and even + + b += minDeltaOrZero; // sets b to min(old a, b) + a >>= Long.numberOfTrailingZeros(a); // divide out all 2s, since 2 doesn't divide b + } + return a << min(aTwos, bTwos); + } + + /** + * Returns the sum of {@code a} and {@code b}, provided it does not overflow. + * + * @throws ArithmeticException if {@code a + b} overflows in signed {@code long} arithmetic + */ + @GwtIncompatible // TODO + public static long checkedAdd(long a, long b) { + long result = a + b; + checkNoOverflow((a ^ b) < 0 | (a ^ result) >= 0, "checkedAdd", a, b); + return result; + } + + /** + * Returns the difference of {@code a} and {@code b}, provided it does not overflow. + * + * @throws ArithmeticException if {@code a - b} overflows in signed {@code long} arithmetic + */ + @GwtIncompatible // TODO + public static long checkedSubtract(long a, long b) { + long result = a - b; + checkNoOverflow((a ^ b) >= 0 | (a ^ result) >= 0, "checkedSubtract", a, b); + return result; + } + + /** + * Returns the product of {@code a} and {@code b}, provided it does not overflow. + * + * @throws ArithmeticException if {@code a * b} overflows in signed {@code long} arithmetic + */ + public static long checkedMultiply(long a, long b) { + // Hacker's Delight, Section 2-12 + int leadingZeros = + Long.numberOfLeadingZeros(a) + + Long.numberOfLeadingZeros(~a) + + Long.numberOfLeadingZeros(b) + + Long.numberOfLeadingZeros(~b); + /* + * If leadingZeros > Long.SIZE + 1 it's definitely fine, if it's < Long.SIZE it's definitely + * bad. We do the leadingZeros check to avoid the division below if at all possible. + * + * Otherwise, if b == Long.MIN_VALUE, then the only allowed values of a are 0 and 1. We take + * care of all a < 0 with their own check, because in particular, the case a == -1 will + * incorrectly pass the division check below. + * + * In all other cases, we check that either a is 0 or the result is consistent with division. + */ + if (leadingZeros > Long.SIZE + 1) { + return a * b; + } + checkNoOverflow(leadingZeros >= Long.SIZE, "checkedMultiply", a, b); + checkNoOverflow(a >= 0 | b != Long.MIN_VALUE, "checkedMultiply", a, b); + long result = a * b; + checkNoOverflow(a == 0 || result / a == b, "checkedMultiply", a, b); + return result; + } + + /** + * Returns the {@code b} to the {@code k}th power, provided it does not overflow. + * + * @throws ArithmeticException if {@code b} to the {@code k}th power overflows in signed {@code + * long} arithmetic + */ + @GwtIncompatible // TODO + public static long checkedPow(long b, int k) { + checkNonNegative("exponent", k); + if (b >= -2 & b <= 2) { + switch ((int) b) { + case 0: + return (k == 0) ? 1 : 0; + case 1: + return 1; + case (-1): + return ((k & 1) == 0) ? 1 : -1; + case 2: + checkNoOverflow(k < Long.SIZE - 1, "checkedPow", b, k); + return 1L << k; + case (-2): + checkNoOverflow(k < Long.SIZE, "checkedPow", b, k); + return ((k & 1) == 0) ? (1L << k) : (-1L << k); + default: + throw new AssertionError(); + } + } + long accum = 1; + while (true) { + switch (k) { + case 0: + return accum; + case 1: + return checkedMultiply(accum, b); + default: + if ((k & 1) != 0) { + accum = checkedMultiply(accum, b); + } + k >>= 1; + if (k > 0) { + checkNoOverflow( + -FLOOR_SQRT_MAX_LONG <= b && b <= FLOOR_SQRT_MAX_LONG, "checkedPow", b, k); + b *= b; + } + } + } + } + + /** + * Returns the sum of {@code a} and {@code b} unless it would overflow or underflow in which case + * {@code Long.MAX_VALUE} or {@code Long.MIN_VALUE} is returned, respectively. + * + * @since 20.0 + */ + @Beta + public static long saturatedAdd(long a, long b) { + long naiveSum = a + b; + if ((a ^ b) < 0 | (a ^ naiveSum) >= 0) { + // If a and b have different signs or a has the same sign as the result then there was no + // overflow, return. + return naiveSum; + } + // we did over/under flow, if the sign is negative we should return MAX otherwise MIN + return Long.MAX_VALUE + ((naiveSum >>> (Long.SIZE - 1)) ^ 1); + } + + /** + * Returns the difference of {@code a} and {@code b} unless it would overflow or underflow in + * which case {@code Long.MAX_VALUE} or {@code Long.MIN_VALUE} is returned, respectively. + * + * @since 20.0 + */ + @Beta + public static long saturatedSubtract(long a, long b) { + long naiveDifference = a - b; + if ((a ^ b) >= 0 | (a ^ naiveDifference) >= 0) { + // If a and b have the same signs or a has the same sign as the result then there was no + // overflow, return. + return naiveDifference; + } + // we did over/under flow + return Long.MAX_VALUE + ((naiveDifference >>> (Long.SIZE - 1)) ^ 1); + } + + /** + * Returns the product of {@code a} and {@code b} unless it would overflow or underflow in which + * case {@code Long.MAX_VALUE} or {@code Long.MIN_VALUE} is returned, respectively. + * + * @since 20.0 + */ + @Beta + public static long saturatedMultiply(long a, long b) { + // see checkedMultiply for explanation + int leadingZeros = + Long.numberOfLeadingZeros(a) + + Long.numberOfLeadingZeros(~a) + + Long.numberOfLeadingZeros(b) + + Long.numberOfLeadingZeros(~b); + if (leadingZeros > Long.SIZE + 1) { + return a * b; + } + // the return value if we will overflow (which we calculate by overflowing a long :) ) + long limit = Long.MAX_VALUE + ((a ^ b) >>> (Long.SIZE - 1)); + if (leadingZeros < Long.SIZE | (a < 0 & b == Long.MIN_VALUE)) { + // overflow + return limit; + } + long result = a * b; + if (a == 0 || result / a == b) { + return result; + } + return limit; + } + + /** + * Returns the {@code b} to the {@code k}th power, unless it would overflow or underflow in which + * case {@code Long.MAX_VALUE} or {@code Long.MIN_VALUE} is returned, respectively. + * + * @since 20.0 + */ + @Beta + public static long saturatedPow(long b, int k) { + checkNonNegative("exponent", k); + if (b >= -2 & b <= 2) { + switch ((int) b) { + case 0: + return (k == 0) ? 1 : 0; + case 1: + return 1; + case (-1): + return ((k & 1) == 0) ? 1 : -1; + case 2: + if (k >= Long.SIZE - 1) { + return Long.MAX_VALUE; + } + return 1L << k; + case (-2): + if (k >= Long.SIZE) { + return Long.MAX_VALUE + (k & 1); + } + return ((k & 1) == 0) ? (1L << k) : (-1L << k); + default: + throw new AssertionError(); + } + } + long accum = 1; + // if b is negative and k is odd then the limit is MIN otherwise the limit is MAX + long limit = Long.MAX_VALUE + ((b >>> Long.SIZE - 1) & (k & 1)); + while (true) { + switch (k) { + case 0: + return accum; + case 1: + return saturatedMultiply(accum, b); + default: + if ((k & 1) != 0) { + accum = saturatedMultiply(accum, b); + } + k >>= 1; + if (k > 0) { + if (-FLOOR_SQRT_MAX_LONG > b | b > FLOOR_SQRT_MAX_LONG) { + return limit; + } + b *= b; + } + } + } + } + + @VisibleForTesting static final long FLOOR_SQRT_MAX_LONG = 3037000499L; + + /** + * Returns {@code n!}, that is, the product of the first {@code n} positive integers, {@code 1} if + * {@code n == 0}, or {@link Long#MAX_VALUE} if the result does not fit in a {@code long}. + * + * @throws IllegalArgumentException if {@code n < 0} + */ + @GwtIncompatible // TODO + public static long factorial(int n) { + checkNonNegative("n", n); + return (n < factorials.length) ? factorials[n] : Long.MAX_VALUE; + } + + static final long[] factorials = { + 1L, + 1L, + 1L * 2, + 1L * 2 * 3, + 1L * 2 * 3 * 4, + 1L * 2 * 3 * 4 * 5, + 1L * 2 * 3 * 4 * 5 * 6, + 1L * 2 * 3 * 4 * 5 * 6 * 7, + 1L * 2 * 3 * 4 * 5 * 6 * 7 * 8, + 1L * 2 * 3 * 4 * 5 * 6 * 7 * 8 * 9, + 1L * 2 * 3 * 4 * 5 * 6 * 7 * 8 * 9 * 10, + 1L * 2 * 3 * 4 * 5 * 6 * 7 * 8 * 9 * 10 * 11, + 1L * 2 * 3 * 4 * 5 * 6 * 7 * 8 * 9 * 10 * 11 * 12, + 1L * 2 * 3 * 4 * 5 * 6 * 7 * 8 * 9 * 10 * 11 * 12 * 13, + 1L * 2 * 3 * 4 * 5 * 6 * 7 * 8 * 9 * 10 * 11 * 12 * 13 * 14, + 1L * 2 * 3 * 4 * 5 * 6 * 7 * 8 * 9 * 10 * 11 * 12 * 13 * 14 * 15, + 1L * 2 * 3 * 4 * 5 * 6 * 7 * 8 * 9 * 10 * 11 * 12 * 13 * 14 * 15 * 16, + 1L * 2 * 3 * 4 * 5 * 6 * 7 * 8 * 9 * 10 * 11 * 12 * 13 * 14 * 15 * 16 * 17, + 1L * 2 * 3 * 4 * 5 * 6 * 7 * 8 * 9 * 10 * 11 * 12 * 13 * 14 * 15 * 16 * 17 * 18, + 1L * 2 * 3 * 4 * 5 * 6 * 7 * 8 * 9 * 10 * 11 * 12 * 13 * 14 * 15 * 16 * 17 * 18 * 19, + 1L * 2 * 3 * 4 * 5 * 6 * 7 * 8 * 9 * 10 * 11 * 12 * 13 * 14 * 15 * 16 * 17 * 18 * 19 * 20 + }; + + /** + * Returns {@code n} choose {@code k}, also known as the binomial coefficient of {@code n} and + * {@code k}, or {@link Long#MAX_VALUE} if the result does not fit in a {@code long}. + * + * @throws IllegalArgumentException if {@code n < 0}, {@code k < 0}, or {@code k > n} + */ + public static long binomial(int n, int k) { + checkNonNegative("n", n); + checkNonNegative("k", k); + checkArgument(k <= n, "k (%s) > n (%s)", k, n); + if (k > (n >> 1)) { + k = n - k; + } + switch (k) { + case 0: + return 1; + case 1: + return n; + default: + if (n < factorials.length) { + return factorials[n] / (factorials[k] * factorials[n - k]); + } else if (k >= biggestBinomials.length || n > biggestBinomials[k]) { + return Long.MAX_VALUE; + } else if (k < biggestSimpleBinomials.length && n <= biggestSimpleBinomials[k]) { + // guaranteed not to overflow + long result = n--; + for (int i = 2; i <= k; n--, i++) { + result *= n; + result /= i; + } + return result; + } else { + int nBits = LongMath.log2(n, RoundingMode.CEILING); + + long result = 1; + long numerator = n--; + long denominator = 1; + + int numeratorBits = nBits; + // This is an upper bound on log2(numerator, ceiling). + + /* + * We want to do this in long math for speed, but want to avoid overflow. We adapt the + * technique previously used by BigIntegerMath: maintain separate numerator and + * denominator accumulators, multiplying the fraction into result when near overflow. + */ + for (int i = 2; i <= k; i++, n--) { + if (numeratorBits + nBits < Long.SIZE - 1) { + // It's definitely safe to multiply into numerator and denominator. + numerator *= n; + denominator *= i; + numeratorBits += nBits; + } else { + // It might not be safe to multiply into numerator and denominator, + // so multiply (numerator / denominator) into result. + result = multiplyFraction(result, numerator, denominator); + numerator = n; + denominator = i; + numeratorBits = nBits; + } + } + return multiplyFraction(result, numerator, denominator); + } + } + } + + /** Returns (x * numerator / denominator), which is assumed to come out to an integral value. */ + static long multiplyFraction(long x, long numerator, long denominator) { + if (x == 1) { + return numerator / denominator; + } + long commonDivisor = gcd(x, denominator); + x /= commonDivisor; + denominator /= commonDivisor; + // We know gcd(x, denominator) = 1, and x * numerator / denominator is exact, + // so denominator must be a divisor of numerator. + return x * (numerator / denominator); + } + + /* + * binomial(biggestBinomials[k], k) fits in a long, but not binomial(biggestBinomials[k] + 1, k). + */ + static final int[] biggestBinomials = { + Integer.MAX_VALUE, + Integer.MAX_VALUE, + Integer.MAX_VALUE, + 3810779, + 121977, + 16175, + 4337, + 1733, + 887, + 534, + 361, + 265, + 206, + 169, + 143, + 125, + 111, + 101, + 94, + 88, + 83, + 79, + 76, + 74, + 72, + 70, + 69, + 68, + 67, + 67, + 66, + 66, + 66, + 66 + }; + + /* + * binomial(biggestSimpleBinomials[k], k) doesn't need to use the slower GCD-based impl, but + * binomial(biggestSimpleBinomials[k] + 1, k) does. + */ + @VisibleForTesting + static final int[] biggestSimpleBinomials = { + Integer.MAX_VALUE, + Integer.MAX_VALUE, + Integer.MAX_VALUE, + 2642246, + 86251, + 11724, + 3218, + 1313, + 684, + 419, + 287, + 214, + 169, + 139, + 119, + 105, + 95, + 87, + 81, + 76, + 73, + 70, + 68, + 66, + 64, + 63, + 62, + 62, + 61, + 61, + 61 + }; + // These values were generated by using checkedMultiply to see when the simple multiply/divide + // algorithm would lead to an overflow. + + static boolean fitsInInt(long x) { + return (int) x == x; + } + + /** + * Returns the arithmetic mean of {@code x} and {@code y}, rounded toward negative infinity. This + * method is resilient to overflow. + * + * @since 14.0 + */ + public static long mean(long x, long y) { + // Efficient method for computing the arithmetic mean. + // The alternative (x + y) / 2 fails for large values. + // The alternative (x + y) >>> 1 fails for negative values. + return (x & y) + ((x ^ y) >> 1); + } + + /* + * This bitmask is used as an optimization for cheaply testing for divisiblity by 2, 3, or 5. + * Each bit is set to 1 for all remainders that indicate divisibility by 2, 3, or 5, so + * 1, 7, 11, 13, 17, 19, 23, 29 are set to 0. 30 and up don't matter because they won't be hit. + */ + private static final int SIEVE_30 = + ~((1 << 1) | (1 << 7) | (1 << 11) | (1 << 13) | (1 << 17) | (1 << 19) | (1 << 23) + | (1 << 29)); + + /** + * Returns {@code true} if {@code n} is a prime number: an integer greater + * than one that cannot be factored into a product of smaller positive integers. + * Returns {@code false} if {@code n} is zero, one, or a composite number (one which can be + * factored into smaller positive integers). + * + *

To test larger numbers, use {@link BigInteger#isProbablePrime}. + * + * @throws IllegalArgumentException if {@code n} is negative + * @since 20.0 + */ + @GwtIncompatible // TODO + @Beta + public static boolean isPrime(long n) { + if (n < 2) { + checkNonNegative("n", n); + return false; + } + if (n == 2 || n == 3 || n == 5 || n == 7 || n == 11 || n == 13) { + return true; + } + + if ((SIEVE_30 & (1 << (n % 30))) != 0) { + return false; + } + if (n % 7 == 0 || n % 11 == 0 || n % 13 == 0) { + return false; + } + if (n < 17 * 17) { + return true; + } + + for (long[] baseSet : millerRabinBaseSets) { + if (n <= baseSet[0]) { + for (int i = 1; i < baseSet.length; i++) { + if (!MillerRabinTester.test(baseSet[i], n)) { + return false; + } + } + return true; + } + } + throw new AssertionError(); + } + + /* + * If n <= millerRabinBases[i][0], then testing n against bases millerRabinBases[i][1..] suffices + * to prove its primality. Values from miller-rabin.appspot.com. + * + * NOTE: We could get slightly better bases that would be treated as unsigned, but benchmarks + * showed negligible performance improvements. + */ + private static final long[][] millerRabinBaseSets = { + {291830, 126401071349994536L}, + {885594168, 725270293939359937L, 3569819667048198375L}, + {273919523040L, 15, 7363882082L, 992620450144556L}, + {47636622961200L, 2, 2570940, 211991001, 3749873356L}, + { + 7999252175582850L, + 2, + 4130806001517L, + 149795463772692060L, + 186635894390467037L, + 3967304179347715805L + }, + { + 585226005592931976L, + 2, + 123635709730000L, + 9233062284813009L, + 43835965440333360L, + 761179012939631437L, + 1263739024124850375L + }, + {Long.MAX_VALUE, 2, 325, 9375, 28178, 450775, 9780504, 1795265022} + }; + + private enum MillerRabinTester { + /** Works for inputs ≤ FLOOR_SQRT_MAX_LONG. */ + SMALL { + @Override + long mulMod(long a, long b, long m) { + /* + * NOTE(lowasser, 2015-Feb-12): Benchmarks suggest that changing this to + * UnsignedLongs.remainder and increasing the threshold to 2^32 doesn't pay for itself, and + * adding another enum constant hurts performance further -- I suspect because bimorphic + * implementation is a sweet spot for the JVM. + */ + return (a * b) % m; + } + + @Override + long squareMod(long a, long m) { + return (a * a) % m; + } + }, + /** Works for all nonnegative signed longs. */ + LARGE { + /** Returns (a + b) mod m. Precondition: {@code 0 <= a}, {@code b < m < 2^63}. */ + private long plusMod(long a, long b, long m) { + return (a >= m - b) ? (a + b - m) : (a + b); + } + + /** Returns (a * 2^32) mod m. a may be any unsigned long. */ + private long times2ToThe32Mod(long a, long m) { + int remainingPowersOf2 = 32; + do { + int shift = Math.min(remainingPowersOf2, Long.numberOfLeadingZeros(a)); + // shift is either the number of powers of 2 left to multiply a by, or the biggest shift + // possible while keeping a in an unsigned long. + a = UnsignedLongs.remainder(a << shift, m); + remainingPowersOf2 -= shift; + } while (remainingPowersOf2 > 0); + return a; + } + + @Override + long mulMod(long a, long b, long m) { + long aHi = a >>> 32; // < 2^31 + long bHi = b >>> 32; // < 2^31 + long aLo = a & 0xFFFFFFFFL; // < 2^32 + long bLo = b & 0xFFFFFFFFL; // < 2^32 + + /* + * a * b == aHi * bHi * 2^64 + (aHi * bLo + aLo * bHi) * 2^32 + aLo * bLo. + * == (aHi * bHi * 2^32 + aHi * bLo + aLo * bHi) * 2^32 + aLo * bLo + * + * We carry out this computation in modular arithmetic. Since times2ToThe32Mod accepts any + * unsigned long, we don't have to do a mod on every operation, only when intermediate + * results can exceed 2^63. + */ + long result = times2ToThe32Mod(aHi * bHi /* < 2^62 */, m); // < m < 2^63 + result += aHi * bLo; // aHi * bLo < 2^63, result < 2^64 + if (result < 0) { + result = UnsignedLongs.remainder(result, m); + } + // result < 2^63 again + result += aLo * bHi; // aLo * bHi < 2^63, result < 2^64 + result = times2ToThe32Mod(result, m); // result < m < 2^63 + return plusMod(result, UnsignedLongs.remainder(aLo * bLo /* < 2^64 */, m), m); + } + + @Override + long squareMod(long a, long m) { + long aHi = a >>> 32; // < 2^31 + long aLo = a & 0xFFFFFFFFL; // < 2^32 + + /* + * a^2 == aHi^2 * 2^64 + aHi * aLo * 2^33 + aLo^2 + * == (aHi^2 * 2^32 + aHi * aLo * 2) * 2^32 + aLo^2 + * We carry out this computation in modular arithmetic. Since times2ToThe32Mod accepts any + * unsigned long, we don't have to do a mod on every operation, only when intermediate + * results can exceed 2^63. + */ + long result = times2ToThe32Mod(aHi * aHi /* < 2^62 */, m); // < m < 2^63 + long hiLo = aHi * aLo * 2; + if (hiLo < 0) { + hiLo = UnsignedLongs.remainder(hiLo, m); + } + // hiLo < 2^63 + result += hiLo; // result < 2^64 + result = times2ToThe32Mod(result, m); // result < m < 2^63 + return plusMod(result, UnsignedLongs.remainder(aLo * aLo /* < 2^64 */, m), m); + } + }; + + static boolean test(long base, long n) { + // Since base will be considered % n, it's okay if base > FLOOR_SQRT_MAX_LONG, + // so long as n <= FLOOR_SQRT_MAX_LONG. + return ((n <= FLOOR_SQRT_MAX_LONG) ? SMALL : LARGE).testWitness(base, n); + } + + /** Returns a * b mod m. */ + abstract long mulMod(long a, long b, long m); + + /** Returns a^2 mod m. */ + abstract long squareMod(long a, long m); + + /** Returns a^p mod m. */ + private long powMod(long a, long p, long m) { + long res = 1; + for (; p != 0; p >>= 1) { + if ((p & 1) != 0) { + res = mulMod(res, a, m); + } + a = squareMod(a, m); + } + return res; + } + + /** Returns true if n is a strong probable prime relative to the specified base. */ + private boolean testWitness(long base, long n) { + int r = Long.numberOfTrailingZeros(n - 1); + long d = (n - 1) >> r; + base %= n; + if (base == 0) { + return true; + } + // Calculate a := base^d mod n. + long a = powMod(base, d, n); + // n passes this test if + // base^d = 1 (mod n) + // or base^(2^j * d) = -1 (mod n) for some 0 <= j < r. + if (a == 1) { + return true; + } + int j = 0; + while (a != n - 1) { + if (++j == r) { + return false; + } + a = squareMod(a, n); + } + return true; + } + } + + private LongMath() {} +} diff --git a/src/main/java/com/google/common/math/MathPreconditions.java b/src/main/java/com/google/common/math/MathPreconditions.java new file mode 100644 index 0000000..b30dd05 --- /dev/null +++ b/src/main/java/com/google/common/math/MathPreconditions.java @@ -0,0 +1,106 @@ +/* + * Copyright (C) 2011 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.math; + +import com.google.common.annotations.GwtCompatible; + +import java.math.BigInteger; +import java.math.RoundingMode; + + +/** + * A collection of preconditions for math functions. + * + * @author Louis Wasserman + */ +@GwtCompatible + +final class MathPreconditions { + static int checkPositive(String role, int x) { + if (x <= 0) { + throw new IllegalArgumentException(role + " (" + x + ") must be > 0"); + } + return x; + } + + static long checkPositive(String role, long x) { + if (x <= 0) { + throw new IllegalArgumentException(role + " (" + x + ") must be > 0"); + } + return x; + } + + static BigInteger checkPositive(String role, BigInteger x) { + if (x.signum() <= 0) { + throw new IllegalArgumentException(role + " (" + x + ") must be > 0"); + } + return x; + } + + static int checkNonNegative(String role, int x) { + if (x < 0) { + throw new IllegalArgumentException(role + " (" + x + ") must be >= 0"); + } + return x; + } + + static long checkNonNegative(String role, long x) { + if (x < 0) { + throw new IllegalArgumentException(role + " (" + x + ") must be >= 0"); + } + return x; + } + + static BigInteger checkNonNegative(String role, BigInteger x) { + if (x.signum() < 0) { + throw new IllegalArgumentException(role + " (" + x + ") must be >= 0"); + } + return x; + } + + static double checkNonNegative(String role, double x) { + if (!(x >= 0)) { // not x < 0, to work with NaN. + throw new IllegalArgumentException(role + " (" + x + ") must be >= 0"); + } + return x; + } + + static void checkRoundingUnnecessary(boolean condition) { + if (!condition) { + throw new ArithmeticException("mode was UNNECESSARY, but rounding was necessary"); + } + } + + static void checkInRangeForRoundingInputs(boolean condition, double input, RoundingMode mode) { + if (!condition) { + throw new ArithmeticException( + "rounded value is out of range for input " + input + " and rounding mode " + mode); + } + } + + static void checkNoOverflow(boolean condition, String methodName, int a, int b) { + if (!condition) { + throw new ArithmeticException("overflow: " + methodName + "(" + a + ", " + b + ")"); + } + } + + static void checkNoOverflow(boolean condition, String methodName, long a, long b) { + if (!condition) { + throw new ArithmeticException("overflow: " + methodName + "(" + a + ", " + b + ")"); + } + } + + private MathPreconditions() {} +} diff --git a/src/main/java/com/google/common/math/PairedStats.java b/src/main/java/com/google/common/math/PairedStats.java new file mode 100644 index 0000000..58e82d0 --- /dev/null +++ b/src/main/java/com/google/common/math/PairedStats.java @@ -0,0 +1,319 @@ +/* + * Copyright (C) 2012 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.math; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.base.Preconditions.checkState; +import static java.lang.Double.NaN; +import static java.lang.Double.doubleToLongBits; +import static java.lang.Double.isNaN; + +import com.google.common.annotations.Beta; +import com.google.common.annotations.GwtIncompatible; +import com.google.common.base.MoreObjects; +import com.google.common.base.Objects; +import java.io.Serializable; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + + +/** + * An immutable value object capturing some basic statistics about a collection of paired double + * values (e.g. points on a plane). Build instances with {@link PairedStatsAccumulator#snapshot}. + * + * @author Pete Gillin + * @since 20.0 + */ +@Beta +@GwtIncompatible +public final class PairedStats implements Serializable { + + private final Stats xStats; + private final Stats yStats; + private final double sumOfProductsOfDeltas; + + /** + * Internal constructor. Users should use {@link PairedStatsAccumulator#snapshot}. + * + *

To ensure that the created instance obeys its contract, the parameters should satisfy the + * following constraints. This is the callers responsibility and is not enforced here. + * + *

    + *
  • Both {@code xStats} and {@code yStats} must have the same {@code count}. + *
  • If that {@code count} is 1, {@code sumOfProductsOfDeltas} must be exactly 0.0. + *
  • If that {@code count} is more than 1, {@code sumOfProductsOfDeltas} must be finite. + *
+ */ + PairedStats(Stats xStats, Stats yStats, double sumOfProductsOfDeltas) { + this.xStats = xStats; + this.yStats = yStats; + this.sumOfProductsOfDeltas = sumOfProductsOfDeltas; + } + + /** Returns the number of pairs in the dataset. */ + public long count() { + return xStats.count(); + } + + /** Returns the statistics on the {@code x} values alone. */ + public Stats xStats() { + return xStats; + } + + /** Returns the statistics on the {@code y} values alone. */ + public Stats yStats() { + return yStats; + } + + /** + * Returns the population covariance of the values. The count must be non-zero. + * + *

This is guaranteed to return zero if the dataset contains a single pair of finite values. It + * is not guaranteed to return zero when the dataset consists of the same pair of values multiple + * times, due to numerical errors. + * + *

Non-finite values

+ * + *

If the dataset contains any non-finite values ({@link Double#POSITIVE_INFINITY}, {@link + * Double#NEGATIVE_INFINITY}, or {@link Double#NaN}) then the result is {@link Double#NaN}. + * + * @throws IllegalStateException if the dataset is empty + */ + public double populationCovariance() { + checkState(count() != 0); + return sumOfProductsOfDeltas / count(); + } + + /** + * Returns the sample covariance of the values. The count must be greater than one. + * + *

This is not guaranteed to return zero when the dataset consists of the same pair of values + * multiple times, due to numerical errors. + * + *

Non-finite values

+ * + *

If the dataset contains any non-finite values ({@link Double#POSITIVE_INFINITY}, {@link + * Double#NEGATIVE_INFINITY}, or {@link Double#NaN}) then the result is {@link Double#NaN}. + * + * @throws IllegalStateException if the dataset is empty or contains a single pair of values + */ + public double sampleCovariance() { + checkState(count() > 1); + return sumOfProductsOfDeltas / (count() - 1); + } + + /** + * Returns the Pearson's or + * product-moment correlation coefficient of the values. The count must greater than one, and + * the {@code x} and {@code y} values must both have non-zero population variance (i.e. {@code + * xStats().populationVariance() > 0.0 && yStats().populationVariance() > 0.0}). The result is not + * guaranteed to be exactly +/-1 even when the data are perfectly (anti-)correlated, due to + * numerical errors. However, it is guaranteed to be in the inclusive range [-1, +1]. + * + *

Non-finite values

+ * + *

If the dataset contains any non-finite values ({@link Double#POSITIVE_INFINITY}, {@link + * Double#NEGATIVE_INFINITY}, or {@link Double#NaN}) then the result is {@link Double#NaN}. + * + * @throws IllegalStateException if the dataset is empty or contains a single pair of values, or + * either the {@code x} and {@code y} dataset has zero population variance + */ + public double pearsonsCorrelationCoefficient() { + checkState(count() > 1); + if (isNaN(sumOfProductsOfDeltas)) { + return NaN; + } + double xSumOfSquaresOfDeltas = xStats().sumOfSquaresOfDeltas(); + double ySumOfSquaresOfDeltas = yStats().sumOfSquaresOfDeltas(); + checkState(xSumOfSquaresOfDeltas > 0.0); + checkState(ySumOfSquaresOfDeltas > 0.0); + // The product of two positive numbers can be zero if the multiplication underflowed. We + // force a positive value by effectively rounding up to MIN_VALUE. + double productOfSumsOfSquaresOfDeltas = + ensurePositive(xSumOfSquaresOfDeltas * ySumOfSquaresOfDeltas); + return ensureInUnitRange(sumOfProductsOfDeltas / Math.sqrt(productOfSumsOfSquaresOfDeltas)); + } + + /** + * Returns a linear transformation giving the best fit to the data according to Ordinary Least Squares linear + * regression of {@code y} as a function of {@code x}. The count must be greater than one, and + * either the {@code x} or {@code y} data must have a non-zero population variance (i.e. {@code + * xStats().populationVariance() > 0.0 || yStats().populationVariance() > 0.0}). The result is + * guaranteed to be horizontal if there is variance in the {@code x} data but not the {@code y} + * data, and vertical if there is variance in the {@code y} data but not the {@code x} data. + * + *

This fit minimizes the root-mean-square error in {@code y} as a function of {@code x}. This + * error is defined as the square root of the mean of the squares of the differences between the + * actual {@code y} values of the data and the values predicted by the fit for the {@code x} + * values (i.e. it is the square root of the mean of the squares of the vertical distances between + * the data points and the best fit line). For this fit, this error is a fraction {@code sqrt(1 - + * R*R)} of the population standard deviation of {@code y}, where {@code R} is the Pearson's + * correlation coefficient (as given by {@link #pearsonsCorrelationCoefficient()}). + * + *

The corresponding root-mean-square error in {@code x} as a function of {@code y} is a + * fraction {@code sqrt(1/(R*R) - 1)} of the population standard deviation of {@code x}. This fit + * does not normally minimize that error: to do that, you should swap the roles of {@code x} and + * {@code y}. + * + *

Non-finite values

+ * + *

If the dataset contains any non-finite values ({@link Double#POSITIVE_INFINITY}, {@link + * Double#NEGATIVE_INFINITY}, or {@link Double#NaN}) then the result is {@link + * LinearTransformation#forNaN()}. + * + * @throws IllegalStateException if the dataset is empty or contains a single pair of values, or + * both the {@code x} and {@code y} dataset must have zero population variance + */ + public LinearTransformation leastSquaresFit() { + checkState(count() > 1); + if (isNaN(sumOfProductsOfDeltas)) { + return LinearTransformation.forNaN(); + } + double xSumOfSquaresOfDeltas = xStats.sumOfSquaresOfDeltas(); + if (xSumOfSquaresOfDeltas > 0.0) { + if (yStats.sumOfSquaresOfDeltas() > 0.0) { + return LinearTransformation.mapping(xStats.mean(), yStats.mean()) + .withSlope(sumOfProductsOfDeltas / xSumOfSquaresOfDeltas); + } else { + return LinearTransformation.horizontal(yStats.mean()); + } + } else { + checkState(yStats.sumOfSquaresOfDeltas() > 0.0); + return LinearTransformation.vertical(xStats.mean()); + } + } + + /** + * {@inheritDoc} + * + *

Note: This tests exact equality of the calculated statistics, including the floating + * point values. Two instances are guaranteed to be considered equal if one is copied from the + * other using {@code second = new PairedStatsAccumulator().addAll(first).snapshot()}, if both + * were obtained by calling {@code snapshot()} on the same {@link PairedStatsAccumulator} without + * adding any values in between the two calls, or if one is obtained from the other after + * round-tripping through java serialization. However, floating point rounding errors mean that it + * may be false for some instances where the statistics are mathematically equal, including + * instances constructed from the same values in a different order... or (in the general case) + * even in the same order. (It is guaranteed to return true for instances constructed from the + * same values in the same order if {@code strictfp} is in effect, or if the system architecture + * guarantees {@code strictfp}-like semantics.) + */ + @Override + public boolean equals(Object obj) { + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + PairedStats other = (PairedStats) obj; + return xStats.equals(other.xStats) + && yStats.equals(other.yStats) + && doubleToLongBits(sumOfProductsOfDeltas) == doubleToLongBits(other.sumOfProductsOfDeltas); + } + + /** + * {@inheritDoc} + * + *

Note: This hash code is consistent with exact equality of the calculated statistics, + * including the floating point values. See the note on {@link #equals} for details. + */ + @Override + public int hashCode() { + return Objects.hashCode(xStats, yStats, sumOfProductsOfDeltas); + } + + @Override + public String toString() { + if (count() > 0) { + return MoreObjects.toStringHelper(this) + .add("xStats", xStats) + .add("yStats", yStats) + .add("populationCovariance", populationCovariance()) + .toString(); + } else { + return MoreObjects.toStringHelper(this) + .add("xStats", xStats) + .add("yStats", yStats) + .toString(); + } + } + + double sumOfProductsOfDeltas() { + return sumOfProductsOfDeltas; + } + + private static double ensurePositive(double value) { + if (value > 0.0) { + return value; + } else { + return Double.MIN_VALUE; + } + } + + private static double ensureInUnitRange(double value) { + if (value >= 1.0) { + return 1.0; + } + if (value <= -1.0) { + return -1.0; + } + return value; + } + + // Serialization helpers + + /** The size of byte array representation in bytes. */ + private static final int BYTES = Stats.BYTES * 2 + Double.SIZE / Byte.SIZE; + + /** + * Gets a byte array representation of this instance. + * + *

Note: No guarantees are made regarding stability of the representation between + * versions. + */ + public byte[] toByteArray() { + ByteBuffer buffer = ByteBuffer.allocate(BYTES).order(ByteOrder.LITTLE_ENDIAN); + xStats.writeTo(buffer); + yStats.writeTo(buffer); + buffer.putDouble(sumOfProductsOfDeltas); + return buffer.array(); + } + + /** + * Creates a {@link PairedStats} instance from the given byte representation which was obtained by + * {@link #toByteArray}. + * + *

Note: No guarantees are made regarding stability of the representation between + * versions. + */ + public static PairedStats fromByteArray(byte[] byteArray) { + checkNotNull(byteArray); + checkArgument( + byteArray.length == BYTES, + "Expected PairedStats.BYTES = %s, got %s", + BYTES, + byteArray.length); + ByteBuffer buffer = ByteBuffer.wrap(byteArray).order(ByteOrder.LITTLE_ENDIAN); + Stats xStats = Stats.readFrom(buffer); + Stats yStats = Stats.readFrom(buffer); + double sumOfProductsOfDeltas = buffer.getDouble(); + return new PairedStats(xStats, yStats, sumOfProductsOfDeltas); + } + + private static final long serialVersionUID = 0; +} diff --git a/src/main/java/com/google/common/math/PairedStatsAccumulator.java b/src/main/java/com/google/common/math/PairedStatsAccumulator.java new file mode 100644 index 0000000..a988495 --- /dev/null +++ b/src/main/java/com/google/common/math/PairedStatsAccumulator.java @@ -0,0 +1,242 @@ +/* + * Copyright (C) 2012 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.math; + +import static com.google.common.base.Preconditions.checkState; +import static com.google.common.primitives.Doubles.isFinite; +import static java.lang.Double.NaN; +import static java.lang.Double.isNaN; + +import com.google.common.annotations.Beta; +import com.google.common.annotations.GwtIncompatible; +import com.google.common.primitives.Doubles; + +/** + * A mutable object which accumulates paired double values (e.g. points on a plane) and tracks some + * basic statistics over all the values added so far. This class is not thread safe. + * + * @author Pete Gillin + * @since 20.0 + */ +@Beta +@GwtIncompatible +public final class PairedStatsAccumulator { + + // These fields must satisfy the requirements of PairedStats' constructor as well as those of the + // stat methods of this class. + private final StatsAccumulator xStats = new StatsAccumulator(); + private final StatsAccumulator yStats = new StatsAccumulator(); + private double sumOfProductsOfDeltas = 0.0; + + /** Adds the given pair of values to the dataset. */ + public void add(double x, double y) { + // We extend the recursive expression for the one-variable case at Art of Computer Programming + // vol. 2, Knuth, 4.2.2, (16) to the two-variable case. We have two value series x_i and y_i. + // We define the arithmetic means X_n = 1/n \sum_{i=1}^n x_i, and Y_n = 1/n \sum_{i=1}^n y_i. + // We also define the sum of the products of the differences from the means + // C_n = \sum_{i=1}^n x_i y_i - n X_n Y_n + // for all n >= 1. Then for all n > 1: + // C_{n-1} = \sum_{i=1}^{n-1} x_i y_i - (n-1) X_{n-1} Y_{n-1} + // C_n - C_{n-1} = x_n y_n - n X_n Y_n + (n-1) X_{n-1} Y_{n-1} + // = x_n y_n - X_n [ y_n + (n-1) Y_{n-1} ] + [ n X_n - x_n ] Y_{n-1} + // = x_n y_n - X_n y_n - x_n Y_{n-1} + X_n Y_{n-1} + // = (x_n - X_n) (y_n - Y_{n-1}) + xStats.add(x); + if (isFinite(x) && isFinite(y)) { + if (xStats.count() > 1) { + sumOfProductsOfDeltas += (x - xStats.mean()) * (y - yStats.mean()); + } + } else { + sumOfProductsOfDeltas = NaN; + } + yStats.add(y); + } + + /** + * Adds the given statistics to the dataset, as if the individual values used to compute the + * statistics had been added directly. + */ + public void addAll(PairedStats values) { + if (values.count() == 0) { + return; + } + + xStats.addAll(values.xStats()); + if (yStats.count() == 0) { + sumOfProductsOfDeltas = values.sumOfProductsOfDeltas(); + } else { + // This is a generalized version of the calculation in add(double, double) above. Note that + // non-finite inputs will have sumOfProductsOfDeltas = NaN, so non-finite values will result + // in NaN naturally. + sumOfProductsOfDeltas += + values.sumOfProductsOfDeltas() + + (values.xStats().mean() - xStats.mean()) + * (values.yStats().mean() - yStats.mean()) + * values.count(); + } + yStats.addAll(values.yStats()); + } + + /** Returns an immutable snapshot of the current statistics. */ + public PairedStats snapshot() { + return new PairedStats(xStats.snapshot(), yStats.snapshot(), sumOfProductsOfDeltas); + } + + /** Returns the number of pairs in the dataset. */ + public long count() { + return xStats.count(); + } + + /** Returns an immutable snapshot of the statistics on the {@code x} values alone. */ + public Stats xStats() { + return xStats.snapshot(); + } + + /** Returns an immutable snapshot of the statistics on the {@code y} values alone. */ + public Stats yStats() { + return yStats.snapshot(); + } + + /** + * Returns the population covariance of the values. The count must be non-zero. + * + *

This is guaranteed to return zero if the dataset contains a single pair of finite values. It + * is not guaranteed to return zero when the dataset consists of the same pair of values multiple + * times, due to numerical errors. + * + *

Non-finite values

+ * + *

If the dataset contains any non-finite values ({@link Double#POSITIVE_INFINITY}, {@link + * Double#NEGATIVE_INFINITY}, or {@link Double#NaN}) then the result is {@link Double#NaN}. + * + * @throws IllegalStateException if the dataset is empty + */ + public double populationCovariance() { + checkState(count() != 0); + return sumOfProductsOfDeltas / count(); + } + + /** + * Returns the sample covariance of the values. The count must be greater than one. + * + *

This is not guaranteed to return zero when the dataset consists of the same pair of values + * multiple times, due to numerical errors. + * + *

Non-finite values

+ * + *

If the dataset contains any non-finite values ({@link Double#POSITIVE_INFINITY}, {@link + * Double#NEGATIVE_INFINITY}, or {@link Double#NaN}) then the result is {@link Double#NaN}. + * + * @throws IllegalStateException if the dataset is empty or contains a single pair of values + */ + public final double sampleCovariance() { + checkState(count() > 1); + return sumOfProductsOfDeltas / (count() - 1); + } + + /** + * Returns the Pearson's or + * product-moment correlation coefficient of the values. The count must greater than one, and + * the {@code x} and {@code y} values must both have non-zero population variance (i.e. {@code + * xStats().populationVariance() > 0.0 && yStats().populationVariance() > 0.0}). The result is not + * guaranteed to be exactly +/-1 even when the data are perfectly (anti-)correlated, due to + * numerical errors. However, it is guaranteed to be in the inclusive range [-1, +1]. + * + *

Non-finite values

+ * + *

If the dataset contains any non-finite values ({@link Double#POSITIVE_INFINITY}, {@link + * Double#NEGATIVE_INFINITY}, or {@link Double#NaN}) then the result is {@link Double#NaN}. + * + * @throws IllegalStateException if the dataset is empty or contains a single pair of values, or + * either the {@code x} and {@code y} dataset has zero population variance + */ + public final double pearsonsCorrelationCoefficient() { + checkState(count() > 1); + if (isNaN(sumOfProductsOfDeltas)) { + return NaN; + } + double xSumOfSquaresOfDeltas = xStats.sumOfSquaresOfDeltas(); + double ySumOfSquaresOfDeltas = yStats.sumOfSquaresOfDeltas(); + checkState(xSumOfSquaresOfDeltas > 0.0); + checkState(ySumOfSquaresOfDeltas > 0.0); + // The product of two positive numbers can be zero if the multiplication underflowed. We + // force a positive value by effectively rounding up to MIN_VALUE. + double productOfSumsOfSquaresOfDeltas = + ensurePositive(xSumOfSquaresOfDeltas * ySumOfSquaresOfDeltas); + return ensureInUnitRange(sumOfProductsOfDeltas / Math.sqrt(productOfSumsOfSquaresOfDeltas)); + } + + /** + * Returns a linear transformation giving the best fit to the data according to Ordinary Least Squares linear + * regression of {@code y} as a function of {@code x}. The count must be greater than one, and + * either the {@code x} or {@code y} data must have a non-zero population variance (i.e. {@code + * xStats().populationVariance() > 0.0 || yStats().populationVariance() > 0.0}). The result is + * guaranteed to be horizontal if there is variance in the {@code x} data but not the {@code y} + * data, and vertical if there is variance in the {@code y} data but not the {@code x} data. + * + *

This fit minimizes the root-mean-square error in {@code y} as a function of {@code x}. This + * error is defined as the square root of the mean of the squares of the differences between the + * actual {@code y} values of the data and the values predicted by the fit for the {@code x} + * values (i.e. it is the square root of the mean of the squares of the vertical distances between + * the data points and the best fit line). For this fit, this error is a fraction {@code sqrt(1 - + * R*R)} of the population standard deviation of {@code y}, where {@code R} is the Pearson's + * correlation coefficient (as given by {@link #pearsonsCorrelationCoefficient()}). + * + *

The corresponding root-mean-square error in {@code x} as a function of {@code y} is a + * fraction {@code sqrt(1/(R*R) - 1)} of the population standard deviation of {@code x}. This fit + * does not normally minimize that error: to do that, you should swap the roles of {@code x} and + * {@code y}. + * + *

Non-finite values

+ * + *

If the dataset contains any non-finite values ({@link Double#POSITIVE_INFINITY}, {@link + * Double#NEGATIVE_INFINITY}, or {@link Double#NaN}) then the result is {@link + * LinearTransformation#forNaN()}. + * + * @throws IllegalStateException if the dataset is empty or contains a single pair of values, or + * both the {@code x} and {@code y} dataset have zero population variance + */ + public final LinearTransformation leastSquaresFit() { + checkState(count() > 1); + if (isNaN(sumOfProductsOfDeltas)) { + return LinearTransformation.forNaN(); + } + double xSumOfSquaresOfDeltas = xStats.sumOfSquaresOfDeltas(); + if (xSumOfSquaresOfDeltas > 0.0) { + if (yStats.sumOfSquaresOfDeltas() > 0.0) { + return LinearTransformation.mapping(xStats.mean(), yStats.mean()) + .withSlope(sumOfProductsOfDeltas / xSumOfSquaresOfDeltas); + } else { + return LinearTransformation.horizontal(yStats.mean()); + } + } else { + checkState(yStats.sumOfSquaresOfDeltas() > 0.0); + return LinearTransformation.vertical(xStats.mean()); + } + } + + private double ensurePositive(double value) { + if (value > 0.0) { + return value; + } else { + return Double.MIN_VALUE; + } + } + + private static double ensureInUnitRange(double value) { + return Doubles.constrainToRange(value, -1.0, 1.0); + } +} diff --git a/src/main/java/com/google/common/math/Quantiles.java b/src/main/java/com/google/common/math/Quantiles.java new file mode 100644 index 0000000..7aac58f --- /dev/null +++ b/src/main/java/com/google/common/math/Quantiles.java @@ -0,0 +1,698 @@ +/* + * Copyright (C) 2014 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.math; + +import static com.google.common.base.Preconditions.checkArgument; +import static java.lang.Double.NEGATIVE_INFINITY; +import static java.lang.Double.NaN; +import static java.lang.Double.POSITIVE_INFINITY; +import static java.util.Arrays.sort; +import static java.util.Collections.unmodifiableMap; + +import com.google.common.annotations.Beta; +import com.google.common.annotations.GwtIncompatible; +import com.google.common.primitives.Doubles; +import com.google.common.primitives.Ints; +import java.math.RoundingMode; +import java.util.Collection; +import java.util.LinkedHashMap; +import java.util.Map; + +/** + * Provides a fluent API for calculating quantiles. + * + *

Examples

+ * + *

To compute the median: + * + *

{@code
+ * double myMedian = median().compute(myDataset);
+ * }
+ * + * where {@link #median()} has been statically imported. + * + *

To compute the 99th percentile: + * + *

{@code
+ * double myPercentile99 = percentiles().index(99).compute(myDataset);
+ * }
+ * + * where {@link #percentiles()} has been statically imported. + * + *

To compute median and the 90th and 99th percentiles: + * + *

{@code
+ * Map myPercentiles =
+ *     percentiles().indexes(50, 90, 99).compute(myDataset);
+ * }
+ * + * where {@link #percentiles()} has been statically imported: {@code myPercentiles} maps the keys + * 50, 90, and 99, to their corresponding quantile values. + * + *

To compute quartiles, use {@link #quartiles()} instead of {@link #percentiles()}. To compute + * arbitrary q-quantiles, use {@link #scale scale(q)}. + * + *

These examples all take a copy of your dataset. If you have a double array, you are okay with + * it being arbitrarily reordered, and you want to avoid that copy, you can use {@code + * computeInPlace} instead of {@code compute}. + * + *

Definition and notes on interpolation

+ * + *

The definition of the kth q-quantile of N values is as follows: define x = k * (N - 1) / q; if + * x is an integer, the result is the value which would appear at index x in the sorted dataset + * (unless there are {@link Double#NaN NaN} values, see below); otherwise, the result is the average + * of the values which would appear at the indexes floor(x) and ceil(x) weighted by (1-frac(x)) and + * frac(x) respectively. This is the same definition as used by Excel and by S, it is the Type 7 + * definition in R, and it is + * described by + * wikipedia as providing "Linear interpolation of the modes for the order statistics for the + * uniform distribution on [0,1]." + * + *

Handling of non-finite values

+ * + *

If any values in the input are {@link Double#NaN NaN} then all values returned are {@link + * Double#NaN NaN}. (This is the one occasion when the behaviour is not the same as you'd get from + * sorting with {@link java.util.Arrays#sort(double[]) Arrays.sort(double[])} or {@link + * java.util.Collections#sort(java.util.List) Collections.sort(List<Double>)} and selecting + * the required value(s). Those methods would sort {@link Double#NaN NaN} as if it is greater than + * any other value and place them at the end of the dataset, even after {@link + * Double#POSITIVE_INFINITY POSITIVE_INFINITY}.) + * + *

Otherwise, {@link Double#NEGATIVE_INFINITY NEGATIVE_INFINITY} and {@link + * Double#POSITIVE_INFINITY POSITIVE_INFINITY} sort to the beginning and the end of the dataset, as + * you would expect. + * + *

If required to do a weighted average between an infinity and a finite value, or between an + * infinite value and itself, the infinite value is returned. If required to do a weighted average + * between {@link Double#NEGATIVE_INFINITY NEGATIVE_INFINITY} and {@link Double#POSITIVE_INFINITY + * POSITIVE_INFINITY}, {@link Double#NaN NaN} is returned (note that this will only happen if the + * dataset contains no finite values). + * + *

Performance

+ * + *

The average time complexity of the computation is O(N) in the size of the dataset. There is a + * worst case time complexity of O(N^2). You are extremely unlikely to hit this quadratic case on + * randomly ordered data (the probability decreases faster than exponentially in N), but if you are + * passing in unsanitized user data then a malicious user could force it. A light shuffle of the + * data using an unpredictable seed should normally be enough to thwart this attack. + * + *

The time taken to compute multiple quantiles on the same dataset using {@link Scale#indexes + * indexes} is generally less than the total time taken to compute each of them separately, and + * sometimes much less. For example, on a large enough dataset, computing the 90th and 99th + * percentiles together takes about 55% as long as computing them separately. + * + *

When calling {@link ScaleAndIndex#compute} (in {@linkplain ScaleAndIndexes#compute either + * form}), the memory requirement is 8*N bytes for the copy of the dataset plus an overhead which is + * independent of N (but depends on the quantiles being computed). When calling {@link + * ScaleAndIndex#computeInPlace computeInPlace} (in {@linkplain ScaleAndIndexes#computeInPlace + * either form}), only the overhead is required. The number of object allocations is independent of + * N in both cases. + * + * @author Pete Gillin + * @since 20.0 + */ +@Beta +@GwtIncompatible +public final class Quantiles { + + /** Specifies the computation of a median (i.e. the 1st 2-quantile). */ + public static ScaleAndIndex median() { + return scale(2).index(1); + } + + /** Specifies the computation of quartiles (i.e. 4-quantiles). */ + public static Scale quartiles() { + return scale(4); + } + + /** Specifies the computation of percentiles (i.e. 100-quantiles). */ + public static Scale percentiles() { + return scale(100); + } + + /** + * Specifies the computation of q-quantiles. + * + * @param scale the scale for the quantiles to be calculated, i.e. the q of the q-quantiles, which + * must be positive + */ + public static Scale scale(int scale) { + return new Scale(scale); + } + + /** + * Describes the point in a fluent API chain where only the scale (i.e. the q in q-quantiles) has + * been specified. + * + * @since 20.0 + */ + public static final class Scale { + + private final int scale; + + private Scale(int scale) { + checkArgument(scale > 0, "Quantile scale must be positive"); + this.scale = scale; + } + + /** + * Specifies a single quantile index to be calculated, i.e. the k in the kth q-quantile. + * + * @param index the quantile index, which must be in the inclusive range [0, q] for q-quantiles + */ + public ScaleAndIndex index(int index) { + return new ScaleAndIndex(scale, index); + } + + /** + * Specifies multiple quantile indexes to be calculated, each index being the k in the kth + * q-quantile. + * + * @param indexes the quantile indexes, each of which must be in the inclusive range [0, q] for + * q-quantiles; the order of the indexes is unimportant, duplicates will be ignored, and the + * set will be snapshotted when this method is called + * @throws IllegalArgumentException if {@code indexes} is empty + */ + public ScaleAndIndexes indexes(int... indexes) { + return new ScaleAndIndexes(scale, indexes.clone()); + } + + /** + * Specifies multiple quantile indexes to be calculated, each index being the k in the kth + * q-quantile. + * + * @param indexes the quantile indexes, each of which must be in the inclusive range [0, q] for + * q-quantiles; the order of the indexes is unimportant, duplicates will be ignored, and the + * set will be snapshotted when this method is called + * @throws IllegalArgumentException if {@code indexes} is empty + */ + public ScaleAndIndexes indexes(Collection indexes) { + return new ScaleAndIndexes(scale, Ints.toArray(indexes)); + } + } + + /** + * Describes the point in a fluent API chain where the scale and a single quantile index (i.e. the + * q and the k in the kth q-quantile) have been specified. + * + * @since 20.0 + */ + public static final class ScaleAndIndex { + + private final int scale; + private final int index; + + private ScaleAndIndex(int scale, int index) { + checkIndex(index, scale); + this.scale = scale; + this.index = index; + } + + /** + * Computes the quantile value of the given dataset. + * + * @param dataset the dataset to do the calculation on, which must be non-empty, which will be + * cast to doubles (with any associated lost of precision), and which will not be mutated by + * this call (it is copied instead) + * @return the quantile value + */ + public double compute(Collection dataset) { + return computeInPlace(Doubles.toArray(dataset)); + } + + /** + * Computes the quantile value of the given dataset. + * + * @param dataset the dataset to do the calculation on, which must be non-empty, which will not + * be mutated by this call (it is copied instead) + * @return the quantile value + */ + public double compute(double... dataset) { + return computeInPlace(dataset.clone()); + } + + /** + * Computes the quantile value of the given dataset. + * + * @param dataset the dataset to do the calculation on, which must be non-empty, which will be + * cast to doubles (with any associated lost of precision), and which will not be mutated by + * this call (it is copied instead) + * @return the quantile value + */ + public double compute(long... dataset) { + return computeInPlace(longsToDoubles(dataset)); + } + + /** + * Computes the quantile value of the given dataset. + * + * @param dataset the dataset to do the calculation on, which must be non-empty, which will be + * cast to doubles, and which will not be mutated by this call (it is copied instead) + * @return the quantile value + */ + public double compute(int... dataset) { + return computeInPlace(intsToDoubles(dataset)); + } + + /** + * Computes the quantile value of the given dataset, performing the computation in-place. + * + * @param dataset the dataset to do the calculation on, which must be non-empty, and which will + * be arbitrarily reordered by this method call + * @return the quantile value + */ + public double computeInPlace(double... dataset) { + checkArgument(dataset.length > 0, "Cannot calculate quantiles of an empty dataset"); + if (containsNaN(dataset)) { + return NaN; + } + + // Calculate the quotient and remainder in the integer division x = k * (N-1) / q, i.e. + // index * (dataset.length - 1) / scale. If there is no remainder, we can just find the value + // whose index in the sorted dataset equals the quotient; if there is a remainder, we + // interpolate between that and the next value. + + // Since index and (dataset.length - 1) are non-negative ints, their product can be expressed + // as a long, without risk of overflow: + long numerator = (long) index * (dataset.length - 1); + // Since scale is a positive int, index is in [0, scale], and (dataset.length - 1) is a + // non-negative int, we can do long-arithmetic on index * (dataset.length - 1) / scale to get + // a rounded ratio and a remainder which can be expressed as ints, without risk of overflow: + int quotient = (int) LongMath.divide(numerator, scale, RoundingMode.DOWN); + int remainder = (int) (numerator - (long) quotient * scale); + selectInPlace(quotient, dataset, 0, dataset.length - 1); + if (remainder == 0) { + return dataset[quotient]; + } else { + selectInPlace(quotient + 1, dataset, quotient + 1, dataset.length - 1); + return interpolate(dataset[quotient], dataset[quotient + 1], remainder, scale); + } + } + } + + /** + * Describes the point in a fluent API chain where the scale and a multiple quantile indexes (i.e. + * the q and a set of values for the k in the kth q-quantile) have been specified. + * + * @since 20.0 + */ + public static final class ScaleAndIndexes { + + private final int scale; + private final int[] indexes; + + private ScaleAndIndexes(int scale, int[] indexes) { + for (int index : indexes) { + checkIndex(index, scale); + } + checkArgument(indexes.length > 0, "Indexes must be a non empty array"); + this.scale = scale; + this.indexes = indexes; + } + + /** + * Computes the quantile values of the given dataset. + * + * @param dataset the dataset to do the calculation on, which must be non-empty, which will be + * cast to doubles (with any associated lost of precision), and which will not be mutated by + * this call (it is copied instead) + * @return an unmodifiable, ordered map of results: the keys will be the specified quantile + * indexes, and the values the corresponding quantile values. When iterating, entries in the + * map are ordered by quantile index in the same order they were passed to the {@code + * indexes} method. + */ + public Map compute(Collection dataset) { + return computeInPlace(Doubles.toArray(dataset)); + } + + /** + * Computes the quantile values of the given dataset. + * + * @param dataset the dataset to do the calculation on, which must be non-empty, which will not + * be mutated by this call (it is copied instead) + * @return an unmodifiable, ordered map of results: the keys will be the specified quantile + * indexes, and the values the corresponding quantile values. When iterating, entries in the + * map are ordered by quantile index in the same order they were passed to the {@code + * indexes} method. + */ + public Map compute(double... dataset) { + return computeInPlace(dataset.clone()); + } + + /** + * Computes the quantile values of the given dataset. + * + * @param dataset the dataset to do the calculation on, which must be non-empty, which will be + * cast to doubles (with any associated lost of precision), and which will not be mutated by + * this call (it is copied instead) + * @return an unmodifiable, ordered map of results: the keys will be the specified quantile + * indexes, and the values the corresponding quantile values. When iterating, entries in the + * map are ordered by quantile index in the same order they were passed to the {@code + * indexes} method. + */ + public Map compute(long... dataset) { + return computeInPlace(longsToDoubles(dataset)); + } + + /** + * Computes the quantile values of the given dataset. + * + * @param dataset the dataset to do the calculation on, which must be non-empty, which will be + * cast to doubles, and which will not be mutated by this call (it is copied instead) + * @return an unmodifiable, ordered map of results: the keys will be the specified quantile + * indexes, and the values the corresponding quantile values. When iterating, entries in the + * map are ordered by quantile index in the same order they were passed to the {@code + * indexes} method. + */ + public Map compute(int... dataset) { + return computeInPlace(intsToDoubles(dataset)); + } + + /** + * Computes the quantile values of the given dataset, performing the computation in-place. + * + * @param dataset the dataset to do the calculation on, which must be non-empty, and which will + * be arbitrarily reordered by this method call + * @return an unmodifiable, ordered map of results: the keys will be the specified quantile + * indexes, and the values the corresponding quantile values. When iterating, entries in the + * map are ordered by quantile index in the same order that the indexes were passed to the + * {@code indexes} method. + */ + public Map computeInPlace(double... dataset) { + checkArgument(dataset.length > 0, "Cannot calculate quantiles of an empty dataset"); + if (containsNaN(dataset)) { + Map nanMap = new LinkedHashMap<>(); + for (int index : indexes) { + nanMap.put(index, NaN); + } + return unmodifiableMap(nanMap); + } + + // Calculate the quotients and remainders in the integer division x = k * (N - 1) / q, i.e. + // index * (dataset.length - 1) / scale for each index in indexes. For each, if there is no + // remainder, we can just select the value whose index in the sorted dataset equals the + // quotient; if there is a remainder, we interpolate between that and the next value. + + int[] quotients = new int[indexes.length]; + int[] remainders = new int[indexes.length]; + // The indexes to select. In the worst case, we'll need one each side of each quantile. + int[] requiredSelections = new int[indexes.length * 2]; + int requiredSelectionsCount = 0; + for (int i = 0; i < indexes.length; i++) { + // Since index and (dataset.length - 1) are non-negative ints, their product can be + // expressed as a long, without risk of overflow: + long numerator = (long) indexes[i] * (dataset.length - 1); + // Since scale is a positive int, index is in [0, scale], and (dataset.length - 1) is a + // non-negative int, we can do long-arithmetic on index * (dataset.length - 1) / scale to + // get a rounded ratio and a remainder which can be expressed as ints, without risk of + // overflow: + int quotient = (int) LongMath.divide(numerator, scale, RoundingMode.DOWN); + int remainder = (int) (numerator - (long) quotient * scale); + quotients[i] = quotient; + remainders[i] = remainder; + requiredSelections[requiredSelectionsCount] = quotient; + requiredSelectionsCount++; + if (remainder != 0) { + requiredSelections[requiredSelectionsCount] = quotient + 1; + requiredSelectionsCount++; + } + } + sort(requiredSelections, 0, requiredSelectionsCount); + selectAllInPlace( + requiredSelections, 0, requiredSelectionsCount - 1, dataset, 0, dataset.length - 1); + Map ret = new LinkedHashMap<>(); + for (int i = 0; i < indexes.length; i++) { + int quotient = quotients[i]; + int remainder = remainders[i]; + if (remainder == 0) { + ret.put(indexes[i], dataset[quotient]); + } else { + ret.put( + indexes[i], interpolate(dataset[quotient], dataset[quotient + 1], remainder, scale)); + } + } + return unmodifiableMap(ret); + } + } + + /** Returns whether any of the values in {@code dataset} are {@code NaN}. */ + private static boolean containsNaN(double... dataset) { + for (double value : dataset) { + if (Double.isNaN(value)) { + return true; + } + } + return false; + } + + /** + * Returns a value a fraction {@code (remainder / scale)} of the way between {@code lower} and + * {@code upper}. Assumes that {@code lower <= upper}. Correctly handles infinities (but not + * {@code NaN}). + */ + private static double interpolate(double lower, double upper, double remainder, double scale) { + if (lower == NEGATIVE_INFINITY) { + if (upper == POSITIVE_INFINITY) { + // Return NaN when lower == NEGATIVE_INFINITY and upper == POSITIVE_INFINITY: + return NaN; + } + // Return NEGATIVE_INFINITY when NEGATIVE_INFINITY == lower <= upper < POSITIVE_INFINITY: + return NEGATIVE_INFINITY; + } + if (upper == POSITIVE_INFINITY) { + // Return POSITIVE_INFINITY when NEGATIVE_INFINITY < lower <= upper == POSITIVE_INFINITY: + return POSITIVE_INFINITY; + } + return lower + (upper - lower) * remainder / scale; + } + + private static void checkIndex(int index, int scale) { + if (index < 0 || index > scale) { + throw new IllegalArgumentException( + "Quantile indexes must be between 0 and the scale, which is " + scale); + } + } + + private static double[] longsToDoubles(long[] longs) { + int len = longs.length; + double[] doubles = new double[len]; + for (int i = 0; i < len; i++) { + doubles[i] = longs[i]; + } + return doubles; + } + + private static double[] intsToDoubles(int[] ints) { + int len = ints.length; + double[] doubles = new double[len]; + for (int i = 0; i < len; i++) { + doubles[i] = ints[i]; + } + return doubles; + } + + /** + * Performs an in-place selection to find the element which would appear at a given index in a + * dataset if it were sorted. The following preconditions should hold: + * + *

    + *
  • {@code required}, {@code from}, and {@code to} should all be indexes into {@code array}; + *
  • {@code required} should be in the range [{@code from}, {@code to}]; + *
  • all the values with indexes in the range [0, {@code from}) should be less than or equal + * to all the values with indexes in the range [{@code from}, {@code to}]; + *
  • all the values with indexes in the range ({@code to}, {@code array.length - 1}] should be + * greater than or equal to all the values with indexes in the range [{@code from}, {@code + * to}]. + *
+ * + * This method will reorder the values with indexes in the range [{@code from}, {@code to}] such + * that all the values with indexes in the range [{@code from}, {@code required}) are less than or + * equal to the value with index {@code required}, and all the values with indexes in the range + * ({@code required}, {@code to}] are greater than or equal to that value. Therefore, the value at + * {@code required} is the value which would appear at that index in the sorted dataset. + */ + private static void selectInPlace(int required, double[] array, int from, int to) { + // If we are looking for the least element in the range, we can just do a linear search for it. + // (We will hit this whenever we are doing quantile interpolation: our first selection finds + // the lower value, our second one finds the upper value by looking for the next least element.) + if (required == from) { + int min = from; + for (int index = from + 1; index <= to; index++) { + if (array[min] > array[index]) { + min = index; + } + } + if (min != from) { + swap(array, min, from); + } + return; + } + + // Let's play quickselect! We'll repeatedly partition the range [from, to] containing the + // required element, as long as it has more than one element. + while (to > from) { + int partitionPoint = partition(array, from, to); + if (partitionPoint >= required) { + to = partitionPoint - 1; + } + if (partitionPoint <= required) { + from = partitionPoint + 1; + } + } + } + + /** + * Performs a partition operation on the slice of {@code array} with elements in the range [{@code + * from}, {@code to}]. Uses the median of {@code from}, {@code to}, and the midpoint between them + * as a pivot. Returns the index which the slice is partitioned around, i.e. if it returns {@code + * ret} then we know that the values with indexes in [{@code from}, {@code ret}) are less than or + * equal to the value at {@code ret} and the values with indexes in ({@code ret}, {@code to}] are + * greater than or equal to that. + */ + private static int partition(double[] array, int from, int to) { + // Select a pivot, and move it to the start of the slice i.e. to index from. + movePivotToStartOfSlice(array, from, to); + double pivot = array[from]; + + // Move all elements with indexes in (from, to] which are greater than the pivot to the end of + // the array. Keep track of where those elements begin. + int partitionPoint = to; + for (int i = to; i > from; i--) { + if (array[i] > pivot) { + swap(array, partitionPoint, i); + partitionPoint--; + } + } + + // We now know that all elements with indexes in (from, partitionPoint] are less than or equal + // to the pivot at from, and all elements with indexes in (partitionPoint, to] are greater than + // it. We swap the pivot into partitionPoint and we know the array is partitioned around that. + swap(array, from, partitionPoint); + return partitionPoint; + } + + /** + * Selects the pivot to use, namely the median of the values at {@code from}, {@code to}, and + * halfway between the two (rounded down), from {@code array}, and ensure (by swapping elements if + * necessary) that that pivot value appears at the start of the slice i.e. at {@code from}. + * Expects that {@code from} is strictly less than {@code to}. + */ + private static void movePivotToStartOfSlice(double[] array, int from, int to) { + int mid = (from + to) >>> 1; + // We want to make a swap such that either array[to] <= array[from] <= array[mid], or + // array[mid] <= array[from] <= array[to]. We know that from < to, so we know mid < to + // (although it's possible that mid == from, if to == from + 1). Note that the postcondition + // would be impossible to fulfil if mid == to unless we also have array[from] == array[to]. + boolean toLessThanMid = (array[to] < array[mid]); + boolean midLessThanFrom = (array[mid] < array[from]); + boolean toLessThanFrom = (array[to] < array[from]); + if (toLessThanMid == midLessThanFrom) { + // Either array[to] < array[mid] < array[from] or array[from] <= array[mid] <= array[to]. + swap(array, mid, from); + } else if (toLessThanMid != toLessThanFrom) { + // Either array[from] <= array[to] < array[mid] or array[mid] <= array[to] < array[from]. + swap(array, from, to); + } + // The postcondition now holds. So the median, our chosen pivot, is at from. + } + + /** + * Performs an in-place selection, like {@link #selectInPlace}, to select all the indexes {@code + * allRequired[i]} for {@code i} in the range [{@code requiredFrom}, {@code requiredTo}]. These + * indexes must be sorted in the array and must all be in the range [{@code from}, {@code to}]. + */ + private static void selectAllInPlace( + int[] allRequired, int requiredFrom, int requiredTo, double[] array, int from, int to) { + // Choose the first selection to do... + int requiredChosen = chooseNextSelection(allRequired, requiredFrom, requiredTo, from, to); + int required = allRequired[requiredChosen]; + + // ...do the first selection... + selectInPlace(required, array, from, to); + + // ...then recursively perform the selections in the range below... + int requiredBelow = requiredChosen - 1; + while (requiredBelow >= requiredFrom && allRequired[requiredBelow] == required) { + requiredBelow--; // skip duplicates of required in the range below + } + if (requiredBelow >= requiredFrom) { + selectAllInPlace(allRequired, requiredFrom, requiredBelow, array, from, required - 1); + } + + // ...and then recursively perform the selections in the range above. + int requiredAbove = requiredChosen + 1; + while (requiredAbove <= requiredTo && allRequired[requiredAbove] == required) { + requiredAbove++; // skip duplicates of required in the range above + } + if (requiredAbove <= requiredTo) { + selectAllInPlace(allRequired, requiredAbove, requiredTo, array, required + 1, to); + } + } + + /** + * Chooses the next selection to do from the required selections. It is required that the array + * {@code allRequired} is sorted and that {@code allRequired[i]} are in the range [{@code from}, + * {@code to}] for all {@code i} in the range [{@code requiredFrom}, {@code requiredTo}]. The + * value returned by this method is the {@code i} in that range such that {@code allRequired[i]} + * is as close as possible to the center of the range [{@code from}, {@code to}]. Choosing the + * value closest to the center of the range first is the most efficient strategy because it + * minimizes the size of the subranges from which the remaining selections must be done. + */ + private static int chooseNextSelection( + int[] allRequired, int requiredFrom, int requiredTo, int from, int to) { + if (requiredFrom == requiredTo) { + return requiredFrom; // only one thing to choose, so choose it + } + + // Find the center and round down. The true center is either centerFloor or halfway between + // centerFloor and centerFloor + 1. + int centerFloor = (from + to) >>> 1; + + // Do a binary search until we're down to the range of two which encloses centerFloor (unless + // all values are lower or higher than centerFloor, in which case we find the two highest or + // lowest respectively). If centerFloor is in allRequired, we will definitely find it. If not, + // but centerFloor + 1 is, we'll definitely find that. The closest value to the true (unrounded) + // center will be at either low or high. + int low = requiredFrom; + int high = requiredTo; + while (high > low + 1) { + int mid = (low + high) >>> 1; + if (allRequired[mid] > centerFloor) { + high = mid; + } else if (allRequired[mid] < centerFloor) { + low = mid; + } else { + return mid; // allRequired[mid] = centerFloor, so we can't get closer than that + } + } + + // Now pick the closest of the two candidates. Note that there is no rounding here. + if (from + to - allRequired[low] - allRequired[high] > 0) { + return high; + } else { + return low; + } + } + + /** Swaps the values at {@code i} and {@code j} in {@code array}. */ + private static void swap(double[] array, int i, int j) { + double temp = array[i]; + array[i] = array[j]; + array[j] = temp; + } +} diff --git a/src/main/java/com/google/common/math/Stats.java b/src/main/java/com/google/common/math/Stats.java new file mode 100644 index 0000000..f0678bc --- /dev/null +++ b/src/main/java/com/google/common/math/Stats.java @@ -0,0 +1,663 @@ +/* + * Copyright (C) 2012 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.math; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.base.Preconditions.checkState; +import static com.google.common.math.DoubleUtils.ensureNonNegative; +import static com.google.common.math.StatsAccumulator.calculateNewMeanNonFinite; +import static com.google.common.primitives.Doubles.isFinite; +import static java.lang.Double.NaN; +import static java.lang.Double.doubleToLongBits; +import static java.lang.Double.isNaN; + +import com.google.common.annotations.Beta; +import com.google.common.annotations.GwtIncompatible; +import com.google.common.base.MoreObjects; +import com.google.common.base.Objects; +import java.io.Serializable; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.util.Iterator; +import java.util.stream.Collector; +import java.util.stream.DoubleStream; +import java.util.stream.IntStream; +import java.util.stream.LongStream; + + +/** + * A bundle of statistical summary values -- sum, count, mean/average, min and max, and several + * forms of variance -- that were computed from a single set of zero or more floating-point values. + * + *

There are two ways to obtain a {@code Stats} instance: + * + *

    + *
  • If all the values you want to summarize are already known, use the appropriate {@code + * Stats.of} factory method below. Primitive arrays, iterables and iterators of any kind of + * {@code Number}, and primitive varargs are supported. + *
  • Or, to avoid storing up all the data first, create a {@link StatsAccumulator} instance, + * feed values to it as you get them, then call {@link StatsAccumulator#snapshot}. + *
+ * + *

Static convenience methods called {@code meanOf} are also provided for users who wish to + * calculate only the mean. + * + *

Java 8 users: If you are not using any of the variance statistics, you may wish to use + * built-in JDK libraries instead of this class. + * + * @author Pete Gillin + * @author Kevin Bourrillion + * @since 20.0 + */ +@Beta +@GwtIncompatible +public final class Stats implements Serializable { + + private final long count; + private final double mean; + private final double sumOfSquaresOfDeltas; + private final double min; + private final double max; + + /** + * Internal constructor. Users should use {@link #of} or {@link StatsAccumulator#snapshot}. + * + *

To ensure that the created instance obeys its contract, the parameters should satisfy the + * following constraints. This is the callers responsibility and is not enforced here. + * + *

    + *
  • If {@code count} is 0, {@code mean} may have any finite value (its only usage will be to + * get multiplied by 0 to calculate the sum), and the other parameters may have any values + * (they will not be used). + *
  • If {@code count} is 1, {@code sumOfSquaresOfDeltas} must be exactly 0.0 or {@link + * Double#NaN}. + *
+ */ + Stats(long count, double mean, double sumOfSquaresOfDeltas, double min, double max) { + this.count = count; + this.mean = mean; + this.sumOfSquaresOfDeltas = sumOfSquaresOfDeltas; + this.min = min; + this.max = max; + } + + /** + * Returns statistics over a dataset containing the given values. + * + * @param values a series of values, which will be converted to {@code double} values (this may + * cause loss of precision) + */ + public static Stats of(Iterable values) { + StatsAccumulator accumulator = new StatsAccumulator(); + accumulator.addAll(values); + return accumulator.snapshot(); + } + + /** + * Returns statistics over a dataset containing the given values. The iterator will be completely + * consumed by this method. + * + * @param values a series of values, which will be converted to {@code double} values (this may + * cause loss of precision) + */ + public static Stats of(Iterator values) { + StatsAccumulator accumulator = new StatsAccumulator(); + accumulator.addAll(values); + return accumulator.snapshot(); + } + + /** + * Returns statistics over a dataset containing the given values. + * + * @param values a series of values + */ + public static Stats of(double... values) { + StatsAccumulator acummulator = new StatsAccumulator(); + acummulator.addAll(values); + return acummulator.snapshot(); + } + + /** + * Returns statistics over a dataset containing the given values. + * + * @param values a series of values + */ + public static Stats of(int... values) { + StatsAccumulator acummulator = new StatsAccumulator(); + acummulator.addAll(values); + return acummulator.snapshot(); + } + + /** + * Returns statistics over a dataset containing the given values. + * + * @param values a series of values, which will be converted to {@code double} values (this may + * cause loss of precision for longs of magnitude over 2^53 (slightly over 9e15)) + */ + public static Stats of(long... values) { + StatsAccumulator acummulator = new StatsAccumulator(); + acummulator.addAll(values); + return acummulator.snapshot(); + } + + /** + * Returns statistics over a dataset containing the given values. The stream will be completely + * consumed by this method. + * + *

If you have a {@code Stream} rather than a {@code DoubleStream}, you should collect + * the values using {@link #toStats()} instead. + * + * @param values a series of values + * @since NEXT + */ + public static Stats of(DoubleStream values) { + return values + .collect(StatsAccumulator::new, StatsAccumulator::add, StatsAccumulator::addAll) + .snapshot(); + } + + /** + * Returns statistics over a dataset containing the given values. The stream will be completely + * consumed by this method. + * + *

If you have a {@code Stream} rather than an {@code IntStream}, you should collect + * the values using {@link #toStats()} instead. + * + * @param values a series of values + * @since NEXT + */ + public static Stats of(IntStream values) { + return values + .collect(StatsAccumulator::new, StatsAccumulator::add, StatsAccumulator::addAll) + .snapshot(); + } + + /** + * Returns statistics over a dataset containing the given values. The stream will be completely + * consumed by this method. + * + *

If you have a {@code Stream} rather than a {@code LongStream}, you should collect the + * values using {@link #toStats()} instead. + * + * @param values a series of values, which will be converted to {@code double} values (this may + * cause loss of precision for longs of magnitude over 2^53 (slightly over 9e15)) + * @since NEXT + */ + public static Stats of(LongStream values) { + return values + .collect(StatsAccumulator::new, StatsAccumulator::add, StatsAccumulator::addAll) + .snapshot(); + } + + /** + * Returns a {@link Collector} which accumulates statistics from a {@link java.util.stream.Stream} + * of any type of boxed {@link Number} into a {@link Stats}. Use by calling {@code + * boxedNumericStream.collect(toStats())}. The numbers will be converted to {@code double} values + * (which may cause loss of precision). + * + *

If you have any of the primitive streams {@code DoubleStream}, {@code IntStream}, or {@code + * LongStream}, you should use the factory method {@link #of} instead. + * + * @since NEXT + */ + public static Collector toStats() { + return Collector.of( + StatsAccumulator::new, + (a, x) -> a.add(x.doubleValue()), + (l, r) -> { + l.addAll(r); + return l; + }, + StatsAccumulator::snapshot, + Collector.Characteristics.UNORDERED); + } + + /** Returns the number of values. */ + public long count() { + return count; + } + + /** + * Returns the arithmetic mean of the + * values. The count must be non-zero. + * + *

If these values are a sample drawn from a population, this is also an unbiased estimator of + * the arithmetic mean of the population. + * + *

Non-finite values

+ * + *

If the dataset contains {@link Double#NaN} then the result is {@link Double#NaN}. If it + * contains both {@link Double#POSITIVE_INFINITY} and {@link Double#NEGATIVE_INFINITY} then the + * result is {@link Double#NaN}. If it contains {@link Double#POSITIVE_INFINITY} and finite values + * only or {@link Double#POSITIVE_INFINITY} only, the result is {@link Double#POSITIVE_INFINITY}. + * If it contains {@link Double#NEGATIVE_INFINITY} and finite values only or {@link + * Double#NEGATIVE_INFINITY} only, the result is {@link Double#NEGATIVE_INFINITY}. + * + *

If you only want to calculate the mean, use {#meanOf} instead of creating a {@link Stats} + * instance. + * + * @throws IllegalStateException if the dataset is empty + */ + public double mean() { + checkState(count != 0); + return mean; + } + + /** + * Returns the sum of the values. + * + *

Non-finite values

+ * + *

If the dataset contains {@link Double#NaN} then the result is {@link Double#NaN}. If it + * contains both {@link Double#POSITIVE_INFINITY} and {@link Double#NEGATIVE_INFINITY} then the + * result is {@link Double#NaN}. If it contains {@link Double#POSITIVE_INFINITY} and finite values + * only or {@link Double#POSITIVE_INFINITY} only, the result is {@link Double#POSITIVE_INFINITY}. + * If it contains {@link Double#NEGATIVE_INFINITY} and finite values only or {@link + * Double#NEGATIVE_INFINITY} only, the result is {@link Double#NEGATIVE_INFINITY}. + */ + public double sum() { + return mean * count; + } + + /** + * Returns the population + * variance of the values. The count must be non-zero. + * + *

This is guaranteed to return zero if the dataset contains only exactly one finite value. It + * is not guaranteed to return zero when the dataset consists of the same value multiple times, + * due to numerical errors. However, it is guaranteed never to return a negative result. + * + *

Non-finite values

+ * + *

If the dataset contains any non-finite values ({@link Double#POSITIVE_INFINITY}, {@link + * Double#NEGATIVE_INFINITY}, or {@link Double#NaN}) then the result is {@link Double#NaN}. + * + * @throws IllegalStateException if the dataset is empty + */ + public double populationVariance() { + checkState(count > 0); + if (isNaN(sumOfSquaresOfDeltas)) { + return NaN; + } + if (count == 1) { + return 0.0; + } + return ensureNonNegative(sumOfSquaresOfDeltas) / count(); + } + + /** + * Returns the + * population standard deviation of the values. The count must be non-zero. + * + *

This is guaranteed to return zero if the dataset contains only exactly one finite value. It + * is not guaranteed to return zero when the dataset consists of the same value multiple times, + * due to numerical errors. However, it is guaranteed never to return a negative result. + * + *

Non-finite values

+ * + *

If the dataset contains any non-finite values ({@link Double#POSITIVE_INFINITY}, {@link + * Double#NEGATIVE_INFINITY}, or {@link Double#NaN}) then the result is {@link Double#NaN}. + * + * @throws IllegalStateException if the dataset is empty + */ + public double populationStandardDeviation() { + return Math.sqrt(populationVariance()); + } + + /** + * Returns the unbiased sample + * variance of the values. If this dataset is a sample drawn from a population, this is an + * unbiased estimator of the population variance of the population. The count must be greater than + * one. + * + *

This is not guaranteed to return zero when the dataset consists of the same value multiple + * times, due to numerical errors. However, it is guaranteed never to return a negative result. + * + *

Non-finite values

+ * + *

If the dataset contains any non-finite values ({@link Double#POSITIVE_INFINITY}, {@link + * Double#NEGATIVE_INFINITY}, or {@link Double#NaN}) then the result is {@link Double#NaN}. + * + * @throws IllegalStateException if the dataset is empty or contains a single value + */ + public double sampleVariance() { + checkState(count > 1); + if (isNaN(sumOfSquaresOfDeltas)) { + return NaN; + } + return ensureNonNegative(sumOfSquaresOfDeltas) / (count - 1); + } + + /** + * Returns the + * corrected sample standard deviation of the values. If this dataset is a sample drawn from a + * population, this is an estimator of the population standard deviation of the population which + * is less biased than {@link #populationStandardDeviation()} (the unbiased estimator depends on + * the distribution). The count must be greater than one. + * + *

This is not guaranteed to return zero when the dataset consists of the same value multiple + * times, due to numerical errors. However, it is guaranteed never to return a negative result. + * + *

Non-finite values

+ * + *

If the dataset contains any non-finite values ({@link Double#POSITIVE_INFINITY}, {@link + * Double#NEGATIVE_INFINITY}, or {@link Double#NaN}) then the result is {@link Double#NaN}. + * + * @throws IllegalStateException if the dataset is empty or contains a single value + */ + public double sampleStandardDeviation() { + return Math.sqrt(sampleVariance()); + } + + /** + * Returns the lowest value in the dataset. The count must be non-zero. + * + *

Non-finite values

+ * + *

If the dataset contains {@link Double#NaN} then the result is {@link Double#NaN}. If it + * contains {@link Double#NEGATIVE_INFINITY} and not {@link Double#NaN} then the result is {@link + * Double#NEGATIVE_INFINITY}. If it contains {@link Double#POSITIVE_INFINITY} and finite values + * only then the result is the lowest finite value. If it contains {@link + * Double#POSITIVE_INFINITY} only then the result is {@link Double#POSITIVE_INFINITY}. + * + * @throws IllegalStateException if the dataset is empty + */ + public double min() { + checkState(count != 0); + return min; + } + + /** + * Returns the highest value in the dataset. The count must be non-zero. + * + *

Non-finite values

+ * + *

If the dataset contains {@link Double#NaN} then the result is {@link Double#NaN}. If it + * contains {@link Double#POSITIVE_INFINITY} and not {@link Double#NaN} then the result is {@link + * Double#POSITIVE_INFINITY}. If it contains {@link Double#NEGATIVE_INFINITY} and finite values + * only then the result is the highest finite value. If it contains {@link + * Double#NEGATIVE_INFINITY} only then the result is {@link Double#NEGATIVE_INFINITY}. + * + * @throws IllegalStateException if the dataset is empty + */ + public double max() { + checkState(count != 0); + return max; + } + + /** + * {@inheritDoc} + * + *

Note: This tests exact equality of the calculated statistics, including the floating + * point values. Two instances are guaranteed to be considered equal if one is copied from the + * other using {@code second = new StatsAccumulator().addAll(first).snapshot()}, if both were + * obtained by calling {@code snapshot()} on the same {@link StatsAccumulator} without adding any + * values in between the two calls, or if one is obtained from the other after round-tripping + * through java serialization. However, floating point rounding errors mean that it may be false + * for some instances where the statistics are mathematically equal, including instances + * constructed from the same values in a different order... or (in the general case) even in the + * same order. (It is guaranteed to return true for instances constructed from the same values in + * the same order if {@code strictfp} is in effect, or if the system architecture guarantees + * {@code strictfp}-like semantics.) + */ + @Override + public boolean equals(Object obj) { + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + Stats other = (Stats) obj; + return (count == other.count) + && (doubleToLongBits(mean) == doubleToLongBits(other.mean)) + && (doubleToLongBits(sumOfSquaresOfDeltas) == doubleToLongBits(other.sumOfSquaresOfDeltas)) + && (doubleToLongBits(min) == doubleToLongBits(other.min)) + && (doubleToLongBits(max) == doubleToLongBits(other.max)); + } + + /** + * {@inheritDoc} + * + *

Note: This hash code is consistent with exact equality of the calculated statistics, + * including the floating point values. See the note on {@link #equals} for details. + */ + @Override + public int hashCode() { + return Objects.hashCode(count, mean, sumOfSquaresOfDeltas, min, max); + } + + @Override + public String toString() { + if (count() > 0) { + return MoreObjects.toStringHelper(this) + .add("count", count) + .add("mean", mean) + .add("populationStandardDeviation", populationStandardDeviation()) + .add("min", min) + .add("max", max) + .toString(); + } else { + return MoreObjects.toStringHelper(this).add("count", count).toString(); + } + } + + double sumOfSquaresOfDeltas() { + return sumOfSquaresOfDeltas; + } + + /** + * Returns the arithmetic mean of the + * values. The count must be non-zero. + * + *

The definition of the mean is the same as {@link Stats#mean}. + * + * @param values a series of values, which will be converted to {@code double} values (this may + * cause loss of precision) + * @throws IllegalArgumentException if the dataset is empty + */ + public static double meanOf(Iterable values) { + return meanOf(values.iterator()); + } + + /** + * Returns the arithmetic mean of the + * values. The count must be non-zero. + * + *

The definition of the mean is the same as {@link Stats#mean}. + * + * @param values a series of values, which will be converted to {@code double} values (this may + * cause loss of precision) + * @throws IllegalArgumentException if the dataset is empty + */ + public static double meanOf(Iterator values) { + checkArgument(values.hasNext()); + long count = 1; + double mean = values.next().doubleValue(); + while (values.hasNext()) { + double value = values.next().doubleValue(); + count++; + if (isFinite(value) && isFinite(mean)) { + // Art of Computer Programming vol. 2, Knuth, 4.2.2, (15) + mean += (value - mean) / count; + } else { + mean = calculateNewMeanNonFinite(mean, value); + } + } + return mean; + } + + /** + * Returns the arithmetic mean of the + * values. The count must be non-zero. + * + *

The definition of the mean is the same as {@link Stats#mean}. + * + * @param values a series of values + * @throws IllegalArgumentException if the dataset is empty + */ + public static double meanOf(double... values) { + checkArgument(values.length > 0); + double mean = values[0]; + for (int index = 1; index < values.length; index++) { + double value = values[index]; + if (isFinite(value) && isFinite(mean)) { + // Art of Computer Programming vol. 2, Knuth, 4.2.2, (15) + mean += (value - mean) / (index + 1); + } else { + mean = calculateNewMeanNonFinite(mean, value); + } + } + return mean; + } + + /** + * Returns the arithmetic mean of the + * values. The count must be non-zero. + * + *

The definition of the mean is the same as {@link Stats#mean}. + * + * @param values a series of values + * @throws IllegalArgumentException if the dataset is empty + */ + public static double meanOf(int... values) { + checkArgument(values.length > 0); + double mean = values[0]; + for (int index = 1; index < values.length; index++) { + double value = values[index]; + if (isFinite(value) && isFinite(mean)) { + // Art of Computer Programming vol. 2, Knuth, 4.2.2, (15) + mean += (value - mean) / (index + 1); + } else { + mean = calculateNewMeanNonFinite(mean, value); + } + } + return mean; + } + + /** + * Returns the arithmetic mean of the + * values. The count must be non-zero. + * + *

The definition of the mean is the same as {@link Stats#mean}. + * + * @param values a series of values, which will be converted to {@code double} values (this may + * cause loss of precision for longs of magnitude over 2^53 (slightly over 9e15)) + * @throws IllegalArgumentException if the dataset is empty + */ + public static double meanOf(long... values) { + checkArgument(values.length > 0); + double mean = values[0]; + for (int index = 1; index < values.length; index++) { + double value = values[index]; + if (isFinite(value) && isFinite(mean)) { + // Art of Computer Programming vol. 2, Knuth, 4.2.2, (15) + mean += (value - mean) / (index + 1); + } else { + mean = calculateNewMeanNonFinite(mean, value); + } + } + return mean; + } + + // Serialization helpers + + /** The size of byte array representation in bytes. */ + static final int BYTES = (Long.SIZE + Double.SIZE * 4) / Byte.SIZE; + + /** + * Gets a byte array representation of this instance. + * + *

Note: No guarantees are made regarding stability of the representation between + * versions. + */ + public byte[] toByteArray() { + ByteBuffer buff = ByteBuffer.allocate(BYTES).order(ByteOrder.LITTLE_ENDIAN); + writeTo(buff); + return buff.array(); + } + + /** + * Writes to the given {@link ByteBuffer} a byte representation of this instance. + * + *

Note: No guarantees are made regarding stability of the representation between + * versions. + * + * @param buffer A {@link ByteBuffer} with at least BYTES {@link ByteBuffer#remaining}, ordered as + * {@link ByteOrder#LITTLE_ENDIAN}, to which a BYTES-long byte representation of this instance + * is written. In the process increases the position of {@link ByteBuffer} by BYTES. + */ + void writeTo(ByteBuffer buffer) { + checkNotNull(buffer); + checkArgument( + buffer.remaining() >= BYTES, + "Expected at least Stats.BYTES = %s remaining , got %s", + BYTES, + buffer.remaining()); + buffer + .putLong(count) + .putDouble(mean) + .putDouble(sumOfSquaresOfDeltas) + .putDouble(min) + .putDouble(max); + } + + /** + * Creates a Stats instance from the given byte representation which was obtained by {@link + * #toByteArray}. + * + *

Note: No guarantees are made regarding stability of the representation between + * versions. + */ + public static Stats fromByteArray(byte[] byteArray) { + checkNotNull(byteArray); + checkArgument( + byteArray.length == BYTES, + "Expected Stats.BYTES = %s remaining , got %s", + BYTES, + byteArray.length); + return readFrom(ByteBuffer.wrap(byteArray).order(ByteOrder.LITTLE_ENDIAN)); + } + + /** + * Creates a Stats instance from the byte representation read from the given {@link ByteBuffer}. + * + *

Note: No guarantees are made regarding stability of the representation between + * versions. + * + * @param buffer A {@link ByteBuffer} with at least BYTES {@link ByteBuffer#remaining}, ordered as + * {@link ByteOrder#LITTLE_ENDIAN}, from which a BYTES-long byte representation of this + * instance is read. In the process increases the position of {@link ByteBuffer} by BYTES. + */ + static Stats readFrom(ByteBuffer buffer) { + checkNotNull(buffer); + checkArgument( + buffer.remaining() >= BYTES, + "Expected at least Stats.BYTES = %s remaining , got %s", + BYTES, + buffer.remaining()); + return new Stats( + buffer.getLong(), + buffer.getDouble(), + buffer.getDouble(), + buffer.getDouble(), + buffer.getDouble()); + } + + private static final long serialVersionUID = 0; +} diff --git a/src/main/java/com/google/common/math/StatsAccumulator.java b/src/main/java/com/google/common/math/StatsAccumulator.java new file mode 100644 index 0000000..5d5dfba --- /dev/null +++ b/src/main/java/com/google/common/math/StatsAccumulator.java @@ -0,0 +1,428 @@ +/* + * Copyright (C) 2012 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.math; + +import static com.google.common.base.Preconditions.checkState; +import static com.google.common.math.DoubleUtils.ensureNonNegative; +import static com.google.common.primitives.Doubles.isFinite; +import static java.lang.Double.NaN; +import static java.lang.Double.isNaN; + +import com.google.common.annotations.Beta; +import com.google.common.annotations.GwtIncompatible; +import java.util.Iterator; +import java.util.stream.DoubleStream; +import java.util.stream.IntStream; +import java.util.stream.LongStream; + +/** + * A mutable object which accumulates double values and tracks some basic statistics over all the + * values added so far. The values may be added singly or in groups. This class is not thread safe. + * + * @author Pete Gillin + * @author Kevin Bourrillion + * @since 20.0 + */ +@Beta +@GwtIncompatible +public final class StatsAccumulator { + + // These fields must satisfy the requirements of Stats' constructor as well as those of the stat + // methods of this class. + private long count = 0; + private double mean = 0.0; // any finite value will do, we only use it to multiply by zero for sum + private double sumOfSquaresOfDeltas = 0.0; + private double min = NaN; // any value will do + private double max = NaN; // any value will do + + /** Adds the given value to the dataset. */ + public void add(double value) { + if (count == 0) { + count = 1; + mean = value; + min = value; + max = value; + if (!isFinite(value)) { + sumOfSquaresOfDeltas = NaN; + } + } else { + count++; + if (isFinite(value) && isFinite(mean)) { + // Art of Computer Programming vol. 2, Knuth, 4.2.2, (15) and (16) + double delta = value - mean; + mean += delta / count; + sumOfSquaresOfDeltas += delta * (value - mean); + } else { + mean = calculateNewMeanNonFinite(mean, value); + sumOfSquaresOfDeltas = NaN; + } + min = Math.min(min, value); + max = Math.max(max, value); + } + } + + /** + * Adds the given values to the dataset. + * + * @param values a series of values, which will be converted to {@code double} values (this may + * cause loss of precision) + */ + public void addAll(Iterable values) { + for (Number value : values) { + add(value.doubleValue()); + } + } + + /** + * Adds the given values to the dataset. + * + * @param values a series of values, which will be converted to {@code double} values (this may + * cause loss of precision) + */ + public void addAll(Iterator values) { + while (values.hasNext()) { + add(values.next().doubleValue()); + } + } + + /** + * Adds the given values to the dataset. + * + * @param values a series of values + */ + public void addAll(double... values) { + for (double value : values) { + add(value); + } + } + + /** + * Adds the given values to the dataset. + * + * @param values a series of values + */ + public void addAll(int... values) { + for (int value : values) { + add(value); + } + } + + /** + * Adds the given values to the dataset. + * + * @param values a series of values, which will be converted to {@code double} values (this may + * cause loss of precision for longs of magnitude over 2^53 (slightly over 9e15)) + */ + public void addAll(long... values) { + for (long value : values) { + add(value); + } + } + + /** + * Adds the given values to the dataset. The stream will be completely consumed by this method. + * + * @param values a series of values + * @since NEXT + */ + public void addAll(DoubleStream values) { + addAll(values.collect(StatsAccumulator::new, StatsAccumulator::add, StatsAccumulator::addAll)); + } + + /** + * Adds the given values to the dataset. The stream will be completely consumed by this method. + * + * @param values a series of values + * @since NEXT + */ + public void addAll(IntStream values) { + addAll(values.collect(StatsAccumulator::new, StatsAccumulator::add, StatsAccumulator::addAll)); + } + + /** + * Adds the given values to the dataset. The stream will be completely consumed by this method. + * + * @param values a series of values, which will be converted to {@code double} values (this may + * cause loss of precision for longs of magnitude over 2^53 (slightly over 9e15)) + * @since NEXT + */ + public void addAll(LongStream values) { + addAll(values.collect(StatsAccumulator::new, StatsAccumulator::add, StatsAccumulator::addAll)); + } + + /** + * Adds the given statistics to the dataset, as if the individual values used to compute the + * statistics had been added directly. + */ + public void addAll(Stats values) { + if (values.count() == 0) { + return; + } + merge(values.count(), values.mean(), values.sumOfSquaresOfDeltas(), values.min(), values.max()); + } + + /** + * Adds the given statistics to the dataset, as if the individual values used to compute the + * statistics had been added directly. + * + * @since NEXT + */ + public void addAll(StatsAccumulator values) { + if (values.count() == 0) { + return; + } + merge(values.count(), values.mean(), values.sumOfSquaresOfDeltas(), values.min(), values.max()); + } + + private void merge( + long otherCount, + double otherMean, + double otherSumOfSquaresOfDeltas, + double otherMin, + double otherMax) { + if (count == 0) { + count = otherCount; + mean = otherMean; + sumOfSquaresOfDeltas = otherSumOfSquaresOfDeltas; + min = otherMin; + max = otherMax; + } else { + count += otherCount; + if (isFinite(mean) && isFinite(otherMean)) { + // This is a generalized version of the calculation in add(double) above. + double delta = otherMean - mean; + mean += delta * otherCount / count; + sumOfSquaresOfDeltas += otherSumOfSquaresOfDeltas + delta * (otherMean - mean) * otherCount; + } else { + mean = calculateNewMeanNonFinite(mean, otherMean); + sumOfSquaresOfDeltas = NaN; + } + min = Math.min(min, otherMin); + max = Math.max(max, otherMax); + } + } + + /** Returns an immutable snapshot of the current statistics. */ + public Stats snapshot() { + return new Stats(count, mean, sumOfSquaresOfDeltas, min, max); + } + + /** Returns the number of values. */ + public long count() { + return count; + } + + /** + * Returns the arithmetic mean of the + * values. The count must be non-zero. + * + *

If these values are a sample drawn from a population, this is also an unbiased estimator of + * the arithmetic mean of the population. + * + *

Non-finite values

+ * + *

If the dataset contains {@link Double#NaN} then the result is {@link Double#NaN}. If it + * contains both {@link Double#POSITIVE_INFINITY} and {@link Double#NEGATIVE_INFINITY} then the + * result is {@link Double#NaN}. If it contains {@link Double#POSITIVE_INFINITY} and finite values + * only or {@link Double#POSITIVE_INFINITY} only, the result is {@link Double#POSITIVE_INFINITY}. + * If it contains {@link Double#NEGATIVE_INFINITY} and finite values only or {@link + * Double#NEGATIVE_INFINITY} only, the result is {@link Double#NEGATIVE_INFINITY}. + * + * @throws IllegalStateException if the dataset is empty + */ + public double mean() { + checkState(count != 0); + return mean; + } + + /** + * Returns the sum of the values. + * + *

Non-finite values

+ * + *

If the dataset contains {@link Double#NaN} then the result is {@link Double#NaN}. If it + * contains both {@link Double#POSITIVE_INFINITY} and {@link Double#NEGATIVE_INFINITY} then the + * result is {@link Double#NaN}. If it contains {@link Double#POSITIVE_INFINITY} and finite values + * only or {@link Double#POSITIVE_INFINITY} only, the result is {@link Double#POSITIVE_INFINITY}. + * If it contains {@link Double#NEGATIVE_INFINITY} and finite values only or {@link + * Double#NEGATIVE_INFINITY} only, the result is {@link Double#NEGATIVE_INFINITY}. + */ + public final double sum() { + return mean * count; + } + + /** + * Returns the population + * variance of the values. The count must be non-zero. + * + *

This is guaranteed to return zero if the dataset contains only exactly one finite value. It + * is not guaranteed to return zero when the dataset consists of the same value multiple times, + * due to numerical errors. However, it is guaranteed never to return a negative result. + * + *

Non-finite values

+ * + *

If the dataset contains any non-finite values ({@link Double#POSITIVE_INFINITY}, {@link + * Double#NEGATIVE_INFINITY}, or {@link Double#NaN}) then the result is {@link Double#NaN}. + * + * @throws IllegalStateException if the dataset is empty + */ + public final double populationVariance() { + checkState(count != 0); + if (isNaN(sumOfSquaresOfDeltas)) { + return NaN; + } + if (count == 1) { + return 0.0; + } + return ensureNonNegative(sumOfSquaresOfDeltas) / count; + } + + /** + * Returns the + * population standard deviation of the values. The count must be non-zero. + * + *

This is guaranteed to return zero if the dataset contains only exactly one finite value. It + * is not guaranteed to return zero when the dataset consists of the same value multiple times, + * due to numerical errors. However, it is guaranteed never to return a negative result. + * + *

Non-finite values

+ * + *

If the dataset contains any non-finite values ({@link Double#POSITIVE_INFINITY}, {@link + * Double#NEGATIVE_INFINITY}, or {@link Double#NaN}) then the result is {@link Double#NaN}. + * + * @throws IllegalStateException if the dataset is empty + */ + public final double populationStandardDeviation() { + return Math.sqrt(populationVariance()); + } + + /** + * Returns the unbiased sample + * variance of the values. If this dataset is a sample drawn from a population, this is an + * unbiased estimator of the population variance of the population. The count must be greater than + * one. + * + *

This is not guaranteed to return zero when the dataset consists of the same value multiple + * times, due to numerical errors. However, it is guaranteed never to return a negative result. + * + *

Non-finite values

+ * + *

If the dataset contains any non-finite values ({@link Double#POSITIVE_INFINITY}, {@link + * Double#NEGATIVE_INFINITY}, or {@link Double#NaN}) then the result is {@link Double#NaN}. + * + * @throws IllegalStateException if the dataset is empty or contains a single value + */ + public final double sampleVariance() { + checkState(count > 1); + if (isNaN(sumOfSquaresOfDeltas)) { + return NaN; + } + return ensureNonNegative(sumOfSquaresOfDeltas) / (count - 1); + } + + /** + * Returns the + * corrected sample standard deviation of the values. If this dataset is a sample drawn from a + * population, this is an estimator of the population standard deviation of the population which + * is less biased than {@link #populationStandardDeviation()} (the unbiased estimator depends on + * the distribution). The count must be greater than one. + * + *

This is not guaranteed to return zero when the dataset consists of the same value multiple + * times, due to numerical errors. However, it is guaranteed never to return a negative result. + * + *

Non-finite values

+ * + *

If the dataset contains any non-finite values ({@link Double#POSITIVE_INFINITY}, {@link + * Double#NEGATIVE_INFINITY}, or {@link Double#NaN}) then the result is {@link Double#NaN}. + * + * @throws IllegalStateException if the dataset is empty or contains a single value + */ + public final double sampleStandardDeviation() { + return Math.sqrt(sampleVariance()); + } + + /** + * Returns the lowest value in the dataset. The count must be non-zero. + * + *

Non-finite values

+ * + *

If the dataset contains {@link Double#NaN} then the result is {@link Double#NaN}. If it + * contains {@link Double#NEGATIVE_INFINITY} and not {@link Double#NaN} then the result is {@link + * Double#NEGATIVE_INFINITY}. If it contains {@link Double#POSITIVE_INFINITY} and finite values + * only then the result is the lowest finite value. If it contains {@link + * Double#POSITIVE_INFINITY} only then the result is {@link Double#POSITIVE_INFINITY}. + * + * @throws IllegalStateException if the dataset is empty + */ + public double min() { + checkState(count != 0); + return min; + } + + /** + * Returns the highest value in the dataset. The count must be non-zero. + * + *

Non-finite values

+ * + *

If the dataset contains {@link Double#NaN} then the result is {@link Double#NaN}. If it + * contains {@link Double#POSITIVE_INFINITY} and not {@link Double#NaN} then the result is {@link + * Double#POSITIVE_INFINITY}. If it contains {@link Double#NEGATIVE_INFINITY} and finite values + * only then the result is the highest finite value. If it contains {@link + * Double#NEGATIVE_INFINITY} only then the result is {@link Double#NEGATIVE_INFINITY}. + * + * @throws IllegalStateException if the dataset is empty + */ + public double max() { + checkState(count != 0); + return max; + } + + double sumOfSquaresOfDeltas() { + return sumOfSquaresOfDeltas; + } + + /** + * Calculates the new value for the accumulated mean when a value is added, in the case where at + * least one of the previous mean and the value is non-finite. + */ + static double calculateNewMeanNonFinite(double previousMean, double value) { + /* + * Desired behaviour is to match the results of applying the naive mean formula. In particular, + * the update formula can subtract infinities in cases where the naive formula would add them. + * + * Consequently: + * 1. If the previous mean is finite and the new value is non-finite then the new mean is that + * value (whether it is NaN or infinity). + * 2. If the new value is finite and the previous mean is non-finite then the mean is unchanged + * (whether it is NaN or infinity). + * 3. If both the previous mean and the new value are non-finite and... + * 3a. ...either or both is NaN (so mean != value) then the new mean is NaN. + * 3b. ...they are both the same infinities (so mean == value) then the mean is unchanged. + * 3c. ...they are different infinities (so mean != value) then the new mean is NaN. + */ + if (isFinite(previousMean)) { + // This is case 1. + return value; + } else if (isFinite(value) || previousMean == value) { + // This is case 2. or 3b. + return previousMean; + } else { + // This is case 3a. or 3c. + return NaN; + } + } +} diff --git a/src/main/java/com/google/common/net/HostAndPort.java b/src/main/java/com/google/common/net/HostAndPort.java new file mode 100644 index 0000000..d5195bc --- /dev/null +++ b/src/main/java/com/google/common/net/HostAndPort.java @@ -0,0 +1,318 @@ +/* + * Copyright (C) 2011 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.net; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.base.Preconditions.checkState; + +import com.google.common.annotations.Beta; +import com.google.common.annotations.GwtCompatible; +import com.google.common.base.Objects; +import com.google.common.base.Strings; + +import java.io.Serializable; + + +/** + * An immutable representation of a host and port. + * + *

Example usage: + * + *

+ * HostAndPort hp = HostAndPort.fromString("[2001:db8::1]")
+ *     .withDefaultPort(80)
+ *     .requireBracketsForIPv6();
+ * hp.getHost();   // returns "2001:db8::1"
+ * hp.getPort();   // returns 80
+ * hp.toString();  // returns "[2001:db8::1]:80"
+ * 
+ * + *

Here are some examples of recognized formats: + * + *

    + *
  • example.com + *
  • example.com:80 + *
  • 192.0.2.1 + *
  • 192.0.2.1:80 + *
  • [2001:db8::1] - {@link #getHost()} omits brackets + *
  • [2001:db8::1]:80 - {@link #getHost()} omits brackets + *
  • 2001:db8::1 - Use {@link #requireBracketsForIPv6()} to prohibit this + *
+ * + *

Note that this is not an exhaustive list, because these methods are only concerned with + * brackets, colons, and port numbers. Full validation of the host field (if desired) is the + * caller's responsibility. + * + * @author Paul Marks + * @since 10.0 + */ +@Beta + +@GwtCompatible +public final class HostAndPort implements Serializable { + /** Magic value indicating the absence of a port number. */ + private static final int NO_PORT = -1; + + /** Hostname, IPv4/IPv6 literal, or unvalidated nonsense. */ + private final String host; + + /** Validated port number in the range [0..65535], or NO_PORT */ + private final int port; + + /** True if the parsed host has colons, but no surrounding brackets. */ + private final boolean hasBracketlessColons; + + private HostAndPort(String host, int port, boolean hasBracketlessColons) { + this.host = host; + this.port = port; + this.hasBracketlessColons = hasBracketlessColons; + } + + /** + * Returns the portion of this {@code HostAndPort} instance that should represent the hostname or + * IPv4/IPv6 literal. + * + *

A successful parse does not imply any degree of sanity in this field. For additional + * validation, see the {@link HostSpecifier} class. + * + * @since 20.0 (since 10.0 as {@code getHostText}) + */ + public String getHost() { + return host; + } + + /** Return true if this instance has a defined port. */ + public boolean hasPort() { + return port >= 0; + } + + /** + * Get the current port number, failing if no port is defined. + * + * @return a validated port number, in the range [0..65535] + * @throws IllegalStateException if no port is defined. You can use {@link #withDefaultPort(int)} + * to prevent this from occurring. + */ + public int getPort() { + checkState(hasPort()); + return port; + } + + /** Returns the current port number, with a default if no port is defined. */ + public int getPortOrDefault(int defaultPort) { + return hasPort() ? port : defaultPort; + } + + /** + * Build a HostAndPort instance from separate host and port values. + * + *

Note: Non-bracketed IPv6 literals are allowed. Use {@link #requireBracketsForIPv6()} to + * prohibit these. + * + * @param host the host string to parse. Must not contain a port number. + * @param port a port number from [0..65535] + * @return if parsing was successful, a populated HostAndPort object. + * @throws IllegalArgumentException if {@code host} contains a port number, or {@code port} is out + * of range. + */ + public static HostAndPort fromParts(String host, int port) { + checkArgument(isValidPort(port), "Port out of range: %s", port); + HostAndPort parsedHost = fromString(host); + checkArgument(!parsedHost.hasPort(), "Host has a port: %s", host); + return new HostAndPort(parsedHost.host, port, parsedHost.hasBracketlessColons); + } + + /** + * Build a HostAndPort instance from a host only. + * + *

Note: Non-bracketed IPv6 literals are allowed. Use {@link #requireBracketsForIPv6()} to + * prohibit these. + * + * @param host the host-only string to parse. Must not contain a port number. + * @return if parsing was successful, a populated HostAndPort object. + * @throws IllegalArgumentException if {@code host} contains a port number. + * @since 17.0 + */ + public static HostAndPort fromHost(String host) { + HostAndPort parsedHost = fromString(host); + checkArgument(!parsedHost.hasPort(), "Host has a port: %s", host); + return parsedHost; + } + + /** + * Split a freeform string into a host and port, without strict validation. + * + *

Note that the host-only formats will leave the port field undefined. You can use {@link + * #withDefaultPort(int)} to patch in a default value. + * + * @param hostPortString the input string to parse. + * @return if parsing was successful, a populated HostAndPort object. + * @throws IllegalArgumentException if nothing meaningful could be parsed. + */ + public static HostAndPort fromString(String hostPortString) { + checkNotNull(hostPortString); + String host; + String portString = null; + boolean hasBracketlessColons = false; + + if (hostPortString.startsWith("[")) { + String[] hostAndPort = getHostAndPortFromBracketedHost(hostPortString); + host = hostAndPort[0]; + portString = hostAndPort[1]; + } else { + int colonPos = hostPortString.indexOf(':'); + if (colonPos >= 0 && hostPortString.indexOf(':', colonPos + 1) == -1) { + // Exactly 1 colon. Split into host:port. + host = hostPortString.substring(0, colonPos); + portString = hostPortString.substring(colonPos + 1); + } else { + // 0 or 2+ colons. Bare hostname or IPv6 literal. + host = hostPortString; + hasBracketlessColons = (colonPos >= 0); + } + } + + int port = NO_PORT; + if (!Strings.isNullOrEmpty(portString)) { + // Try to parse the whole port string as a number. + // JDK7 accepts leading plus signs. We don't want to. + checkArgument(!portString.startsWith("+"), "Unparseable port number: %s", hostPortString); + try { + port = Integer.parseInt(portString); + } catch (NumberFormatException e) { + throw new IllegalArgumentException("Unparseable port number: " + hostPortString); + } + checkArgument(isValidPort(port), "Port number out of range: %s", hostPortString); + } + + return new HostAndPort(host, port, hasBracketlessColons); + } + + /** + * Parses a bracketed host-port string, throwing IllegalArgumentException if parsing fails. + * + * @param hostPortString the full bracketed host-port specification. Post might not be specified. + * @return an array with 2 strings: host and port, in that order. + * @throws IllegalArgumentException if parsing the bracketed host-port string fails. + */ + private static String[] getHostAndPortFromBracketedHost(String hostPortString) { + int colonIndex = 0; + int closeBracketIndex = 0; + checkArgument( + hostPortString.charAt(0) == '[', + "Bracketed host-port string must start with a bracket: %s", + hostPortString); + colonIndex = hostPortString.indexOf(':'); + closeBracketIndex = hostPortString.lastIndexOf(']'); + checkArgument( + colonIndex > -1 && closeBracketIndex > colonIndex, + "Invalid bracketed host/port: %s", + hostPortString); + + String host = hostPortString.substring(1, closeBracketIndex); + if (closeBracketIndex + 1 == hostPortString.length()) { + return new String[] {host, ""}; + } else { + checkArgument( + hostPortString.charAt(closeBracketIndex + 1) == ':', + "Only a colon may follow a close bracket: %s", + hostPortString); + for (int i = closeBracketIndex + 2; i < hostPortString.length(); ++i) { + checkArgument( + Character.isDigit(hostPortString.charAt(i)), + "Port must be numeric: %s", + hostPortString); + } + return new String[] {host, hostPortString.substring(closeBracketIndex + 2)}; + } + } + + /** + * Provide a default port if the parsed string contained only a host. + * + *

You can chain this after {@link #fromString(String)} to include a port in case the port was + * omitted from the input string. If a port was already provided, then this method is a no-op. + * + * @param defaultPort a port number, from [0..65535] + * @return a HostAndPort instance, guaranteed to have a defined port. + */ + public HostAndPort withDefaultPort(int defaultPort) { + checkArgument(isValidPort(defaultPort)); + if (hasPort()) { + return this; + } + return new HostAndPort(host, defaultPort, hasBracketlessColons); + } + + /** + * Generate an error if the host might be a non-bracketed IPv6 literal. + * + *

URI formatting requires that IPv6 literals be surrounded by brackets, like "[2001:db8::1]". + * Chain this call after {@link #fromString(String)} to increase the strictness of the parser, and + * disallow IPv6 literals that don't contain these brackets. + * + *

Note that this parser identifies IPv6 literals solely based on the presence of a colon. To + * perform actual validation of IP addresses, see the {@link InetAddresses#forString(String)} + * method. + * + * @return {@code this}, to enable chaining of calls. + * @throws IllegalArgumentException if bracketless IPv6 is detected. + */ + public HostAndPort requireBracketsForIPv6() { + checkArgument(!hasBracketlessColons, "Possible bracketless IPv6 literal: %s", host); + return this; + } + + @Override + public boolean equals(Object other) { + if (this == other) { + return true; + } + if (other instanceof HostAndPort) { + HostAndPort that = (HostAndPort) other; + return Objects.equal(this.host, that.host) && this.port == that.port; + } + return false; + } + + @Override + public int hashCode() { + return Objects.hashCode(host, port); + } + + /** Rebuild the host:port string, including brackets if necessary. */ + @Override + public String toString() { + // "[]:12345" requires 8 extra bytes. + StringBuilder builder = new StringBuilder(host.length() + 8); + if (host.indexOf(':') >= 0) { + builder.append('[').append(host).append(']'); + } else { + builder.append(host); + } + if (hasPort()) { + builder.append(':').append(port); + } + return builder.toString(); + } + + /** Return true for valid port numbers. */ + private static boolean isValidPort(int port) { + return port >= 0 && port <= 65535; + } + + private static final long serialVersionUID = 0; +} diff --git a/src/main/java/com/google/common/net/HostSpecifier.java b/src/main/java/com/google/common/net/HostSpecifier.java new file mode 100644 index 0000000..ac90628 --- /dev/null +++ b/src/main/java/com/google/common/net/HostSpecifier.java @@ -0,0 +1,168 @@ +/* + * Copyright (C) 2009 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.net; + +import com.google.common.annotations.Beta; +import com.google.common.annotations.GwtIncompatible; +import com.google.common.base.Preconditions; +import java.net.InetAddress; +import java.text.ParseException; + + +/** + * A syntactically valid host specifier, suitable for use in a URI. This may be either a numeric IP + * address in IPv4 or IPv6 notation, or a domain name. + * + *

Because this class is intended to represent host specifiers which can reasonably be used in a + * URI, the domain name case is further restricted to include only those domain names which end in a + * recognized public suffix; see {@link InternetDomainName#isPublicSuffix()} for details. + * + *

Note that no network lookups are performed by any {@code HostSpecifier} methods. No attempt is + * made to verify that a provided specifier corresponds to a real or accessible host. Only syntactic + * and pattern-based checks are performed. + * + *

If you know that a given string represents a numeric IP address, use {@link InetAddresses} to + * obtain and manipulate a {@link java.net.InetAddress} instance from it rather than using this + * class. Similarly, if you know that a given string represents a domain name, use {@link + * InternetDomainName} rather than this class. + * + * @author Craig Berry + * @since 5.0 + */ +@Beta +@GwtIncompatible +public final class HostSpecifier { + + private final String canonicalForm; + + private HostSpecifier(String canonicalForm) { + this.canonicalForm = canonicalForm; + } + + /** + * Returns a {@code HostSpecifier} built from the provided {@code specifier}, which is already + * known to be valid. If the {@code specifier} might be invalid, use {@link #from(String)} + * instead. + * + *

The specifier must be in one of these formats: + * + *

    + *
  • A domain name, like {@code google.com} + *
  • A IPv4 address string, like {@code 127.0.0.1} + *
  • An IPv6 address string with or without brackets, like {@code [2001:db8::1]} or {@code + * 2001:db8::1} + *
+ * + * @throws IllegalArgumentException if the specifier is not valid. + */ + public static HostSpecifier fromValid(String specifier) { + // Verify that no port was specified, and strip optional brackets from + // IPv6 literals. + final HostAndPort parsedHost = HostAndPort.fromString(specifier); + Preconditions.checkArgument(!parsedHost.hasPort()); + final String host = parsedHost.getHost(); + + // Try to interpret the specifier as an IP address. Note we build + // the address rather than using the .is* methods because we want to + // use InetAddresses.toUriString to convert the result to a string in + // canonical form. + InetAddress addr = null; + try { + addr = InetAddresses.forString(host); + } catch (IllegalArgumentException e) { + // It is not an IPv4 or IPv6 literal + } + + if (addr != null) { + return new HostSpecifier(InetAddresses.toUriString(addr)); + } + + // It is not any kind of IP address; must be a domain name or invalid. + + // TODO(user): different versions of this for different factories? + final InternetDomainName domain = InternetDomainName.from(host); + + if (domain.hasPublicSuffix()) { + return new HostSpecifier(domain.toString()); + } + + throw new IllegalArgumentException( + "Domain name does not have a recognized public suffix: " + host); + } + + /** + * Attempts to return a {@code HostSpecifier} for the given string, throwing an exception if + * parsing fails. Always use this method in preference to {@link #fromValid(String)} for a + * specifier that is not already known to be valid. + * + * @throws ParseException if the specifier is not valid. + */ + public static HostSpecifier from(String specifier) throws ParseException { + try { + return fromValid(specifier); + } catch (IllegalArgumentException e) { + // Since the IAE can originate at several different points inside + // fromValid(), we implement this method in terms of that one rather + // than the reverse. + + ParseException parseException = new ParseException("Invalid host specifier: " + specifier, 0); + parseException.initCause(e); + throw parseException; + } + } + + /** + * Determines whether {@code specifier} represents a valid {@link HostSpecifier} as described in + * the documentation for {@link #fromValid(String)}. + */ + public static boolean isValid(String specifier) { + try { + fromValid(specifier); + return true; + } catch (IllegalArgumentException e) { + return false; + } + } + + @Override + public boolean equals(Object other) { + if (this == other) { + return true; + } + + if (other instanceof HostSpecifier) { + final HostSpecifier that = (HostSpecifier) other; + return this.canonicalForm.equals(that.canonicalForm); + } + + return false; + } + + @Override + public int hashCode() { + return canonicalForm.hashCode(); + } + + /** + * Returns a string representation of the host specifier suitable for inclusion in a URI. If the + * host specifier is a domain name, the string will be normalized to all lower case. If the + * specifier was an IPv6 address without brackets, brackets are added so that the result will be + * usable in the host part of a URI. + */ + @Override + public String toString() { + return canonicalForm; + } +} diff --git a/src/main/java/com/google/common/net/HttpHeaders.java b/src/main/java/com/google/common/net/HttpHeaders.java new file mode 100644 index 0000000..b9fe1ec --- /dev/null +++ b/src/main/java/com/google/common/net/HttpHeaders.java @@ -0,0 +1,559 @@ +/* + * Copyright (C) 2011 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.net; + +import com.google.common.annotations.Beta; +import com.google.common.annotations.GwtCompatible; + +/** + * Contains constant definitions for the HTTP header field names. See: + * + * + * + * + * @author Kurt Alfred Kluever + * @since 11.0 + */ +@GwtCompatible +public final class HttpHeaders { + private HttpHeaders() {} + + // HTTP Request and Response header fields + + /** The HTTP {@code Cache-Control} header field name. */ + public static final String CACHE_CONTROL = "Cache-Control"; + /** The HTTP {@code Content-Length} header field name. */ + public static final String CONTENT_LENGTH = "Content-Length"; + /** The HTTP {@code Content-Type} header field name. */ + public static final String CONTENT_TYPE = "Content-Type"; + /** The HTTP {@code Date} header field name. */ + public static final String DATE = "Date"; + /** The HTTP {@code Pragma} header field name. */ + public static final String PRAGMA = "Pragma"; + /** The HTTP {@code Via} header field name. */ + public static final String VIA = "Via"; + /** The HTTP {@code Warning} header field name. */ + public static final String WARNING = "Warning"; + + // HTTP Request header fields + + /** The HTTP {@code Accept} header field name. */ + public static final String ACCEPT = "Accept"; + /** The HTTP {@code Accept-Charset} header field name. */ + public static final String ACCEPT_CHARSET = "Accept-Charset"; + /** The HTTP {@code Accept-Encoding} header field name. */ + public static final String ACCEPT_ENCODING = "Accept-Encoding"; + /** The HTTP {@code Accept-Language} header field name. */ + public static final String ACCEPT_LANGUAGE = "Accept-Language"; + /** The HTTP {@code Access-Control-Request-Headers} header field name. */ + public static final String ACCESS_CONTROL_REQUEST_HEADERS = "Access-Control-Request-Headers"; + /** The HTTP {@code Access-Control-Request-Method} header field name. */ + public static final String ACCESS_CONTROL_REQUEST_METHOD = "Access-Control-Request-Method"; + /** The HTTP {@code Authorization} header field name. */ + public static final String AUTHORIZATION = "Authorization"; + /** The HTTP {@code Connection} header field name. */ + public static final String CONNECTION = "Connection"; + /** The HTTP {@code Cookie} header field name. */ + public static final String COOKIE = "Cookie"; + /** + * The HTTP {@code + * Cross-Origin-Resource-Policy} header field name. + * + * @since 28.0 + */ + public static final String CROSS_ORIGIN_RESOURCE_POLICY = "Cross-Origin-Resource-Policy"; + /** + * The HTTP {@code Early-Data} header field + * name. + * + * @since 27.0 + */ + public static final String EARLY_DATA = "Early-Data"; + /** The HTTP {@code Expect} header field name. */ + public static final String EXPECT = "Expect"; + /** The HTTP {@code From} header field name. */ + public static final String FROM = "From"; + /** + * The HTTP {@code Forwarded} header field name. + * + * @since 20.0 + */ + public static final String FORWARDED = "Forwarded"; + /** + * The HTTP {@code Follow-Only-When-Prerender-Shown} header field name. + * + * @since 17.0 + */ + @Beta + public static final String FOLLOW_ONLY_WHEN_PRERENDER_SHOWN = "Follow-Only-When-Prerender-Shown"; + /** The HTTP {@code Host} header field name. */ + public static final String HOST = "Host"; + /** + * The HTTP {@code HTTP2-Settings} + * header field name. + * + * @since 24.0 + */ + public static final String HTTP2_SETTINGS = "HTTP2-Settings"; + /** The HTTP {@code If-Match} header field name. */ + public static final String IF_MATCH = "If-Match"; + /** The HTTP {@code If-Modified-Since} header field name. */ + public static final String IF_MODIFIED_SINCE = "If-Modified-Since"; + /** The HTTP {@code If-None-Match} header field name. */ + public static final String IF_NONE_MATCH = "If-None-Match"; + /** The HTTP {@code If-Range} header field name. */ + public static final String IF_RANGE = "If-Range"; + /** The HTTP {@code If-Unmodified-Since} header field name. */ + public static final String IF_UNMODIFIED_SINCE = "If-Unmodified-Since"; + /** The HTTP {@code Last-Event-ID} header field name. */ + public static final String LAST_EVENT_ID = "Last-Event-ID"; + /** The HTTP {@code Max-Forwards} header field name. */ + public static final String MAX_FORWARDS = "Max-Forwards"; + /** The HTTP {@code Origin} header field name. */ + public static final String ORIGIN = "Origin"; + /** The HTTP {@code Proxy-Authorization} header field name. */ + public static final String PROXY_AUTHORIZATION = "Proxy-Authorization"; + /** The HTTP {@code Range} header field name. */ + public static final String RANGE = "Range"; + /** The HTTP {@code Referer} header field name. */ + public static final String REFERER = "Referer"; + /** + * The HTTP {@code Referrer-Policy} header + * field name. + * + * @since 23.4 + */ + public static final String REFERRER_POLICY = "Referrer-Policy"; + + /** + * Values for the {@code Referrer-Policy} + * header. + * + * @since 23.4 + */ + public static final class ReferrerPolicyValues { + private ReferrerPolicyValues() {} + + public static final String NO_REFERRER = "no-referrer"; + public static final String NO_REFFERER_WHEN_DOWNGRADE = "no-referrer-when-downgrade"; + public static final String SAME_ORIGIN = "same-origin"; + public static final String ORIGIN = "origin"; + public static final String STRICT_ORIGIN = "strict-origin"; + public static final String ORIGIN_WHEN_CROSS_ORIGIN = "origin-when-cross-origin"; + public static final String STRICT_ORIGIN_WHEN_CROSS_ORIGIN = "strict-origin-when-cross-origin"; + public static final String UNSAFE_URL = "unsafe-url"; + } + + /** + * The HTTP {@code + * Service-Worker} header field name. + * + * @since 20.0 + */ + public static final String SERVICE_WORKER = "Service-Worker"; + /** The HTTP {@code TE} header field name. */ + public static final String TE = "TE"; + /** The HTTP {@code Upgrade} header field name. */ + public static final String UPGRADE = "Upgrade"; + /** + * The HTTP {@code + * Upgrade-Insecure-Requests} header field name. + * + * @since 28.1 + */ + public static final String UPGRADE_INSECURE_REQUESTS = "Upgrade-Insecure-Requests"; + + /** The HTTP {@code User-Agent} header field name. */ + public static final String USER_AGENT = "User-Agent"; + + // HTTP Response header fields + + /** The HTTP {@code Accept-Ranges} header field name. */ + public static final String ACCEPT_RANGES = "Accept-Ranges"; + /** The HTTP {@code Access-Control-Allow-Headers} header field name. */ + public static final String ACCESS_CONTROL_ALLOW_HEADERS = "Access-Control-Allow-Headers"; + /** The HTTP {@code Access-Control-Allow-Methods} header field name. */ + public static final String ACCESS_CONTROL_ALLOW_METHODS = "Access-Control-Allow-Methods"; + /** The HTTP {@code Access-Control-Allow-Origin} header field name. */ + public static final String ACCESS_CONTROL_ALLOW_ORIGIN = "Access-Control-Allow-Origin"; + /** The HTTP {@code Access-Control-Allow-Credentials} header field name. */ + public static final String ACCESS_CONTROL_ALLOW_CREDENTIALS = "Access-Control-Allow-Credentials"; + /** The HTTP {@code Access-Control-Expose-Headers} header field name. */ + public static final String ACCESS_CONTROL_EXPOSE_HEADERS = "Access-Control-Expose-Headers"; + /** The HTTP {@code Access-Control-Max-Age} header field name. */ + public static final String ACCESS_CONTROL_MAX_AGE = "Access-Control-Max-Age"; + /** The HTTP {@code Age} header field name. */ + public static final String AGE = "Age"; + /** The HTTP {@code Allow} header field name. */ + public static final String ALLOW = "Allow"; + /** The HTTP {@code Content-Disposition} header field name. */ + public static final String CONTENT_DISPOSITION = "Content-Disposition"; + /** The HTTP {@code Content-Encoding} header field name. */ + public static final String CONTENT_ENCODING = "Content-Encoding"; + /** The HTTP {@code Content-Language} header field name. */ + public static final String CONTENT_LANGUAGE = "Content-Language"; + /** The HTTP {@code Content-Location} header field name. */ + public static final String CONTENT_LOCATION = "Content-Location"; + /** The HTTP {@code Content-MD5} header field name. */ + public static final String CONTENT_MD5 = "Content-MD5"; + /** The HTTP {@code Content-Range} header field name. */ + public static final String CONTENT_RANGE = "Content-Range"; + /** + * The HTTP {@code + * Content-Security-Policy} header field name. + * + * @since 15.0 + */ + public static final String CONTENT_SECURITY_POLICY = "Content-Security-Policy"; + /** + * The HTTP + * {@code Content-Security-Policy-Report-Only} header field name. + * + * @since 15.0 + */ + public static final String CONTENT_SECURITY_POLICY_REPORT_ONLY = + "Content-Security-Policy-Report-Only"; + /** + * The HTTP nonstandard {@code X-Content-Security-Policy} header field name. It was introduced in + * CSP v.1 and used by the Firefox until + * version 23 and the Internet Explorer version 10. Please, use {@link #CONTENT_SECURITY_POLICY} + * to pass the CSP. + * + * @since 20.0 + */ + public static final String X_CONTENT_SECURITY_POLICY = "X-Content-Security-Policy"; + /** + * The HTTP nonstandard {@code X-Content-Security-Policy-Report-Only} header field name. It was + * introduced in CSP v.1 and used by the + * Firefox until version 23 and the Internet Explorer version 10. Please, use {@link + * #CONTENT_SECURITY_POLICY_REPORT_ONLY} to pass the CSP. + * + * @since 20.0 + */ + public static final String X_CONTENT_SECURITY_POLICY_REPORT_ONLY = + "X-Content-Security-Policy-Report-Only"; + /** + * The HTTP nonstandard {@code X-WebKit-CSP} header field name. It was introduced in CSP v.1 and used by the Chrome until + * version 25. Please, use {@link #CONTENT_SECURITY_POLICY} to pass the CSP. + * + * @since 20.0 + */ + public static final String X_WEBKIT_CSP = "X-WebKit-CSP"; + /** + * The HTTP nonstandard {@code X-WebKit-CSP-Report-Only} header field name. It was introduced in + * CSP v.1 and used by the Chrome until + * version 25. Please, use {@link #CONTENT_SECURITY_POLICY_REPORT_ONLY} to pass the CSP. + * + * @since 20.0 + */ + public static final String X_WEBKIT_CSP_REPORT_ONLY = "X-WebKit-CSP-Report-Only"; + /** + * The HTTP Cross-Origin-Opener-Policy header field name. + * + * @since NEXT + */ + public static final String CROSS_ORIGIN_OPENER_POLICY = "Cross-Origin-Opener-Policy"; + /** The HTTP {@code ETag} header field name. */ + public static final String ETAG = "ETag"; + /** The HTTP {@code Expires} header field name. */ + public static final String EXPIRES = "Expires"; + /** The HTTP {@code Last-Modified} header field name. */ + public static final String LAST_MODIFIED = "Last-Modified"; + /** The HTTP {@code Link} header field name. */ + public static final String LINK = "Link"; + /** The HTTP {@code Location} header field name. */ + public static final String LOCATION = "Location"; + /** + * The HTTP {@code Origin-Trial} + * header field name. + * + * @since 27.1 + */ + public static final String ORIGIN_TRIAL = "Origin-Trial"; + /** The HTTP {@code P3P} header field name. Limited browser support. */ + public static final String P3P = "P3P"; + /** The HTTP {@code Proxy-Authenticate} header field name. */ + public static final String PROXY_AUTHENTICATE = "Proxy-Authenticate"; + /** The HTTP {@code Refresh} header field name. Non-standard header supported by most browsers. */ + public static final String REFRESH = "Refresh"; + /** + * The HTTP {@code Report-To} header field name. + * + * @since 27.1 + */ + public static final String REPORT_TO = "Report-To"; + /** The HTTP {@code Retry-After} header field name. */ + public static final String RETRY_AFTER = "Retry-After"; + /** The HTTP {@code Server} header field name. */ + public static final String SERVER = "Server"; + /** + * The HTTP {@code Server-Timing} header field + * name. + * + * @since 23.6 + */ + public static final String SERVER_TIMING = "Server-Timing"; + /** + * The HTTP {@code + * Service-Worker-Allowed} header field name. + * + * @since 20.0 + */ + public static final String SERVICE_WORKER_ALLOWED = "Service-Worker-Allowed"; + /** The HTTP {@code Set-Cookie} header field name. */ + public static final String SET_COOKIE = "Set-Cookie"; + /** The HTTP {@code Set-Cookie2} header field name. */ + public static final String SET_COOKIE2 = "Set-Cookie2"; + + /** + * The HTTP {@code SourceMap} header field name. + * + * @since 27.1 + */ + @Beta public static final String SOURCE_MAP = "SourceMap"; + + /** + * The HTTP {@code + * Strict-Transport-Security} header field name. + * + * @since 15.0 + */ + public static final String STRICT_TRANSPORT_SECURITY = "Strict-Transport-Security"; + /** + * The HTTP {@code + * Timing-Allow-Origin} header field name. + * + * @since 15.0 + */ + public static final String TIMING_ALLOW_ORIGIN = "Timing-Allow-Origin"; + /** The HTTP {@code Trailer} header field name. */ + public static final String TRAILER = "Trailer"; + /** The HTTP {@code Transfer-Encoding} header field name. */ + public static final String TRANSFER_ENCODING = "Transfer-Encoding"; + /** The HTTP {@code Vary} header field name. */ + public static final String VARY = "Vary"; + /** The HTTP {@code WWW-Authenticate} header field name. */ + public static final String WWW_AUTHENTICATE = "WWW-Authenticate"; + + // Common, non-standard HTTP header fields + + /** The HTTP {@code DNT} header field name. */ + public static final String DNT = "DNT"; + /** The HTTP {@code X-Content-Type-Options} header field name. */ + public static final String X_CONTENT_TYPE_OPTIONS = "X-Content-Type-Options"; + /** The HTTP {@code X-Do-Not-Track} header field name. */ + public static final String X_DO_NOT_TRACK = "X-Do-Not-Track"; + /** The HTTP {@code X-Forwarded-For} header field name (superseded by {@code Forwarded}). */ + public static final String X_FORWARDED_FOR = "X-Forwarded-For"; + /** The HTTP {@code X-Forwarded-Proto} header field name. */ + public static final String X_FORWARDED_PROTO = "X-Forwarded-Proto"; + /** + * The HTTP {@code X-Forwarded-Host} header field name. + * + * @since 20.0 + */ + public static final String X_FORWARDED_HOST = "X-Forwarded-Host"; + /** + * The HTTP {@code X-Forwarded-Port} header field name. + * + * @since 20.0 + */ + public static final String X_FORWARDED_PORT = "X-Forwarded-Port"; + /** The HTTP {@code X-Frame-Options} header field name. */ + public static final String X_FRAME_OPTIONS = "X-Frame-Options"; + /** The HTTP {@code X-Powered-By} header field name. */ + public static final String X_POWERED_BY = "X-Powered-By"; + /** + * The HTTP {@code + * Public-Key-Pins} header field name. + * + * @since 15.0 + */ + @Beta public static final String PUBLIC_KEY_PINS = "Public-Key-Pins"; + /** + * The HTTP {@code + * Public-Key-Pins-Report-Only} header field name. + * + * @since 15.0 + */ + @Beta public static final String PUBLIC_KEY_PINS_REPORT_ONLY = "Public-Key-Pins-Report-Only"; + /** The HTTP {@code X-Requested-With} header field name. */ + public static final String X_REQUESTED_WITH = "X-Requested-With"; + /** The HTTP {@code X-User-IP} header field name. */ + public static final String X_USER_IP = "X-User-IP"; + /** + * The HTTP {@code X-Download-Options} header field name. + * + *

When the new X-Download-Options header is present with the value {@code noopen}, the user is + * prevented from opening a file download directly; instead, they must first save the file + * locally. + * + * @since 24.1 + */ + @Beta public static final String X_DOWNLOAD_OPTIONS = "X-Download-Options"; + /** The HTTP {@code X-XSS-Protection} header field name. */ + public static final String X_XSS_PROTECTION = "X-XSS-Protection"; + /** + * The HTTP {@code + * X-DNS-Prefetch-Control} header controls DNS prefetch behavior. Value can be "on" or "off". + * By default, DNS prefetching is "on" for HTTP pages and "off" for HTTPS pages. + */ + public static final String X_DNS_PREFETCH_CONTROL = "X-DNS-Prefetch-Control"; + /** + * The HTTP + * {@code Ping-From} header field name. + * + * @since 19.0 + */ + public static final String PING_FROM = "Ping-From"; + /** + * The HTTP + * {@code Ping-To} header field name. + * + * @since 19.0 + */ + public static final String PING_TO = "Ping-To"; + + /** + * The HTTP {@code + * Purpose} header field name. + * + * @since 28.0 + */ + public static final String PURPOSE = "Purpose"; + /** + * The HTTP {@code + * X-Purpose} header field name. + * + * @since 28.0 + */ + public static final String X_PURPOSE = "X-Purpose"; + /** + * The HTTP {@code + * X-Moz} header field name. + * + * @since 28.0 + */ + public static final String X_MOZ = "X-Moz"; + + /** + * The HTTP {@code Sec-Fetch-Dest} + * header field name. + * + * @since 27.1 + */ + public static final String SEC_FETCH_DEST = "Sec-Fetch-Dest"; + /** + * The HTTP {@code Sec-Fetch-Mode} + * header field name. + * + * @since 27.1 + */ + public static final String SEC_FETCH_MODE = "Sec-Fetch-Mode"; + /** + * The HTTP {@code Sec-Fetch-Site} + * header field name. + * + * @since 27.1 + */ + public static final String SEC_FETCH_SITE = "Sec-Fetch-Site"; + /** + * The HTTP {@code Sec-Fetch-User} + * header field name. + * + * @since 27.1 + */ + public static final String SEC_FETCH_USER = "Sec-Fetch-User"; + /** + * The HTTP {@code Sec-Metadata} + * header field name. + * + * @since 26.0 + */ + public static final String SEC_METADATA = "Sec-Metadata"; + /** + * The HTTP {@code + * Sec-Token-Binding} header field name. + * + * @since 25.1 + */ + public static final String SEC_TOKEN_BINDING = "Sec-Token-Binding"; + /** + * The HTTP {@code + * Sec-Provided-Token-Binding-ID} header field name. + * + * @since 25.1 + */ + public static final String SEC_PROVIDED_TOKEN_BINDING_ID = "Sec-Provided-Token-Binding-ID"; + /** + * The HTTP {@code + * Sec-Referred-Token-Binding-ID} header field name. + * + * @since 25.1 + */ + public static final String SEC_REFERRED_TOKEN_BINDING_ID = "Sec-Referred-Token-Binding-ID"; + /** + * The HTTP {@code Sec-WebSocket-Accept} header + * field name. + * + * @since 28.0 + */ + public static final String SEC_WEBSOCKET_ACCEPT = "Sec-WebSocket-Accept"; + /** + * The HTTP {@code Sec-WebSocket-Extensions} + * header field name. + * + * @since 28.0 + */ + public static final String SEC_WEBSOCKET_EXTENSIONS = "Sec-WebSocket-Extensions"; + /** + * The HTTP {@code Sec-WebSocket-Key} header + * field name. + * + * @since 28.0 + */ + public static final String SEC_WEBSOCKET_KEY = "Sec-WebSocket-Key"; + /** + * The HTTP {@code Sec-WebSocket-Protocol} + * header field name. + * + * @since 28.0 + */ + public static final String SEC_WEBSOCKET_PROTOCOL = "Sec-WebSocket-Protocol"; + /** + * The HTTP {@code Sec-WebSocket-Version} header + * field name. + * + * @since 28.0 + */ + public static final String SEC_WEBSOCKET_VERSION = "Sec-WebSocket-Version"; + /** + * The HTTP {@code CDN-Loop} header field name. + * + * @since 28.0 + */ + public static final String CDN_LOOP = "CDN-Loop"; +} diff --git a/src/main/java/com/google/common/net/InetAddresses.java b/src/main/java/com/google/common/net/InetAddresses.java new file mode 100644 index 0000000..8b91975 --- /dev/null +++ b/src/main/java/com/google/common/net/InetAddresses.java @@ -0,0 +1,1082 @@ +/* + * Copyright (C) 2008 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.net; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.annotations.Beta; +import com.google.common.annotations.GwtIncompatible; +import com.google.common.base.MoreObjects; +import com.google.common.base.Splitter; +import com.google.common.collect.Iterables; +import com.google.common.hash.Hashing; +import com.google.common.io.ByteStreams; +import com.google.common.primitives.Ints; +import java.math.BigInteger; +import java.net.Inet4Address; +import java.net.Inet6Address; +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.nio.ByteBuffer; +import java.util.Arrays; +import java.util.List; +import java.util.Locale; + + +/** + * Static utility methods pertaining to {@link InetAddress} instances. + * + *

Important note: Unlike {@code InetAddress.getByName()}, the methods of this class never + * cause DNS services to be accessed. For this reason, you should prefer these methods as much as + * possible over their JDK equivalents whenever you are expecting to handle only IP address string + * literals -- there is no blocking DNS penalty for a malformed string. + * + *

When dealing with {@link Inet4Address} and {@link Inet6Address} objects as byte arrays (vis. + * {@code InetAddress.getAddress()}) they are 4 and 16 bytes in length, respectively, and represent + * the address in network byte order. + * + *

Examples of IP addresses and their byte representations: + * + *

+ *
The IPv4 loopback address, {@code "127.0.0.1"}. + *
{@code 7f 00 00 01} + *
The IPv6 loopback address, {@code "::1"}. + *
{@code 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 01} + *
From the IPv6 reserved documentation prefix ({@code 2001:db8::/32}), {@code "2001:db8::1"}. + *
{@code 20 01 0d b8 00 00 00 00 00 00 00 00 00 00 00 01} + *
An IPv6 "IPv4 compatible" (or "compat") address, {@code "::192.168.0.1"}. + *
{@code 00 00 00 00 00 00 00 00 00 00 00 00 c0 a8 00 01} + *
An IPv6 "IPv4 mapped" address, {@code "::ffff:192.168.0.1"}. + *
{@code 00 00 00 00 00 00 00 00 00 00 ff ff c0 a8 00 01} + *
+ * + *

A few notes about IPv6 "IPv4 mapped" addresses and their observed use in Java. + * + *

"IPv4 mapped" addresses were originally a representation of IPv4 addresses for use on an IPv6 + * socket that could receive both IPv4 and IPv6 connections (by disabling the {@code IPV6_V6ONLY} + * socket option on an IPv6 socket). Yes, it's confusing. Nevertheless, these "mapped" addresses + * were never supposed to be seen on the wire. That assumption was dropped, some say mistakenly, in + * later RFCs with the apparent aim of making IPv4-to-IPv6 transition simpler. + * + *

Technically one can create a 128bit IPv6 address with the wire format of a "mapped" + * address, as shown above, and transmit it in an IPv6 packet header. However, Java's InetAddress + * creation methods appear to adhere doggedly to the original intent of the "mapped" address: all + * "mapped" addresses return {@link Inet4Address} objects. + * + *

For added safety, it is common for IPv6 network operators to filter all packets where either + * the source or destination address appears to be a "compat" or "mapped" address. Filtering + * suggestions usually recommend discarding any packets with source or destination addresses in the + * invalid range {@code ::/3}, which includes both of these bizarre address formats. For more + * information on "bogons", including lists of IPv6 bogon space, see: + * + *

+ * + * @author Erik Kline + * @since 5.0 + */ +@Beta +@GwtIncompatible +public final class InetAddresses { + private static final int IPV4_PART_COUNT = 4; + private static final int IPV6_PART_COUNT = 8; + private static final Splitter IPV4_SPLITTER = Splitter.on('.').limit(IPV4_PART_COUNT); + private static final Splitter IPV6_SPLITTER = Splitter.on(':').limit(IPV6_PART_COUNT + 2); + private static final Inet4Address LOOPBACK4 = (Inet4Address) forString("127.0.0.1"); + private static final Inet4Address ANY4 = (Inet4Address) forString("0.0.0.0"); + + private InetAddresses() {} + + /** + * Returns an {@link Inet4Address}, given a byte array representation of the IPv4 address. + * + * @param bytes byte array representing an IPv4 address (should be of length 4) + * @return {@link Inet4Address} corresponding to the supplied byte array + * @throws IllegalArgumentException if a valid {@link Inet4Address} can not be created + */ + private static Inet4Address getInet4Address(byte[] bytes) { + checkArgument( + bytes.length == 4, + "Byte array has invalid length for an IPv4 address: %s != 4.", + bytes.length); + + // Given a 4-byte array, this cast should always succeed. + return (Inet4Address) bytesToInetAddress(bytes); + } + + /** + * Returns the {@link InetAddress} having the given string representation. + * + *

This deliberately avoids all nameservice lookups (e.g. no DNS). + * + *

Anything after a {@code %} in an IPv6 address is ignored (assumed to be a Scope ID). + * + * @param ipString {@code String} containing an IPv4 or IPv6 string literal, e.g. {@code + * "192.168.0.1"} or {@code "2001:db8::1"} + * @return {@link InetAddress} representing the argument + * @throws IllegalArgumentException if the argument is not a valid IP string literal + */ + public static InetAddress forString(String ipString) { + byte[] addr = ipStringToBytes(ipString); + + // The argument was malformed, i.e. not an IP string literal. + if (addr == null) { + throw formatIllegalArgumentException("'%s' is not an IP string literal.", ipString); + } + + return bytesToInetAddress(addr); + } + + /** + * Returns {@code true} if the supplied string is a valid IP string literal, {@code false} + * otherwise. + * + * @param ipString {@code String} to evaluated as an IP string literal + * @return {@code true} if the argument is a valid IP string literal + */ + public static boolean isInetAddress(String ipString) { + return ipStringToBytes(ipString) != null; + } + + /** Returns {@code null} if unable to parse into a {@code byte[]}. */ + private static byte [] ipStringToBytes(String ipString) { + // Make a first pass to categorize the characters in this string. + boolean hasColon = false; + boolean hasDot = false; + int percentIndex = -1; + for (int i = 0; i < ipString.length(); i++) { + char c = ipString.charAt(i); + if (c == '.') { + hasDot = true; + } else if (c == ':') { + if (hasDot) { + return null; // Colons must not appear after dots. + } + hasColon = true; + } else if (c == '%') { + percentIndex = i; + break; // everything after a '%' is ignored (it's a Scope ID): http://superuser.com/a/99753 + } else if (Character.digit(c, 16) == -1) { + return null; // Everything else must be a decimal or hex digit. + } + } + + // Now decide which address family to parse. + if (hasColon) { + if (hasDot) { + ipString = convertDottedQuadToHex(ipString); + if (ipString == null) { + return null; + } + } + if (percentIndex != -1) { + ipString = ipString.substring(0, percentIndex); + } + return textToNumericFormatV6(ipString); + } else if (hasDot) { + return textToNumericFormatV4(ipString); + } + return null; + } + + private static byte [] textToNumericFormatV4(String ipString) { + byte[] bytes = new byte[IPV4_PART_COUNT]; + int i = 0; + try { + for (String octet : IPV4_SPLITTER.split(ipString)) { + bytes[i++] = parseOctet(octet); + } + } catch (NumberFormatException ex) { + return null; + } + + return i == IPV4_PART_COUNT ? bytes : null; + } + + private static byte [] textToNumericFormatV6(String ipString) { + // An address can have [2..8] colons, and N colons make N+1 parts. + List parts = IPV6_SPLITTER.splitToList(ipString); + if (parts.size() < 3 || parts.size() > IPV6_PART_COUNT + 1) { + return null; + } + + // Disregarding the endpoints, find "::" with nothing in between. + // This indicates that a run of zeroes has been skipped. + int skipIndex = -1; + for (int i = 1; i < parts.size() - 1; i++) { + if (parts.get(i).length() == 0) { + if (skipIndex >= 0) { + return null; // Can't have more than one :: + } + skipIndex = i; + } + } + + int partsHi; // Number of parts to copy from above/before the "::" + int partsLo; // Number of parts to copy from below/after the "::" + if (skipIndex >= 0) { + // If we found a "::", then check if it also covers the endpoints. + partsHi = skipIndex; + partsLo = parts.size() - skipIndex - 1; + if (parts.get(0).length() == 0 && --partsHi != 0) { + return null; // ^: requires ^:: + } + if (Iterables.getLast(parts).length() == 0 && --partsLo != 0) { + return null; // :$ requires ::$ + } + } else { + // Otherwise, allocate the entire address to partsHi. The endpoints + // could still be empty, but parseHextet() will check for that. + partsHi = parts.size(); + partsLo = 0; + } + + // If we found a ::, then we must have skipped at least one part. + // Otherwise, we must have exactly the right number of parts. + int partsSkipped = IPV6_PART_COUNT - (partsHi + partsLo); + if (!(skipIndex >= 0 ? partsSkipped >= 1 : partsSkipped == 0)) { + return null; + } + + // Now parse the hextets into a byte array. + ByteBuffer rawBytes = ByteBuffer.allocate(2 * IPV6_PART_COUNT); + try { + for (int i = 0; i < partsHi; i++) { + rawBytes.putShort(parseHextet(parts.get(i))); + } + for (int i = 0; i < partsSkipped; i++) { + rawBytes.putShort((short) 0); + } + for (int i = partsLo; i > 0; i--) { + rawBytes.putShort(parseHextet(parts.get(parts.size() - i))); + } + } catch (NumberFormatException ex) { + return null; + } + return rawBytes.array(); + } + + private static String convertDottedQuadToHex(String ipString) { + int lastColon = ipString.lastIndexOf(':'); + String initialPart = ipString.substring(0, lastColon + 1); + String dottedQuad = ipString.substring(lastColon + 1); + byte[] quad = textToNumericFormatV4(dottedQuad); + if (quad == null) { + return null; + } + String penultimate = Integer.toHexString(((quad[0] & 0xff) << 8) | (quad[1] & 0xff)); + String ultimate = Integer.toHexString(((quad[2] & 0xff) << 8) | (quad[3] & 0xff)); + return initialPart + penultimate + ":" + ultimate; + } + + private static byte parseOctet(String ipPart) { + // Note: we already verified that this string contains only hex digits. + int octet = Integer.parseInt(ipPart); + // Disallow leading zeroes, because no clear standard exists on + // whether these should be interpreted as decimal or octal. + if (octet > 255 || (ipPart.startsWith("0") && ipPart.length() > 1)) { + throw new NumberFormatException(); + } + return (byte) octet; + } + + private static short parseHextet(String ipPart) { + // Note: we already verified that this string contains only hex digits. + int hextet = Integer.parseInt(ipPart, 16); + if (hextet > 0xffff) { + throw new NumberFormatException(); + } + return (short) hextet; + } + + /** + * Convert a byte array into an InetAddress. + * + *

{@link InetAddress#getByAddress} is documented as throwing a checked exception "if IP + * address is of illegal length." We replace it with an unchecked exception, for use by callers + * who already know that addr is an array of length 4 or 16. + * + * @param addr the raw 4-byte or 16-byte IP address in big-endian order + * @return an InetAddress object created from the raw IP address + */ + private static InetAddress bytesToInetAddress(byte[] addr) { + try { + return InetAddress.getByAddress(addr); + } catch (UnknownHostException e) { + throw new AssertionError(e); + } + } + + /** + * Returns the string representation of an {@link InetAddress}. + * + *

For IPv4 addresses, this is identical to {@link InetAddress#getHostAddress()}, but for IPv6 + * addresses, the output follows RFC 5952 section + * 4. The main difference is that this method uses "::" for zero compression, while Java's version + * uses the uncompressed form. + * + *

This method uses hexadecimal for all IPv6 addresses, including IPv4-mapped IPv6 addresses + * such as "::c000:201". The output does not include a Scope ID. + * + * @param ip {@link InetAddress} to be converted to an address string + * @return {@code String} containing the text-formatted IP address + * @since 10.0 + */ + public static String toAddrString(InetAddress ip) { + checkNotNull(ip); + if (ip instanceof Inet4Address) { + // For IPv4, Java's formatting is good enough. + return ip.getHostAddress(); + } + checkArgument(ip instanceof Inet6Address); + byte[] bytes = ip.getAddress(); + int[] hextets = new int[IPV6_PART_COUNT]; + for (int i = 0; i < hextets.length; i++) { + hextets[i] = Ints.fromBytes((byte) 0, (byte) 0, bytes[2 * i], bytes[2 * i + 1]); + } + compressLongestRunOfZeroes(hextets); + return hextetsToIPv6String(hextets); + } + + /** + * Identify and mark the longest run of zeroes in an IPv6 address. + * + *

Only runs of two or more hextets are considered. In case of a tie, the leftmost run wins. If + * a qualifying run is found, its hextets are replaced by the sentinel value -1. + * + * @param hextets {@code int[]} mutable array of eight 16-bit hextets + */ + private static void compressLongestRunOfZeroes(int[] hextets) { + int bestRunStart = -1; + int bestRunLength = -1; + int runStart = -1; + for (int i = 0; i < hextets.length + 1; i++) { + if (i < hextets.length && hextets[i] == 0) { + if (runStart < 0) { + runStart = i; + } + } else if (runStart >= 0) { + int runLength = i - runStart; + if (runLength > bestRunLength) { + bestRunStart = runStart; + bestRunLength = runLength; + } + runStart = -1; + } + } + if (bestRunLength >= 2) { + Arrays.fill(hextets, bestRunStart, bestRunStart + bestRunLength, -1); + } + } + + /** + * Convert a list of hextets into a human-readable IPv6 address. + * + *

In order for "::" compression to work, the input should contain negative sentinel values in + * place of the elided zeroes. + * + * @param hextets {@code int[]} array of eight 16-bit hextets, or -1s + */ + private static String hextetsToIPv6String(int[] hextets) { + // While scanning the array, handle these state transitions: + // start->num => "num" start->gap => "::" + // num->num => ":num" num->gap => "::" + // gap->num => "num" gap->gap => "" + StringBuilder buf = new StringBuilder(39); + boolean lastWasNumber = false; + for (int i = 0; i < hextets.length; i++) { + boolean thisIsNumber = hextets[i] >= 0; + if (thisIsNumber) { + if (lastWasNumber) { + buf.append(':'); + } + buf.append(Integer.toHexString(hextets[i])); + } else { + if (i == 0 || lastWasNumber) { + buf.append("::"); + } + } + lastWasNumber = thisIsNumber; + } + return buf.toString(); + } + + /** + * Returns the string representation of an {@link InetAddress} suitable for inclusion in a URI. + * + *

For IPv4 addresses, this is identical to {@link InetAddress#getHostAddress()}, but for IPv6 + * addresses it compresses zeroes and surrounds the text with square brackets; for example {@code + * "[2001:db8::1]"}. + * + *

Per section 3.2.2 of RFC 3986, a URI containing an IPv6 + * string literal is of the form {@code "http://[2001:db8::1]:8888/index.html"}. + * + *

Use of either {@link InetAddresses#toAddrString}, {@link InetAddress#getHostAddress()}, or + * this method is recommended over {@link InetAddress#toString()} when an IP address string + * literal is desired. This is because {@link InetAddress#toString()} prints the hostname and the + * IP address string joined by a "/". + * + * @param ip {@link InetAddress} to be converted to URI string literal + * @return {@code String} containing URI-safe string literal + */ + public static String toUriString(InetAddress ip) { + if (ip instanceof Inet6Address) { + return "[" + toAddrString(ip) + "]"; + } + return toAddrString(ip); + } + + /** + * Returns an InetAddress representing the literal IPv4 or IPv6 host portion of a URL, encoded in + * the format specified by RFC 3986 section 3.2.2. + * + *

This function is similar to {@link InetAddresses#forString(String)}, however, it requires + * that IPv6 addresses are surrounded by square brackets. + * + *

This function is the inverse of {@link InetAddresses#toUriString(java.net.InetAddress)}. + * + * @param hostAddr A RFC 3986 section 3.2.2 encoded IPv4 or IPv6 address + * @return an InetAddress representing the address in {@code hostAddr} + * @throws IllegalArgumentException if {@code hostAddr} is not a valid IPv4 address, or IPv6 + * address surrounded by square brackets + */ + public static InetAddress forUriString(String hostAddr) { + InetAddress addr = forUriStringNoThrow(hostAddr); + if (addr == null) { + throw formatIllegalArgumentException("Not a valid URI IP literal: '%s'", hostAddr); + } + + return addr; + } + + private static InetAddress forUriStringNoThrow(String hostAddr) { + checkNotNull(hostAddr); + + // Decide if this should be an IPv6 or IPv4 address. + String ipString; + int expectBytes; + if (hostAddr.startsWith("[") && hostAddr.endsWith("]")) { + ipString = hostAddr.substring(1, hostAddr.length() - 1); + expectBytes = 16; + } else { + ipString = hostAddr; + expectBytes = 4; + } + + // Parse the address, and make sure the length/version is correct. + byte[] addr = ipStringToBytes(ipString); + if (addr == null || addr.length != expectBytes) { + return null; + } + + return bytesToInetAddress(addr); + } + + /** + * Returns {@code true} if the supplied string is a valid URI IP string literal, {@code false} + * otherwise. + * + * @param ipString {@code String} to evaluated as an IP URI host string literal + * @return {@code true} if the argument is a valid IP URI host + */ + public static boolean isUriInetAddress(String ipString) { + return forUriStringNoThrow(ipString) != null; + } + + /** + * Evaluates whether the argument is an IPv6 "compat" address. + * + *

An "IPv4 compatible", or "compat", address is one with 96 leading bits of zero, with the + * remaining 32 bits interpreted as an IPv4 address. These are conventionally represented in + * string literals as {@code "::192.168.0.1"}, though {@code "::c0a8:1"} is also considered an + * IPv4 compatible address (and equivalent to {@code "::192.168.0.1"}). + * + *

For more on IPv4 compatible addresses see section 2.5.5.1 of RFC 4291. + * + *

NOTE: This method is different from {@link Inet6Address#isIPv4CompatibleAddress} in that it + * more correctly classifies {@code "::"} and {@code "::1"} as proper IPv6 addresses (which they + * are), NOT IPv4 compatible addresses (which they are generally NOT considered to be). + * + * @param ip {@link Inet6Address} to be examined for embedded IPv4 compatible address format + * @return {@code true} if the argument is a valid "compat" address + */ + public static boolean isCompatIPv4Address(Inet6Address ip) { + if (!ip.isIPv4CompatibleAddress()) { + return false; + } + + byte[] bytes = ip.getAddress(); + if ((bytes[12] == 0) + && (bytes[13] == 0) + && (bytes[14] == 0) + && ((bytes[15] == 0) || (bytes[15] == 1))) { + return false; + } + + return true; + } + + /** + * Returns the IPv4 address embedded in an IPv4 compatible address. + * + * @param ip {@link Inet6Address} to be examined for an embedded IPv4 address + * @return {@link Inet4Address} of the embedded IPv4 address + * @throws IllegalArgumentException if the argument is not a valid IPv4 compatible address + */ + public static Inet4Address getCompatIPv4Address(Inet6Address ip) { + checkArgument( + isCompatIPv4Address(ip), "Address '%s' is not IPv4-compatible.", toAddrString(ip)); + + return getInet4Address(Arrays.copyOfRange(ip.getAddress(), 12, 16)); + } + + /** + * Evaluates whether the argument is a 6to4 address. + * + *

6to4 addresses begin with the {@code "2002::/16"} prefix. The next 32 bits are the IPv4 + * address of the host to which IPv6-in-IPv4 tunneled packets should be routed. + * + *

For more on 6to4 addresses see section 2 of RFC 3056. + * + * @param ip {@link Inet6Address} to be examined for 6to4 address format + * @return {@code true} if the argument is a 6to4 address + */ + public static boolean is6to4Address(Inet6Address ip) { + byte[] bytes = ip.getAddress(); + return (bytes[0] == (byte) 0x20) && (bytes[1] == (byte) 0x02); + } + + /** + * Returns the IPv4 address embedded in a 6to4 address. + * + * @param ip {@link Inet6Address} to be examined for embedded IPv4 in 6to4 address + * @return {@link Inet4Address} of embedded IPv4 in 6to4 address + * @throws IllegalArgumentException if the argument is not a valid IPv6 6to4 address + */ + public static Inet4Address get6to4IPv4Address(Inet6Address ip) { + checkArgument(is6to4Address(ip), "Address '%s' is not a 6to4 address.", toAddrString(ip)); + + return getInet4Address(Arrays.copyOfRange(ip.getAddress(), 2, 6)); + } + + /** + * A simple immutable data class to encapsulate the information to be found in a Teredo address. + * + *

All of the fields in this class are encoded in various portions of the IPv6 address as part + * of the protocol. More protocols details can be found at: http://en.wikipedia. + * org/wiki/Teredo_tunneling. + * + *

The RFC can be found here: RFC + * 4380. + * + * @since 5.0 + */ + @Beta + public static final class TeredoInfo { + private final Inet4Address server; + private final Inet4Address client; + private final int port; + private final int flags; + + /** + * Constructs a TeredoInfo instance. + * + *

Both server and client can be {@code null}, in which case the value {@code "0.0.0.0"} will + * be assumed. + * + * @throws IllegalArgumentException if either of the {@code port} or the {@code flags} arguments + * are out of range of an unsigned short + */ + // TODO: why is this public? + public TeredoInfo( + Inet4Address server, Inet4Address client, int port, int flags) { + checkArgument( + (port >= 0) && (port <= 0xffff), "port '%s' is out of range (0 <= port <= 0xffff)", port); + checkArgument( + (flags >= 0) && (flags <= 0xffff), + "flags '%s' is out of range (0 <= flags <= 0xffff)", + flags); + + this.server = MoreObjects.firstNonNull(server, ANY4); + this.client = MoreObjects.firstNonNull(client, ANY4); + this.port = port; + this.flags = flags; + } + + public Inet4Address getServer() { + return server; + } + + public Inet4Address getClient() { + return client; + } + + public int getPort() { + return port; + } + + public int getFlags() { + return flags; + } + } + + /** + * Evaluates whether the argument is a Teredo address. + * + *

Teredo addresses begin with the {@code "2001::/32"} prefix. + * + * @param ip {@link Inet6Address} to be examined for Teredo address format + * @return {@code true} if the argument is a Teredo address + */ + public static boolean isTeredoAddress(Inet6Address ip) { + byte[] bytes = ip.getAddress(); + return (bytes[0] == (byte) 0x20) + && (bytes[1] == (byte) 0x01) + && (bytes[2] == 0) + && (bytes[3] == 0); + } + + /** + * Returns the Teredo information embedded in a Teredo address. + * + * @param ip {@link Inet6Address} to be examined for embedded Teredo information + * @return extracted {@code TeredoInfo} + * @throws IllegalArgumentException if the argument is not a valid IPv6 Teredo address + */ + public static TeredoInfo getTeredoInfo(Inet6Address ip) { + checkArgument(isTeredoAddress(ip), "Address '%s' is not a Teredo address.", toAddrString(ip)); + + byte[] bytes = ip.getAddress(); + Inet4Address server = getInet4Address(Arrays.copyOfRange(bytes, 4, 8)); + + int flags = ByteStreams.newDataInput(bytes, 8).readShort() & 0xffff; + + // Teredo obfuscates the mapped client port, per section 4 of the RFC. + int port = ~ByteStreams.newDataInput(bytes, 10).readShort() & 0xffff; + + byte[] clientBytes = Arrays.copyOfRange(bytes, 12, 16); + for (int i = 0; i < clientBytes.length; i++) { + // Teredo obfuscates the mapped client IP, per section 4 of the RFC. + clientBytes[i] = (byte) ~clientBytes[i]; + } + Inet4Address client = getInet4Address(clientBytes); + + return new TeredoInfo(server, client, port, flags); + } + + /** + * Evaluates whether the argument is an ISATAP address. + * + *

From RFC 5214: "ISATAP interface identifiers are constructed in Modified EUI-64 format [...] + * by concatenating the 24-bit IANA OUI (00-00-5E), the 8-bit hexadecimal value 0xFE, and a 32-bit + * IPv4 address in network byte order [...]" + * + *

For more on ISATAP addresses see section 6.1 of RFC 5214. + * + * @param ip {@link Inet6Address} to be examined for ISATAP address format + * @return {@code true} if the argument is an ISATAP address + */ + public static boolean isIsatapAddress(Inet6Address ip) { + + // If it's a Teredo address with the right port (41217, or 0xa101) + // which would be encoded as 0x5efe then it can't be an ISATAP address. + if (isTeredoAddress(ip)) { + return false; + } + + byte[] bytes = ip.getAddress(); + + if ((bytes[8] | (byte) 0x03) != (byte) 0x03) { + + // Verify that high byte of the 64 bit identifier is zero, modulo + // the U/L and G bits, with which we are not concerned. + return false; + } + + return (bytes[9] == (byte) 0x00) && (bytes[10] == (byte) 0x5e) && (bytes[11] == (byte) 0xfe); + } + + /** + * Returns the IPv4 address embedded in an ISATAP address. + * + * @param ip {@link Inet6Address} to be examined for embedded IPv4 in ISATAP address + * @return {@link Inet4Address} of embedded IPv4 in an ISATAP address + * @throws IllegalArgumentException if the argument is not a valid IPv6 ISATAP address + */ + public static Inet4Address getIsatapIPv4Address(Inet6Address ip) { + checkArgument(isIsatapAddress(ip), "Address '%s' is not an ISATAP address.", toAddrString(ip)); + + return getInet4Address(Arrays.copyOfRange(ip.getAddress(), 12, 16)); + } + + /** + * Examines the Inet6Address to determine if it is an IPv6 address of one of the specified address + * types that contain an embedded IPv4 address. + * + *

NOTE: ISATAP addresses are explicitly excluded from this method due to their trivial + * spoofability. With other transition addresses spoofing involves (at least) infection of one's + * BGP routing table. + * + * @param ip {@link Inet6Address} to be examined for embedded IPv4 client address + * @return {@code true} if there is an embedded IPv4 client address + * @since 7.0 + */ + public static boolean hasEmbeddedIPv4ClientAddress(Inet6Address ip) { + return isCompatIPv4Address(ip) || is6to4Address(ip) || isTeredoAddress(ip); + } + + /** + * Examines the Inet6Address to extract the embedded IPv4 client address if the InetAddress is an + * IPv6 address of one of the specified address types that contain an embedded IPv4 address. + * + *

NOTE: ISATAP addresses are explicitly excluded from this method due to their trivial + * spoofability. With other transition addresses spoofing involves (at least) infection of one's + * BGP routing table. + * + * @param ip {@link Inet6Address} to be examined for embedded IPv4 client address + * @return {@link Inet4Address} of embedded IPv4 client address + * @throws IllegalArgumentException if the argument does not have a valid embedded IPv4 address + */ + public static Inet4Address getEmbeddedIPv4ClientAddress(Inet6Address ip) { + if (isCompatIPv4Address(ip)) { + return getCompatIPv4Address(ip); + } + + if (is6to4Address(ip)) { + return get6to4IPv4Address(ip); + } + + if (isTeredoAddress(ip)) { + return getTeredoInfo(ip).getClient(); + } + + throw formatIllegalArgumentException("'%s' has no embedded IPv4 address.", toAddrString(ip)); + } + + /** + * Evaluates whether the argument is an "IPv4 mapped" IPv6 address. + * + *

An "IPv4 mapped" address is anything in the range ::ffff:0:0/96 (sometimes written as + * ::ffff:0.0.0.0/96), with the last 32 bits interpreted as an IPv4 address. + * + *

For more on IPv4 mapped addresses see section 2.5.5.2 of RFC 4291. + * + *

Note: This method takes a {@code String} argument because {@link InetAddress} automatically + * collapses mapped addresses to IPv4. (It is actually possible to avoid this using one of the + * obscure {@link Inet6Address} methods, but it would be unwise to depend on such a + * poorly-documented feature.) + * + * @param ipString {@code String} to be examined for embedded IPv4-mapped IPv6 address format + * @return {@code true} if the argument is a valid "mapped" address + * @since 10.0 + */ + public static boolean isMappedIPv4Address(String ipString) { + byte[] bytes = ipStringToBytes(ipString); + if (bytes != null && bytes.length == 16) { + for (int i = 0; i < 10; i++) { + if (bytes[i] != 0) { + return false; + } + } + for (int i = 10; i < 12; i++) { + if (bytes[i] != (byte) 0xff) { + return false; + } + } + return true; + } + return false; + } + + /** + * Coerces an IPv6 address into an IPv4 address. + * + *

HACK: As long as applications continue to use IPv4 addresses for indexing into tables, + * accounting, et cetera, it may be necessary to coerce IPv6 addresses into IPv4 addresses. + * This function does so by hashing 64 bits of the IPv6 address into {@code 224.0.0.0/3} (64 bits + * into 29 bits): + * + *

    + *
  • If the IPv6 address contains an embedded IPv4 address, the function hashes that. + *
  • Otherwise, it hashes the upper 64 bits of the IPv6 address. + *
+ * + *

A "coerced" IPv4 address is equivalent to itself. + * + *

NOTE: This function is failsafe for security purposes: ALL IPv6 addresses (except localhost + * (::1)) are hashed to avoid the security risk associated with extracting an embedded IPv4 + * address that might permit elevated privileges. + * + * @param ip {@link InetAddress} to "coerce" + * @return {@link Inet4Address} represented "coerced" address + * @since 7.0 + */ + public static Inet4Address getCoercedIPv4Address(InetAddress ip) { + if (ip instanceof Inet4Address) { + return (Inet4Address) ip; + } + + // Special cases: + byte[] bytes = ip.getAddress(); + boolean leadingBytesOfZero = true; + for (int i = 0; i < 15; ++i) { + if (bytes[i] != 0) { + leadingBytesOfZero = false; + break; + } + } + if (leadingBytesOfZero && (bytes[15] == 1)) { + return LOOPBACK4; // ::1 + } else if (leadingBytesOfZero && (bytes[15] == 0)) { + return ANY4; // ::0 + } + + Inet6Address ip6 = (Inet6Address) ip; + long addressAsLong = 0; + if (hasEmbeddedIPv4ClientAddress(ip6)) { + addressAsLong = getEmbeddedIPv4ClientAddress(ip6).hashCode(); + } else { + // Just extract the high 64 bits (assuming the rest is user-modifiable). + addressAsLong = ByteBuffer.wrap(ip6.getAddress(), 0, 8).getLong(); + } + + // Many strategies for hashing are possible. This might suffice for now. + int coercedHash = Hashing.murmur3_32().hashLong(addressAsLong).asInt(); + + // Squash into 224/4 Multicast and 240/4 Reserved space (i.e. 224/3). + coercedHash |= 0xe0000000; + + // Fixup to avoid some "illegal" values. Currently the only potential + // illegal value is 255.255.255.255. + if (coercedHash == 0xffffffff) { + coercedHash = 0xfffffffe; + } + + return getInet4Address(Ints.toByteArray(coercedHash)); + } + + /** + * Returns an integer representing an IPv4 address regardless of whether the supplied argument is + * an IPv4 address or not. + * + *

IPv6 addresses are coerced to IPv4 addresses before being converted to integers. + * + *

As long as there are applications that assume that all IP addresses are IPv4 addresses and + * can therefore be converted safely to integers (for whatever purpose) this function can be used + * to handle IPv6 addresses as well until the application is suitably fixed. + * + *

NOTE: an IPv6 address coerced to an IPv4 address can only be used for such purposes as + * rudimentary identification or indexing into a collection of real {@link InetAddress}es. They + * cannot be used as real addresses for the purposes of network communication. + * + * @param ip {@link InetAddress} to convert + * @return {@code int}, "coerced" if ip is not an IPv4 address + * @since 7.0 + */ + public static int coerceToInteger(InetAddress ip) { + return ByteStreams.newDataInput(getCoercedIPv4Address(ip).getAddress()).readInt(); + } + + /** + * Returns a BigInteger representing the address. + * + *

Unlike {@code coerceToInteger}, IPv6 addresses are not coerced to IPv4 addresses. + * + * @param address {@link InetAddress} to convert + * @return {@code BigInteger} representation of the address + * @since NEXT + */ + public static BigInteger toBigInteger(InetAddress address) { + return new BigInteger(1, address.getAddress()); + } + + /** + * Returns an Inet4Address having the integer value specified by the argument. + * + * @param address {@code int}, the 32bit integer address to be converted + * @return {@link Inet4Address} equivalent of the argument + */ + public static Inet4Address fromInteger(int address) { + return getInet4Address(Ints.toByteArray(address)); + } + + /** + * Returns the {@code Inet4Address} corresponding to a given {@code BigInteger}. + * + * @param address BigInteger representing the IPv4 address + * @return Inet4Address representation of the given BigInteger + * @throws IllegalArgumentException if the BigInteger is not between 0 and 2^32-1 + * @since NEXT + */ + public static Inet4Address fromIpv4BigInteger(BigInteger address) { + return (Inet4Address) fromBigInteger(address, false); + } + /** + * Returns the {@code Inet6Address} corresponding to a given {@code BigInteger}. + * + * @param address BigInteger representing the IPv6 address + * @return Inet6Address representation of the given BigInteger + * @throws IllegalArgumentException if the BigInteger is not between 0 and 2^128-1 + * @since NEXT + */ + public static Inet6Address fromIpv6BigInteger(BigInteger address) { + return (Inet6Address) fromBigInteger(address, true); + } + + /** + * Converts a BigInteger to either an IPv4 or IPv6 address. If the IP is IPv4, it must be + * constrainted to 32 bits, otherwise it is constrained to 128 bits. + * + * @param address the address represented as a big integer + * @param isIpv6 whether the created address should be IPv4 or IPv6 + * @return the BigInteger converted to an address + * @throws IllegalArgumentException if the BigInteger is not between 0 and maximum value for IPv4 + * or IPv6 respectively + */ + private static InetAddress fromBigInteger(BigInteger address, boolean isIpv6) { + checkArgument(address.signum() >= 0, "BigInteger must be greater than or equal to 0"); + + int numBytes = isIpv6 ? 16 : 4; + + byte[] addressBytes = address.toByteArray(); + byte[] targetCopyArray = new byte[numBytes]; + + int srcPos = Math.max(0, addressBytes.length - numBytes); + int copyLength = addressBytes.length - srcPos; + int destPos = numBytes - copyLength; + + // Check the extra bytes in the BigInteger are all zero. + for (int i = 0; i < srcPos; i++) { + if (addressBytes[i] != 0x00) { + throw formatIllegalArgumentException( + "BigInteger cannot be converted to InetAddress because it has more than %d" + + " bytes: %s", + numBytes, address); + } + } + + // Copy the bytes into the least significant positions. + System.arraycopy(addressBytes, srcPos, targetCopyArray, destPos, copyLength); + + try { + return InetAddress.getByAddress(targetCopyArray); + } catch (UnknownHostException impossible) { + throw new AssertionError(impossible); + } + } + + /** + * Returns an address from a little-endian ordered byte array (the opposite of what {@link + * InetAddress#getByAddress} expects). + * + *

IPv4 address byte array must be 4 bytes long and IPv6 byte array must be 16 bytes long. + * + * @param addr the raw IP address in little-endian byte order + * @return an InetAddress object created from the raw IP address + * @throws UnknownHostException if IP address is of illegal length + */ + public static InetAddress fromLittleEndianByteArray(byte[] addr) throws UnknownHostException { + byte[] reversed = new byte[addr.length]; + for (int i = 0; i < addr.length; i++) { + reversed[i] = addr[addr.length - i - 1]; + } + return InetAddress.getByAddress(reversed); + } + + /** + * Returns a new InetAddress that is one less than the passed in address. This method works for + * both IPv4 and IPv6 addresses. + * + * @param address the InetAddress to decrement + * @return a new InetAddress that is one less than the passed in address + * @throws IllegalArgumentException if InetAddress is at the beginning of its range + * @since 18.0 + */ + public static InetAddress decrement(InetAddress address) { + byte[] addr = address.getAddress(); + int i = addr.length - 1; + while (i >= 0 && addr[i] == (byte) 0x00) { + addr[i] = (byte) 0xff; + i--; + } + + checkArgument(i >= 0, "Decrementing %s would wrap.", address); + + addr[i]--; + return bytesToInetAddress(addr); + } + + /** + * Returns a new InetAddress that is one more than the passed in address. This method works for + * both IPv4 and IPv6 addresses. + * + * @param address the InetAddress to increment + * @return a new InetAddress that is one more than the passed in address + * @throws IllegalArgumentException if InetAddress is at the end of its range + * @since 10.0 + */ + public static InetAddress increment(InetAddress address) { + byte[] addr = address.getAddress(); + int i = addr.length - 1; + while (i >= 0 && addr[i] == (byte) 0xff) { + addr[i] = 0; + i--; + } + + checkArgument(i >= 0, "Incrementing %s would wrap.", address); + + addr[i]++; + return bytesToInetAddress(addr); + } + + /** + * Returns true if the InetAddress is either 255.255.255.255 for IPv4 or + * ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff for IPv6. + * + * @return true if the InetAddress is either 255.255.255.255 for IPv4 or + * ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff for IPv6 + * @since 10.0 + */ + public static boolean isMaximum(InetAddress address) { + byte[] addr = address.getAddress(); + for (int i = 0; i < addr.length; i++) { + if (addr[i] != (byte) 0xff) { + return false; + } + } + return true; + } + + private static IllegalArgumentException formatIllegalArgumentException( + String format, Object... args) { + return new IllegalArgumentException(String.format(Locale.ROOT, format, args)); + } +} diff --git a/src/main/java/com/google/common/net/InternetDomainName.java b/src/main/java/com/google/common/net/InternetDomainName.java new file mode 100644 index 0000000..eb24d2f --- /dev/null +++ b/src/main/java/com/google/common/net/InternetDomainName.java @@ -0,0 +1,640 @@ +/* + * Copyright (C) 2009 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.net; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.base.Preconditions.checkState; + +import com.google.common.annotations.Beta; +import com.google.common.annotations.GwtCompatible; +import com.google.common.base.Ascii; +import com.google.common.base.CharMatcher; +import com.google.common.base.Joiner; +import com.google.common.base.Optional; +import com.google.common.base.Splitter; +import com.google.common.collect.ImmutableList; + +import java.util.List; + + +/** + * An immutable well-formed internet domain name, such as {@code com} or {@code foo.co.uk}. Only + * syntactic analysis is performed; no DNS lookups or other network interactions take place. Thus + * there is no guarantee that the domain actually exists on the internet. + * + *

One common use of this class is to determine whether a given string is likely to represent an + * addressable domain on the web -- that is, for a candidate string {@code "xxx"}, might browsing to + * {@code "http://xxx/"} result in a webpage being displayed? In the past, this test was frequently + * done by determining whether the domain ended with a {@linkplain #isPublicSuffix() public suffix} + * but was not itself a public suffix. However, this test is no longer accurate. There are many + * domains which are both public suffixes and addressable as hosts; {@code "uk.com"} is one example. + * Using the subset of public suffixes that are {@linkplain #isRegistrySuffix() registry suffixes}, + * one can get a better result, as only a few registry suffixes are addressable. However, the most + * useful test to determine if a domain is a plausible web host is {@link #hasPublicSuffix()}. This + * will return {@code true} for many domains which (currently) are not hosts, such as {@code "com"}, + * but given that any public suffix may become a host without warning, it is better to err on the + * side of permissiveness and thus avoid spurious rejection of valid sites. Of course, to actually + * determine addressability of any host, clients of this class will need to perform their own DNS + * lookups. + * + *

During construction, names are normalized in two ways: + * + *

    + *
  1. ASCII uppercase characters are converted to lowercase. + *
  2. Unicode dot separators other than the ASCII period ({@code '.'}) are converted to the ASCII + * period. + *
+ * + *

The normalized values will be returned from {@link #toString()} and {@link #parts()}, and will + * be reflected in the result of {@link #equals(Object)}. + * + *

Internationalized domain + * names such as {@code 网络.cn} are supported, as are the equivalent IDNA Punycode-encoded + * versions. + * + * @author Catherine Berry + * @since 5.0 + */ +@Beta +@GwtCompatible + +public final class InternetDomainName { + + private static final CharMatcher DOTS_MATCHER = CharMatcher.anyOf(".\u3002\uFF0E\uFF61"); + private static final Splitter DOT_SPLITTER = Splitter.on('.'); + private static final Joiner DOT_JOINER = Joiner.on('.'); + + /** + * Value of {@link #publicSuffixIndex} or {@link #registrySuffixIndex} which indicates that no + * relevant suffix was found. + */ + private static final int NO_SUFFIX_FOUND = -1; + + /** + * Maximum parts (labels) in a domain name. This value arises from the 255-octet limit described + * in RFC 2181 part 11 with the fact that the + * encoding of each part occupies at least two bytes (dot plus label externally, length byte plus + * label internally). Thus, if all labels have the minimum size of one byte, 127 of them will fit. + */ + private static final int MAX_PARTS = 127; + + /** + * Maximum length of a full domain name, including separators, and leaving room for the root + * label. See RFC 2181 part 11. + */ + private static final int MAX_LENGTH = 253; + + /** + * Maximum size of a single part of a domain name. See RFC 2181 part 11. + */ + private static final int MAX_DOMAIN_PART_LENGTH = 63; + + /** The full domain name, converted to lower case. */ + private final String name; + + /** The parts of the domain name, converted to lower case. */ + private final ImmutableList parts; + + /** + * The index in the {@link #parts()} list at which the public suffix begins. For example, for the + * domain name {@code myblog.blogspot.co.uk}, the value would be 1 (the index of the {@code + * blogspot} part). The value is negative (specifically, {@link #NO_SUFFIX_FOUND}) if no public + * suffix was found. + */ + private final int publicSuffixIndex; + + /** + * The index in the {@link #parts()} list at which the registry suffix begins. For example, for + * the domain name {@code myblog.blogspot.co.uk}, the value would be 2 (the index of the {@code + * co} part). The value is negative (specifically, {@link #NO_SUFFIX_FOUND}) if no registry suffix + * was found. + */ + private final int registrySuffixIndex; + + /** Constructor used to implement {@link #from(String)}, and from subclasses. */ + InternetDomainName(String name) { + // Normalize: + // * ASCII characters to lowercase + // * All dot-like characters to '.' + // * Strip trailing '.' + + name = Ascii.toLowerCase(DOTS_MATCHER.replaceFrom(name, '.')); + + if (name.endsWith(".")) { + name = name.substring(0, name.length() - 1); + } + + checkArgument(name.length() <= MAX_LENGTH, "Domain name too long: '%s':", name); + this.name = name; + + this.parts = ImmutableList.copyOf(DOT_SPLITTER.split(name)); + checkArgument(parts.size() <= MAX_PARTS, "Domain has too many parts: '%s'", name); + checkArgument(validateSyntax(parts), "Not a valid domain name: '%s'", name); + + this.publicSuffixIndex = findSuffixOfType(Optional.absent()); + this.registrySuffixIndex = findSuffixOfType(Optional.of(PublicSuffixType.REGISTRY)); + } + + /** + * Returns the index of the leftmost part of the suffix, or -1 if not found. Note that the value + * defined as a suffix may not produce {@code true} results from {@link #isPublicSuffix()} or + * {@link #isRegistrySuffix()} if the domain ends with an excluded domain pattern such as {@code + * "nhs.uk"}. + * + *

If a {@code desiredType} is specified, this method only finds suffixes of the given type. + * Otherwise, it finds the first suffix of any type. + */ + private int findSuffixOfType(Optional desiredType) { + final int partsSize = parts.size(); + + for (int i = 0; i < partsSize; i++) { + String ancestorName = DOT_JOINER.join(parts.subList(i, partsSize)); + + if (matchesType( + desiredType, Optional.fromNullable(PublicSuffixPatterns.EXACT.get(ancestorName)))) { + return i; + } + + // Excluded domains (e.g. !nhs.uk) use the next highest + // domain as the effective public suffix (e.g. uk). + + if (PublicSuffixPatterns.EXCLUDED.containsKey(ancestorName)) { + return i + 1; + } + + if (matchesWildcardSuffixType(desiredType, ancestorName)) { + return i; + } + } + + return NO_SUFFIX_FOUND; + } + + /** + * Returns an instance of {@link InternetDomainName} after lenient validation. Specifically, + * validation against RFC 3490 + * ("Internationalizing Domain Names in Applications") is skipped, while validation against RFC 1035 is relaxed in the following ways: + * + *

    + *
  • Any part containing non-ASCII characters is considered valid. + *
  • Underscores ('_') are permitted wherever dashes ('-') are permitted. + *
  • Parts other than the final part may start with a digit, as mandated by RFC 1123. + *
+ * + * + * @param domain A domain name (not IP address) + * @throws IllegalArgumentException if {@code domain} is not syntactically valid according to + * {@link #isValid} + * @since 10.0 (previously named {@code fromLenient}) + */ + public static InternetDomainName from(String domain) { + return new InternetDomainName(checkNotNull(domain)); + } + + /** + * Validation method used by {@code from} to ensure that the domain name is syntactically valid + * according to RFC 1035. + * + * @return Is the domain name syntactically valid? + */ + private static boolean validateSyntax(List parts) { + final int lastIndex = parts.size() - 1; + + // Validate the last part specially, as it has different syntax rules. + + if (!validatePart(parts.get(lastIndex), true)) { + return false; + } + + for (int i = 0; i < lastIndex; i++) { + String part = parts.get(i); + if (!validatePart(part, false)) { + return false; + } + } + + return true; + } + + private static final CharMatcher DASH_MATCHER = CharMatcher.anyOf("-_"); + + private static final CharMatcher DIGIT_MATCHER = CharMatcher.inRange('0', '9'); + + private static final CharMatcher LETTER_MATCHER = + CharMatcher.inRange('a', 'z').or(CharMatcher.inRange('A', 'Z')); + + private static final CharMatcher PART_CHAR_MATCHER = + DIGIT_MATCHER.or(LETTER_MATCHER).or(DASH_MATCHER); + + /** + * Helper method for {@link #validateSyntax(List)}. Validates that one part of a domain name is + * valid. + * + * @param part The domain name part to be validated + * @param isFinalPart Is this the final (rightmost) domain part? + * @return Whether the part is valid + */ + private static boolean validatePart(String part, boolean isFinalPart) { + + // These tests could be collapsed into one big boolean expression, but + // they have been left as independent tests for clarity. + + if (part.length() < 1 || part.length() > MAX_DOMAIN_PART_LENGTH) { + return false; + } + + /* + * GWT claims to support java.lang.Character's char-classification methods, but it actually only + * works for ASCII. So for now, assume any non-ASCII characters are valid. The only place this + * seems to be documented is here: + * https://groups.google.com/d/topic/google-web-toolkit-contributors/1UEzsryq1XI + * + *

ASCII characters in the part are expected to be valid per RFC 1035, with underscore also + * being allowed due to widespread practice. + */ + + String asciiChars = CharMatcher.ascii().retainFrom(part); + + if (!PART_CHAR_MATCHER.matchesAllOf(asciiChars)) { + return false; + } + + // No initial or final dashes or underscores. + + if (DASH_MATCHER.matches(part.charAt(0)) + || DASH_MATCHER.matches(part.charAt(part.length() - 1))) { + return false; + } + + /* + * Note that we allow (in contravention of a strict interpretation of the relevant RFCs) domain + * parts other than the last may begin with a digit (for example, "3com.com"). It's important to + * disallow an initial digit in the last part; it's the only thing that stops an IPv4 numeric + * address like 127.0.0.1 from looking like a valid domain name. + */ + + if (isFinalPart && DIGIT_MATCHER.matches(part.charAt(0))) { + return false; + } + + return true; + } + + /** + * Returns the individual components of this domain name, normalized to all lower case. For + * example, for the domain name {@code mail.google.com}, this method returns the list {@code + * ["mail", "google", "com"]}. + */ + public ImmutableList parts() { + return parts; + } + + /** + * Indicates whether this domain name represents a public suffix, as defined by the Mozilla + * Foundation's Public Suffix List (PSL). A public suffix + * is one under which Internet users can directly register names, such as {@code com}, {@code + * co.uk} or {@code pvt.k12.wy.us}. Examples of domain names that are not public suffixes + * include {@code google.com}, {@code foo.co.uk}, and {@code myblog.blogspot.com}. + * + *

Public suffixes are a proper superset of {@linkplain #isRegistrySuffix() registry suffixes}. + * The list of public suffixes additionally contains privately owned domain names under which + * Internet users can register subdomains. An example of a public suffix that is not a registry + * suffix is {@code blogspot.com}. Note that it is true that all public suffixes have + * registry suffixes, since domain name registries collectively control all internet domain names. + * + *

For considerations on whether the public suffix or registry suffix designation is more + * suitable for your application, see this article. + * + * @return {@code true} if this domain name appears exactly on the public suffix list + * @since 6.0 + */ + public boolean isPublicSuffix() { + return publicSuffixIndex == 0; + } + + /** + * Indicates whether this domain name ends in a {@linkplain #isPublicSuffix() public suffix}, + * including if it is a public suffix itself. For example, returns {@code true} for {@code + * www.google.com}, {@code foo.co.uk} and {@code com}, but not for {@code invalid} or {@code + * google.invalid}. This is the recommended method for determining whether a domain is potentially + * an addressable host. + * + *

Note that this method is equivalent to {@link #hasRegistrySuffix()} because all registry + * suffixes are public suffixes and all public suffixes have registry suffixes. + * + * @since 6.0 + */ + public boolean hasPublicSuffix() { + return publicSuffixIndex != NO_SUFFIX_FOUND; + } + + /** + * Returns the {@linkplain #isPublicSuffix() public suffix} portion of the domain name, or {@code + * null} if no public suffix is present. + * + * @since 6.0 + */ + public InternetDomainName publicSuffix() { + return hasPublicSuffix() ? ancestor(publicSuffixIndex) : null; + } + + /** + * Indicates whether this domain name ends in a {@linkplain #isPublicSuffix() public suffix}, + * while not being a public suffix itself. For example, returns {@code true} for {@code + * www.google.com}, {@code foo.co.uk} and {@code myblog.blogspot.com}, but not for {@code com}, + * {@code co.uk}, {@code google.invalid}, or {@code blogspot.com}. + * + *

This method can be used to determine whether it will probably be possible to set cookies on + * the domain, though even that depends on individual browsers' implementations of cookie + * controls. See RFC 2109 for details. + * + * @since 6.0 + */ + public boolean isUnderPublicSuffix() { + return publicSuffixIndex > 0; + } + + /** + * Indicates whether this domain name is composed of exactly one subdomain component followed by a + * {@linkplain #isPublicSuffix() public suffix}. For example, returns {@code true} for {@code + * google.com} {@code foo.co.uk}, and {@code myblog.blogspot.com}, but not for {@code + * www.google.com}, {@code co.uk}, or {@code blogspot.com}. + * + *

This method can be used to determine whether a domain is probably the highest level for + * which cookies may be set, though even that depends on individual browsers' implementations of + * cookie controls. See RFC 2109 for details. + * + * @since 6.0 + */ + public boolean isTopPrivateDomain() { + return publicSuffixIndex == 1; + } + + /** + * Returns the portion of this domain name that is one level beneath the {@linkplain + * #isPublicSuffix() public suffix}. For example, for {@code x.adwords.google.co.uk} it returns + * {@code google.co.uk}, since {@code co.uk} is a public suffix. Similarly, for {@code + * myblog.blogspot.com} it returns the same domain, {@code myblog.blogspot.com}, since {@code + * blogspot.com} is a public suffix. + * + *

If {@link #isTopPrivateDomain()} is true, the current domain name instance is returned. + * + *

This method can be used to determine the probable highest level parent domain for which + * cookies may be set, though even that depends on individual browsers' implementations of cookie + * controls. + * + * @throws IllegalStateException if this domain does not end with a public suffix + * @since 6.0 + */ + public InternetDomainName topPrivateDomain() { + if (isTopPrivateDomain()) { + return this; + } + checkState(isUnderPublicSuffix(), "Not under a public suffix: %s", name); + return ancestor(publicSuffixIndex - 1); + } + + /** + * Indicates whether this domain name represents a registry suffix, as defined by a subset + * of the Mozilla Foundation's Public Suffix List (PSL). A + * registry suffix is one under which Internet users can directly register names via a domain name + * registrar, and have such registrations lawfully protected by internet-governing bodies such as + * ICANN. Examples of registry suffixes include {@code com}, {@code co.uk}, and {@code + * pvt.k12.wy.us}. Examples of domain names that are not registry suffixes include {@code + * google.com} and {@code foo.co.uk}. + * + *

Registry suffixes are a proper subset of {@linkplain #isPublicSuffix() public suffixes}. The + * list of public suffixes additionally contains privately owned domain names under which Internet + * users can register subdomains. An example of a public suffix that is not a registry suffix is + * {@code blogspot.com}. Note that it is true that all public suffixes have registry + * suffixes, since domain name registries collectively control all internet domain names. + * + *

For considerations on whether the public suffix or registry suffix designation is more + * suitable for your application, see this article. + * + * @return {@code true} if this domain name appears exactly on the public suffix list as part of + * the registry suffix section (labelled "ICANN"). + * @since 23.3 + */ + public boolean isRegistrySuffix() { + return registrySuffixIndex == 0; + } + + /** + * Indicates whether this domain name ends in a {@linkplain #isRegistrySuffix() registry suffix}, + * including if it is a registry suffix itself. For example, returns {@code true} for {@code + * www.google.com}, {@code foo.co.uk} and {@code com}, but not for {@code invalid} or {@code + * google.invalid}. + * + *

Note that this method is equivalent to {@link #hasPublicSuffix()} because all registry + * suffixes are public suffixes and all public suffixes have registry suffixes. + * + * @since 23.3 + */ + public boolean hasRegistrySuffix() { + return registrySuffixIndex != NO_SUFFIX_FOUND; + } + + /** + * Returns the {@linkplain #isRegistrySuffix() registry suffix} portion of the domain name, or + * {@code null} if no registry suffix is present. + * + * @since 23.3 + */ + public InternetDomainName registrySuffix() { + return hasRegistrySuffix() ? ancestor(registrySuffixIndex) : null; + } + + /** + * Indicates whether this domain name ends in a {@linkplain #isRegistrySuffix() registry suffix}, + * while not being a registry suffix itself. For example, returns {@code true} for {@code + * www.google.com}, {@code foo.co.uk} and {@code blogspot.com}, but not for {@code com}, {@code + * co.uk}, or {@code google.invalid}. + * + * @since 23.3 + */ + public boolean isUnderRegistrySuffix() { + return registrySuffixIndex > 0; + } + + /** + * Indicates whether this domain name is composed of exactly one subdomain component followed by a + * {@linkplain #isRegistrySuffix() registry suffix}. For example, returns {@code true} for {@code + * google.com}, {@code foo.co.uk}, and {@code blogspot.com}, but not for {@code www.google.com}, + * {@code co.uk}, or {@code myblog.blogspot.com}. + * + *

Warning: This method should not be used to determine the probable highest level + * parent domain for which cookies may be set. Use {@link #topPrivateDomain()} for that purpose. + * + * @since 23.3 + */ + public boolean isTopDomainUnderRegistrySuffix() { + return registrySuffixIndex == 1; + } + + /** + * Returns the portion of this domain name that is one level beneath the {@linkplain + * #isRegistrySuffix() registry suffix}. For example, for {@code x.adwords.google.co.uk} it + * returns {@code google.co.uk}, since {@code co.uk} is a registry suffix. Similarly, for {@code + * myblog.blogspot.com} it returns {@code blogspot.com}, since {@code com} is a registry suffix. + * + *

If {@link #isTopDomainUnderRegistrySuffix()} is true, the current domain name instance is + * returned. + * + *

Warning: This method should not be used to determine whether a domain is probably the + * highest level for which cookies may be set. Use {@link #isTopPrivateDomain()} for that purpose. + * + * @throws IllegalStateException if this domain does not end with a registry suffix + * @since 23.3 + */ + public InternetDomainName topDomainUnderRegistrySuffix() { + if (isTopDomainUnderRegistrySuffix()) { + return this; + } + checkState(isUnderRegistrySuffix(), "Not under a registry suffix: %s", name); + return ancestor(registrySuffixIndex - 1); + } + + /** Indicates whether this domain is composed of two or more parts. */ + public boolean hasParent() { + return parts.size() > 1; + } + + /** + * Returns an {@code InternetDomainName} that is the immediate ancestor of this one; that is, the + * current domain with the leftmost part removed. For example, the parent of {@code + * www.google.com} is {@code google.com}. + * + * @throws IllegalStateException if the domain has no parent, as determined by {@link #hasParent} + */ + public InternetDomainName parent() { + checkState(hasParent(), "Domain '%s' has no parent", name); + return ancestor(1); + } + + /** + * Returns the ancestor of the current domain at the given number of levels "higher" (rightward) + * in the subdomain list. The number of levels must be non-negative, and less than {@code N-1}, + * where {@code N} is the number of parts in the domain. + * + *

TODO: Reasonable candidate for addition to public API. + */ + private InternetDomainName ancestor(int levels) { + return from(DOT_JOINER.join(parts.subList(levels, parts.size()))); + } + + /** + * Creates and returns a new {@code InternetDomainName} by prepending the argument and a dot to + * the current name. For example, {@code InternetDomainName.from("foo.com").child("www.bar")} + * returns a new {@code InternetDomainName} with the value {@code www.bar.foo.com}. Only lenient + * validation is performed, as described {@link #from(String) here}. + * + * @throws NullPointerException if leftParts is null + * @throws IllegalArgumentException if the resulting name is not valid + */ + public InternetDomainName child(String leftParts) { + return from(checkNotNull(leftParts) + "." + name); + } + + /** + * Indicates whether the argument is a syntactically valid domain name using lenient validation. + * Specifically, validation against RFC 3490 + * ("Internationalizing Domain Names in Applications") is skipped. + * + *

The following two code snippets are equivalent: + * + *

{@code
+   * domainName = InternetDomainName.isValid(name)
+   *     ? InternetDomainName.from(name)
+   *     : DEFAULT_DOMAIN;
+   * }
+ * + *
{@code
+   * try {
+   *   domainName = InternetDomainName.from(name);
+   * } catch (IllegalArgumentException e) {
+   *   domainName = DEFAULT_DOMAIN;
+   * }
+   * }
+ * + * @since 8.0 (previously named {@code isValidLenient}) + */ + public static boolean isValid(String name) { + try { + from(name); + return true; + } catch (IllegalArgumentException e) { + return false; + } + } + + /** + * Does the domain name match one of the "wildcard" patterns (e.g. {@code "*.ar"})? If a {@code + * desiredType} is specified, the wildcard pattern must also match that type. + */ + private static boolean matchesWildcardSuffixType( + Optional desiredType, String domain) { + List pieces = DOT_SPLITTER.limit(2).splitToList(domain); + return pieces.size() == 2 + && matchesType( + desiredType, Optional.fromNullable(PublicSuffixPatterns.UNDER.get(pieces.get(1)))); + } + + /** + * If a {@code desiredType} is specified, returns true only if the {@code actualType} is + * identical. Otherwise, returns true as long as {@code actualType} is present. + */ + private static boolean matchesType( + Optional desiredType, Optional actualType) { + return desiredType.isPresent() ? desiredType.equals(actualType) : actualType.isPresent(); + } + + /** Returns the domain name, normalized to all lower case. */ + @Override + public String toString() { + return name; + } + + /** + * Equality testing is based on the text supplied by the caller, after normalization as described + * in the class documentation. For example, a non-ASCII Unicode domain name and the Punycode + * version of the same domain name would not be considered equal. + */ + @Override + public boolean equals(Object object) { + if (object == this) { + return true; + } + + if (object instanceof InternetDomainName) { + InternetDomainName that = (InternetDomainName) object; + return this.name.equals(that.name); + } + + return false; + } + + @Override + public int hashCode() { + return name.hashCode(); + } +} diff --git a/src/main/java/com/google/common/net/MediaType.java b/src/main/java/com/google/common/net/MediaType.java new file mode 100644 index 0000000..1ae84f8 --- /dev/null +++ b/src/main/java/com/google/common/net/MediaType.java @@ -0,0 +1,1138 @@ +/* + * Copyright (C) 2011 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.net; + +import static com.google.common.base.CharMatcher.ascii; +import static com.google.common.base.CharMatcher.javaIsoControl; +import static com.google.common.base.Charsets.UTF_8; +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.base.Preconditions.checkState; + +import com.google.common.annotations.Beta; +import com.google.common.annotations.GwtCompatible; +import com.google.common.base.Ascii; +import com.google.common.base.CharMatcher; +import com.google.common.base.Function; +import com.google.common.base.Joiner; +import com.google.common.base.Joiner.MapJoiner; +import com.google.common.base.MoreObjects; +import com.google.common.base.Objects; +import com.google.common.base.Optional; +import com.google.common.collect.ImmutableListMultimap; +import com.google.common.collect.ImmutableMultiset; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Maps; +import com.google.common.collect.Multimap; +import com.google.common.collect.Multimaps; + + +import java.nio.charset.Charset; +import java.nio.charset.IllegalCharsetNameException; +import java.nio.charset.UnsupportedCharsetException; +import java.util.Collection; +import java.util.Map; +import java.util.Map.Entry; + + +/** + * Represents an Internet Media Type + * (also known as a MIME Type or Content Type). This class also supports the concept of media ranges + * defined by HTTP/1.1. + * As such, the {@code *} character is treated as a wildcard and is used to represent any acceptable + * type or subtype value. A media type may not have wildcard type with a declared subtype. The + * {@code *} character has no special meaning as part of a parameter. All values for type, subtype, + * parameter attributes or parameter values must be valid according to RFCs 2045 and 2046. + * + *

All portions of the media type that are case-insensitive (type, subtype, parameter attributes) + * are normalized to lowercase. The value of the {@code charset} parameter is normalized to + * lowercase, but all others are left as-is. + * + *

Note that this specifically does not represent the value of the MIME {@code + * Content-Type} header and as such has no support for header-specific considerations such as line + * folding and comments. + * + *

For media types that take a charset the predefined constants default to UTF-8 and have a + * "_UTF_8" suffix. To get a version without a character set, use {@link #withoutParameters}. + * + * @since 12.0 + * @author Gregory Kick + */ +@Beta +@GwtCompatible + +public final class MediaType { + private static final String CHARSET_ATTRIBUTE = "charset"; + private static final ImmutableListMultimap UTF_8_CONSTANT_PARAMETERS = + ImmutableListMultimap.of(CHARSET_ATTRIBUTE, Ascii.toLowerCase(UTF_8.name())); + + /** Matcher for type, subtype and attributes. */ + private static final CharMatcher TOKEN_MATCHER = + ascii() + .and(javaIsoControl().negate()) + .and(CharMatcher.isNot(' ')) + .and(CharMatcher.noneOf("()<>@,;:\\\"/[]?=")); + + private static final CharMatcher QUOTED_TEXT_MATCHER = ascii().and(CharMatcher.noneOf("\"\\\r")); + + /* + * This matches the same characters as linear-white-space from RFC 822, but we make no effort to + * enforce any particular rules with regards to line folding as stated in the class docs. + */ + private static final CharMatcher LINEAR_WHITE_SPACE = CharMatcher.anyOf(" \t\r\n"); + + // TODO(gak): make these public? + private static final String APPLICATION_TYPE = "application"; + private static final String AUDIO_TYPE = "audio"; + private static final String IMAGE_TYPE = "image"; + private static final String TEXT_TYPE = "text"; + private static final String VIDEO_TYPE = "video"; + + private static final String WILDCARD = "*"; + + private static final Map KNOWN_TYPES = Maps.newHashMap(); + + private static MediaType createConstant(String type, String subtype) { + MediaType mediaType = + addKnownType(new MediaType(type, subtype, ImmutableListMultimap.of())); + mediaType.parsedCharset = Optional.absent(); + return mediaType; + } + + private static MediaType createConstantUtf8(String type, String subtype) { + MediaType mediaType = addKnownType(new MediaType(type, subtype, UTF_8_CONSTANT_PARAMETERS)); + mediaType.parsedCharset = Optional.of(UTF_8); + return mediaType; + } + + private static MediaType addKnownType(MediaType mediaType) { + KNOWN_TYPES.put(mediaType, mediaType); + return mediaType; + } + + /* + * The following constants are grouped by their type and ordered alphabetically by the constant + * name within that type. The constant name should be a sensible identifier that is closest to the + * "common name" of the media. This is often, but not necessarily the same as the subtype. + * + * Be sure to declare all constants with the type and subtype in all lowercase. For types that + * take a charset (e.g. all text/* types), default to UTF-8 and suffix the constant name with + * "_UTF_8". + */ + + public static final MediaType ANY_TYPE = createConstant(WILDCARD, WILDCARD); + public static final MediaType ANY_TEXT_TYPE = createConstant(TEXT_TYPE, WILDCARD); + public static final MediaType ANY_IMAGE_TYPE = createConstant(IMAGE_TYPE, WILDCARD); + public static final MediaType ANY_AUDIO_TYPE = createConstant(AUDIO_TYPE, WILDCARD); + public static final MediaType ANY_VIDEO_TYPE = createConstant(VIDEO_TYPE, WILDCARD); + public static final MediaType ANY_APPLICATION_TYPE = createConstant(APPLICATION_TYPE, WILDCARD); + + /* text types */ + public static final MediaType CACHE_MANIFEST_UTF_8 = + createConstantUtf8(TEXT_TYPE, "cache-manifest"); + public static final MediaType CSS_UTF_8 = createConstantUtf8(TEXT_TYPE, "css"); + public static final MediaType CSV_UTF_8 = createConstantUtf8(TEXT_TYPE, "csv"); + public static final MediaType HTML_UTF_8 = createConstantUtf8(TEXT_TYPE, "html"); + public static final MediaType I_CALENDAR_UTF_8 = createConstantUtf8(TEXT_TYPE, "calendar"); + public static final MediaType PLAIN_TEXT_UTF_8 = createConstantUtf8(TEXT_TYPE, "plain"); + + /** + * RFC 4329 declares {@link + * #JAVASCRIPT_UTF_8 application/javascript} to be the correct media type for JavaScript, but this + * may be necessary in certain situations for compatibility. + */ + public static final MediaType TEXT_JAVASCRIPT_UTF_8 = createConstantUtf8(TEXT_TYPE, "javascript"); + /** + * Tab separated + * values. + * + * @since 15.0 + */ + public static final MediaType TSV_UTF_8 = createConstantUtf8(TEXT_TYPE, "tab-separated-values"); + + public static final MediaType VCARD_UTF_8 = createConstantUtf8(TEXT_TYPE, "vcard"); + + /** + * UTF-8 encoded Wireless Markup + * Language. + * + * @since 13.0 + */ + public static final MediaType WML_UTF_8 = createConstantUtf8(TEXT_TYPE, "vnd.wap.wml"); + + /** + * As described in RFC 3023, this constant + * ({@code text/xml}) is used for XML documents that are "readable by casual users." {@link + * #APPLICATION_XML_UTF_8} is provided for documents that are intended for applications. + */ + public static final MediaType XML_UTF_8 = createConstantUtf8(TEXT_TYPE, "xml"); + + /** + * As described in the VTT spec, this is + * used for Web Video Text Tracks (WebVTT) files, used with the HTML5 track element. + * + * @since 20.0 + */ + public static final MediaType VTT_UTF_8 = createConstantUtf8(TEXT_TYPE, "vtt"); + + /** + * Bitmap file format ({@code bmp} + * files). + * + * @since 13.0 + */ + public static final MediaType BMP = createConstant(IMAGE_TYPE, "bmp"); + + /** + * The Canon Image File + * Format ({@code crw} files), a widely-used "raw image" format for cameras. It is found in + * {@code /etc/mime.types}, e.g. in Debian 3.48-1. + * + * @since 15.0 + */ + public static final MediaType CRW = createConstant(IMAGE_TYPE, "x-canon-crw"); + + public static final MediaType GIF = createConstant(IMAGE_TYPE, "gif"); + public static final MediaType ICO = createConstant(IMAGE_TYPE, "vnd.microsoft.icon"); + public static final MediaType JPEG = createConstant(IMAGE_TYPE, "jpeg"); + public static final MediaType PNG = createConstant(IMAGE_TYPE, "png"); + + /** + * The Photoshop File Format ({@code psd} files) as defined by IANA, and + * found in {@code /etc/mime.types}, e.g. of the + * Apache HTTPD project; for the specification, see + * Adobe Photoshop Document Format and Wikipedia; this is the + * regular output/input of Photoshop (which can also export to various image formats; note that + * files with extension "PSB" are in a distinct but related format). + * + *

This is a more recent replacement for the older, experimental type {@code x-photoshop}: RFC-2046.6. + * + * @since 15.0 + */ + public static final MediaType PSD = createConstant(IMAGE_TYPE, "vnd.adobe.photoshop"); + + public static final MediaType SVG_UTF_8 = createConstantUtf8(IMAGE_TYPE, "svg+xml"); + public static final MediaType TIFF = createConstant(IMAGE_TYPE, "tiff"); + + /** + * WebP image format. + * + * @since 13.0 + */ + public static final MediaType WEBP = createConstant(IMAGE_TYPE, "webp"); + + /** + * HEIF image format. + * + * @since 28.1 + */ + public static final MediaType HEIF = createConstant(IMAGE_TYPE, "heif"); + + /** + * JP2K image format. + * + * @since 28.1 + */ + public static final MediaType JP2K = createConstant(IMAGE_TYPE, "jp2"); + + /* audio types */ + public static final MediaType MP4_AUDIO = createConstant(AUDIO_TYPE, "mp4"); + public static final MediaType MPEG_AUDIO = createConstant(AUDIO_TYPE, "mpeg"); + public static final MediaType OGG_AUDIO = createConstant(AUDIO_TYPE, "ogg"); + public static final MediaType WEBM_AUDIO = createConstant(AUDIO_TYPE, "webm"); + + /** + * L16 audio, as defined by RFC 2586. + * + * @since 24.1 + */ + public static final MediaType L16_AUDIO = createConstant(AUDIO_TYPE, "l16"); + + /** + * L24 audio, as defined by RFC 3190. + * + * @since 20.0 + */ + public static final MediaType L24_AUDIO = createConstant(AUDIO_TYPE, "l24"); + + /** + * Basic Audio, as defined by RFC + * 2046. + * + * @since 20.0 + */ + public static final MediaType BASIC_AUDIO = createConstant(AUDIO_TYPE, "basic"); + + /** + * Advanced Audio Coding. For more information, see Advanced Audio Coding. + * + * @since 20.0 + */ + public static final MediaType AAC_AUDIO = createConstant(AUDIO_TYPE, "aac"); + + /** + * Vorbis Audio, as defined by RFC 5215. + * + * @since 20.0 + */ + public static final MediaType VORBIS_AUDIO = createConstant(AUDIO_TYPE, "vorbis"); + + /** + * Windows Media Audio. For more information, see file + * name extensions for Windows Media metafiles. + * + * @since 20.0 + */ + public static final MediaType WMA_AUDIO = createConstant(AUDIO_TYPE, "x-ms-wma"); + + /** + * Windows Media metafiles. For more information, see file + * name extensions for Windows Media metafiles. + * + * @since 20.0 + */ + public static final MediaType WAX_AUDIO = createConstant(AUDIO_TYPE, "x-ms-wax"); + + /** + * Real Audio. For more information, see this link. + * + * @since 20.0 + */ + public static final MediaType VND_REAL_AUDIO = createConstant(AUDIO_TYPE, "vnd.rn-realaudio"); + + /** + * WAVE format, as defined by RFC 2361. + * + * @since 20.0 + */ + public static final MediaType VND_WAVE_AUDIO = createConstant(AUDIO_TYPE, "vnd.wave"); + + /* video types */ + public static final MediaType MP4_VIDEO = createConstant(VIDEO_TYPE, "mp4"); + public static final MediaType MPEG_VIDEO = createConstant(VIDEO_TYPE, "mpeg"); + public static final MediaType OGG_VIDEO = createConstant(VIDEO_TYPE, "ogg"); + public static final MediaType QUICKTIME = createConstant(VIDEO_TYPE, "quicktime"); + public static final MediaType WEBM_VIDEO = createConstant(VIDEO_TYPE, "webm"); + public static final MediaType WMV = createConstant(VIDEO_TYPE, "x-ms-wmv"); + + /** + * Flash video. For more information, see this link. + * + * @since 20.0 + */ + public static final MediaType FLV_VIDEO = createConstant(VIDEO_TYPE, "x-flv"); + + /** + * The 3GP multimedia container format. For more information, see 3GPP TS + * 26.244. + * + * @since 20.0 + */ + public static final MediaType THREE_GPP_VIDEO = createConstant(VIDEO_TYPE, "3gpp"); + + /** + * The 3G2 multimedia container format. For more information, see 3GPP2 + * C.S0050-B. + * + * @since 20.0 + */ + public static final MediaType THREE_GPP2_VIDEO = createConstant(VIDEO_TYPE, "3gpp2"); + + /* application types */ + /** + * As described in RFC 3023, this constant + * ({@code application/xml}) is used for XML documents that are "unreadable by casual users." + * {@link #XML_UTF_8} is provided for documents that may be read by users. + * + * @since 14.0 + */ + public static final MediaType APPLICATION_XML_UTF_8 = createConstantUtf8(APPLICATION_TYPE, "xml"); + + public static final MediaType ATOM_UTF_8 = createConstantUtf8(APPLICATION_TYPE, "atom+xml"); + public static final MediaType BZIP2 = createConstant(APPLICATION_TYPE, "x-bzip2"); + + /** + * Files in the dart + * programming language. + * + * @since 19.0 + */ + public static final MediaType DART_UTF_8 = createConstantUtf8(APPLICATION_TYPE, "dart"); + + /** + * Apple Passbook. + * + * @since 19.0 + */ + public static final MediaType APPLE_PASSBOOK = + createConstant(APPLICATION_TYPE, "vnd.apple.pkpass"); + + /** + * Embedded OpenType fonts. This is + * registered + * with the IANA. + * + * @since 17.0 + */ + public static final MediaType EOT = createConstant(APPLICATION_TYPE, "vnd.ms-fontobject"); + + /** + * As described in the International Digital Publishing Forum + * EPUB is the distribution and interchange format standard for digital publications and + * documents. This media type is defined in the EPUB Open Container Format + * specification. + * + * @since 15.0 + */ + public static final MediaType EPUB = createConstant(APPLICATION_TYPE, "epub+zip"); + + public static final MediaType FORM_DATA = + createConstant(APPLICATION_TYPE, "x-www-form-urlencoded"); + + /** + * As described in PKCS #12: Personal + * Information Exchange Syntax Standard, PKCS #12 defines an archive file format for storing + * many cryptography objects as a single file. + * + * @since 15.0 + */ + public static final MediaType KEY_ARCHIVE = createConstant(APPLICATION_TYPE, "pkcs12"); + + /** + * This is a non-standard media type, but is commonly used in serving hosted binary files as it is + * + * known not to trigger content sniffing in current browsers. It should not be used in + * other situations as it is not specified by any RFC and does not appear in the /IANA MIME Media Types list. Consider + * {@link #OCTET_STREAM} for binary data that is not being served to a browser. + * + * @since 14.0 + */ + public static final MediaType APPLICATION_BINARY = createConstant(APPLICATION_TYPE, "binary"); + + /** + * Media type for the GeoJSON Format, a + * geospatial data interchange format based on JSON. + * + * @since 28.0 + */ + public static final MediaType GEO_JSON = createConstant(APPLICATION_TYPE, "geo+json"); + + public static final MediaType GZIP = createConstant(APPLICATION_TYPE, "x-gzip"); + + /** + * JSON Hypertext + * Application Language (HAL) documents. + * + * @since 26.0 + */ + public static final MediaType HAL_JSON = createConstant(APPLICATION_TYPE, "hal+json"); + + /** + * RFC 4329 declares this to be the + * correct media type for JavaScript, but {@link #TEXT_JAVASCRIPT_UTF_8 text/javascript} may be + * necessary in certain situations for compatibility. + */ + public static final MediaType JAVASCRIPT_UTF_8 = + createConstantUtf8(APPLICATION_TYPE, "javascript"); + + /** + * For JWS or JWE objects using the Compact + * Serialization. + * + * @since 27.1 + */ + public static final MediaType JOSE = createConstant(APPLICATION_TYPE, "jose"); + + /** + * For JWS or JWE objects using the JSON + * Serialization. + * + * @since 27.1 + */ + public static final MediaType JOSE_JSON = createConstant(APPLICATION_TYPE, "jose+json"); + + public static final MediaType JSON_UTF_8 = createConstantUtf8(APPLICATION_TYPE, "json"); + + /** + * The Manifest for a web application. + * + * @since 19.0 + */ + public static final MediaType MANIFEST_JSON_UTF_8 = + createConstantUtf8(APPLICATION_TYPE, "manifest+json"); + + /** + * OGC KML (Keyhole Markup Language). + */ + public static final MediaType KML = createConstant(APPLICATION_TYPE, "vnd.google-earth.kml+xml"); + + /** + * OGC KML (Keyhole Markup Language), + * compressed using the ZIP format into KMZ archives. + */ + public static final MediaType KMZ = createConstant(APPLICATION_TYPE, "vnd.google-earth.kmz"); + + /** + * The mbox database format. + * + * @since 13.0 + */ + public static final MediaType MBOX = createConstant(APPLICATION_TYPE, "mbox"); + + /** + * Apple over-the-air mobile configuration profiles. + * + * @since 18.0 + */ + public static final MediaType APPLE_MOBILE_CONFIG = + createConstant(APPLICATION_TYPE, "x-apple-aspen-config"); + + /** Microsoft Excel spreadsheets. */ + public static final MediaType MICROSOFT_EXCEL = createConstant(APPLICATION_TYPE, "vnd.ms-excel"); + + /** + * Microsoft Outlook items. + * + * @since 27.1 + */ + public static final MediaType MICROSOFT_OUTLOOK = + createConstant(APPLICATION_TYPE, "vnd.ms-outlook"); + + /** Microsoft Powerpoint presentations. */ + public static final MediaType MICROSOFT_POWERPOINT = + createConstant(APPLICATION_TYPE, "vnd.ms-powerpoint"); + + /** Microsoft Word documents. */ + public static final MediaType MICROSOFT_WORD = createConstant(APPLICATION_TYPE, "msword"); + + /** + * Media type for Dynamic Adaptive + * Streaming over HTTP (DASH). This is registered with + * the IANA. + * + * @since NEXT + */ + public static final MediaType MEDIA_PRESENTATION_DESCRIPTION = + createConstant(APPLICATION_TYPE, "dash+xml"); + + /** + * WASM applications. For more information see the Web Assembly + * overview. + * + * @since 27.0 + */ + public static final MediaType WASM_APPLICATION = createConstant(APPLICATION_TYPE, "wasm"); + + /** + * NaCl applications. For more information see the + * Developer Guide for Native Client Application Structure. + * + * @since 20.0 + */ + public static final MediaType NACL_APPLICATION = createConstant(APPLICATION_TYPE, "x-nacl"); + + /** + * NaCl portable applications. For more information see the + * Developer Guide for Native Client Application Structure. + * + * @since 20.0 + */ + public static final MediaType NACL_PORTABLE_APPLICATION = + createConstant(APPLICATION_TYPE, "x-pnacl"); + + public static final MediaType OCTET_STREAM = createConstant(APPLICATION_TYPE, "octet-stream"); + + public static final MediaType OGG_CONTAINER = createConstant(APPLICATION_TYPE, "ogg"); + public static final MediaType OOXML_DOCUMENT = + createConstant( + APPLICATION_TYPE, "vnd.openxmlformats-officedocument.wordprocessingml.document"); + public static final MediaType OOXML_PRESENTATION = + createConstant( + APPLICATION_TYPE, "vnd.openxmlformats-officedocument.presentationml.presentation"); + public static final MediaType OOXML_SHEET = + createConstant(APPLICATION_TYPE, "vnd.openxmlformats-officedocument.spreadsheetml.sheet"); + public static final MediaType OPENDOCUMENT_GRAPHICS = + createConstant(APPLICATION_TYPE, "vnd.oasis.opendocument.graphics"); + public static final MediaType OPENDOCUMENT_PRESENTATION = + createConstant(APPLICATION_TYPE, "vnd.oasis.opendocument.presentation"); + public static final MediaType OPENDOCUMENT_SPREADSHEET = + createConstant(APPLICATION_TYPE, "vnd.oasis.opendocument.spreadsheet"); + public static final MediaType OPENDOCUMENT_TEXT = + createConstant(APPLICATION_TYPE, "vnd.oasis.opendocument.text"); + + /** + * OpenSearch + * Description files are XML files that describe how a website can be used as a search engine by + * consumers (e.g. web browsers). + * + * @since NEXT + */ + public static final MediaType OPENSEARCH_DESCRIPTION_UTF_8 = + createConstantUtf8(APPLICATION_TYPE, "opensearchdescription+xml"); + + public static final MediaType PDF = createConstant(APPLICATION_TYPE, "pdf"); + public static final MediaType POSTSCRIPT = createConstant(APPLICATION_TYPE, "postscript"); + + /** + * Protocol buffers + * + * @since 15.0 + */ + public static final MediaType PROTOBUF = createConstant(APPLICATION_TYPE, "protobuf"); + + /** + * RDF/XML documents, which are XML + * serializations of Resource Description + * Framework graphs. + * + * @since 14.0 + */ + public static final MediaType RDF_XML_UTF_8 = createConstantUtf8(APPLICATION_TYPE, "rdf+xml"); + + public static final MediaType RTF_UTF_8 = createConstantUtf8(APPLICATION_TYPE, "rtf"); + + /** + * SFNT fonts (which includes TrueType and OpenType fonts). This is registered with + * the IANA. + * + * @since 17.0 + */ + public static final MediaType SFNT = createConstant(APPLICATION_TYPE, "font-sfnt"); + + public static final MediaType SHOCKWAVE_FLASH = + createConstant(APPLICATION_TYPE, "x-shockwave-flash"); + + /** + * {@code skp} files produced by the 3D Modeling software SketchUp + * + * @since 13.0 + */ + public static final MediaType SKETCHUP = createConstant(APPLICATION_TYPE, "vnd.sketchup.skp"); + + /** + * As described in RFC 3902, this constant + * ({@code application/soap+xml}) is used to identify SOAP 1.2 message envelopes that have been + * serialized with XML 1.0. + * + *

For SOAP 1.1 messages, see {@code XML_UTF_8} per W3C Note on Simple Object Access Protocol + * (SOAP) 1.1 + * + * @since 20.0 + */ + public static final MediaType SOAP_XML_UTF_8 = createConstantUtf8(APPLICATION_TYPE, "soap+xml"); + + public static final MediaType TAR = createConstant(APPLICATION_TYPE, "x-tar"); + + /** + * Web Open Font Format (WOFF) defined by the W3C. This is registered with + * the IANA. + * + * @since 17.0 + */ + public static final MediaType WOFF = createConstant(APPLICATION_TYPE, "font-woff"); + + /** + * Web Open Font Format (WOFF) + * version 2 defined by the W3C. + * + * @since 20.0 + */ + public static final MediaType WOFF2 = createConstant(APPLICATION_TYPE, "font-woff2"); + + public static final MediaType XHTML_UTF_8 = createConstantUtf8(APPLICATION_TYPE, "xhtml+xml"); + + /** + * Extensible Resource Descriptors. This is not yet registered with the IANA, but it is specified + * by OASIS in the XRD + * definition and implemented in projects such as WebFinger. + * + * @since 14.0 + */ + public static final MediaType XRD_UTF_8 = createConstantUtf8(APPLICATION_TYPE, "xrd+xml"); + + public static final MediaType ZIP = createConstant(APPLICATION_TYPE, "zip"); + + private final String type; + private final String subtype; + private final ImmutableListMultimap parameters; + + private String toString; + + private int hashCode; + + private Optional parsedCharset; + + private MediaType(String type, String subtype, ImmutableListMultimap parameters) { + this.type = type; + this.subtype = subtype; + this.parameters = parameters; + } + + /** Returns the top-level media type. For example, {@code "text"} in {@code "text/plain"}. */ + public String type() { + return type; + } + + /** Returns the media subtype. For example, {@code "plain"} in {@code "text/plain"}. */ + public String subtype() { + return subtype; + } + + /** Returns a multimap containing the parameters of this media type. */ + public ImmutableListMultimap parameters() { + return parameters; + } + + private Map> parametersAsMap() { + return Maps.transformValues( + parameters.asMap(), + new Function, ImmutableMultiset>() { + @Override + public ImmutableMultiset apply(Collection input) { + return ImmutableMultiset.copyOf(input); + } + }); + } + + /** + * Returns an optional charset for the value of the charset parameter if it is specified. + * + * @throws IllegalStateException if multiple charset values have been set for this media type + * @throws IllegalCharsetNameException if a charset value is present, but illegal + * @throws UnsupportedCharsetException if a charset value is present, but no support is available + * in this instance of the Java virtual machine + */ + public Optional charset() { + // racy single-check idiom, this is safe because Optional is immutable. + Optional local = parsedCharset; + if (local == null) { + String value = null; + local = Optional.absent(); + for (String currentValue : parameters.get(CHARSET_ATTRIBUTE)) { + if (value == null) { + value = currentValue; + local = Optional.of(Charset.forName(value)); + } else if (!value.equals(currentValue)) { + throw new IllegalStateException( + "Multiple charset values defined: " + value + ", " + currentValue); + } + } + parsedCharset = local; + } + return local; + } + + /** + * Returns a new instance with the same type and subtype as this instance, but without any + * parameters. + */ + public MediaType withoutParameters() { + return parameters.isEmpty() ? this : create(type, subtype); + } + + /** + * Replaces all parameters with the given parameters. + * + * @throws IllegalArgumentException if any parameter or value is invalid + */ + public MediaType withParameters(Multimap parameters) { + return create(type, subtype, parameters); + } + + /** + * Replaces all parameters with the given attribute with parameters using the given + * values. If there are no values, any existing parameters with the given attribute are removed. + * + * @throws IllegalArgumentException if either {@code attribute} or {@code values} is invalid + * @since 24.0 + */ + public MediaType withParameters(String attribute, Iterable values) { + checkNotNull(attribute); + checkNotNull(values); + String normalizedAttribute = normalizeToken(attribute); + ImmutableListMultimap.Builder builder = ImmutableListMultimap.builder(); + for (Entry entry : parameters.entries()) { + String key = entry.getKey(); + if (!normalizedAttribute.equals(key)) { + builder.put(key, entry.getValue()); + } + } + for (String value : values) { + builder.put(normalizedAttribute, normalizeParameterValue(normalizedAttribute, value)); + } + MediaType mediaType = new MediaType(type, subtype, builder.build()); + // if the attribute isn't charset, we can just inherit the current parsedCharset + if (!normalizedAttribute.equals(CHARSET_ATTRIBUTE)) { + mediaType.parsedCharset = this.parsedCharset; + } + // Return one of the constants if the media type is a known type. + return MoreObjects.firstNonNull(KNOWN_TYPES.get(mediaType), mediaType); + } + + /** + * Replaces all parameters with the given attribute with a single parameter with the + * given value. If multiple parameters with the same attributes are necessary use {@link + * #withParameters(String, Iterable)}. Prefer {@link #withCharset} for setting the {@code charset} + * parameter when using a {@link Charset} object. + * + * @throws IllegalArgumentException if either {@code attribute} or {@code value} is invalid + */ + public MediaType withParameter(String attribute, String value) { + return withParameters(attribute, ImmutableSet.of(value)); + } + + /** + * Returns a new instance with the same type and subtype as this instance, with the {@code + * charset} parameter set to the {@link Charset#name name} of the given charset. Only one {@code + * charset} parameter will be present on the new instance regardless of the number set on this + * one. + * + *

If a charset must be specified that is not supported on this JVM (and thus is not + * representable as a {@link Charset} instance, use {@link #withParameter}. + */ + public MediaType withCharset(Charset charset) { + checkNotNull(charset); + MediaType withCharset = withParameter(CHARSET_ATTRIBUTE, charset.name()); + // precache the charset so we don't need to parse it + withCharset.parsedCharset = Optional.of(charset); + return withCharset; + } + + /** Returns true if either the type or subtype is the wildcard. */ + public boolean hasWildcard() { + return WILDCARD.equals(type) || WILDCARD.equals(subtype); + } + + /** + * Returns {@code true} if this instance falls within the range (as defined by the HTTP Accept header) given + * by the argument according to three criteria: + * + *

    + *
  1. The type of the argument is the wildcard or equal to the type of this instance. + *
  2. The subtype of the argument is the wildcard or equal to the subtype of this instance. + *
  3. All of the parameters present in the argument are present in this instance. + *
+ * + *

For example: + * + *

{@code
+   * PLAIN_TEXT_UTF_8.is(PLAIN_TEXT_UTF_8) // true
+   * PLAIN_TEXT_UTF_8.is(HTML_UTF_8) // false
+   * PLAIN_TEXT_UTF_8.is(ANY_TYPE) // true
+   * PLAIN_TEXT_UTF_8.is(ANY_TEXT_TYPE) // true
+   * PLAIN_TEXT_UTF_8.is(ANY_IMAGE_TYPE) // false
+   * PLAIN_TEXT_UTF_8.is(ANY_TEXT_TYPE.withCharset(UTF_8)) // true
+   * PLAIN_TEXT_UTF_8.withoutParameters().is(ANY_TEXT_TYPE.withCharset(UTF_8)) // false
+   * PLAIN_TEXT_UTF_8.is(ANY_TEXT_TYPE.withCharset(UTF_16)) // false
+   * }
+ * + *

Note that while it is possible to have the same parameter declared multiple times within a + * media type this method does not consider the number of occurrences of a parameter. For example, + * {@code "text/plain; charset=UTF-8"} satisfies {@code "text/plain; charset=UTF-8; + * charset=UTF-8"}. + */ + public boolean is(MediaType mediaTypeRange) { + return (mediaTypeRange.type.equals(WILDCARD) || mediaTypeRange.type.equals(this.type)) + && (mediaTypeRange.subtype.equals(WILDCARD) || mediaTypeRange.subtype.equals(this.subtype)) + && this.parameters.entries().containsAll(mediaTypeRange.parameters.entries()); + } + + /** + * Creates a new media type with the given type and subtype. + * + * @throws IllegalArgumentException if type or subtype is invalid or if a wildcard is used for the + * type, but not the subtype. + */ + public static MediaType create(String type, String subtype) { + MediaType mediaType = create(type, subtype, ImmutableListMultimap.of()); + mediaType.parsedCharset = Optional.absent(); + return mediaType; + } + + private static MediaType create( + String type, String subtype, Multimap parameters) { + checkNotNull(type); + checkNotNull(subtype); + checkNotNull(parameters); + String normalizedType = normalizeToken(type); + String normalizedSubtype = normalizeToken(subtype); + checkArgument( + !WILDCARD.equals(normalizedType) || WILDCARD.equals(normalizedSubtype), + "A wildcard type cannot be used with a non-wildcard subtype"); + ImmutableListMultimap.Builder builder = ImmutableListMultimap.builder(); + for (Entry entry : parameters.entries()) { + String attribute = normalizeToken(entry.getKey()); + builder.put(attribute, normalizeParameterValue(attribute, entry.getValue())); + } + MediaType mediaType = new MediaType(normalizedType, normalizedSubtype, builder.build()); + // Return one of the constants if the media type is a known type. + return MoreObjects.firstNonNull(KNOWN_TYPES.get(mediaType), mediaType); + } + + /** + * Creates a media type with the "application" type and the given subtype. + * + * @throws IllegalArgumentException if subtype is invalid + */ + static MediaType createApplicationType(String subtype) { + return create(APPLICATION_TYPE, subtype); + } + + /** + * Creates a media type with the "audio" type and the given subtype. + * + * @throws IllegalArgumentException if subtype is invalid + */ + static MediaType createAudioType(String subtype) { + return create(AUDIO_TYPE, subtype); + } + + /** + * Creates a media type with the "image" type and the given subtype. + * + * @throws IllegalArgumentException if subtype is invalid + */ + static MediaType createImageType(String subtype) { + return create(IMAGE_TYPE, subtype); + } + + /** + * Creates a media type with the "text" type and the given subtype. + * + * @throws IllegalArgumentException if subtype is invalid + */ + static MediaType createTextType(String subtype) { + return create(TEXT_TYPE, subtype); + } + + /** + * Creates a media type with the "video" type and the given subtype. + * + * @throws IllegalArgumentException if subtype is invalid + */ + static MediaType createVideoType(String subtype) { + return create(VIDEO_TYPE, subtype); + } + + private static String normalizeToken(String token) { + checkArgument(TOKEN_MATCHER.matchesAllOf(token)); + checkArgument(!token.isEmpty()); + return Ascii.toLowerCase(token); + } + + private static String normalizeParameterValue(String attribute, String value) { + checkNotNull(value); // for GWT + checkArgument(ascii().matchesAllOf(value), "parameter values must be ASCII: %s", value); + return CHARSET_ATTRIBUTE.equals(attribute) ? Ascii.toLowerCase(value) : value; + } + + /** + * Parses a media type from its string representation. + * + * @throws IllegalArgumentException if the input is not parsable + */ + public static MediaType parse(String input) { + checkNotNull(input); + Tokenizer tokenizer = new Tokenizer(input); + try { + String type = tokenizer.consumeToken(TOKEN_MATCHER); + tokenizer.consumeCharacter('/'); + String subtype = tokenizer.consumeToken(TOKEN_MATCHER); + ImmutableListMultimap.Builder parameters = ImmutableListMultimap.builder(); + while (tokenizer.hasMore()) { + tokenizer.consumeTokenIfPresent(LINEAR_WHITE_SPACE); + tokenizer.consumeCharacter(';'); + tokenizer.consumeTokenIfPresent(LINEAR_WHITE_SPACE); + String attribute = tokenizer.consumeToken(TOKEN_MATCHER); + tokenizer.consumeCharacter('='); + final String value; + if ('"' == tokenizer.previewChar()) { + tokenizer.consumeCharacter('"'); + StringBuilder valueBuilder = new StringBuilder(); + while ('"' != tokenizer.previewChar()) { + if ('\\' == tokenizer.previewChar()) { + tokenizer.consumeCharacter('\\'); + valueBuilder.append(tokenizer.consumeCharacter(ascii())); + } else { + valueBuilder.append(tokenizer.consumeToken(QUOTED_TEXT_MATCHER)); + } + } + value = valueBuilder.toString(); + tokenizer.consumeCharacter('"'); + } else { + value = tokenizer.consumeToken(TOKEN_MATCHER); + } + parameters.put(attribute, value); + } + return create(type, subtype, parameters.build()); + } catch (IllegalStateException e) { + throw new IllegalArgumentException("Could not parse '" + input + "'", e); + } + } + + private static final class Tokenizer { + final String input; + int position = 0; + + Tokenizer(String input) { + this.input = input; + } + + String consumeTokenIfPresent(CharMatcher matcher) { + checkState(hasMore()); + int startPosition = position; + position = matcher.negate().indexIn(input, startPosition); + return hasMore() ? input.substring(startPosition, position) : input.substring(startPosition); + } + + String consumeToken(CharMatcher matcher) { + int startPosition = position; + String token = consumeTokenIfPresent(matcher); + checkState(position != startPosition); + return token; + } + + char consumeCharacter(CharMatcher matcher) { + checkState(hasMore()); + char c = previewChar(); + checkState(matcher.matches(c)); + position++; + return c; + } + + char consumeCharacter(char c) { + checkState(hasMore()); + checkState(previewChar() == c); + position++; + return c; + } + + char previewChar() { + checkState(hasMore()); + return input.charAt(position); + } + + boolean hasMore() { + return (position >= 0) && (position < input.length()); + } + } + + @Override + public boolean equals(Object obj) { + if (obj == this) { + return true; + } else if (obj instanceof MediaType) { + MediaType that = (MediaType) obj; + return this.type.equals(that.type) + && this.subtype.equals(that.subtype) + // compare parameters regardless of order + && this.parametersAsMap().equals(that.parametersAsMap()); + } else { + return false; + } + } + + @Override + public int hashCode() { + // racy single-check idiom + int h = hashCode; + if (h == 0) { + h = Objects.hashCode(type, subtype, parametersAsMap()); + hashCode = h; + } + return h; + } + + private static final MapJoiner PARAMETER_JOINER = Joiner.on("; ").withKeyValueSeparator("="); + + /** + * Returns the string representation of this media type in the format described in RFC 2045. + */ + @Override + public String toString() { + // racy single-check idiom, safe because String is immutable + String result = toString; + if (result == null) { + result = computeToString(); + toString = result; + } + return result; + } + + private String computeToString() { + StringBuilder builder = new StringBuilder().append(type).append('/').append(subtype); + if (!parameters.isEmpty()) { + builder.append("; "); + Multimap quotedParameters = + Multimaps.transformValues( + parameters, + new Function() { + @Override + public String apply(String value) { + return (TOKEN_MATCHER.matchesAllOf(value) && !value.isEmpty()) + ? value + : escapeAndQuote(value); + } + }); + PARAMETER_JOINER.appendTo(builder, quotedParameters.entries()); + } + return builder.toString(); + } + + private static String escapeAndQuote(String value) { + StringBuilder escaped = new StringBuilder(value.length() + 16).append('"'); + for (int i = 0; i < value.length(); i++) { + char ch = value.charAt(i); + if (ch == '\r' || ch == '\\' || ch == '"') { + escaped.append('\\'); + } + escaped.append(ch); + } + return escaped.append('"').toString(); + } +} diff --git a/src/main/java/com/google/common/net/PercentEscaper.java b/src/main/java/com/google/common/net/PercentEscaper.java new file mode 100644 index 0000000..554d04d --- /dev/null +++ b/src/main/java/com/google/common/net/PercentEscaper.java @@ -0,0 +1,233 @@ +/* + * Copyright (C) 2008 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.net; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.annotations.Beta; +import com.google.common.annotations.GwtCompatible; +import com.google.common.escape.UnicodeEscaper; + +/** + * A {@code UnicodeEscaper} that escapes some set of Java characters using a UTF-8 based percent + * encoding scheme. The set of safe characters (those which remain unescaped) can be specified on + * construction. + * + *

This class is primarily used for creating URI escapers in {@link UrlEscapers} but can be used + * directly if required. While URI escapers impose specific semantics on which characters are + * considered 'safe', this class has a minimal set of restrictions. + * + *

When escaping a String, the following rules apply: + * + *

    + *
  • All specified safe characters remain unchanged. + *
  • If {@code plusForSpace} was specified, the space character " " is converted into a plus + * sign {@code "+"}. + *
  • All other characters are converted into one or more bytes using UTF-8 encoding and each + * byte is then represented by the 3-character string "%XX", where "XX" is the two-digit, + * uppercase, hexadecimal representation of the byte value. + *
+ * + *

For performance reasons the only currently supported character encoding of this class is + * UTF-8. + * + *

Note: This escaper produces uppercase hexadecimal sequences. + * + * @author David Beaumont + * @since 15.0 + */ +@Beta +@GwtCompatible +public final class PercentEscaper extends UnicodeEscaper { + + // In some escapers spaces are escaped to '+' + private static final char[] PLUS_SIGN = {'+'}; + + // Percent escapers output upper case hex digits (uri escapers require this). + private static final char[] UPPER_HEX_DIGITS = "0123456789ABCDEF".toCharArray(); + + /** If true we should convert space to the {@code +} character. */ + private final boolean plusForSpace; + + /** + * An array of flags where for any {@code char c} if {@code safeOctets[c]} is true then {@code c} + * should remain unmodified in the output. If {@code c >= safeOctets.length} then it should be + * escaped. + */ + private final boolean[] safeOctets; + + /** + * Constructs a percent escaper with the specified safe characters and optional handling of the + * space character. + * + *

Not that it is allowed, but not necessarily desirable to specify {@code %} as a safe + * character. This has the effect of creating an escaper which has no well defined inverse but it + * can be useful when escaping additional characters. + * + * @param safeChars a non null string specifying additional safe characters for this escaper (the + * ranges 0..9, a..z and A..Z are always safe and should not be specified here) + * @param plusForSpace true if ASCII space should be escaped to {@code +} rather than {@code %20} + * @throws IllegalArgumentException if any of the parameters were invalid + */ + public PercentEscaper(String safeChars, boolean plusForSpace) { + // TODO(dbeaumont): Switch to static factory methods for creation now that class is final. + // TODO(dbeaumont): Support escapers where alphanumeric chars are not safe. + checkNotNull(safeChars); // eager for GWT. + // Avoid any misunderstandings about the behavior of this escaper + if (safeChars.matches(".*[0-9A-Za-z].*")) { + throw new IllegalArgumentException( + "Alphanumeric characters are always 'safe' and should not be explicitly specified"); + } + safeChars += "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; + // Avoid ambiguous parameters. Safe characters are never modified so if + // space is a safe character then setting plusForSpace is meaningless. + if (plusForSpace && safeChars.contains(" ")) { + throw new IllegalArgumentException( + "plusForSpace cannot be specified when space is a 'safe' character"); + } + this.plusForSpace = plusForSpace; + this.safeOctets = createSafeOctets(safeChars); + } + + /** + * Creates a boolean array with entries corresponding to the character values specified in + * safeChars set to true. The array is as small as is required to hold the given character + * information. + */ + private static boolean[] createSafeOctets(String safeChars) { + int maxChar = -1; + char[] safeCharArray = safeChars.toCharArray(); + for (char c : safeCharArray) { + maxChar = Math.max(c, maxChar); + } + boolean[] octets = new boolean[maxChar + 1]; + for (char c : safeCharArray) { + octets[c] = true; + } + return octets; + } + + /* + * Overridden for performance. For unescaped strings this improved the performance of the uri + * escaper from ~760ns to ~400ns as measured by {@link CharEscapersBenchmark}. + */ + @Override + protected int nextEscapeIndex(CharSequence csq, int index, int end) { + checkNotNull(csq); + for (; index < end; index++) { + char c = csq.charAt(index); + if (c >= safeOctets.length || !safeOctets[c]) { + break; + } + } + return index; + } + + /* + * Overridden for performance. For unescaped strings this improved the performance of the uri + * escaper from ~400ns to ~170ns as measured by {@link CharEscapersBenchmark}. + */ + @Override + public String escape(String s) { + checkNotNull(s); + int slen = s.length(); + for (int index = 0; index < slen; index++) { + char c = s.charAt(index); + if (c >= safeOctets.length || !safeOctets[c]) { + return escapeSlow(s, index); + } + } + return s; + } + + /** Escapes the given Unicode code point in UTF-8. */ + @Override + protected char[] escape(int cp) { + // We should never get negative values here but if we do it will throw an + // IndexOutOfBoundsException, so at least it will get spotted. + if (cp < safeOctets.length && safeOctets[cp]) { + return null; + } else if (cp == ' ' && plusForSpace) { + return PLUS_SIGN; + } else if (cp <= 0x7F) { + // Single byte UTF-8 characters + // Start with "%--" and fill in the blanks + char[] dest = new char[3]; + dest[0] = '%'; + dest[2] = UPPER_HEX_DIGITS[cp & 0xF]; + dest[1] = UPPER_HEX_DIGITS[cp >>> 4]; + return dest; + } else if (cp <= 0x7ff) { + // Two byte UTF-8 characters [cp >= 0x80 && cp <= 0x7ff] + // Start with "%--%--" and fill in the blanks + char[] dest = new char[6]; + dest[0] = '%'; + dest[3] = '%'; + dest[5] = UPPER_HEX_DIGITS[cp & 0xF]; + cp >>>= 4; + dest[4] = UPPER_HEX_DIGITS[0x8 | (cp & 0x3)]; + cp >>>= 2; + dest[2] = UPPER_HEX_DIGITS[cp & 0xF]; + cp >>>= 4; + dest[1] = UPPER_HEX_DIGITS[0xC | cp]; + return dest; + } else if (cp <= 0xffff) { + // Three byte UTF-8 characters [cp >= 0x800 && cp <= 0xffff] + // Start with "%E-%--%--" and fill in the blanks + char[] dest = new char[9]; + dest[0] = '%'; + dest[1] = 'E'; + dest[3] = '%'; + dest[6] = '%'; + dest[8] = UPPER_HEX_DIGITS[cp & 0xF]; + cp >>>= 4; + dest[7] = UPPER_HEX_DIGITS[0x8 | (cp & 0x3)]; + cp >>>= 2; + dest[5] = UPPER_HEX_DIGITS[cp & 0xF]; + cp >>>= 4; + dest[4] = UPPER_HEX_DIGITS[0x8 | (cp & 0x3)]; + cp >>>= 2; + dest[2] = UPPER_HEX_DIGITS[cp]; + return dest; + } else if (cp <= 0x10ffff) { + char[] dest = new char[12]; + // Four byte UTF-8 characters [cp >= 0xffff && cp <= 0x10ffff] + // Start with "%F-%--%--%--" and fill in the blanks + dest[0] = '%'; + dest[1] = 'F'; + dest[3] = '%'; + dest[6] = '%'; + dest[9] = '%'; + dest[11] = UPPER_HEX_DIGITS[cp & 0xF]; + cp >>>= 4; + dest[10] = UPPER_HEX_DIGITS[0x8 | (cp & 0x3)]; + cp >>>= 2; + dest[8] = UPPER_HEX_DIGITS[cp & 0xF]; + cp >>>= 4; + dest[7] = UPPER_HEX_DIGITS[0x8 | (cp & 0x3)]; + cp >>>= 2; + dest[5] = UPPER_HEX_DIGITS[cp & 0xF]; + cp >>>= 4; + dest[4] = UPPER_HEX_DIGITS[0x8 | (cp & 0x3)]; + cp >>>= 2; + dest[2] = UPPER_HEX_DIGITS[cp & 0x7]; + return dest; + } else { + // If this ever happens it is due to bug in UnicodeEscaper, not bad input. + throw new IllegalArgumentException("Invalid unicode character value " + cp); + } + } +} diff --git a/src/main/java/com/google/common/net/PublicSuffixPatterns.java b/src/main/java/com/google/common/net/PublicSuffixPatterns.java new file mode 100644 index 0000000..7d99748 --- /dev/null +++ b/src/main/java/com/google/common/net/PublicSuffixPatterns.java @@ -0,0 +1,63 @@ +// GENERATED FILE - DO NOT EDIT + +/* + * Copyright (C) 2008 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.net; + +import com.google.common.annotations.Beta; +import com.google.common.annotations.GwtCompatible; +import com.google.common.collect.ImmutableMap; + +/** + * Do not use this class directly. For access to public-suffix information, + * use {@link com.google.common.net.InternetDomainName}. + * + * A generated static class containing public members which provide domain + * name patterns used in determining whether a given domain name is an + * effective top-level domain (public suffix). + * + *

Because this class is used in GWT, the data members are stored in + * a space-efficient manner. + * + * @since 16.0 + */ +@GwtCompatible +@Beta +public final class PublicSuffixPatterns { + private PublicSuffixPatterns() {} + + /** If a hostname is contained as a key in this map, it is a public suffix. */ + public static final ImmutableMap EXACT = + TrieParser.parseTrie("a&0&0trk9--nx?27qjf--nx?e9ebgn--nx?nbb0c7abgm--nx??1&2oa08--nx?hbbgm--nx?rdceqa08--nx??2&8ugbgm--nx?eyh3la2ckx--nx?qbd9--nx??3&2wqq1--nx?60a0y8--nx??4x1d77xrck--nx?6&1f4a3abgm--nx?2yqyn--nx?3np8lv81qo3--nx?5b06t--nx?lbgw--nx??883xnn--nx?9d2c24--nx?a&a?it??b!.&gro?lim?moc?t&en?opsgolb,?ude?vog??abila?c?ihsot?m?n??c!.&b&a?m?n??c&b?g?q??ep?fn?k&s?y??ln?no?oc,pi-on,sn?t&n?opsgolb,?un?ysrab,?i&ma?nofelet?r&emarp?fa??sroc??naiva?s??d&ats?n&eit?oh??om?sa?tl??eg?f&c?ob??g!emo?naripi?oy??hskihs?i&cnal?dem?hs?k!on??sa!.snduolc,??jnin?k&aso?dov?ede?usto??l!.&c,gro?m&oc?yn,?ofni?r&ep?nb,?t&en?ni??ude?vog??irgnahs?le&nisiuc?rbmuder???m!.&ca?gro?oc?sserp?ten?vog??ahokoy?e00sf7vqn--nx?m??n!.&ac?cc?eman?gro?ibom?loohcs?moc?ni?o&c?fni?rp??r&d?o??s&u?w??vt?xm??av?is?olecrab?tea??p!.&bog?ca?d&em?ls??g&ni?ro??mo&c?n??oba?ten?ude??c?g7hyabgm--nx?ra!.&461e?6pi?iru?nru?rdda-ni?siri???s??q!.&eman?gro?hcs?lim?mo&c?n,?t&en?opsgolb,?ude?vog???r&az?emac?f4a3abgm--nx?n!d5uhf8le58r4w--nx??u&kas?tan???s!.&bup?dem?gro?hcs?moc?ten?ude?vog??ac!.uban.iu,?iv??t&ad?elhta?led?oyot??u!.&a&cinniv?emirc?i&hzhziropaz?stynniv??s&edo?sedo??tlay?vatlop??bs?c&c,inimod??d&argovorik?o!roghzu??tl,?e&hzhziropaz?nvir?t??f&i?ni,?g&l?ro??hk?i&stvinrehc?ykstynlemhk??k&c?m?s&nagul?t&enod?ul??v&iknarf-onavi?orteporp&end?ind?????l&iponret?opotsa&bes?ves??p??m&k?oc?s?yrk??n&c?d?i?osrehk?v?ylov??o&c,nvor??p&d?p,z??r&c?imotihz?k?ymotyhz??sk?t&en?l?z??ude?v&c?e&alokin?ik??i&alokym?hinrehc?krahk?vl?yk??k?l?o&g!inrehc??krahk??r??y&ikstinlemhk?mus?s&akrehc?sakrehc?tvonrehc???z&ib,u????v!aj?bb?et?iv??waniko?x&a?iacal??yogan?z&.&bew?c&a?i&n?rga???gro?l&im?oohcs??m&on?t??o&c!.topsgolb,?gn??radnorg?sin?t&en?la??ude?vog?wal??zip???b&00ave5a9iabgm--nx?1&25qhx--nx?68quv--nx?e2kc1--nx??2xtbgm--nx?3&b2kcc--nx?jca1d--nx??4&6&1rfz--nx?qif--nx??96rzc--nx??7w9u16qlj--nx?88uvor--nx?a&0dc4xbgm--nx?c?her?n?ra?t??b!.&erots?gro?moc?o&c?fni??ten?ude?v&og?t??zib??a??c&j?s??d&hesa08--nx?mi??ec?g?l!.&gro?moc?ten?ude?vog??m??s!.&gro?moc?ten?ude?vog???tc-retarebsnegmrev--nx?u&lc!.&snduolc,y&nop,srab,??smas??p!.ysrab,??wp-gnutarebsnegmrev--nx??c&1&1q54--nx?hbgw--nx??2e9c2czf--nx?4&4ub1km--nx?a1e--nx?byj9q--nx?erd5a9b1kcb--nx??779tbp--nx?8&4xx2g--nx?c9jrb2h--nx??9jr&b&2h--nx?54--nx?9s--nx??c&eg--nx?h3--nx?s2--nx???a!.&gro?lim?moc?ten?ude?vog??3a09--nx!.&ca1o--nx?gva1c--nx?h&ca1o--nx?za09--nx??ta1d--nx?ua08--nx???da??b&a?b?ci?f76a0c7ylqbgm--nx?sh??c!.&eugaelysatnaf,gnipparcs,liamwt,revres-emag,s&nduolc,otohpym,seccaptf,??0atf7b45--nx?a1l--nx??e!.&21k?bog?dem?gro?lim?m&oc?yn,?nif?o&fni?rp??ten?ude?vog??beuq?n?smoc?tnamys??fdh?i&l&buperananab?ohtac??n&agro?ilc?osanap??tic??l!.&gro?m&oc?yn,?oc?ten?ude?vog?yo,?l??m!.&mt?ossa??p1akcq--nx??n!.&mon?ossa??i?p??relcel?s!.&gro?moc?ten?ude?vog??c??t!s?w??v!.&gro?lim?mo&c?n,?ten?ude?v&g:.d,,og???q??wp?yn??d&2urzc--nx?3&1wrpk--nx?c&4b11--nx?9jrcpf--nx???5xq55--nx?697uto--nx?75yrpk--nx?9ctdvkce--nx?a!.mon?d?er?olnwod??b2babgm--nx?c!.vog?g9a2g2b0ae0chclc--nx??e&m!bulc??r!k??sopxe?timil?w??fc?g!.mon,?h&d3tbgm--nx?p?t??i!.&ased?bew?ca?hcs?lim?o&c!.topsgolb,?g??ro?sepnop?ten?ym?zib??ar?b?ordna?p?rdam??l&iub?og?row??m!.topsgolb,?n&a&b?l!.citats:.&setis,ved,?,lohwen?raas???ob?uf??o&of?rp??r&a&c&tiderc?yalcrab??ugnav??ef506w4b--nx?k!.&oc,ude,?jh3a1habgm--nx??of??s!.&dem?gro?moc?ofni?ten?ude?v&og?t???m!kcrem???t!.topsgolb,l??uolc!.&drayknil,r&epolroov,opav,?xelpciffart,??za5cbgn--nx??e&1&53wlf--nx?7a1hbbgm--nx?ta3kg--nx??2a6a1b6b1i--nx?3ma0e1cvr--nx?418txh--nx?707b0e3--nx?a!.&ca?gro?hcs?lim?mon,oc?t&en?opsgolb,?vog??09--nx??b!.&ca?gnitsohbew,topsgolb,?ortal?ut!uoy???c&a&lp!.oc,?ps!.&lla4sx,rebu,slootiknil,?artxe??sla??i!ffo??n&a&d?iler?nif?rus&e?ni!efil?srelevart????eics!.oby,??rofria??d!.&1sndnyd,42pi-nyd,7erauqs,amil4,brb-ni,decalpb,e&daregtmueart,mohsnd,nihcamyek,?keegnietsi,lsd-ni,moc,n&-i-g-o-l,aw-ym,esgnutiel,i&emtsi,lreb-n&i,yd,??oitatsksid-ygolonys,pv&-n&i,yd,?nyd,??p&h21,iog:ol,,?r&e&ntrapdeeps.remotsuc,su&-lautriv,lautriv,?t&adpusnd,tub-ni,uor-ym,?vres&-e&bucl,mohym,?bew-emoh:.nyd,,??ogiv-&niem,ym,??s&d-&onys,ygolonys,?nd&-&dd,nufiat,sehcsimanyd,tenretni,yard,?isoc.nyd,ps,yard,?oper-&nvs,tig,?sndd:.&nyd,sndnyd,?,?topsgolb,vresi-&niem,tset,?xi2,y&awetag-&llawerif,ym,?srab,tic-amil,?zten&mitbel,sadtretteuf,??a&lg?rt!.oby,??i&s&doow?ruoyno??ug?wnoitan??nil?on--nx??e!.&bil?dem?eif?gro?irp?kiir?moc!.topsgolb,?pia?ude?vog??ei?ffoc?gg?r&f?ged???f&a&c?s??il!tem???g!.&gro?lim?mo&c?n,?t&en?vp??ude?vog??a&f?gtrom?p!.ycvrp,?rots?yov??dod?elloc?na&hcxe?ro??roeg?ug??i!.&myn,topsgolb,vog??tilop?v&bba?om???j!.&gro?oc?ten???k!.&c&a?s??e&m?n??ibom?mon,o&c!.topsgolb,?fni?g??ro??i&b?l?n???l&a&dmrif?s!.rof,rof???b&a?i&b?dua???c&aro?ric??dnik?g!oog??i&bom?ms??l&asal?erauqa??ppa?uhcs?yts!efil???m!.&4&32i,pct,?66c,ailisarb,b&dnevar,g-raegelif,?ca?duolcsd,e&d-raegelif,i&-raegelif,lpad:.tsohlacol,,??g&ro?s-raegelif,?hctilg,myn,noitatsksid,o&bmoy,c?t&nigol,poh,??p&ion,j-raegelif,ohbew,?r&aegelif,ofsnd,?s&dym,ndd,ti??t&en?s&acdnuos,ohon,??u&a-raegelif,de?tcn,?v&irp?og??y&golonys,olpedew,srab,??a&g?n!.&reh.togrof,sih.togrof,???em?i&rp?twohs??o&cnal?htathgir?rhc??w??n!goloc?i&lno!.ysrab,?w??o!.&derno:.gnigats,,knilemoh,rof,?hp?latipac?ts&der?e&gdirb?rif???z!.&66duolc,amil,sh,???ruoblem??om?p!.&bog?gro?lim?m&o&c?n??yn,?t&en?opsgolb,?ude??irg?yks??r!.&mo&c?n??ossa?topsgolb,?a&c!htlaeh??pmoc?wtfos??bc?eh?if?ots?taeht?u&ces?sni?t&inruf?necca??za???s!.&a?b!ibnal?rofmok??c!a??d!b?n&arb?ubroflanummok???e?f!noc,?g!ro??h!f??i!trap??k!shf??l?m!oc,t??n!mygskurbrutan??o?p!p??r?s!serp??t!opsgolb,?u?vhf?w?x!uvmok??y?z??a&c?el?hc??i&er?urc??nesemoh?roh?uoh??t&a&d?ts&e!laer??lla???is!.&areduolc,enilnigol,n&eyb,oyc,?xulel,ysrab,?bew??ov?ra?t&ioled?ol??utitsni??u&lb?qi&nilc?tuob???v!.&21e?b&ew?og??ce&r?t??erots?gro?lim?m&oc?rif??o&c?fni??stra?t&en?ni??ude?vog??as?e3gerb2h--nx?i&l?rd?ssergorp??ol??w&kct--nx?r??xul??f&0f3rkcg--nx?198xim--nx?280xim--nx?617upk--nx?7vqn--nx?a!.&gro?mo&c?n,?ten?ude?vog???b!.vog?wa9bgm--nx??c!.topsgolb,a1p--nx?ns??ea1j--nx?fo?g?iam?l&a1d--nx?og??n!.&bew?cer?erots?m&oc?rif??ofni?re&hto?p??stra?ten???orp?p!.&gro?moc?ude???rus?t!w??vd7ckaabgm--nx?w??g&2&4wq55--nx?8zrf6--nx??3&44sd3--nx?91w6j--nx!.&a5wqmg--nx?d&22svcw--nx?5xq55--nx??gla0do--nx?m1qtxm--nx?vta0cu--nx????455ses--nx?5&7vtse--nx?mzt5--nx??69vqhr--nx?7&8a4d5a4prebgm--nx?rb2c--nx??a!.&gro?mo&c?n??oc?ten??vd??b!.&0?1?2?3?4?5?6?7?8?9?a?b?c?d?e?f?g?h?i?j?k?l?m?n?o?p?q?r?s?t!opsgolb,?u?v?w?x?y!srab,?z???c!b?za9a0cbgm--nx??e!.&eman?gro?ics?lim?moc!.topsgolb,?nue?ten?ude?vog??a??g!.&ayc,gro?oc?saak,ten???i&a?v??k!.&gro?lim?moc?ten?ude?vog???m!.&drp?gro?lim?m&o&c?n??t??oc?ude?vog??pk??n!.&dtl,eman?gro?hcs?i!bom??l&im?oc,?m&oc!.topsgolb,?rif,?neg,ten?ude?vog??aw?i!b!mulp??car?d&art?dew??h&sif?tolc??k&iv?oo&b?c???ls?n&aelc?iart??p!pohs??re&enigne?tac??t&ad?ekram?hgil?lusnoc?neg?ov?soh!.tfarcnepo,?tebdaerps??vi&g?l???o!s??u&rehcisrev?smas?tarebsnegömrev???o&d?lb?og!.duolc,??r&ebmoolb?o!.&77ndc.c:sr,,a&remacytirucesym,t&neimip,sivretla,?z,?d&ab-yrev-si,e&sufnocsim,vas-si,?nuof-si,oog-yrev-si,uolcarfniarodef,?e&a,cin-yrev-si,grofpeh,l&as-4-ffuts,poeparodef,?m&-morf,agevres,ohruoyslles,?n&ozdop,uma.elet,?r&ehwongniogyldlob,iwym,uces-77ndc.nigiro.lss,?t&adidnac-a-si,is&-ybboh,golb,???fehc-a-si,golbymdaer,k&eeg-a&-si,si,?h,nut,?l&i&amwt,ve-yrev-si,?lawerif&-ym,ym,?sd-ni,?m&acssecca,edom-elbac,?n&af&blm,cfu,egelloc,lfn,s&citlec-a-si,niurb-a-si,tap-a-si,?xos-a-si,?o&itatsksid,rviop,?pv-ni,?o&jodsnd,tp&az,oh,??p&i&-on,fles,?o&hbew,tksedeerf,?tf&e&moh,vres,?ym,??r&e&gatop,ppepteews,su-xunil-a-si,?gmtrec,vdmac,?s&a&ila&nyd,snd,?nymsd,?b&alfmw,bevres,?dylimaf,eirfotatophcuoc,gulku,j,koob-daer,ltbup,nd&-won,deerf,emoh,golb,kcud,mood,nyd:.&emoh,og,?,ps,rvd,tog,uolc,?s&a-skcik,ndd,?tnemhcattaomb,u,?t&ce&jorparodef.&duolc,gts.so.ppa,so.ppa,?riderbew,?e&ews-yrev-si,nretni&ehtfodne,fodne,??hgink-a-si,igude,oi-allizom,s&ixetn&od,seod,?o&h-emag,l-si,?rifyam,??ue:.&a&-q,c,?cm,dc,e&b,d,e,i,m,s,?g&b,n,?hc,i&f,s,?k&d,m,s,u,?l&a,i,n,p,?n&c,i,?o&n,r,ssa,?pj,r&f,g,h,k,t,?s&e,i:rap,,u,?t&a,en,i,l,m,ni,p,?u&a,de,h,l,r,?vl,y&c,m,?z&c,n,??,vresnyd,x&inuemoh,unilemoh,?y&limafxut,srab,???ub&mah?oj???s!.&gro?moc?rep?t&en?opsgolb,?ude?vog??gb639j43us5--nx??t?u!.&c&a?s??en?gro?mo&c?n,?o&c?g??ro?topsgolb,??v!.mon,a1c--nx??wsa08--nx??h&0ee5a3ld2ckx--nx?4wc3o--nx!.&a&2xyc3o--nx?3j0hc3m--nx?ve4b3c0oc21--nx??id1kzuc3h--nx?l8bxi8ifc21--nx?rb0ef1c21--nx???8&8yvfe--nx?a7maabgm--nx??b!.&gro?moc?ten?ude?vog??mg??c!.&7erauqs,amil4,duolc-drayknil,gniksnd,ph21,sndtog,topsgolb,xi2,ytic-amil,?aoc?et?ir!euz??r&aes!errecnac??uhc??sob?taw!s???d0sbgp--nx?f&2lpbgm--nx?k??g!.&gro?lim?moc?ude?vog???iesac?m!a1j--nx??ocir?p!.&gro?i?lim?moc?ogn?ten?ude?vog???s!.&g&nabhsah,ro??lim?moc?ten?vog?won,yolpedew,?a&c?nom??i&d?f?ri???t!.&ca?enilno,im?ni?o&c?g??pohs,ro?ten??iaf!.oby,?laeh?orxer?ra&ba?e???vo!.lopdren,?zb??i&3tupk--nx?7a0oi--nx?a!.&ffo?gro?mo&c?n,?ten?uwu,?1p--nx?bud?dnuyh?tnihc??b!.&gro?moc?oc?ro?ude??ahduba?o!m!.&duolcsd,ysrab,???s??c!.&ayb-tropora--nx?ca?d&e?m??esserp?gro?moc?nif,o&c?g?ssa??ro?t&en?ni?roporéa??ude?vuog??cug?t??d&dk?ua??e&bhf--nx?piat??f!.&aw5-nenikkh--nx,dnala?iki,nenikkäh,topsgolb,yd,?onas??g!.&d&om?tl??gro?moc?ude?vog???h&c&atih?ra??s&abodoy?ibustim???juohs?k!.&gro?moc?ofni?ten?ude?vog?zib??b4gc--nx?iw?nisleh?s?uzus??l!.&aac,m&on,yn,?topsgolb,?drahcir?iamsi??maim?n!.&b&ew?og??ca?gro?lim?mo&c?n??ni?o&c?fni??pp?t&en?ni??ude?zib??airpic?i&hgrobmal?m??re??om?rarref?s!.&mon,topsgolb,?ed??t&aresam?i&c?nifni??rahb?tagub??ut?v!.&21k?gro?moc?oc?ten???wik?xa&rp?t??yf??j&6pqgza9iabgm--nx?8da1tabbgl--nx?b!.&ossa?topsgolb,uaerrab?vuog???d?nj?s?t!.&bew?c&a?in??eman?gro?lim?mo&c?n,?o&c?g??t&en?ni?set??ude?vog?zib???yqx94qit--nx??k&8uxp3--nx?924tcf--nx?arfel?c&a&bdeef?lb??ebdnul?ilc?reme?ud??d!.&erots,ger,mrif,oc,topsgolb,zib,?t??e&es?samet??h!.&a&4ya0cu--nx?5wqmg--nx??b3qa0do--nx?cni,d&2&2svcw--nx?3rvcl--nx??5xq55--nx?tl,?g&a0nt--nx?la0do--nx?ro??i&050qmg--nx?7a0oi--nx?xa0km--nx??m&1qtxm--nx?oc?yn,?npqic--nx?t&en?opsgolb,?ude?v&di?og?ta0cu--nx??xva0fz--nx?人&个?個?箇??司公?府政?絡&網?网??織&組?组??织&組?组??络&網?网??育&敎?教???n??i&tsob?vdnas??l!.&bew?c&a?os??dtl?gro?hcs?letoh?moc?nssa?ogn?prg?t&en?ni??ude?vog??at?cd?is??m!.&eman?fni?gro?mo&c?n,?t&en?opsgolb,?ude?vog???n&ab!cfdh?etats?mmoc?t&en?fos??u??i!.gn,l!.&noyc,pepym,??p???oob?p!.&b&ew?og??gro?kog?m&af?oc??nog?ofni?pog?sog?ten?ude?vog?zib???row!.&fo,ot,?ten!.&htumiza,o&c,vra,??doof???s!.&myn,topsgolb,??t?u!.&c&a?lp??d&om?tl??e&cilop?m??gro!.&gul:g,,sgul,??oc!.&e&lddiwg,n&ilnoysrab,ozgniebllew,??krametyb.&hd,mv,?pi-on,topsgolb,vres-hn,ysrab,??shn?ten?vog!.eci&ffoemoh,vres,??ysrab,???l&04sr4w--nx?a!.&gro?lim?mo&c?n,?t&en?opsgolb,?ude?vog??bolg?c?ed?g!el??i&c&nanif!.oc,lpl??os??romem?tnedurp??n&if?oitanretni??t&i&gid!.sppaduolc:.nodnol,,?p&ac?soh???ned?ot??utum!nretsewhtron???c!.&bog?lim?mon,oc?topsgolb,vog???dil?e&datic?n&ahc?nahc!gnikooc?levart?rehtaew???t!ni?ria?tam??vart??f&8f&pbgo--nx?tbgm--nx??a?n??g!.&gro?mo&c?n,?oc?ten?ude?zib,??h&d?op??i!.&21k?ca?fdi?gro?inum?oc!.topsgolb,?ten?vog??a&f?m&e?g?toh???m?r?xil??l&a&b&esab?t&eksab?oof!.fo,???c?mt??e&d?hs??ihmailliw?j??m!.&esserp?gro?moc?ten?ude?v&og?uog????n!.&n&iemodleeutriv,o&med,rtsic,??oc,retsulc-gnitsoh,topsgolb,yalphk,?o??o&a?btuf?l?o&c!.ed,?hcs!.gn,??rit?u??p!.&a&cin&diws?gel??d&g,ortso?urawon??i&dem?mraw?nydg,?k&elo&guld?rtso??slopolam?tsu?ytsyrut??l&ip?o&kzs?w&-awolats?oksnok????n&img?zcel,?rog&-ai&bab?nelej??j?z??syn?tsaim?w&a&l&eib?i?o??zsraw??o&namil?tainop,??z&eiwolaib?mol???c&e&iw&alselob?o&nsos?rtso???le&im?zrogz???orw,p??d&em,ia?ragrats??e&c&i&lrog?w&ilg,o&hc&arats?orp??klop?tak????yzreibok??i&csjuoniws?ksromop?saldop??l&ahdop?opo??napokaz,tatselaer?z&romop?swozam???g&alble?ezrbo&lok?nrat??ro??hcyzrblaw?i&csomohcurein?grat?klawus??k&e&rut?walcolw??in&byr?diws,sark,?le?o&nas?tsylaib??rob&el?lam??s&als?jazel?nadg,puls?rowezrp???l&colw?e&r?vart??i&am?m???m&o&c?dar?n?tyb??s&g?iruot??t!a???n&a&gaz?nzop,?i&bul?cezczs?lbul,molow?nok?zd&eb?obeiws???uleiw?y&tzslo?z&rtek?seic????o&c,fni?k&celo?zdolk??lkan?n&leim?pek?t&uk?yzczs??z&copo?eing?rowaj???rga?tua?w&ejarg?ogarm???p&e&eb,lks??klwwortso?ohs??romophcaz?sos?t&aiwop?en?opos,ra,sezc??ude?v&irp?og!.&a&p?s!w???bni&p?w??ci?dtiw?essp?fiw?g&imu?u??hiiw?m&igu?rio?u!o???nds?o&ks?p!pu??s?wtsorats??p&a?sp!mk?pk?wk??u&m?p??wk?z??r&ksw?s??s&i?oiw?u?zu??talusnok?w&gzr?i&p?rg?w??m?opu?u!imzw???zouw????w&a&l&corw?sizdow??w??o&golg?k&ark,ul?zsurp??r&az?gew??t&rabul,sugua??z&coks?sezr????xes?y&buzsak?d&azczseib?ikseb??hcyt?n&jes?lod-zreimizak??pal?r&ogt?uzam??walup?zutrak??z&am-awar?c&aprak?iwol?zsogdyb??dalezc?ib?s&i&lak?p??uklo????l??r&as?f?s??s!.&gro?moc?ten?ude?vog???t!.vog??ubnatsi?x3b689qq6--nx?yc5rb54--nx??m&00tsb3--nx?1qtxm--nx?981rvj--nx?a!.&enummoc?gro?moc?oc?t&en?opsgolb,??c!bew??dretsma?e&rts?t??fma?rirhs?xq--nx??b!.&gro?moc?ten?ude?vog??i??c!.&moc?oc?ten?vog???d!.&gro?moc?ten?ude?vog???f!i??g!vu96d8syzf--nx??h?i!.&ca?gro?mo&c?n,?o&c!.&clp?dtl???r,?t&en?t??vt??k?rbg4--nx??k!.&drp?e&rianiretev?sserp??gro?lim?m&o&c?n??t??nicedem?ossa?pooc?s&eriaton?neicamrahp?sa??ude?v&og?uog????l&if?ohkcots??o!.&dem?gro?m&oc?uesum??o&c?rp??ten?ude?vog??b?c!.&2aq,3pmevres,a&c&-morf,irfa,?g&-morf,oy-sehcaet,?i-morf,m&-morf,all&-a-si,amai,??p&-morf,c-a-si,?remacytirucesym,s,tadtsudgniht,v-morf,w-morf,z,?b&dnevarym,ew&-sndnyd,ottad,?g,ildts.ipa,uhnnylf,?c&amytirucesemoh,d-morf,esyrcs,n&-morf,vym,?p&kroweht,ytirucesemoh,?q,rievres,s-morf,?d&aerotffuts,e&calpb,ifitrec-&si,ton-si,?llortnocduolc,?i-morf,m-morf,n&-morf,abeht-htiw-si,?s-morf,uolc&hr,panqym:-&ahpla,ved,?,smetsystuo,ved&j,pw,??wetomer,?e&butuoyhtiw,ciffo-sndnyd,d:-morf,o&celgoog,n&il.&recnalabedon,srebmem,?neve.&1-&su,ue,?2-&su,ue,?3-&su,ue,?4-&su,ue,????,erf&-sndnyd,sndd,?filflahevres,gnahcxeevres,i&hcet-a-si,p-sekil,?k&auqevres,irtsretnuocevres,?l&bitpa-no,googhtiw,?m&agevres,ina-otni-si,oh-&sndnyd,ta-sndnyd,??n&-morf,ilnoysrab,og-si,?r&alfduolcyrt,ihcec,uzanoppanex,?srun-a-si,t&i&nuarepo,s&-ybboh,aloy,tipohs,??omer-sndnyd,ysgolb,?v&als-elcibuc-a-si,i&lsndd,tavresnoc-a-si,??z&amkcar,eelg,iig,??fehc-a-si,g&ni&gats-swennwot,ksndd,robsikrow,?o&fgp,lb&-sndnyd,sihtsetirw,???h&n-morf,o-morf,?i&fiwehtno,h-morf,kiw-sndnyd,m-morf,r-morf,w-morf,z&ihcppa,nilppa,??jn-morf,k&a&-morf,erfocsic,?cils-si,eeg&-a&-si,si,?sndd,?h,latsnaebcitsale:.&1-&htuos-pa,lartnec-&ac,ue,?ts&ae&-&as,su,?ht&ron-pa,uos-pa,??ew-&su,ue,vog-su,???2-ts&ae&-su,ht&ron-pa,uos-pa,??ew-&su,ue,??3-ts&aehtron-pa,ew-ue,??,o-morf,row&-&sndnyd,ta-sndnyd,?ten-orehkcats,?u,?l&a&-morf,colottad,rebil-a-si,?f-morf,i&-morf,am-sndnyd,?l&ecelffaw,uf-ytnuob:.a&hpla,teb,?,?ru-&elpmis,taen,?ssukoreh,?m&n-morf,pml.ppa,rofererac-htlaeh,sacrasevres,uirarret-yltsaf,?n&a&cilbuper-a-si,f&-sllub-a-si,racsan-a-si,?i&cisum-a-si,ratrebil-a-si,??c,eerg-a-si,i-morf,m-morf,o&ehtnaptog,isam-al-a-tse,ollabtib,rtap-el-tse,s&iam-al-a-tse,replausunu,??pj,t-morf,?o&bordym,c,jodsnd,m-morf,n:iloxip,,ttadym,?p&2pevres,aelutym,i&-sndnyd,fles,ogol,ruoy&esol,hctid,?ymteg,?pa&anis:piv,,esaberif,k1,lortnocduolc,oifilauq,roetem:.ue,,tnorfegap,ukoreh,?t&fevres,thevres,??r&a:-morf,tskcor-a-si,,b,e&d&ivorpnwo,ner&.ppa,no,??e&bevres,nigne-na-si,?ggolb-a-si,h&caet-a-si,pargotohp-a-si,?krow-drah-a-si,n&gised-a-si,ia&rtlanosrep-a-si,tretne-na-si,??p&acsdnal-a-si,eekkoob-a-si,?retac-a-si,tn&ecysrab,iap-a-si,uh-a-si,?vres&-s&ndnyd,pvtsaf,?inim,nmad,?y&alp-a-si,wal-a-si,?zilibomdeepsegap,?g,k,mgrp.nex,o&-morf,sivdalaicnanif-a-si,t&c&a-na-si,od-a-si,?susaym,??p-morf,u&as-o-nyd,eugolb-nom-tse,omuhevres,??s&a&ila&nyd,snd,?nymsd,?bbevres,ci&p&-sndnyd,evres,?tcatytiruces,?dylimaf,e&cived-anelab,itilitu3,lahw-eht-sevas,mag-otni-si,tyskciuq,?i&ht2tniop,paelgoog,?k&-morf,aerf-ten,colbpohsym,?m&-morf,cxolb,?n&d&-pmet,dyard,golb,mood,tog,?nyd,ootrac-otni-si,?o&-xobeerf,xobeerf,?r&ac-otni-si,etsohmaerd,?s&e&l-rof-slles,rtca-na-si,?ibodym,?u,wanozama.&1-&htuos-pa&-3s,.&3s,etisbew-3s,kcatslaud.3s,??la&nretxe-3s,rtnec-&ac&-3s,.&3s,etisbew-3s,kcatslaud.3s,??ue&-3s,.&3s,etisbew-3s,kcatslaud.3s,????ts&ae&-&as&-&3s,etisbew-3s,?.kcatslaud.3s,?su:-etisbew-3s,.kcatslaud.3s,,?ht&ron-pa&-&3s,etisbew-3s,?.kcatslaud.3s,?uos-pa&-&3s,etisbew-3s,?.kcatslaud.3s,???ew-&su-&3s,etisbew-3s,?ue&-&3s,etisbew-3s,?.kcatslaud.3s,?vog-su-&3s,spif-3s,????2-ts&ae&-su&-3s,.&3s,etisbew-3s,kcatslaud.3s,??ht&ron-pa&-3s,.&3s,etisbew-3s,kcatslaud.3s,??uos-pa&-&3s,etisbew-3s,?.kcatslaud.3s,???ew-&su-&3s,etisbew-3s,?ue&-3s,.&3s,etisbew-3s,kcatslaud.3s,????3&-tsew-ue&-3s,.&3s,etisbew-3s,kcatslaud.3s,??s,???t&arcomed-a-si,c-morf,eel&-si,rebu-si,?m-morf,n&atnuocca-na-si,e&duts-a-si,r-ot-ecaps,tnocresu&buhtig,pl,???ops&edoc,golb,ppa,?s&i&hcrana-&a-si,na-si,?laicos-a-si,pareht-a-si,tra-na-si,xetn&od,seod,??oh&piym,sfn,??u&-morf,nyekcoh-asi,?v-morf,?u&-rof-slles,4,e,h,oynahtretramssi,r:ug-a-si,,?v&n-morf,w-morf,?w&ozok,ww100,?x&bsbf.sppa,em,inuemoh,obaniateb,t-morf,unilemoh,?y&a&bnx:.&2u,lacol-2u,?,lerottad,wetag-llawerif,?dnacsekil,filten,k&-morf,niksisnd,?rotceridevitcaym,u:goo,,w-morf,x&alagkeeg,orphsilbup,???inu??m!.&dna,rof,??or?tsla??p!.nwo,?raf!.jrots,etats??s?t!.&gro?lim?mo&c?n??oc?ten?ude?vog???u&esum!.&a&92chg-seacinumocelet-e-soierroc--nx?atnav?c&i&aduj?rfatsae??rollam??d&anac?enomaledasac?irolf??e&raaihpledalihp?srednu??g&hannavas?oonattahc??hamo?i&auhsu?bmuloc!hsitirb??dem?groeg?hpledalihp?l&artsua?etalif??n&igriv?rofilac??ssur?tsonod??ksa&la?rben??l&lojal?q-snl--nx?uossim!trof???m&a&bala?nap??enic?o&m?r???n&a&cirema?idni??edasap?ilorachtuos?olecrab??r&abrabatnas?ezzivs??su?t&nalta?osennim??zalp??c&dnotgnihsaw?ebeuq?i&depolcycne?ficap?hpargonaeco?lbup?sum?t&carporihc?lec?naltadim??vu??yn??d&a&dhgab?etsmraf?m?orliar??i&rdam?ulegnedleeb??leif?n&a!l&gne?nif?ragyduj?t&ocs?rop??yram???u&brofsdgybmeh?osdnaegami???r&augria?ofxo???e&c&a&l&ap?phtrib??ps??n&a&lubma?tsiser??e&fedlatsaoc?gilletni?ics!foyrotsih????pein?rof??d&nukneklov?revasem??e&rt?tsurt??f&atnas?ildliw??g&a&lliv?tireh!lanoitan???dirbmac?rog??i&cnum?nollaw??koorbrehs?l&ab?bib?cycrotom?i&ssim?txet??oks?tsac??m&affollah?it!iram??utsoc??n&golos?ilno?recul??r&a&uqs?waled!foetats???i&hs&acnal?kroy?pmahwen??otsih??omitlab?ut&an?cetihcra?inruf?luc!irga?su???vuol??s&abatad?iacnarf?sius?uoh!lum???t&a&locohc?rak?ts!e!yrtnuoc!su?????imesoy?tevroc??u&qihpargonaeco?velleb??vit&caretni?omotua???f&iuj?ohgrub??g&n&i&dliub?ginerevmuesum?kiv?lahw?nim?peekemit?vil??ulmmastsnuk??orf?r&ebnrats?u&b&ierf?le?m&ah?uan??ram?s&mailliw!lainoloc??naitsirhc?retepts??zlas??ob&irf?mexul?????h&atu?c&raeser?sirotsih?uot??g&ea1h--nx?rubsttip??si&tirb?wej??t&laeh?ro&n?wtrof??uo&mnom?y????i&d6glbhbd9--nx?iawah?k&nisleh?s??lad!rodavlas??sissa?tannicnic??k&c&nivleeg?olc!-dna-hctaw?dnahctaw???fj?inebis?l&is?ofron??na&rfenna?t??oorbnarc?r&am&ned?reiets??oy!wen????l&a&ci&dem?golo&eahcra?meg?oz??natob?rotsih??ertnom?iromem?noita&cude?n??oc?rutluc?trop?utriv?van??e&nurb?s&ab?surb??utriv??i&artnogero?sarb??l&a&besab?hsnoegrus??e&hs?rdnevle??i&b?m!dniw????o&bup?ohcs?tsirb???m&a&dretsma?ets?h&netlehc?rud???ct?elas!urej??l&if?ohkcots?u??raf?silanruoj?u&esumyrotsihlarutan?ira&tenalp?uqa??terobra???n&a&c!irema!evitan???gihcim?i&dni?tpyge??mfoelsi?wehctaksas??e&d&alokohcs?ews?rag!cinatob?lacinatob?s&nerdlihc?u????gahnepoc?hcneum?laftsew?ppahcsnetewruutan?r&dlihc?ednaalv?hu!dnutamieh???sseig??gised!dn&atra?utsnuk???h&ab!nesie??ojts??i&lreb?tsua??l&eok?ocnil??n&ob?urbneohcs??o&dnol?gero?i&s&iv&dnadnuos?elet??nam??t&a&c&inummoc?ude!tra???dnuof?erc?i&cossa?va??kinummokelet?nissassa?r&belectsevrah?oproc?tsulli??silivic?t&nalp?s??vres&erp?noclatnemnorivne??zilivic??c&elloc?if-ecneics??ibihxe???ri?s&dnah?imaj?reffej?sral??t&erbepac?nilc?sob???r&e&b?dom?tsew?uab?zul??obredap??vahnebeok?wot??o&2a6v-seacinumoc--nx?ablib?c&edtra?ixemwen?sicnarfnas??elap?g&a&cihc?to??eidnas??i&cadnuf?diserp?ratno??llecitnom?mitiram?nirot?r&htna?ienajedoir???pohskrow?qari?r&aw!dloc?livic??dd?e&b&ma?yc??irrac?llimsiwel?naksiznarf?papswen?t&aeht?exe?nec!ecneics?larutluc?muesum?tra??s&ehc&nam?or??neum??upmoc???ia!nepo??obal?u&asonid?obal?takirak???s&a&l&g?l&ad?eh???xet??di&k?pardnarg??e&cneics!larutan??dnal?hcsi&deuj?rotsih!nizidem?rutan??selhcs??itinamuh?l&aw?egnasol?l&e&rutansecneics?xurb??iasrev???r&e&em?ugif??tsac??suohcirotsih?u&en?q&adac?itna!nacirema?su????õçacinumoc!elet-e-soierroc???gnirpsmlap?htab?i&lopanaidni?rap?uoltnias?xa??l&essurb?lod??mraeriflanoitan?n&a&blats?l??erdlihc?oi&snam?tacinummoc!elet-dna-stsop???äl??re&dnalf?lttes?mraf?nim?tnececneics??s&alg?erp??t&farc!dnastra??nalp?olip?ra!e&nif?vitaroced!su???su?xuaeb???u&b!muloc??cric???t&agilltrop?cejorp?dats?e&esum?kramnaidni??iorted?ne&m&elttes?norivne?piuqemraf??vnoc??oped?r&a!drib?enif?gttuts?hsiwej?kcor?n&acirema?ootrac??tamsa?yraropmetnoc??op&aes?snart?wen??ufknarf??s&a&cdaorb?octsae??ewhtuos?ilayol?nuk?r&ohnemled?uhlyram??urt???u&a&bgreb?etalpodaroloc??rmyc??w&ocsom?rn??x&esse?ineohp?nam?tas??y&a&bekaepasehc?w&etag?liar???camrahp?doc?e&hsub?l&ekreb?l&av!eniwydnarb??ort???n&dys?om??rrus?s&nreug?rejwen???golo&e&ahcra?g??motne?nh&cet?te??oz?po&rhtna?t??roh??hpargotohp?l&etalihp?imaf??m&edaca?onortsa??n&atob?yn??ps?r&a&ropmetnoc?tilim??e&diorbme?llag!tra??vocsid??lewej?nosameerf?otsih!dnaecneics?ecneics?gnivil!su??la&col?rutan??retupmoc?su??tsudnidnaecneics??spelipe?t&eicos!lacirotsih??i&nummoc?srevinu??nuoc???z&arg?iewhcs?nil?ojadab?urcatnas??моки?םילשורי???rof??z!.&ca?gro?hcs?lim?moc?o&c?fni??ten?ude?vog?zib????n&315rmi--nx?a&brud?cilbuper?f?grompj?hkaga?idraug?m!raw??ol?ssin?u&hix?qna??varac?yalo??b!.&gro?moc?oc,ten?ude?vog??c??c!.&ah?bh?c&a?s??d&5xq55--nx?g?s?uolctnatsni,?eh?g&la0do--nx?ro??h&a?q?s??i&7a0oi--nx?h??j&b?f?t?x?z??kh?l&h?im?j??m&n?oc!.swanozama.&1-htron-nc.3s,be.1-&htron-nc,tsewhtron-nc,????n&h?l?s?y??om?qc?s&g?j??ten?ude?vog?wt?x&g?j?n?s??z&g?x??司公?絡網?络网??b??d&g!.ypnc,?ka??e&drag?erg?fuak?gawsklov?hctik?i&libommi?w??m!.rof,?po?r!ednaalv??sier?ves??g!.&ca?gro?moc?ten?ude?vog??is&ed!.ssb,?irev???h!.&bog?gro?lim?mo&c?n,?ten?ude???i!.&c&a?in??dni?gro?lim?mrif?neg?oc?s&er?nduolc,?t&en?opsgolb,?ude?vog?ysrab,?elknivlac?griv?ks?lreb?p!ul??v?w?x??k!.&gro?ten?ude?vog???l&eok?ocnil??m!.&cyn,gro?myn,ude?vog???o&dnol!.&fo,ni,??i&hsaf!.&fo,no,??n&o?utiderc??siv!orue??t&a&cude!.oc,?dnuof?tsyalp??c&etorp?u&a?rtsnoc?????kin?las?mrom?nac?p&q?uoc??s&ia&il?m??nhojcs?pe?scire??t&ron?sob???p!.&gro?oc?ten?ude?vog??k??r&e&c?yab??op??s!.&gro?moc?osrep?t&opsgolb,ra??ude?v&inu?uog????t!.&dni?esnefed?gro?ltni?m&oc!nim??siruot??n&erut?if??o&fni?srep??sn&e?r??t&an?en!irga?ude??rnr??unr?vog??m??u&f?r!.&bdnevar,lper,sh,tnempoleved,??stad?xamay?y??v!.&ca?eman?gro?htlaeh?moc?o&fni?rp??t&en?ni?opsgolb,?ude?vog?zib???wo&rc?t!epac????o&76i4orfy--nx?a!.&bp?de?go?oc?ti?vg??boat??b!.&a&ci&sum?tilop??i&c&arcomed?neic??golo&ce?ncet??m&edaca?onoce??rt&ap?sudni??vilob??n&egidni?icidem??serpme?tsiver?vitarepooc??b&ew?og??dulas?e&rbmon?tr&a?op&ed?snart????g&olb?ro??ikiw?l&a&noi&canirulp?seforp??rutan??im??moc?o&fni?lbeup?rga?tneimivom??saiciton?t&askt?en?ni??ude?vt??h?iew?olg??c!.&bew?cer?dr&c,rac,?gro?ipym,l&im?per,?m&o&c!.topsgolb,?n??rif?udon,?ofni?s&egap&dael,l,?tra??t&4n,en?ni??ude?vog??a?e!vi??in?mara?s&edarb?ic???d!.&b&ew?og??dls?gro?lim?moc?t&en?ra??ude?vog??agoba?if?zd7acbgm--nx??e&c?d&iv?or??morafla??f!ni!.&e&g&delwonk-fo-l&errab,lerrab,?ellocevoli,?ht-skorg,rom-rof-ereh,tadpusn,?llatiswonk,macrvd,ofni-v,p&i&-on,fles,?ohbew,?ruo-rof,s&iht-skorg,nd&-cimanyd,nyd,uolc,??tsrifyam,ysrab,zmurof,???g&el?ia?n!am?ib???hwsohw?i!.&35nyd,8302,a&minifed,tad-b,?b&altig,uhtig,?c&inone:.remotsuc,,zh,?d&in,u&olc&iaznab.ppa,noitacilppa,ropav,?rd,??e&civedniser,egipa,nilnigol,sufxob,t&isnoehtnap,newtu,??gnigatsniser.secived,k&orgn,ramytefasresworb,?m&oc?udon,?nyded,p&opilol,pa&-arusah,cs,enalpkcab,??r&e&niatnoceruza,vres&cisab,lautriv,??ial.sppa,?s&codehtdaer,nemeis-om,pparevelc,tacdnas,?t&enotorp,i&belet,detfihs,kecaps,?raedon.egats,sudgniht.&cersid.tsuc,dorp.tsuc,gnitset.tsuc,ved.tsuc,??vgib.0ku,y&olpedew,srab,??b?d&ar?u&a?ts???j?r?syhp??j!.&eman?gro?hcs?lim?moc?ten?ude?vog???ll&ag?o??m!.&gro?moc?ten?ude?vog??g?il?mi?orp??n!.&a&0&b-ekhgnark--nx?c-iehsrgev--nx?g-lksedlig--nx?k-negnanvk--nx??1&p-nedragy--nx?q-&asierrs--nx?grebsnt--nx?lado-rs--nx?n&egnidl--nx?orf-rs--nx??regnayh--nx?ssofenh--nx??r-datsgrt--nx?s-ladrjts--nx?v-y&senner--nx?vrejks--nx???3g-datsobegh--nx?4&5-&dnaleprj--nx?goksnerl--nx?tednalyh--nx??6-neladnjm--nx?s-&antouvachb--nx?impouvtalm--nx??y-&agrjnevvad--nx?ikhvlaraeb--nx???7k-antouvacchb--nx?8&k-rekie-erv--nx?l-ladrua-rs--nx?m-darehsdrk--nx??a!.sg??bct-eimeuvejsemn--nx?d&do?iisevvad?lov?narts?uas??f&1-&l--nx?s--nx??2-h--nx??g&10aq0-ineve--nx?av?ev?lot?r&ajn&evvad?u??ájn&evvad?u????h?iz-lf--nx?j&ddadab?sel??k&el?hoj&sarak?šárák??iiv&ag&na&el?g??ŋ&ael?ág???ran???l&f?lahrevo?o&ms?s??sennev?t-&ilm--nx?tom--nx??u&-edr--nx?s??øms??muar?n&0-tsr--nx?2-dob--nx?5-&asir--nx?tals--nx??a&r!-i-om?f?t??t??douvsatvid?kiv?m&os?øs??n&od?ød??ra?sen?t&aouvatheig?ouv&a&c&ch&ab?áb??h&ab?áb???n??i&ag?ág??sa&mo?ttvid??án???z-rey--nx?ær&f?t???o&p-&ladr--nx?sens--nx??q-nagv--nx?r-asns--nx?s-kjks--nx?v-murb--nx?w-&anr&f--nx?t--nx??ublk--nx???ppol?q&0-t&baol--nx?soum--nx?veib--nx??x-&ipphl--nx?r&embh--nx?imph--nx???y-tinks--nx??r&f-atsr--nx?g-&an&ms--nx?nd--nx??e&drf--nx?ngs--nx??murs--nx?netl--nx?olmb--nx?sorr--nx??h-&a&lms--nx?yrf--nx??emjt--nx??i&-&lboh--nx?rsir--nx?y&d&ar--nx?na--nx??ksa--nx?lem--nx?r&ul--nx?yd--nx????stu??j-&drav--nx?rolf--nx?sdav--nx??kua?l-&drojf--nx?lares--nx??m-tlohr--nx?n-esans--nx?olf?p-sdnil--nx?s-ladrl--nx?tih?v-rvsyt--nx??s&a&ns?ons??i&ar?er&dron?r&os?øs???ár??la&g?h??mor!t??sir?uf?åns??t&koulo&nka?ŋká??la?p-raddjb--nx?r-agrjnu--nx?s&aefr&ammah?ámmáh??orf?r&o?ø???u-vreiks--nx??u&h-dnusel--nx?i-&drojfk--nx?vleslm--nx??j-ekerom--nx?k-rekrem--nx?u-&dnalr--nx?goksr--nx?sensk--nx??v-nekyr--nx?w-&k&abrd--nx?ivjg--nx??oryso--nx??y-y&dnas--nx?mrak--nx?n&art--nx?nif--nx??reva--nx??z-smort--nx??v!.sg?ledatskork?reiks??wh-antouvn--nx?x&9-dlofts--nx.aoq-relv--nx?d-nmaherk--nx?f-dnalnks--nx?h-neltloh--nx?i-drgeppo--nx?j-gve&gnal--nx?lreb--nx??m-negnilr--nx?n-drojfvk--nx??y&7-ujdaehal--nx?8-antouvig--nx?b-&dlofrs--nx?goksmr--nx?kivryr--nx?retslj--nx??e-nejsom--nx?f-y&krajb--nx?re&dni--nx?tso--nx??stivk--nx??g-regark--nx?orf?ørf??z9-drojfstb--nx??b&25-akiivagael--nx?53ay7-olousech--nx?a&iy-gv--nx?le-tl&b--nx?s--nx??n0-ydr--nx??c&0-dnal-erdns--nx?z-netot-erts--nx??g&g-regnarav-rs--nx?o-nejssendnas--nx??ju-erdils-ertsy--nx?nj-dnalh-goksrua--nx?q&q-ladsmor-go-erm--nx.&ari-yreh--nx?ednas??s-neslahsladrjts--nx???ca&4s-atsaefrmmh--nx?8m-dnusynnrb--nx?il-tl--nx?le-slg--nx?n5-rdib--nx?op-drgl--nx?uw-ynnrb--nx??d&a&qx-tggrv--nx?reh!nnivk?sd&ork?ørk??uas??ts&e&bi?kkar?llyh?nnan??g&ort?ørt??k&alf?irderf??levev?mirg?obeg&ah?æh??r&ah?ejg????barm-jdddb--nx?ie!rah?s&etivk?ladman???lof&r&os?øs??ts&ev.ednas?o.relav?ø.relåv???n&a&l&-erd&n&os?øs??ron??adroh.so?dron.&a&g5-b--nx?ri-yreh--nx??ob?y&oreh?øreh??øb??e&m!lejh??pr&oj?øj??vi??gyb?n&aks?åks??o&h-goksrua?rf??r&o?ua?ø??tros?øh-goksrua??rts!e&devt?lab?mloh???s&ellil?naitsirk?rof???u&l!os??s!d&im?lejt??e&guah?l&a?å???kkoh?lavk?naitsirk?r&af?eg&e?ie???tef?y&onnorb?ønnørb?????r&a&blavs!.sg??g&eppo?la???o&j&f&a!dniv?k?vk??die?e&dnas?kkelf??llins?r&iel?ots??s&lab?t&ab?åb??yt??å!k??ævk??les??ts??åg&eppo?lå???ureksub.sen??e&ayb-yrettn--nx?d&ar?lom?r&of?øf??år??g&gyr?nats??i&meuv&ejsem&aan?åån??sekaal??rjea??j&d&ef?oks??les??k&er&aom?åom??hgna&ark?årk??iregnir?kot!s??s&ig?uaf???l&bmab?kyb?l&av?ehtats??oh??m&it?ojt?øjt??n&arg?g&os?øs??meh?reil?te?ummok?yrb??r&dils-erts&ev?y&o?ø???ua?vod??sa&ans?åns??t&robraa?spaav??urg??f&62ats-ugsrop--nx?a&10-ujvrekkhr--nx?7k-tajjrv-attm--nx??o!.sg?h??s!.sg??v!.sg???g&5aly-yr&n--nx?v--nx??a&llor?ve&gnal?lreb???n&av!snellu??org??oks&die?m&or?ør??ner&ol?øl??r&o?ø???r&eb!adnar?edyps?s&die?elf?gnok?n&ot?øt????obspras??uahatsla?åve&gnal?lreb???h&0alu-ysm--nx?7&4ay8-akiivagg--nx?5ay7-atkoulok--nx??a!.sg???i&e&hsr&agev?ågev??rf??k&h&avlaraeb?ávlaraeb??s??lm&a?å??mpouvtal&am?ám??pph&al?ál??rrounaddleid?ssaneve?ššáneve??j&0aoq-ysgv--nx?94bawh-akhojrk--nx??k&a&b&ord?ørd??jks?lleis??iv!aklejps?l&am?evs?u??mag?nel?ojg?r&a&l?n??epok?iel?y&or?ør???s&ah?kel?om??øjg??kabene?ojsarak?ram&deh.&aoq-relv--nx?rel&av?åv??so??e&let.&ag5-b--nx?ob?øb??ra???åjks??l&a!d&anrus?d&numurb?ron??e&gnard?nte?s&meh?sin??ttin??g&is?nyl??kro?l&em?l&ejfttah?of??u&ag-ertdim?s???n&am?era?gos?i&b?nroh?r??kos?nus?oj??o-&dron?r&os?øs???ppo?r&a!l?nram??e&gne?l?v??is?o&jts?ts??u&a-&dron?r&os?øs???h??å?æl?øjts??s&e&jg?nivk?ryf??kav?mor-go-er&om.&ednas?yoreh??øm.&ednas?yøreh???uag??t&las?rajh?suan??v&l&a?e-rots??u-go-eron??yt??ksedlig?res&a?å???bib&eklof?seklyf??es!dah??h!.sg??i&m?syrt??l&ejf?ov&etsua?gnit?ksa?sdie???n!.sg??o!.sg?boh?g?h??r!.sg??å!ksedlig??øboh??m&a&rah?vk??f!.sg??h!.sg??i&e&h&dnort?rtsua?ssej??rkrejb??ksa??ol?t!.sg??u&dom?esum?r&ab?drejg?evle?os?uh?æb?øs??ttals???n&a&g&av?okssman?åv??jlis?or?r&g?rev???e&d&do&sen?ton??lah?r&agy&o?ø??ojfsam???g&iets?n&a&l&as?lab??n&avk?ævk??t&arg?ddosen??v&al?essov???i&d&ol?øl??l&ar?ær???yl??reb??iks?k&srot?y&or?ør???l&a&d&gnos?n&er?ojm?øjm??om??tloh??ug?åtloh??mmard?ojs&om?sendnas??ppolg?s&lahsladr&ojts?øjts??o??t&o&l?t-erts&ev?o?ø???roh?øl??vly&kkys?nav??yam-naj!.sg??øjs&om?sendnas???g&orf?ujb??i&dnaort?vnarg??kob?ladendua?maherk&a?å??n&it?urgsrop??orf-&dron?r&os?øs???r&aieb?evats??sfev?uaks?yrts??o&6axi-ygvtsev--nx?c,d&ob?rav??ievs?kssouf?l&m&ob?øb??ous&adna?ech&ac?áč???so!.sg???msdeks?niekotuak?r&egark?olf?y&oso?øso???s&dav?mort???p&ed?p&akdron?elk???r&a&d&dj&ab?áb??iab??jtif?luag?mah?vsyt??e&gn&a&k&iel?ro??merb?n&at?mas??rav-r&os?øs??srop?talf?v&ats?el??y&oh?øh???ivsgnok??il?jkniets?k&a&nvej?rem?s&gnir?nellu???ie-er&den?v&o?ø???ram?sa?årem??la&jf?vh??m&b&ah?áh??mahellil??nnul?ts&l&oj?øj??ul??y&o?ø???imp&ah?áh??m!.sg??osir?t!.sg??ádiáb?ævsyt?øsir??s&adnil?en&dnas?e&dga?k&ri&b?k??som??ve??me&h?jg??nroh-go-ejve?s&a?ednil?k&o?ø??of?yt?å??tsev??gv?hf?igaval?o&r&or?ør??sman??so&fen&oh?øh??m?v??uh&lem?sreka.sen??å!dnil???t&a&baol?g&aov?grav??jjr&av-attam?áv-attám??l&a&b?s??ás??soum?ts?v&eib?our???e&dnaly&oh?øh??f?s&nyt?rokomsdeks?sen??vtpiks??in&aks?áks??loh&ar?år??n!.sg??o&m&a?å??psgolb,?s!.sg?efremmah?or?ør??terdi?á&baol?ggráv?lá&b?s??soum?veib???u&b!.sg?alk?e&dna?gnir?nner??les?ælk??dra&b?eb??g&nasrop?vi?ŋásrop??j&daehal&a?á??jedub?v&arekkhar?árekkhár???ksiouf?n&diaegadvoug?taed???v&irp?lesl&am?åm???y&b&essen?nart?sebel?tsev??o&d&ar?na!s??or??gavtsev?k&rajb?sa??lem?mrak?n&art?n&if?orb???r&a&mah?n?v??e&dni?t&so?ton??va??ul?yd??s&am?enner?gav?lrak?tivk??vrejks??ø&d&ar?na!s??ør??gåvtsev?k&rajb?sa??lem?mrak?n&art?n&if?ørb???r&e&dni?t&so?tøn??va??ul?yd?æ&n?v???s&enner?gåv?tivk?åm??vrejks???á&slág?tlá?vreiks??å&gåv?h?jddådåb?lf??ø&d&ob?rav??r&egark?olf??s&dav?mort????aki?i&sac?tal??u??o&b?f?g?hay?o?ttat??r!.&cer?erots?gro?m&o&c?n??rif?t?yn,?ofni?pohs,stra?t&n?opsgolb,?www??e&a!.&a&ac?cgd?idem??bulc!orea??ci&ffartria?taborea??e&cn&a&l&lievrus-ria?ubma??netniam?rusni??erefnoc??gnahcxe?mordorea?ni&gne?lria?zagam??rawtfos??gni&d&art?ilg!arap?gnah???l&dnahdnuorg?ledom??noollab?retac?sael?t&lusnoc?uhcarap??vidyks??hcraeser?l&anruoj?euf?icnuoc?ortnoc!-ciffart-ria???n&gised?oi&nu?t&a&cifitrec?ercer?gi&tsevni-tnedicca?van??i&cossa!-regnessap??valivic??redef??cudorp?neverp-tnedicca????ograc?p&ihsnoipmahc?uorg!gnikrow???r&e&dart?enigne?korb?niart?trahc??o&htua?tacude???s&citsigol?e&civres?r??krow?serp!xe??tnega??t&farcr&ia?otor??hgi&erf?l&f?orcim???liubemoh?n&atlusnoc?e&duts?m&esuma?n&iatretne?revog??piuqe????olip?ropria?si&lanruoj?tneics???w&erc?ohs??y&cnegreme?dobper?tefas????rref?z??p!.&a&aa?ca?pc??dem?ecartsnd.icb,gne?r&ab?uj??snduolc,t&acova?cca?hcer??wal?ysrab,???s!.&em?gro?hcs,moc?ten?ude?vog???t!.&gro?lim?moc?sulpnpv,t&cennockciuq.tcerid,en??ude?vog??o&hp?m?v?yk??tol?ua??v&iv?lov??xas?ykot??p&a&ehc?g?m?s??cj?eej?g!.&gro?ibom?moc?ossa?ten?ude???i&r!.nalc,?v?z??j!.&a&3&5xq6f--nx?xqi0ostn--nx??5wtb6--nx?85uwuu--nx?9xtlk--nx?bihc!.&a&bihciakoy?don?ma&him?ye&ragan?tat???r&a&bom?gan?hihci??u&agedos?kas?ustak???s&os?ufomihs??t&amihcay?iran??w&a&g&im&anah?o??omak??kihci?zustum??ihsak??y&agamak?imonihci???e&akas?nagot??i&azni?esohc?h&asa?s&abanuf?ohc???ka&to?zok??musi?orihs?r&akihabihsokoy?o&dim?tak??ukujuk??usihs??nano&hc?yk??o&d&iakustoy?ustam??hsonhot?k&a&rihs?t??iba??nihsaran?sobimanim?tas&arihsimao?imot??uhc?yihcay??u&kujno?s&ayaru?t&imik?tuf???zarasik????g&as!.&a&gas?m&a&tamah?yik??ihsak??rat?t&a&gatik?hatik??ira!ihsin????e&kaira?nimimak??i&akneg?g&aruyk?o??h&c&amo?uo??siorihs??kaznak?modukuf?ra&gonihsoy?mi???nezih?u&k&at?ohuok??s&ot?tarak?????ihs!.&a&kok?m&a&hagan?yirom??ihsakat??rabiam?wagoton??e&miharot?nokih??houyr?i&azaihsin?esok?kustakat?moihsagih??na&mihcahimo?nok??o&hsia?mag?t&asoyot?ok?tir???us&ay?t&asuk?o??????k&aso!.&a&d&awihsik?eki??k&a&noyot?s&akaayahihc?oihsagih???oadat?uziak??m&ayas!akaso??odak??r&a&bustam?wihsak??ediijuf??t&akarih?i&k?us???wag&ayen?odoyihsagih???e&son?tawanojihs??honim?i&akas?h&cugirom?s&ayabadnot?i&a&kat?t??n??oyimusihsagih???k&a&rabi?sim??ustakat??muzi?r&ijat?otamuk???nan&ak?n&ah?es???o&ay?n&a&ganihcawak?simuzi?tak??eba?ikibah?oyot??t&anim?iad?omamihs??uhc??ust&oimuzi?tes????ou&kuf!.&a&d&amay?eos??g&no?ok?usak??hiku?k&awayim?uzii??ma&kan?y&asih?im???rawak?t&a&gon?ka&h?num?t???umo??wa&g&a&kan?nay?t??ias??ko!rih???y&ihsa?usak???e&m&ay?uruk??taruk?us??i&a&nohs?raihcat??goruk?h&cukuf?s&a&gih?hukuy??in???k&a&gako?muzim??iust?o?ustani??m&anim?otihsoynihs?u??r&ogo?ugasas??usu??ne&siek?zu&b?kihc???o&gukihc?h&ak?ot?ukihc??j&ono?ukihc??kayim?nihsukihc?to?uhc??u&fiazad?gnihs?stoyot????zihs!.&a&bmetog?d&amihs?eijuf?ihsoy?omihs??kouzihs?mihsim?ra&biah?honikam??tawi?wa&g&ekak?ukik??kijuf??yimonijuf??i&a&ra?sok??hcamirom?juf?kaz&eamo?ustam??ma&nnak?ta??nukonuzi?orukuf??nohenawak?o&nosus?ti??u&stamamah?z&a&mun?wak??i!ay?i&hs&agih?in??manim??mihs????????m&a&tias!.&a&d&ihsoy?ot?usah??k&a&dih?sa??o&arihs?s???m&a&tias?y&as?o&rom?tah??ustamihsagih???i&hsagurust?jawak??uri??ni?wa&g&e&ko?man??ikot?o??k&ara?i&hsoy?mak???ru?zorokot??y&a&g&amuk?ihsok?otah??kuf??imo??ziin??e&bakusak?ogawak?sogo?ttas?zokoy??i&baraw?h&cugawak?s&oyim?ubustam???iroy?k&ato?ihs?u&k?stawi???m&akoyr?i&hsoy?juf??uziimak???naznar?o&dakas?ihsay?jnoh?n&a&go?nim??imijuf?nah?oy??r&ihsayim?otagan??t&asim!ak??igus?omatik??zak??u&bihcihc!ihsagih??sonuok?ynah????y&ak&aw!.&a&d&ira?notimak??kadih?ma&h&arihs?im??y&a&kaw?tik??oduk???ru&ustakihcan?y??sauy?wa&g&a&dira?zok??orih??konik??yok?zok??e&banat?dawi??i&garustak?jiat?mani??naniak?o&bog?nimik?t&asim?omihs&ah?uk????ugnihs???o!.&a&jos?koasak?m&ay&ako?ust??ihsayah??r&abi?ukawaihsin??wi&aka?nam???e&gakay?kaw??i&gan?h&cu&kasa?otes??sahakat??k&asim?ihsaruk??miin??n&anemuk?ezib??o&hsotas?jnihs?n&amat?imagak??ohs?uhcibik?????ot!.&a&damay?got?koakat?may&etat?ot??nahoj?riat?waki&inakan?reman???eb&ayo?oruk??i&h&asa?ciimak?sahanuf??kuzanu?m&an&i?ot??ih???nezuyn?otnan?u&hcuf?stimukuf?z&imi?ou???????ihs&o&gak!.&a&m&ayuok?ihsogak??si?yonak??e&banawak?n&at&akan?imanim??uka??tomoonihsin??i&adnesamustas?k&azarukam?oih??m&ama?uzi??usuy??nesi?o&knik?os?tomustam??uzimurat???rih!.&a&ka&n?s??m&ayukuf?i&hsorihihsagih?j&ate?imakikaso????r&a&bohs?h&ekat?im???es??tiak?wiad??e&kato?ruk??i&h&ci&akustah?mono?nihs??s&inares?oyim???manimasa?uk??negokikesnij?o&gnoh?namuk??uhcuf????uk&ot!.&a&bihci?mi&hsu&kot?stamok??m??wagakan??egihsustam?i&gum?h&coganas?soyim??kijaw?m&anim?uzia??ukihsihs??nan&a?iak??o&nati?turan????uf!.&a&batuf?m&a&to?y&enak?irok???ihs&im?ukuf??os?uko??r&aboihsatik?uganat??ta&katik?mawak?rih??w&a&g&akus?emas?uy??k&a&mat?rihs?sa??ihsi??nah??ohs???e&gnabuzia?iman?ta&d?tii???i&adnab?enet?hs&agih?iimagak??k&a&wi?zimuzi??ubay??minuk?r&ook?ustamay???nihsiat?o&g&etomo?ihsin?nan?omihs??no!duruf?rih??rihsawani?ta&may?simuzia???u&rahim?stamakawuzia?zia&ihsin?nay???????nug!.&a&bawak?doyihc?k&anna?oi&hsoy?juf?mot???m&ayakat?ustagaihsagih??n&ihsatak?nak??r&ahonagan?nak?o?u&kati?mamat???t&amun?inomihs?o??w&akubihs?iem?ohs???i&hsa&beam?yabetat??kas&akat?esi??m&akanim?uzio??ogamust?rodim??o&jonakan?n&eu?oyikust??tnihs??u&komnan?stasuk?yrik?????ran!.&a&bihsak?d&akatotamay?u!o???guraki?m&ay&atik&imak?omihs??irokotamay??oki??ra&hihsak?n??wa&geson?knet???e&kayim?ozamay?sog?ustim??i&a&rukas?wak??garustak?h&ciomihs?sinawak??jo?ka&mnak?toruk??makawak?nos?r&net?otakat?ugeh???o&d&na?oyo??gnas?jnihs?nihsoy!ihsagih??tomarawat?yrok????t&ag&amay!.&a&dihsio?k&atarihs?ourust??may&a&kan?rum??enak?onimak??rukho?ta&ga&may?nuf??hakat?kas??wa&g&ekas?orumam??ki&hsin?m??z&anabo?enoy?ot???zuy??e&agas?bonamay?dii?nihsagih?o??i&a&gan?nohs??h&asa?sinawak??nugo??o&dnet?jnihs?ynan??ukohak???iin!.&a&ga?k&ium?oagan??munou!imanim??t&a&bihs?giin??ioy??w&a&gioti?kikes?zuy??irak??yijo??e&kustim?mabust??i&aniat?hcamakot?kaz&awihsak?omuzi??m&a&gat?karum??o???n&anust?esog??o&das?ihcot?jnas?k&ihay?oym??mak?naga?ries??u&ories?steoj?????i&ka!.&a&go?k&asok?oimak??t&ago!rihcah??ika!atik???w&aki?oyk???e&mojog?natim?suranihsagih?t&ado?okoy???i&hsoyirom?magatak?naokimak??nesiad?o&hakin?jnoh!iruy??nuzak?rihson?tasi&juf?m??yjnoh??u&kobmes?oppah????o!.&a&dakatognub?m&asah?ihsemih??su?t&ekat?i&h?o????e&onokok?ustimak??i&jih?k&asinuk?ias?usu??mukust??onoognub?u&fuy?juk?ppeb?suk??????wa&ga&k!.&a&mihsoan?rihotok?waga&kihsagih?ya???emaguram?i&j&nonak?ustnez??kunas?monihcu??o&hsonot?nnam?yotim??u&st&amakat?odat??zatu????nak!.&a&dustam?kus&okoy?tarih??maz?nibe?r&a&gihsaimanim?h&esi?imagas??wa&do?guy???u&im?kamak???tikamay?wa&k&ia?oyik?umas??sijuf??yimonin??e&nokah?saya??i&akan?esiak?gusta?hsuz?kasagihc?o?ukust??o&nadah?sio?tamay?????kihsi!.&a&danihcu?gak?kihs?mijaw?t&abust?ikawak??wazanak??i&gurust?hcionon?mon?ukah??nasukah?o&anan?ton!akan???u&kohak?stamok?z&imana?us?????niko!.&a&han?m&arat?ijemuk?uru??n&e&dak?zi??no??ra&hihsin?rih??wa&kihsi?niko??yehi?zonig??e&osaru?seay??i&hsagih?jomihs?k&a&gihsi?not??ihsakot??m&a&ginuk?kihsug?maz??igo?otekat??nuga!noy???n&a&moti?timoy?wonig??i&jikan?k???o&gan?jnan?tiad&atik?imanim???u&botom?kusug&akan!atik??imot??rab&anoy?eah???????c&204ugv--nx?462a0t7--nx?678z7vq5d--nx?94ptr5--nx?a??d&17sql1--nx?3thr--nx?5&20xbz--nx?40sj5--nx??7&87tlk--nx?ptlk--nx??861ti4--nx?a?e??e&16thr--nx?5&1a4m2--nx?9ny7k--nx??im!.&a&bot?k&asustam?uzus??m&a&him?y&emak?im???ihs??nawuk?wi&em?k???e&bani?ogawak?si!imanim???i&arataw?gusim?h&asa?ciakkoy??k&a&mat?sosik?t??iat??raban??o&dat?hik?n&amuk?ihseru?o&du?mok????ust???mihe!.&a&m&a&h&ataway?iin??yustam??ij&awu?imak???taki!man???ebot?i&anoh?kasam?rabami??n&ania?egokamuk?oot??o&jias?kihcu?nustam?uhcukokihs?yi!es???u&kohik?zo????n!.&nriheg,teniesa.resu,?amihs!.&a&d&amah?ho?usam??kustay?m&a?ihsoni&hsin?ko???wakih??e&namihs?ustam??i&g&aka?usay??konikak?mikih??nannu?o&mu&kay?zi!ihsagih?uko???nawust?tasim??u&stog?yamat?????tawi!.&a&bahay?d&amay?on??koirom?t&a&honat?katnezukir??imus??w&as&ijuf?uzim??ihs???e&hon&i&hci?n??uk??tawi??i&a&duf?murak?wak??h&custo?si&amak?ukuzihs???j&oboj?uk??k&a&m&anah?uzuk??sagenak??esonihci??m&akatik?uzia&rih?wi????o&kayim?no&rih?t??tanufo??uhso????g&3zsiu--nx?71qstn--nx?l??h&03pv23--nx?13ynr--nx?22tsiu--nx?61qqle--nx??i&54urkm--nx?g&ayim!.&a&dukak?m&a&goihs?kihs??ihsustam!ihsagih??unawi??r&awago?iho??ta&bihs?rum??w&a&gano?kuruf??iat??y&imot?ukaw???e&mot?nimes??i&hsiorihs?ka&monihsi?s&awak?o???mak?r&ataw?o&muram?tan????o&az?jagat?t&asim?omamay???u&fir?k&irnasimanim?uhsakihcihs?????ihcot!.&a&g&a&h?kihsa??ust??kom?m&ay&o?usarak??unak??r&a&boihsusan?watho??iho?ukas??t&akihsin?iay??wa&konimak?zenakat??y&imonustu?oihs???e&iiju?kustomihs?nufawi??i&akihci?g&etom?ihcot?on???o&k&ihsam?kin??nas?sioruk?tab??u&bim?san?????h&c&ia!.&a&dnah?m&a!h&akat?im??yuni??ihs&ibot?ust???r&a&hat?tihs??ik?u&ihsagih?kawi???t&ihc?o&k?yot???wa&koyot?zani??yi&monihci?rak???e&inak?k&aoyot?usa??manokot?noyot??i&a&gusak?kot?sia??eot?h&asairawo?cugo?s&ahoyot?oyim???k&a&mok?zako??ihssi??motay?rogamag??n&an&ikeh?ok??ihssin??o&got?ihsin?jna?rihsnihs?suf?tes??u&bo?raho?s&oyik?takihs??yrihc?zah????ok!.&a&dusay?kadih?mayotom?r&ah&im?usuy??umakan??sot!ihsin??wa&g&atik?odoyin??k&as?o????i&esieg?hco!k??jamu?k&a!sus??usto??ma&gak?k??rahan??o&mukus?n&i?ust!ihsagih???torum?yot!o???u&koknan?zimihsasot????ugamay!.&a&m&ayukot?ihso??toyot??e&bu?subat??i&gah?kesonomihs?nukawi?rakih??nanuhs?otagan?u&ba?foh?otim?stamaduk?uy?????sanamay!.&a&dihsoyijuf?mayabat?r&ahoneu?ustakihsin??w&a&k&ayah?ijuf??suran??ohs???egusok?i&ak?h&cimakan?s&anamay?od???k&asarin?u&feuf?sto????o&k&akanamay?ihcugawakijuf??nihso?t&asimawakihci?ukoh??uhc??spla-imanim?u&b&nan?onim??fok?hsok?rust?????ka&rabi!.&a&bukust?gok?kan!ihcatih??m&a&sak?timo?wi??ihsak?ustomihs??ni?r&a&hihcu?way??u&agimusak?ihcust???t&ag&amay?eman??oihcatih??w&ag&arukas?o??os??yi&moihcatih?rom???e&bomot?dirot?not?tadomihs??i&a&k&as?ot??rao??esukihc?gahakat?h&asa?catih??k&a&rabi?saguyr??ihsani?uy??ma?rukustamat??o&dnab?giad?him?kati?rihsijuf?soj?t&asorihs?im??yihcay??u&fius?kihsu?simak????sagan!.&a&m&abo?ihsust??natawak?r&abamihs?u&mo?ustam???wijihc?yahasi??i&akias?hies?k&asagan?i??masah??neznu?o&besas?darih?t&eso?og!imaknihs????ust&igot?onihcuk?uf????zayim!.&a&biihs?guyh?k&oebon?ustorom??mihsuk?r&emihsin?uatik??ta&katik?mim??wag&atik?odak??ya??e&banakat?sakog??i&hsayabok?kaza&kat?yim??m&animawak?ot&inuk?nihs????nanihcin?o&j&ik?onokayim??n&ibe?ust??tias??urahakat????ro&moa!.&a&dawot?turust?wasim??e&hon&ihc&ah?ihs??nas?og?ukor??sario??i&anarih?ganayati?hsioruk?jehon?kasorih?makihsah?nawo?r&amodakan?omoa???o&gnihs?kkat??u&ragust?stum????ttot!.&a&r&ahawak?uotok??sa&kaw?sim???egok?irottot?nanihcin?o&ganoy?nih?tanimiakas??u&bnan?z&ay?ihc??????ukuf!.&a&deki?gurust?ma&bo?h&akat?im??yustak??sakaw??eabas?i&akas?ho?jiehie?ukuf??nezihce!imanim??ono????k&26rtl8--nx?4&3qtr5--nx?ytjd--nx??522tin--nx?797ti4--nx??l33ussp--nx?m&11tqqq--nx?41s3c--nx??n&30sql1--nx?65zqhe--nx?n7p7qrt0--nx??o&131rot--nx?7qrbk--nx?c?diakkoh!.&a&deki?gakihset?hcebihs?k&adih?u&fib?narihs???m&ayiruk?hot?ihs&orihatik?ukuf??oras?usta??r&ib&a!ka??o?uruf??ozo?u&gakihsagih?oyot???sakim?ta&gikust?mun??w&a&ga&k&an?uf??nus!imak???k&aru?i&h&asa?sagih??kat?mak??omihs?um??zimawi??ine?oyk??yot??e&a&mustam?nan??b&a&kihs?yak??o&noroh?to???ian?k&ihsam?ufoto??nakami?ppoko!ihsin??sotihc?tad!okah??uonikat??i&a&bib?mokamot?n&a&k&kaw?oroh??wi??eomak?ihsatu?okik?usta&moruk?sakan????eib?h&c&ioy?u&bmek?irihs???s&ase?ekka?oknar?uesom???jufirihsir?k&amamihs?i&at?n???m&atik?otoyot??oa&kihs?rihs??r&a&hs?kihsi?mot??ihs&aba?ir??otarib???n&a&hctuk?rorum?se?tokahs??uber??o&kayot?m&ire?ukay??naruf!ima&k?nim???orih?r&ih&ibo?suk??o&bah?h&i&b?hsimak??sa??pnan?yan??umen??t&asoyik?eko?ukoh???u&bassa?kotnihs?m&assaw?uo??pp&akiin?en&ioto?nuk??ip??rato?s&akat?t&eb&e?i&a?hs!a??robon??m&e?o&m?takan???no&h?tamah??o&mik?s?t??u&kir?ppihc?st???onihsnihs?ufuras??uaru??yru!koh??zimihs!ok?????g!oyh!.&a&bmat?dnas?gusak?k&at?o&oyot?y??uzarakat??m&ayasas?irah??wa&g&ani?okak??k&i&hci?mak??oy???yi&hsa?monihsin???i&asak?hs&aka?i&at?nawak???j&awa!imanim??emih??k&a&goa?s&agama?ukuf??wihsin??i&hsog?m???mati?oia?rogimak??n&annas?esnonihs??o&gasa!kat??ka?n&ikat?o?ustat??rihsay?sihs?tomus?yas??u&bay?gnihs?????nagan!.&a&bukah?d&a&w?yim??e&ki?u??ii??k&a&s&ay?uki??zus??ihsoo?ousay??m&ay&akat?ii??i&hsukufosik?jii??ukihc??n&i!hsetat??uzii??r&ah?ugot??saim?t&agamay?oyim??w&a&g&a&kan?n??o??kustam?ziurak??onim!imanim??u&koo?s!omihs????ya&ko?rih???e&akas?nagamok?subo??i&gakat?h&asa?c&a!mo!nanihs???uonamay??sukagot??k&a&kas?mimanim?to??ia&atik?imanim??oa?uzihcom??m&akawak?ijuf?o!t???r&ato?ijoihs?omakat???n&ana?esnoawazon??o&hukas?n&a&gan?kan??i&hc?muza??ustat??romok?si&gan?k??tomustam??u&k&as?ohukihc??stamega????to&mamuk!.&a&gamay?rahihsin?sukama!imak??tamanim??enufim?i&hcukik?k&ihsam?u??nugo!imanim??romakat??o&ara?rihsustay?sa?t&amay?om&amuk?us??u!koyg???yohc??u&sagan?zo????yk!.&a&bmatoyk?k&ies?oemak?uzaw??mayi&h&cukuf?sagih??muk??nihsamay?rawatiju?t&away?ik???e&ba&nat!oyk??ya??di?ni??i&ju?kazamayo?manim??natnan?o&gnatoyk?kum?mak?rihsamayimanim?y&gakan?ka&koagan?s??oj???u&ruziam?z&ayim?ik??????wtc1--nx?ykot!.&a&d&i&hcam?mus??oyihc??k&atim?ihsustak??m&a&t!uko??yarumihsa&gih?sum???i&hs&agoa?ika?o!t??uzuok??ren???r&a&honih?wasago??iadok?umah??ssuf?t&ik?o??wa&g&anihs?ode??k&ara?ihcat???y&agates?ubihs???e&amok?donih?m&o?urukihsagih??soyik??i&enagok?gani?h&ca&da?tinuk??sabati??j&nubukok?oihcah??manigus??o&huzim?jihcah?n&akan?ih!sasum??urika??rugem?t&a&mayihsagih?nim??iat?ok??uhc?yknub??u&fohc?hcuf?kujnihs?????r&2xro6--nx?g?o??s&9nvfe--nx?xvp4--nx??t&netnocresu,opsgolb,?u&4rvp8--nx?fig!.&a&d&eki?ih??kimot?m&ayakat?ihsah??ne?raha&gi&kes?makak??sak??taga&may?tik??wa&g&ibi?ustakan??karihs!ihsagih????e&katim?uawak??i&gohakas?hc&apna?uonaw??k&ago?es?ot??m&anuzim?ijat??nak?urat??nanig?o&dog?jug?makonim?nim?roy?sihcih??u&fig?s&otom?t&amasak?oay???????x5ytlk--nx?yu6d27srjd--nx?z72thr--nx?井福?京東?分大?取鳥?口山?城&宮?茨??媛愛?山&富?岡?歌和??岡&福?静??島&児鹿?広?徳?福??崎&宮?長??川&奈神?石?香??庫兵?形山?手岩?木栃?本熊?根島?梨山?森青?潟新?玉埼?田秋?知&愛?高??縄沖?良奈?葉千?賀&佐?滋??道海北?都京?重三?野長?阜岐?阪大?馬群???k!.&art?gro?moc?per?ude?vog???l&eh?l??m!ac?j??nd?o&g?h&pih?s!.ysrab,??lnud?oc?t!.&lldtn,snd-won,???pa!.&arusah,enilnigol,nur:.a,,t&ibelet,xenw,???ra&a?hs??u&ekam?llag?org!.esruocsid,cts?kouk?nayalo???vsr?xece4ibgm--nx??q&a!3a9y--nx??g?i!.&gro?lim?moc?ten?ude?vog???m?se??r&a!.&acisum?bog?gro?lim?moc!.topsgolb,?rut?t&en?ni??ude?vog??4d5a4prebgm--nx?b?c?eydoog?los?pom?t&at?s!ivom?uen???ugaj??b!.&21g?a&b&a&coros?iuc??itiruc??cnogoas?dicerapa?gniram?i&naiog?ramatnas??n&erom?irdnol??op?p&acam?irolf?ma&j?s???rief?tsivaob??b!aj?mi?sb??c&ba?er?js?sp?t!e???d&em?mb?n&f?i??rt??e&dnarganipmac?ficer?ht?llivnioj?rdnaotnas??f&dj?ed?gg?ni??g&el!.&a&b,m,p,?bp,c&a,s,?e&c,p,s,?fd,gm,ip,jr,la,ma,nr,o&g,r,t,?p&a,s,?r&p,r,?s&e,m,r,?tm,??l&s?z??n&c?e?o??ol&b?f?v??pp?ro??hvp?i&du?kiw?nana?oretin?r&c?eurab??sp?te?xat??l&at&an?rof??el?im?sq??m&a?da?e&gatnoc?leb??f?ic?oc!.topsgolb,??nce?o&ariebir?c&e?narboir?saso??d&o?ranreboas??et?i&b?dar?ecam?r??rp?t&a?erpoir???p&m!e?t??ooc?se??qra?r&af?ga?o&davlas?j??tn?ut??s&a&ixac?mlap?nipmac??u&anam?j?m???t&am?e&n?v??nc?o&f?n??ra?sf??u&caug9?de?ja?rg??v&da?og!.&a&b?m?p??bp?c&a?s??e&c?p?s??fd?gm?ip?jr?la?ma?nr?o&g?r?t??p&a?s??r&p?r??s&e?m?r??tm???rs?t??xiv?z&hb?ls?of????c!.&as?ca?de?if?o&c?g??ro???e&bew?ccos?dnik?e&b?n&igne?oip??rac??gni&arg?rheob??h&cor?sok?t&aew?orb???itnorf?k&col?o&p?rb???l&aed?ffeahcs?syrhc??mal?nes?pinuj?t&a&eht?rebsnegömrev??law?nec?s&acnal?nom?ubkcolb??upmoc??v&o&c&sid?tfiws??rdnal??resbo??wulksretlow?ywal?zifp??f!.&aterg?bew-no,drp?e&c&itsuj-reissiuh?narf-ne-setsitned-sneigrurihc,?rianiretev??i&cc?rgabmahc??m&o&c?n??t??n&eicamrahp?icedem??ossa?s&e&lbatpmoc-strepxe?riaton?tsitned-sneigrurihc?uova??o&-x&bf,obeerf,?x&bf,obeerf,???t&acova?opsgolb,r&epxe-ertemoeg?op!orea????vuog??avc7ylqbgm--nx?s??g!.&gro?m&oc?yn,?t&en?opsgolb,?ude?vog???h!.&e&erf,man??mo&c?rf??topsgolb,zi??ur??i!.&a&61f4a3abgm--nx?rf4a3abgm--nx??ca?di?gro?hcs?oc?ten?vog?نار&يا?یا???a&h?per??ew?lf??k!.&c&a?s??e&n?p?r??gk?iggnoeyg?kub&gn&oeyg?uhc??noej??l&im?uoes??man&gn&oeyg?uhc??noej??n&as&lu?ub??o&e&hcni?jead??wgnag???o&c?g??ro?s&e?h?m??topsgolb,u&gead?j&ej?gnawg????cilf??l!.&gro?moc?ten?ude?vog???m!.&topsgolb,vog???n!.&gro?moc?ofni?ten?ude?vog?zib???o&cs?htua?odtnorf?t&c&a?od??laer???p!.&alsi?ca?eman?forp?gro?moc?o&fni?rp??t&en?se??ude?vog?zib???s?t!.&21k?bew?cn!.vog??eman?gro?kst?l&e&b?t??im?op??moc!.topsgolb,?neg?ofni?pek?rd?sbb?ten?ude?v&a?og?t??zib??f?m??ubad?vd??s&8sqif--nx?9zqif--nx?a!.vog?birappnb?gev?lliv?mtsirhc?s??b!.&ew,gro?moc?ten?ude?vog??c?oj?s?u??c&i&hparg?p?t&sigolyrrek?ylana???od??d&a?d?l?n&iwriaf?omaid??oogemoh?rac??e!.&bog?gro?mo&c!.topsgolb,?n??ude??civres!.enilnigol,?d&d2bgm--nx?oc??h&ctaw?guh??i&lppus?rtsudni?treporp!yrrek???jaiv?korbdal?l&aw?cycrotom?etoh?gnis?pats??m&ag?oh?reh??nut?ohs?picer?r&it?ut&cip!.7331,?nev???s!i&rpretne?urc??ruoc??taicossa?vig??g!nidloh??h5c822qif--nx?i!.&ekacpuc,gro?moc?t&en?ni?opsgolb,?ude?vog??a09--nx?nnet?rap?targ??k&c&or!.&ecapsbew,snddym,ytic-amil,??us??hxda08--nx?row??l!.&c&a?s??gro?o&c?fni??ten?ude?vog?zib??a&ed?tner??e&ssurb?toh!yrrek???lahsram?m?oot??m!.&bal,gro?moc?ten?ude?vog??b?etsys!.tniopthgink,?ialc??n&a&f?gorf?ol??egassap?i&a&grab?mod??giro??o&it&acav?cudorp?ulos??puoc???o&dnoc?geuj?leuv?ppaz?t&ohp?ua???p!.&ces?gro?moc?olp?ten?ude?vog??i&hsralohcs?lihp?t??u??r!.&au,ca?gro?mon,ni?oc?topsgolb,ude?vog?xo,?a&c?p?tiug??c?e&dliub?erac?gor?levart?mraf?n&niw?trap??wolf??ot&cartnoc?omatat??pj?uot??s!.&gro?moc?ten?ude?vog?zib??alg?e&n&isub!.oc,?tif??rp!xe!nacirema???xnal??iws??t&a&e&b?ytic??ob??ek&cit?ram??fig?h&cay?gilf??n&atnuocca?e&mt&rapa?sevni??ve!.oc,???rap??u!.&a&c!.&21k?bil?cc???g!.&21k?bil?cc???i!.&21k?bil?cc???l!.&21k?bil?cc???m!.&21k!.&hcorap?rthc?tvp???bil?cc???p!.&21k?bil?cc???si?v!.&21k?bil?cc???w!.&21k?bil?cc????c&d!.&21k?bil?cc???n!.&21k?bil?cc???s!.&21k?bil?cc????d&ef?i!.&21k?bil?cc???m!.&21k?bil?cc???n!.&bil?cc???s!.&bil?cc???urd,?e&d!.&21k?bil,cc???las-4-&dnal,ffuts,?m!.&21k?bil?cc???n!.&21k?bil?cc????h&n!.&21k?bil?cc???o!.&21k?bil?cc????i&h!.&bil?cc???m!.&21k?bil?c&c?et??goc?n&eg?otae??robra-nna?sum?tsd?wanethsaw???nd?r!.&21k?bil?cc???v!.&21k?bil?cc???w!.&21k?bil?cc????jn!.&21k?bil?cc???k&a!.&21k?bil?cc???o!.&21k?bil?cc????l&a!.&21k?bil?cc???f!.&21k?bil?cc???i!.&21k?bil?cc????mn!.&21k?bil?cc???n&afflog,i!.&21k?bil?cc???m!.&21k?bil?cc???sn?t!.&21k?bil?cc????o&c!.&21k?bil?cc???m!.&21k?bil?cc???ttniop,?pion,r&a!.&21k?bil?cc???o!.&21k?bil?cc???p!.&21k?bil?cc????s&a!.&21k?bil?cc???dik?k!.&21k?bil?cc???m!.&21k?bil?cc???nd&deerf,uolc,??t&c!.&21k?bil?cc???m!.&21k?bil?cc???u!.&21k?bil?cc???v!.&21k?bil?cc????ug!.&21k?bil?cc???v&n!.&21k?bil?cc???w!.cc???xt!.&21k?bil?cc???y&b-si,k!.&21k?bil?cc???n!.&21k?bil?cc???w!.&21k?bil?cc????za!.&21k?bil?cc????ah!uab??bria?col?e!.ytrap.resu,?ineserf?lp?xe&l?n???vt?w!.&66duolc,gro?moc?s&ndnyd,tepym,?ten?ude?vog??a?e&iver?n??odniw??y&alcrab?cam?ot???t&0srzc--nx?a!.&amil4,ca?gni&liamerutuf,tsoherutuf,?o&c!.topsgolb,?fni,?ph21,ro?v&g?irp,?xi2,ytic-amil,zib,?c?e!s??hc?if?l!asite??mami?rcomed??b!.&gro?moc?ten?ude?vog??b?gl??c&atnoc?e&les?nnocu?rid!.lenaptsaf,txen????dimhcs?e!.&eman?gro?moc?ofni?ten?ude?vog?zib??b?em?grat?id?k&circ?ram??n!.&5inu,6vnyd,7&7ndc.r,erauqs,?a&l&-morf,moob,?minifed,remacytirucesym,tadsyawla,z,?b&boi,g,lyltsaf:.pam,,?c&inagro-gnitae,paidemym,?d&ecalpb,nab-eht-ni,uolcxednay:.e&garots,tisbew,?,?e&cnarusnihtlaehezitavirp,ht-no-eciffo,l&acsnoom,ibom-eruza,?m&ecnuob,ohtanyd,tcerider,?nozdop,rehurht,s,tis-repparcs,zamkcar,?f&aeletis,crs.&cos,resu,?ehc-a-si,?g&nitsohnnylf,olbevres,?k&eeg-a&-si,si,?u,?l&acolottad,iamwt,s&d-ni,s-77ndc,??mac&asac,ih,?n&a&f&agp,lhn,?ibed,?dcduabkcalb,i,pv-ni,?o&c-morf,jodsnd,ttadym,?p&i&-&etsef,on,?emoh,fles,nwo,?j,mac-dnab-ta,o&-oidar-mah,hbew,?paduolc,tfe&moh,vres,?usnd,?r&e&tsulcyduolc,vres-xnk,?vdslennahc,?s&a&ila&nyd,snd,?nymsd,?bbevres,dylimaf,e&suohsyub,t&isbeweruza,ys,??kekokohcs,n&d&-won,d,golb,npv,?oitcnufduolc,?s&a-skcik,ecca&-citats,duolc,???t&ceffeym,e&nretnifodne,smem,?farcenimevres,i-&ekorb,s&eod,lles,teg,??n&essidym,orfduolc,?r0p3l3t,s&ixetnod,ohgnik,??u&h,nyd,r,?x&inuemoh,spym,unilemoh,?y&awetag-llawerif,ltsaf.&dorp.&a,labolg,?lss.&a,b,labolg,?pam,slteerf,?n&-morf,ofipi,?srab,tieduolc,?z&a-morf,tirfym,???p?tcip?v??f&ig?o&l?sorcim???g!.&bog?dni?gro?lim?mo&c?n,?ten?ude???h!.&dem?gro?l&er?op??m&oc?rif??o&fni?rp?s&rep?sa???po&hs?oc??t&en?luda?ra??ude?vuog???i!.&a&2n-loritds--nx?7e-etsoaellav--nx?8&c-aneseclrof--nx?i-lrofanesec--nx??at?b?c!cul??dv?i&blo&-oipmet?oipmet??cserb?drabmol?g&gof?urep??l&gup?i&cis?me&-oigger?oigger???uig&-&aizenev&-iluirf?iluirf??ev&-iluirf?iluirf??v&-iluirf?iluirf???aizenev&-iluirf?iluirf??ev&-iluirf?iluirf??v&-iluirf?iluirf????n&a&brev?cul?pmac?tac??idras?obrac&-saiselgi?saiselgi??resi??otsip?r&b&alac!-oigger?oigger??mu??dna&-&attelrab-inart?inart-attelrab??attelrabinart?inartattelrab?ssela??epmi?ugil??tnelav&-obiv?obiv??vap?z&e&nev?ps&-al?al???irog???l&iuqa!l??leib??m&or?rap??n!acsot?e&dom?is?sec&-&ilrof?ìlrof??ilrof?ìlrof???g&amor&-ailime?ailime??edras?olob??i&ssem?tal??ne!var??o&cna?merc?rev?vas???oneg?p?r!a&csep?rr&ac&-assam?assam??ef??von??etam?tsailgo!-lled?lled???s!ip?sam&-ararrac?ararrac??u&caris?gar???t!a&cilisab?recam??resac?soa!-&d&-&ellav?lav??ellav?lav??ellav??d&-&ellav?lav??ellav?lav??ellav??te&lrab&-&airdna-inart?inart-airdna??airdnainart?inartairdna??ssinatlac???udap?v!o&dap?neg?tnam???zn&airb&-a&lled-e-aznom?znom??a&lledeaznom?znom??eaznom??e&c&aip?iv??soc?top??om???b&-&23,46,61,?3c-lorit-ds-onitnert--nx?be-etsoa&-ellav--nx?dellav--nx??c!f-anesec-lrof--nx?m-lrof-anesec--nx??he-etsoa-d-ellav--nx?m!u??o2-loritds-nezob--nx?sn-loritds&-nasl&ab--nx?ub--nx??nitnert--nx??v!6-lorit-dsnitnert--nx?7-loritds&-nitnert--nx?onitnert--nx???z&r-lorit-ds&-nitnert--nx?onitnert--nx??s-loritds-onitnert--nx???c&f?is?l?m?p?r?v??d&p?u!olcnys,??e&c!cel?inev?nerolf??f?g!ida&-&a&-onitnert?onitnert??otla!-onitnert?onitnert???a&-onitnert?onitnert??otla!-on&azlob?itnert??onitnert????hcram?l?m!or??n&idu?o&n&edrop?isorf??torc???p?r?s&erav?ilom??t!nomeip?s&eirt?oa!-&d-e&ellav?éllav??e&ellav?éllav???de&ellav?éllav??e&ellav?éllav?????v?znerif??g&a?b?f?il?o?p?r?up?vf??hc?i&b?c?dol?f?l!lecrev?opan?rof&-anesec?anesec???m?n&a&part?rt&-attelrab-airdna?attelrabairdna???imir?ret??p?r!a&b?ilgac?ssas???s!idnirb??t&ei&hc?r??sa??v??l&a!c??b?c?o&m?rit&-&d&eus&-&nitnert?onitnert??nitnert?onitnert??us&-&nitnert?onitnert??nitnert?onitnert??üs&-&nitnert?onitnert??nitnert?onitnert???s&-onitnert?onitnert???d&eus!-&n&asl&ab?ub??ezob?itnert??onitnert??nitnert?onitnert??us&-&n&asl&ab?ub??ezob?itnert??onitnert??nitnert?onitnert??üs!-&n&asl&ab?ub??ezob?itnert??onitnert??nitnert?onitnert???s&-onitnert?onitnert?????m&ac?f?i?ol?r??n&a!lim?sl&ab?ub???b?c?e!v?zob??irut?m!p??p?r?t??o&a!v??b!retiv??c!cel??enuc?g!ivor??i&dem&-onadipmac?onadipmac??pmet&-aiblo?aiblo??rdnos?zal??l?m!a&greb?ret??oc?re&f?lap???n!a&dipmac&-oidem?oidem??lim?tsiro?zlob??ecip&-ilocsa?ilocsa??i&bru&-orasep?orasep??lleva?rot?tnert??r&elas?ovil??ulleb??p?r!a&sep&-onibru?onibru??znatac??oun??s!ivert?sabopmac??t!arp?e&nev?ssorg??n&arat?e&girga?rt?veneb????zz&era?urba???p&a?s?t??qa?r&a!m?s??b!a??c?f?g?k?me?o?p?s?t?v??s&a&b?iselgi&-ainobrac?ainobrac???b?c?elpan?i?m?ot?s?t?v??t&a?b?c?l?m?nomdeip?o!psgolb,?p?v??u&de?l?n?p??v&a?og?p?s?t?v??y&drabmol?ellav&-atsoa?atsoa??licis?nacsut??z&al?b?c?p??ìlrof&-anesec?anesec???derc?er?f!.sulptp,?m!r??utni??je3a3abgm--nx?kh?l!.&myn,topsgolb,vog??uda??m!.&gro?moc!.topsgolb,?ten?ude???n&a&morockivdnas?ruatser?tnuocca??e&g?m&eganam!.retuor,?piuqe??r??i!.ue?m?opdlog?rpatsiv??opud?uocsid??o&b?cs!.vog,?d?g?h?j?oferab?p&edemoh?s???p!.&emon?gro?lbup?m&oc?yn,?t&en?ni?opsgolb,?ude?vog???r&a!m&law?s???epxe?op&er?pus!.ysrab,?s??s??s!.&adaxiabme?e&motoas?picnirp?rots??gro?lim?mo&c?n,?o&c?dalusnoc?hon,?ten?ude?vog??a&cmoc?f??e&b?padub?r?uq??i!rolf?tned??o&h!.&duolcp,etiseerf,flah,sseccaduolc,??p!sua???urt??t!.&eman?gro?ibom?levart?m&oc?uesum??o&c?fni?r&ea?p???pooc?sboj?t&en?ni??ude?vog?zib??ayh?n?o!bba?irram???uognah?xen?y?ztej??u&2&5te9--nx?yssp--nx??a!.&a&s?w??civ?d&i?lq??fnoc?gro?moc!.topsgolb,?nsa?ofni?sat?t&ca?en?n??ude!.&a&s?w??ci&lohtac?v??dlq?qe?sat!.noitacude??t&ca?n??wsn!.sloohcs????vog!.&a&s?w??civ?dlq?sat???wsn?zo??ti??c!.&fni?gro?moc?ten?ude?vog??i??d&e!.tir.segap-tig,?iab??e!.&dcym,enozgniebllew,noitatsksid,snd&ps,uolc,?ysrab,??g!.&bew?gro?m&aug?oc??ofni?ten?ude?vog???h!.&0002?a&citore?idem?kitore??edszot?gro?ilus?letoh?m&alker?lif?t?urof??naltagni?o&c?ediv?fni?levynok?nisac??pohs?rarga?s&a&kal?zatu??emag?wen??t&lob?opsgolb,rops??virp?xe&s?zs??ytic?zsagoj??os?sut??l!.&myn,topsgolb,??m!.&ca?gro?moc?oc?ro?ten?vog???n!.&duolcesirpretne,eni&esrem,m,?mon,redliub.etis,tenkcahs,?em!.ysrab,??o&ggnaw?y!c???r!.&a&i&kymlak,rikhsab,vodrom,?yegyda,?bps,ca?eniram,g&bc,ro,?ianatsuk,k&ihclan,s&m,rogitayp,??li&amdlc.bh,m??moc,natsegad,onijym,pp,ri&b,midalv,?s&ar,itym,?t&en,ni?opsgolb,set??ude?vo&g?n,?ynzorg,zakvakidalv,?myc?p?ug??s!.&a&d&golov,nagarak,?gulak,i&groeg,kymlak,lerak,nemra,rikhsab,ssakahk,vodrom,zahkba,?lut,rahkub,vut,yegyda,znep,?bps,da&baghsa,rgonilest,?gunel,i&anatsuk,hcos,ovan,ttailgot,?k&alhsygnam,ihclan,s&legnahkra,m,n&a&mrum,yrb,?i&buytka,nbo,??tiort,vorkop,??l&ocarak,ybmaj,?myn,na&gruk,jiabreza,ts&egad,hkazak-&htron,tsae,???ovonavi,r&adonsark,imidalv,?t&enxe,nek&hsat,mihc,??vo&hsalab,n,?ynzorg,z&akvakidalv,emret,??t&amok?i&juf?masih????v!.&gro?moc?ten?ude???ykuyr??v&b?c!.topsgolb,?ed!.&enilnigol,srekrow,vresi,??ih?l!.&di?fnoc?gro?lim?moc?nsa?ten?ude?vog???m!.&eman?gro?lim?m&oc?uesum??o&fni?r&ea?p???pooc?t&en?ni??ude?vog?zib???o&g?m??rt?s!.&bog?der?gro?moc?ude???t!.&bew-eht-no,naht-&esrow,retteb,?sndnyd,?d?gh?i?won??uqhv--nx??w&a!.moc?hs?l??b!.&gro?oc???c!.&gro?moc?ten?ude??cp??e&iver!.oby,?n?s??g?k!.&bme?dni?gro?moc?ten?ude?vog???m!.&ca?gro?m&oc?uesum??oc?pooc?t&en?ni??ude?vog?zib??b??o&csom?h!s??n?w??p!.&344x,de?en?mon,o&c?g??ro?snduolc,ualeb???r!.&ca?gro?lim?oc?pooc?ten?vog??n??t!.&a46oa0fz--nx?b&82wrzc--nx?ulc??emag?gro?l&im?ru,?m&oc!.reliamym,?yn,?t&en?opsgolb,?ude?v&di?og?ta0cu--nx??zibe?業商?織組?路網???z!.&ca?gro?lim?oc?vog????x&a!t??c!.&hta,ofni,vog???e&d&an?ef?nay??ma!nab??rof?s??ilften?jt?m!.&bog?gro?m&oc?yn,?t&en?opsgolb,?ude??g?ma2ibgy--nx??o&b!x??f?rex!ijuf???rbgn--nx?s!.&myn,vog???x&am&jt?kt??x???y&4punu--nx?7rr03--nx?a&d!i&loh?rfkcalb??ot??g?lp?p!ila??rot?ssin?wdaorb??b!.&fo?lim?m&oc!.topsgolb,?yn,?vog??ab?gur??c!.&ca?dtl?eman?gro?m&oc!.topsgolb,?t??orp?s&egolke?serp??t&en?nemailrap??vog?zib??amrahp?nega??d&dadog?uts??e&kcoh?ltneb?n&dys?om?rotta??snikcm??g!.&gro?m&oc?yn,?oc?ten?ude?vog??olonhcet!.oc,?rene??hpargotohp?id?k!.&gro?moc?ten?ude?vog??s??l!.&clp?d&em?i??gro?hcs?moc?ten?ude?vog??f?imaf!nacirema??l&a?il??ppus??m!.&eman?gro?lim?moc?t&en?opsgolb,?ude?vog??edaca!.laiciffo,?ra??n&a&ffit?pmoc!ylimafa???os??o&j?s??p!.&gro?lim?moc?pooc?ten?ude?vog???r&e&corg?grus?llag?viled??lewej?otcerid?tnuoc?uxul??s!.&gro?lim?moc?ten?ude?vog??pil??t&efas?i&c!.gn,?ledif?n&ifx?ummoc!.bdnevar,??r&ahc?uces??srevinu??laer?r&ap!.oby,?eporp??uaeb??u!.&bug?gro?lim?mo&c!.topsgolb,?n,?ten?ude??b!tseb???van!dlo??xes??z&a!.&eman?gro?lim?moc?o&fni?rp??pp?t&en?ni??ude?vog?zib???b!.&az,gro?m&oc?yn,?ten?ude?vog???c!.&4e,inum.duolc.&rsu,tlf,?m&laer,urtnecatem.&duolc,motsuc,??oc,topsgolb,??d!.&gro?lop?moc?ossa?t&en?ra??ude?vog???ib!.&duolcsd,e&ht-rof,mos-rof,rom-rof,?lpb,nafamm,p&i&-on,fles,?ohbew,tfym,?retteb-rof,snd&nyd,uolc,?xro,?g??k!.&gro?lim?m&oc?yn,?ten?ude?vog???m!.&ca?gro?lim?oc?ten?ude?v&da?og????n!.&asq-irom--nx?ca?gro?htlaeh?i&r&c?o&am?ām???wi!k???keeg?l&im?oohcs??myn,neg?oc!.topsgolb,?t&en?nemailrap?vog???a!niflla???rawhcs?s!.&ca?gro?oc???t!.&c&a?s??e&m?n??ibom?l&etoh?im??o&c?fni?g??ro?vt???u!.&gro?moc?oc?ten??rwon??yx!.&etisgolb,gnitfarc,otpaz,ppahf,??zub??λε?авксом?брс!.&гро?до?ка?р&бо?п!у?????г&б?ро??дкм?зақ?итед?килотак?леб?мок?н&йално?ом??рку?сур?тйас?фр?юе?յահ?םוק?اي&روس?سيلم?ناتيروم??بر&ع?غملا??ة&كبش?ي&دوعسلا?روس??یدوعسلا??ت&ا&راما?لاصتا??را&ب?ڀ?ھب???ر&ئازجلا?ازاب?صم?طق??سنوت?عقوم?قارع?ك&تيب?يلوثاك??موك?ن&ا&تس&كاپ?کاپ??دوس?ر&يا?یا??مع?يلعلا??درالا?ميلا?يطسلف??ه&ارمه?يدوعسلا??وكمارا?يبظوبا?ۃیدوعسلا?टेन?त&राभ?ोराभ??नठगंस?मॉक?्मतराभ?ত&রাভ?ৰাভ??ালংাব?ਤਰਾਭ?તરાભ?ତରାଭ?ாயித்நஇ?ைக்ஙலஇ?்ரூப்பக்ஙிச?్తరాభ?ತರಾಭ?ംതരാഭ?ාකංල?มอค?ยทไ!.&จิกรุธ?ต็นเ?ร&ก์คงอ?าหท??ลาบฐัร?าษกึศ???ეგ?なんみ?アトス?トンイポ?ドウラク?ムコ?ル&グーグ?ーセ??ンョシッァフ?业企?东广?乐娱?亚基诺?你爱我?信中?务政?动移?博微?卦八?厅餐?司公?品食?善慈?团集?国中?國中?址网?坡加新?城商?宝珠?尚时?山佛?店&商?网?酒大里嘉??府政?康健?息信?戏游?拉里格香?拿大?教主天?机手?构机!织组??标商?歌谷?浦利飞?港香!.&人個?司公?府政?絡網?織組?育教???湾台?灣&台?臺??物购?界世?益公?看点?科盈訊電?站网?籍書?线在?络网?网文中?聘招?行工?表手?販通?车汽众大?通联?里嘉?锡马淡?門澳?门澳?闻新?電家?국한?넷닷?성삼?컴닷??"); + + /** + * If a hostname is not a key in the EXCLUDE map, and if removing its + * leftmost component results in a name which is a key in this map, it is a + * public suffix. + */ + public static final ImmutableMap UNDER = + TrieParser.parseTrie("ac.vedwa,d&b?uolc.&etiso&isnes,tnegam,?rehcnar-no,scitats,??e&b.lrusnart,d.ecapsrebu,noz.notirt,t&atse.etupmoc,is.hsmroftalp,?y??gp?h&k?s.mroftalp,trae.sppad:.zzb,,?jf?k&c?f?nil.bewd,rowten.secla,u.hcs??ln.lrusnart,m&j?m?oc.&mme0,s&tnemelepiuq,wanozama.&1-etupmoc,ble,etupmoc,??tneyoj.snc,??nc.moc.swanozama.&ble,etupmoc,?o&c.pato,i.&elacsnoom,oir-no,solots,y5s,??p&j.&a&mahokoy?yogan??ebok?i&adnes?kasawak??oroppas?uhsuykatik??n??r&b.mon?e??sw.rosivda,t&a.&ofnistro.&nednuk,xe,?smcerutuf:.&ni,xe,?,?en.cimonotpyrc,?u&e.lrusnart,r.onijym.&gni&dnal,tsoh,?murtceps,spv,??ved.&erahbew,gts,lcl,?zyx.tibelet,?"); + + /** + * The elements in this map would pass the UNDER test, but are known not to + * be public suffixes and are thus excluded from consideration. Since it + * refers to elements in UNDER of the same type, the type is actually not + * important here. The map is simply used for consistency reasons. + */ + public static final ImmutableMap EXCLUDED = + TrieParser.parseTrie("kc.www?pj.&a&mahokoy.ytic?yogan.ytic??ebok.ytic?i&adnes.ytic?kasawak.ytic??oroppas.ytic?uhsuykatik.ytic???"); +} diff --git a/src/main/java/com/google/common/net/PublicSuffixType.java b/src/main/java/com/google/common/net/PublicSuffixType.java new file mode 100644 index 0000000..d3fbc2c --- /dev/null +++ b/src/main/java/com/google/common/net/PublicSuffixType.java @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2013 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.net; + +import com.google.common.annotations.Beta; +import com.google.common.annotations.GwtCompatible; + +/** + * Do not use this class directly. For access to public-suffix information, use {@link + * com.google.common.net.InternetDomainName}. + * + *

Specifies the type of a top-level domain definition. + * + * @since 23.3 + */ +@Beta +@GwtCompatible +public enum PublicSuffixType { + + /** Public suffix that is provided by a private company, e.g. "blogspot.com" */ + PRIVATE(':', ','), + /** Public suffix that is backed by an ICANN-style domain name registry */ + REGISTRY('!', '?'); + + /** The character used for an inner node in the trie encoding */ + private final char innerNodeCode; + + /** The character used for a leaf node in the trie encoding */ + private final char leafNodeCode; + + PublicSuffixType(char innerNodeCode, char leafNodeCode) { + this.innerNodeCode = innerNodeCode; + this.leafNodeCode = leafNodeCode; + } + + char getLeafNodeCode() { + return leafNodeCode; + } + + char getInnerNodeCode() { + return innerNodeCode; + } + + /** Returns a PublicSuffixType of the right type according to the given code */ + static PublicSuffixType fromCode(char code) { + for (PublicSuffixType value : values()) { + if (value.getInnerNodeCode() == code || value.getLeafNodeCode() == code) { + return value; + } + } + throw new IllegalArgumentException("No enum corresponding to given code: " + code); + } + + static PublicSuffixType fromIsPrivate(boolean isPrivate) { + return isPrivate ? PRIVATE : REGISTRY; + } +} diff --git a/src/main/java/com/google/common/net/TrieParser.java b/src/main/java/com/google/common/net/TrieParser.java new file mode 100644 index 0000000..3db2536 --- /dev/null +++ b/src/main/java/com/google/common/net/TrieParser.java @@ -0,0 +1,102 @@ +/* + * Copyright (C) 2008 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.net; + +import com.google.common.annotations.GwtCompatible; +import com.google.common.base.Joiner; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Lists; +import java.util.List; + +/** Parser for a map of reversed domain names stored as a serialized radix tree. */ +@GwtCompatible +final class TrieParser { + private static final Joiner PREFIX_JOINER = Joiner.on(""); + + /** + * Parses a serialized trie representation of a map of reversed public suffixes into an immutable + * map of public suffixes. + */ + static ImmutableMap parseTrie(CharSequence encoded) { + ImmutableMap.Builder builder = ImmutableMap.builder(); + int encodedLen = encoded.length(); + int idx = 0; + while (idx < encodedLen) { + idx += doParseTrieToBuilder(Lists.newLinkedList(), encoded, idx, builder); + } + return builder.build(); + } + + /** + * Parses a trie node and returns the number of characters consumed. + * + * @param stack The prefixes that precede the characters represented by this node. Each entry of + * the stack is in reverse order. + * @param encoded The serialized trie. + * @param start An index in the encoded serialized trie to begin reading characters from. + * @param builder A map builder to which all entries will be added. + * @return The number of characters consumed from {@code encoded}. + */ + private static int doParseTrieToBuilder( + List stack, + CharSequence encoded, + int start, + ImmutableMap.Builder builder) { + + int encodedLen = encoded.length(); + int idx = start; + char c = '\0'; + + // Read all of the characters for this node. + for (; idx < encodedLen; idx++) { + c = encoded.charAt(idx); + if (c == '&' || c == '?' || c == '!' || c == ':' || c == ',') { + break; + } + } + + stack.add(0, reverse(encoded.subSequence(start, idx))); + + if (c == '!' || c == '?' || c == ':' || c == ',') { + // '!' represents an interior node that represents a REGISTRY entry in the map. + // '?' represents a leaf node, which represents a REGISTRY entry in map. + // ':' represents an interior node that represents a private entry in the map + // ',' represents a leaf node, which represents a private entry in the map. + String domain = PREFIX_JOINER.join(stack); + if (domain.length() > 0) { + builder.put(domain, PublicSuffixType.fromCode(c)); + } + } + idx++; + + if (c != '?' && c != ',') { + while (idx < encodedLen) { + // Read all the children + idx += doParseTrieToBuilder(stack, encoded, idx, builder); + if (encoded.charAt(idx) == '?' || encoded.charAt(idx) == ',') { + // An extra '?' or ',' after a child node indicates the end of all children of this node. + idx++; + break; + } + } + } + stack.remove(0); + return idx - start; + } + + private static CharSequence reverse(CharSequence s) { + return new StringBuilder(s).reverse(); + } +} diff --git a/src/main/java/com/google/common/net/UrlEscapers.java b/src/main/java/com/google/common/net/UrlEscapers.java new file mode 100644 index 0000000..d4b9f94 --- /dev/null +++ b/src/main/java/com/google/common/net/UrlEscapers.java @@ -0,0 +1,148 @@ +/* + * Copyright (C) 2009 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.net; + +import com.google.common.annotations.GwtCompatible; +import com.google.common.escape.Escaper; + +/** + * {@code Escaper} instances suitable for strings to be included in particular sections of URLs. + * + *

If the resulting URLs are inserted into an HTML or XML document, they will require additional + * escaping with {@link com.google.common.html.HtmlEscapers} or {@link + * com.google.common.xml.XmlEscapers}. + * + * + * @author David Beaumont + * @author Chris Povirk + * @since 15.0 + */ +@GwtCompatible +public final class UrlEscapers { + private UrlEscapers() {} + + // For each xxxEscaper() method, please add links to external reference pages + // that are considered authoritative for the behavior of that escaper. + + static final String URL_FORM_PARAMETER_OTHER_SAFE_CHARS = "-_.*"; + + static final String URL_PATH_OTHER_SAFE_CHARS_LACKING_PLUS = + "-._~" // Unreserved characters. + + "!$'()*,;&=" // The subdelim characters (excluding '+'). + + "@:"; // The gendelim characters permitted in paths. + + /** + * Returns an {@link Escaper} instance that escapes strings so they can be safely included in URL form parameter names and values. Escaping is performed + * with the UTF-8 character encoding. The caller is responsible for replacing any unpaired carriage return or line feed characters + * with a CR+LF pair on any non-file inputs before escaping them with this escaper. + * + *

When escaping a String, the following rules apply: + * + *

    + *
  • The alphanumeric characters "a" through "z", "A" through "Z" and "0" through "9" remain + * the same. + *
  • The special characters ".", "-", "*", and "_" remain the same. + *
  • The space character " " is converted into a plus sign "+". + *
  • All other characters are converted into one or more bytes using UTF-8 encoding and each + * byte is then represented by the 3-character string "%XY", where "XY" is the two-digit, + * uppercase, hexadecimal representation of the byte value. + *
+ * + *

This escaper is suitable for escaping parameter names and values even when using the non-standard semicolon, rather than the ampersand, as + * a parameter delimiter. Nevertheless, we recommend using the ampersand unless you must + * interoperate with systems that require semicolons. + * + *

Note: Unlike other escapers, URL escapers produce uppercase hexadecimal sequences. + * + */ + public static Escaper urlFormParameterEscaper() { + return URL_FORM_PARAMETER_ESCAPER; + } + + private static final Escaper URL_FORM_PARAMETER_ESCAPER = + new PercentEscaper(URL_FORM_PARAMETER_OTHER_SAFE_CHARS, true); + + /** + * Returns an {@link Escaper} instance that escapes strings so they can be safely included in URL path segments. The returned escaper escapes all non-ASCII + * characters, even though many of these are accepted in modern + * URLs. (If the escaper were to leave these characters + * unescaped, they would be escaped by the consumer at parse time, anyway.) Additionally, the + * escaper escapes the slash character ("/"). While slashes are acceptable in URL paths, they are + * considered by the specification to be separators between "path segments." This implies that, if + * you wish for your path to contain slashes, you must escape each segment separately and then + * join them. + * + *

When escaping a String, the following rules apply: + * + *

    + *
  • The alphanumeric characters "a" through "z", "A" through "Z" and "0" through "9" remain + * the same. + *
  • The unreserved characters ".", "-", "~", and "_" remain the same. + *
  • The general delimiters "@" and ":" remain the same. + *
  • The subdelimiters "!", "$", "&", "'", "(", ")", "*", "+", ",", ";", and "=" remain + * the same. + *
  • The space character " " is converted into %20. + *
  • All other characters are converted into one or more bytes using UTF-8 encoding and each + * byte is then represented by the 3-character string "%XY", where "XY" is the two-digit, + * uppercase, hexadecimal representation of the byte value. + *
+ * + *

Note: Unlike other escapers, URL escapers produce uppercase hexadecimal sequences. + */ + public static Escaper urlPathSegmentEscaper() { + return URL_PATH_SEGMENT_ESCAPER; + } + + private static final Escaper URL_PATH_SEGMENT_ESCAPER = + new PercentEscaper(URL_PATH_OTHER_SAFE_CHARS_LACKING_PLUS + "+", false); + + /** + * Returns an {@link Escaper} instance that escapes strings so they can be safely included in a URL fragment. The returned escaper escapes all non-ASCII + * characters, even though many of these are accepted in modern + * URLs. + * + *

When escaping a String, the following rules apply: + * + *

    + *
  • The alphanumeric characters "a" through "z", "A" through "Z" and "0" through "9" remain + * the same. + *
  • The unreserved characters ".", "-", "~", and "_" remain the same. + *
  • The general delimiters "@" and ":" remain the same. + *
  • The subdelimiters "!", "$", "&", "'", "(", ")", "*", "+", ",", ";", and "=" remain + * the same. + *
  • The space character " " is converted into %20. + *
  • Fragments allow unescaped "/" and "?", so they remain the same. + *
  • All other characters are converted into one or more bytes using UTF-8 encoding and each + * byte is then represented by the 3-character string "%XY", where "XY" is the two-digit, + * uppercase, hexadecimal representation of the byte value. + *
+ * + *

Note: Unlike other escapers, URL escapers produce uppercase hexadecimal sequences. + */ + public static Escaper urlFragmentEscaper() { + return URL_FRAGMENT_ESCAPER; + } + + private static final Escaper URL_FRAGMENT_ESCAPER = + new PercentEscaper(URL_PATH_OTHER_SAFE_CHARS_LACKING_PLUS + "+/?", false); +} diff --git a/src/main/java/com/google/common/primitives/Booleans.java b/src/main/java/com/google/common/primitives/Booleans.java new file mode 100644 index 0000000..175d32b --- /dev/null +++ b/src/main/java/com/google/common/primitives/Booleans.java @@ -0,0 +1,553 @@ +/* + * Copyright (C) 2008 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.primitives; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkElementIndex; +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.base.Preconditions.checkPositionIndexes; + +import com.google.common.annotations.Beta; +import com.google.common.annotations.GwtCompatible; +import java.io.Serializable; +import java.util.AbstractList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; +import java.util.RandomAccess; + + +/** + * Static utility methods pertaining to {@code boolean} primitives, that are not already found in + * either {@link Boolean} or {@link Arrays}. + * + *

See the Guava User Guide article on primitive utilities. + * + * @author Kevin Bourrillion + * @since 1.0 + */ +@GwtCompatible +public final class Booleans { + private Booleans() {} + + /** Comparators for {@code Boolean} values. */ + private enum BooleanComparator implements Comparator { + TRUE_FIRST(1, "Booleans.trueFirst()"), + FALSE_FIRST(-1, "Booleans.falseFirst()"); + + private final int trueValue; + private final String toString; + + BooleanComparator(int trueValue, String toString) { + this.trueValue = trueValue; + this.toString = toString; + } + + @Override + public int compare(Boolean a, Boolean b) { + int aVal = a ? trueValue : 0; + int bVal = b ? trueValue : 0; + return bVal - aVal; + } + + @Override + public String toString() { + return toString; + } + } + + /** + * Returns a {@code Comparator} that sorts {@code true} before {@code false}. + * + *

This is particularly useful in Java 8+ in combination with {@code Comparators.comparing}, + * e.g. {@code Comparators.comparing(Foo::hasBar, trueFirst())}. + * + * @since 21.0 + */ + @Beta + public static Comparator trueFirst() { + return BooleanComparator.TRUE_FIRST; + } + + /** + * Returns a {@code Comparator} that sorts {@code false} before {@code true}. + * + *

This is particularly useful in Java 8+ in combination with {@code Comparators.comparing}, + * e.g. {@code Comparators.comparing(Foo::hasBar, falseFirst())}. + * + * @since 21.0 + */ + @Beta + public static Comparator falseFirst() { + return BooleanComparator.FALSE_FIRST; + } + + /** + * Returns a hash code for {@code value}; equal to the result of invoking {@code ((Boolean) + * value).hashCode()}. + * + *

Java 8 users: use {@link Boolean#hashCode(boolean)} instead. + * + * @param value a primitive {@code boolean} value + * @return a hash code for the value + */ + public static int hashCode(boolean value) { + return value ? 1231 : 1237; + } + + /** + * Compares the two specified {@code boolean} values in the standard way ({@code false} is + * considered less than {@code true}). The sign of the value returned is the same as that of + * {@code ((Boolean) a).compareTo(b)}. + * + *

Note for Java 7 and later: this method should be treated as deprecated; use the + * equivalent {@link Boolean#compare} method instead. + * + * @param a the first {@code boolean} to compare + * @param b the second {@code boolean} to compare + * @return a positive number if only {@code a} is {@code true}, a negative number if only {@code + * b} is true, or zero if {@code a == b} + */ + public static int compare(boolean a, boolean b) { + return (a == b) ? 0 : (a ? 1 : -1); + } + + /** + * Returns {@code true} if {@code target} is present as an element anywhere in {@code array}. + * + *

Note: consider representing the array as a {@link java.util.BitSet} instead, + * replacing {@code Booleans.contains(array, true)} with {@code !bitSet.isEmpty()} and {@code + * Booleans.contains(array, false)} with {@code bitSet.nextClearBit(0) == sizeOfBitSet}. + * + * @param array an array of {@code boolean} values, possibly empty + * @param target a primitive {@code boolean} value + * @return {@code true} if {@code array[i] == target} for some value of {@code i} + */ + public static boolean contains(boolean[] array, boolean target) { + for (boolean value : array) { + if (value == target) { + return true; + } + } + return false; + } + + /** + * Returns the index of the first appearance of the value {@code target} in {@code array}. + * + *

Note: consider representing the array as a {@link java.util.BitSet} instead, and + * using {@link java.util.BitSet#nextSetBit(int)} or {@link java.util.BitSet#nextClearBit(int)}. + * + * @param array an array of {@code boolean} values, possibly empty + * @param target a primitive {@code boolean} value + * @return the least index {@code i} for which {@code array[i] == target}, or {@code -1} if no + * such index exists. + */ + public static int indexOf(boolean[] array, boolean target) { + return indexOf(array, target, 0, array.length); + } + + // TODO(kevinb): consider making this public + private static int indexOf(boolean[] array, boolean target, int start, int end) { + for (int i = start; i < end; i++) { + if (array[i] == target) { + return i; + } + } + return -1; + } + + /** + * Returns the start position of the first occurrence of the specified {@code target} within + * {@code array}, or {@code -1} if there is no such occurrence. + * + *

More formally, returns the lowest index {@code i} such that {@code Arrays.copyOfRange(array, + * i, i + target.length)} contains exactly the same elements as {@code target}. + * + * @param array the array to search for the sequence {@code target} + * @param target the array to search for as a sub-sequence of {@code array} + */ + public static int indexOf(boolean[] array, boolean[] target) { + checkNotNull(array, "array"); + checkNotNull(target, "target"); + if (target.length == 0) { + return 0; + } + + outer: + for (int i = 0; i < array.length - target.length + 1; i++) { + for (int j = 0; j < target.length; j++) { + if (array[i + j] != target[j]) { + continue outer; + } + } + return i; + } + return -1; + } + + /** + * Returns the index of the last appearance of the value {@code target} in {@code array}. + * + * @param array an array of {@code boolean} values, possibly empty + * @param target a primitive {@code boolean} value + * @return the greatest index {@code i} for which {@code array[i] == target}, or {@code -1} if no + * such index exists. + */ + public static int lastIndexOf(boolean[] array, boolean target) { + return lastIndexOf(array, target, 0, array.length); + } + + // TODO(kevinb): consider making this public + private static int lastIndexOf(boolean[] array, boolean target, int start, int end) { + for (int i = end - 1; i >= start; i--) { + if (array[i] == target) { + return i; + } + } + return -1; + } + + /** + * Returns the values from each provided array combined into a single array. For example, {@code + * concat(new boolean[] {a, b}, new boolean[] {}, new boolean[] {c}} returns the array {@code {a, + * b, c}}. + * + * @param arrays zero or more {@code boolean} arrays + * @return a single array containing all the values from the source arrays, in order + */ + public static boolean[] concat(boolean[]... arrays) { + int length = 0; + for (boolean[] array : arrays) { + length += array.length; + } + boolean[] result = new boolean[length]; + int pos = 0; + for (boolean[] array : arrays) { + System.arraycopy(array, 0, result, pos, array.length); + pos += array.length; + } + return result; + } + + /** + * Returns an array containing the same values as {@code array}, but guaranteed to be of a + * specified minimum length. If {@code array} already has a length of at least {@code minLength}, + * it is returned directly. Otherwise, a new array of size {@code minLength + padding} is + * returned, containing the values of {@code array}, and zeroes in the remaining places. + * + * @param array the source array + * @param minLength the minimum length the returned array must guarantee + * @param padding an extra amount to "grow" the array by if growth is necessary + * @throws IllegalArgumentException if {@code minLength} or {@code padding} is negative + * @return an array containing the values of {@code array}, with guaranteed minimum length {@code + * minLength} + */ + public static boolean[] ensureCapacity(boolean[] array, int minLength, int padding) { + checkArgument(minLength >= 0, "Invalid minLength: %s", minLength); + checkArgument(padding >= 0, "Invalid padding: %s", padding); + return (array.length < minLength) ? Arrays.copyOf(array, minLength + padding) : array; + } + + /** + * Returns a string containing the supplied {@code boolean} values separated by {@code separator}. + * For example, {@code join("-", false, true, false)} returns the string {@code + * "false-true-false"}. + * + * @param separator the text that should appear between consecutive values in the resulting string + * (but not at the start or end) + * @param array an array of {@code boolean} values, possibly empty + */ + public static String join(String separator, boolean... array) { + checkNotNull(separator); + if (array.length == 0) { + return ""; + } + + // For pre-sizing a builder, just get the right order of magnitude + StringBuilder builder = new StringBuilder(array.length * 7); + builder.append(array[0]); + for (int i = 1; i < array.length; i++) { + builder.append(separator).append(array[i]); + } + return builder.toString(); + } + + /** + * Returns a comparator that compares two {@code boolean} arrays lexicographically. That is, it + * compares, using {@link #compare(boolean, boolean)}), the first pair of values that follow any + * common prefix, or when one array is a prefix of the other, treats the shorter array as the + * lesser. For example, {@code [] < [false] < [false, true] < [true]}. + * + *

The returned comparator is inconsistent with {@link Object#equals(Object)} (since arrays + * support only identity equality), but it is consistent with {@link Arrays#equals(boolean[], + * boolean[])}. + * + * @since 2.0 + */ + public static Comparator lexicographicalComparator() { + return LexicographicalComparator.INSTANCE; + } + + private enum LexicographicalComparator implements Comparator { + INSTANCE; + + @Override + public int compare(boolean[] left, boolean[] right) { + int minLength = Math.min(left.length, right.length); + for (int i = 0; i < minLength; i++) { + int result = Booleans.compare(left[i], right[i]); + if (result != 0) { + return result; + } + } + return left.length - right.length; + } + + @Override + public String toString() { + return "Booleans.lexicographicalComparator()"; + } + } + + /** + * Copies a collection of {@code Boolean} instances into a new array of primitive {@code boolean} + * values. + * + *

Elements are copied from the argument collection as if by {@code collection.toArray()}. + * Calling this method is as thread-safe as calling that method. + * + *

Note: consider representing the collection as a {@link java.util.BitSet} instead. + * + * @param collection a collection of {@code Boolean} objects + * @return an array containing the same values as {@code collection}, in the same order, converted + * to primitives + * @throws NullPointerException if {@code collection} or any of its elements is null + */ + public static boolean[] toArray(Collection collection) { + if (collection instanceof BooleanArrayAsList) { + return ((BooleanArrayAsList) collection).toBooleanArray(); + } + + Object[] boxedArray = collection.toArray(); + int len = boxedArray.length; + boolean[] array = new boolean[len]; + for (int i = 0; i < len; i++) { + // checkNotNull for GWT (do not optimize) + array[i] = (Boolean) checkNotNull(boxedArray[i]); + } + return array; + } + + /** + * Returns a fixed-size list backed by the specified array, similar to {@link + * Arrays#asList(Object[])}. The list supports {@link List#set(int, Object)}, but any attempt to + * set a value to {@code null} will result in a {@link NullPointerException}. + * + *

The returned list maintains the values, but not the identities, of {@code Boolean} objects + * written to or read from it. For example, whether {@code list.get(0) == list.get(0)} is true for + * the returned list is unspecified. + * + * @param backingArray the array to back the list + * @return a list view of the array + */ + public static List asList(boolean... backingArray) { + if (backingArray.length == 0) { + return Collections.emptyList(); + } + return new BooleanArrayAsList(backingArray); + } + + @GwtCompatible + private static class BooleanArrayAsList extends AbstractList + implements RandomAccess, Serializable { + final boolean[] array; + final int start; + final int end; + + BooleanArrayAsList(boolean[] array) { + this(array, 0, array.length); + } + + BooleanArrayAsList(boolean[] array, int start, int end) { + this.array = array; + this.start = start; + this.end = end; + } + + @Override + public int size() { + return end - start; + } + + @Override + public boolean isEmpty() { + return false; + } + + @Override + public Boolean get(int index) { + checkElementIndex(index, size()); + return array[start + index]; + } + + @Override + public boolean contains(Object target) { + // Overridden to prevent a ton of boxing + return (target instanceof Boolean) + && Booleans.indexOf(array, (Boolean) target, start, end) != -1; + } + + @Override + public int indexOf(Object target) { + // Overridden to prevent a ton of boxing + if (target instanceof Boolean) { + int i = Booleans.indexOf(array, (Boolean) target, start, end); + if (i >= 0) { + return i - start; + } + } + return -1; + } + + @Override + public int lastIndexOf(Object target) { + // Overridden to prevent a ton of boxing + if (target instanceof Boolean) { + int i = Booleans.lastIndexOf(array, (Boolean) target, start, end); + if (i >= 0) { + return i - start; + } + } + return -1; + } + + @Override + public Boolean set(int index, Boolean element) { + checkElementIndex(index, size()); + boolean oldValue = array[start + index]; + // checkNotNull for GWT (do not optimize) + array[start + index] = checkNotNull(element); + return oldValue; + } + + @Override + public List subList(int fromIndex, int toIndex) { + int size = size(); + checkPositionIndexes(fromIndex, toIndex, size); + if (fromIndex == toIndex) { + return Collections.emptyList(); + } + return new BooleanArrayAsList(array, start + fromIndex, start + toIndex); + } + + @Override + public boolean equals(Object object) { + if (object == this) { + return true; + } + if (object instanceof BooleanArrayAsList) { + BooleanArrayAsList that = (BooleanArrayAsList) object; + int size = size(); + if (that.size() != size) { + return false; + } + for (int i = 0; i < size; i++) { + if (array[start + i] != that.array[that.start + i]) { + return false; + } + } + return true; + } + return super.equals(object); + } + + @Override + public int hashCode() { + int result = 1; + for (int i = start; i < end; i++) { + result = 31 * result + Booleans.hashCode(array[i]); + } + return result; + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(size() * 7); + builder.append(array[start] ? "[true" : "[false"); + for (int i = start + 1; i < end; i++) { + builder.append(array[i] ? ", true" : ", false"); + } + return builder.append(']').toString(); + } + + boolean[] toBooleanArray() { + return Arrays.copyOfRange(array, start, end); + } + + private static final long serialVersionUID = 0; + } + + /** + * Returns the number of {@code values} that are {@code true}. + * + * @since 16.0 + */ + @Beta + public static int countTrue(boolean... values) { + int count = 0; + for (boolean value : values) { + if (value) { + count++; + } + } + return count; + } + + /** + * Reverses the elements of {@code array}. This is equivalent to {@code + * Collections.reverse(Booleans.asList(array))}, but is likely to be more efficient. + * + * @since 23.1 + */ + public static void reverse(boolean[] array) { + checkNotNull(array); + reverse(array, 0, array.length); + } + + /** + * Reverses the elements of {@code array} between {@code fromIndex} inclusive and {@code toIndex} + * exclusive. This is equivalent to {@code + * Collections.reverse(Booleans.asList(array).subList(fromIndex, toIndex))}, but is likely to be + * more efficient. + * + * @throws IndexOutOfBoundsException if {@code fromIndex < 0}, {@code toIndex > array.length}, or + * {@code toIndex > fromIndex} + * @since 23.1 + */ + public static void reverse(boolean[] array, int fromIndex, int toIndex) { + checkNotNull(array); + checkPositionIndexes(fromIndex, toIndex, array.length); + for (int i = fromIndex, j = toIndex - 1; i < j; i++, j--) { + boolean tmp = array[i]; + array[i] = array[j]; + array[j] = tmp; + } + } +} diff --git a/src/main/java/com/google/common/primitives/Bytes.java b/src/main/java/com/google/common/primitives/Bytes.java new file mode 100644 index 0000000..c6bb949 --- /dev/null +++ b/src/main/java/com/google/common/primitives/Bytes.java @@ -0,0 +1,398 @@ +/* + * Copyright (C) 2008 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.primitives; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkElementIndex; +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.base.Preconditions.checkPositionIndexes; + +import com.google.common.annotations.GwtCompatible; +import java.io.Serializable; +import java.util.AbstractList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.RandomAccess; + + +/** + * Static utility methods pertaining to {@code byte} primitives, that are not already found in + * either {@link Byte} or {@link Arrays}, and interpret bytes as neither signed nor unsigned. + * The methods which specifically treat bytes as signed or unsigned are found in {@link SignedBytes} + * and {@link UnsignedBytes}. + * + *

See the Guava User Guide article on primitive utilities. + * + * @author Kevin Bourrillion + * @since 1.0 + */ +// TODO(kevinb): how to prevent warning on UnsignedBytes when building GWT +// javadoc? +@GwtCompatible +public final class Bytes { + private Bytes() {} + + /** + * Returns a hash code for {@code value}; equal to the result of invoking {@code ((Byte) + * value).hashCode()}. + * + *

Java 8 users: use {@link Byte#hashCode(byte)} instead. + * + * @param value a primitive {@code byte} value + * @return a hash code for the value + */ + public static int hashCode(byte value) { + return value; + } + + /** + * Returns {@code true} if {@code target} is present as an element anywhere in {@code array}. + * + * @param array an array of {@code byte} values, possibly empty + * @param target a primitive {@code byte} value + * @return {@code true} if {@code array[i] == target} for some value of {@code i} + */ + public static boolean contains(byte[] array, byte target) { + for (byte value : array) { + if (value == target) { + return true; + } + } + return false; + } + + /** + * Returns the index of the first appearance of the value {@code target} in {@code array}. + * + * @param array an array of {@code byte} values, possibly empty + * @param target a primitive {@code byte} value + * @return the least index {@code i} for which {@code array[i] == target}, or {@code -1} if no + * such index exists. + */ + public static int indexOf(byte[] array, byte target) { + return indexOf(array, target, 0, array.length); + } + + // TODO(kevinb): consider making this public + private static int indexOf(byte[] array, byte target, int start, int end) { + for (int i = start; i < end; i++) { + if (array[i] == target) { + return i; + } + } + return -1; + } + + /** + * Returns the start position of the first occurrence of the specified {@code target} within + * {@code array}, or {@code -1} if there is no such occurrence. + * + *

More formally, returns the lowest index {@code i} such that {@code Arrays.copyOfRange(array, + * i, i + target.length)} contains exactly the same elements as {@code target}. + * + * @param array the array to search for the sequence {@code target} + * @param target the array to search for as a sub-sequence of {@code array} + */ + public static int indexOf(byte[] array, byte[] target) { + checkNotNull(array, "array"); + checkNotNull(target, "target"); + if (target.length == 0) { + return 0; + } + + outer: + for (int i = 0; i < array.length - target.length + 1; i++) { + for (int j = 0; j < target.length; j++) { + if (array[i + j] != target[j]) { + continue outer; + } + } + return i; + } + return -1; + } + + /** + * Returns the index of the last appearance of the value {@code target} in {@code array}. + * + * @param array an array of {@code byte} values, possibly empty + * @param target a primitive {@code byte} value + * @return the greatest index {@code i} for which {@code array[i] == target}, or {@code -1} if no + * such index exists. + */ + public static int lastIndexOf(byte[] array, byte target) { + return lastIndexOf(array, target, 0, array.length); + } + + // TODO(kevinb): consider making this public + private static int lastIndexOf(byte[] array, byte target, int start, int end) { + for (int i = end - 1; i >= start; i--) { + if (array[i] == target) { + return i; + } + } + return -1; + } + + /** + * Returns the values from each provided array combined into a single array. For example, {@code + * concat(new byte[] {a, b}, new byte[] {}, new byte[] {c}} returns the array {@code {a, b, c}}. + * + * @param arrays zero or more {@code byte} arrays + * @return a single array containing all the values from the source arrays, in order + */ + public static byte[] concat(byte[]... arrays) { + int length = 0; + for (byte[] array : arrays) { + length += array.length; + } + byte[] result = new byte[length]; + int pos = 0; + for (byte[] array : arrays) { + System.arraycopy(array, 0, result, pos, array.length); + pos += array.length; + } + return result; + } + + /** + * Returns an array containing the same values as {@code array}, but guaranteed to be of a + * specified minimum length. If {@code array} already has a length of at least {@code minLength}, + * it is returned directly. Otherwise, a new array of size {@code minLength + padding} is + * returned, containing the values of {@code array}, and zeroes in the remaining places. + * + * @param array the source array + * @param minLength the minimum length the returned array must guarantee + * @param padding an extra amount to "grow" the array by if growth is necessary + * @throws IllegalArgumentException if {@code minLength} or {@code padding} is negative + * @return an array containing the values of {@code array}, with guaranteed minimum length {@code + * minLength} + */ + public static byte[] ensureCapacity(byte[] array, int minLength, int padding) { + checkArgument(minLength >= 0, "Invalid minLength: %s", minLength); + checkArgument(padding >= 0, "Invalid padding: %s", padding); + return (array.length < minLength) ? Arrays.copyOf(array, minLength + padding) : array; + } + + /** + * Returns an array containing each value of {@code collection}, converted to a {@code byte} value + * in the manner of {@link Number#byteValue}. + * + *

Elements are copied from the argument collection as if by {@code collection.toArray()}. + * Calling this method is as thread-safe as calling that method. + * + * @param collection a collection of {@code Number} instances + * @return an array containing the same values as {@code collection}, in the same order, converted + * to primitives + * @throws NullPointerException if {@code collection} or any of its elements is null + * @since 1.0 (parameter was {@code Collection} before 12.0) + */ + public static byte[] toArray(Collection collection) { + if (collection instanceof ByteArrayAsList) { + return ((ByteArrayAsList) collection).toByteArray(); + } + + Object[] boxedArray = collection.toArray(); + int len = boxedArray.length; + byte[] array = new byte[len]; + for (int i = 0; i < len; i++) { + // checkNotNull for GWT (do not optimize) + array[i] = ((Number) checkNotNull(boxedArray[i])).byteValue(); + } + return array; + } + + /** + * Returns a fixed-size list backed by the specified array, similar to {@link + * Arrays#asList(Object[])}. The list supports {@link List#set(int, Object)}, but any attempt to + * set a value to {@code null} will result in a {@link NullPointerException}. + * + *

The returned list maintains the values, but not the identities, of {@code Byte} objects + * written to or read from it. For example, whether {@code list.get(0) == list.get(0)} is true for + * the returned list is unspecified. + * + * @param backingArray the array to back the list + * @return a list view of the array + */ + public static List asList(byte... backingArray) { + if (backingArray.length == 0) { + return Collections.emptyList(); + } + return new ByteArrayAsList(backingArray); + } + + @GwtCompatible + private static class ByteArrayAsList extends AbstractList + implements RandomAccess, Serializable { + final byte[] array; + final int start; + final int end; + + ByteArrayAsList(byte[] array) { + this(array, 0, array.length); + } + + ByteArrayAsList(byte[] array, int start, int end) { + this.array = array; + this.start = start; + this.end = end; + } + + @Override + public int size() { + return end - start; + } + + @Override + public boolean isEmpty() { + return false; + } + + @Override + public Byte get(int index) { + checkElementIndex(index, size()); + return array[start + index]; + } + + @Override + public boolean contains(Object target) { + // Overridden to prevent a ton of boxing + return (target instanceof Byte) && Bytes.indexOf(array, (Byte) target, start, end) != -1; + } + + @Override + public int indexOf(Object target) { + // Overridden to prevent a ton of boxing + if (target instanceof Byte) { + int i = Bytes.indexOf(array, (Byte) target, start, end); + if (i >= 0) { + return i - start; + } + } + return -1; + } + + @Override + public int lastIndexOf(Object target) { + // Overridden to prevent a ton of boxing + if (target instanceof Byte) { + int i = Bytes.lastIndexOf(array, (Byte) target, start, end); + if (i >= 0) { + return i - start; + } + } + return -1; + } + + @Override + public Byte set(int index, Byte element) { + checkElementIndex(index, size()); + byte oldValue = array[start + index]; + // checkNotNull for GWT (do not optimize) + array[start + index] = checkNotNull(element); + return oldValue; + } + + @Override + public List subList(int fromIndex, int toIndex) { + int size = size(); + checkPositionIndexes(fromIndex, toIndex, size); + if (fromIndex == toIndex) { + return Collections.emptyList(); + } + return new ByteArrayAsList(array, start + fromIndex, start + toIndex); + } + + @Override + public boolean equals(Object object) { + if (object == this) { + return true; + } + if (object instanceof ByteArrayAsList) { + ByteArrayAsList that = (ByteArrayAsList) object; + int size = size(); + if (that.size() != size) { + return false; + } + for (int i = 0; i < size; i++) { + if (array[start + i] != that.array[that.start + i]) { + return false; + } + } + return true; + } + return super.equals(object); + } + + @Override + public int hashCode() { + int result = 1; + for (int i = start; i < end; i++) { + result = 31 * result + Bytes.hashCode(array[i]); + } + return result; + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(size() * 5); + builder.append('[').append(array[start]); + for (int i = start + 1; i < end; i++) { + builder.append(", ").append(array[i]); + } + return builder.append(']').toString(); + } + + byte[] toByteArray() { + return Arrays.copyOfRange(array, start, end); + } + + private static final long serialVersionUID = 0; + } + + /** + * Reverses the elements of {@code array}. This is equivalent to {@code + * Collections.reverse(Bytes.asList(array))}, but is likely to be more efficient. + * + * @since 23.1 + */ + public static void reverse(byte[] array) { + checkNotNull(array); + reverse(array, 0, array.length); + } + + /** + * Reverses the elements of {@code array} between {@code fromIndex} inclusive and {@code toIndex} + * exclusive. This is equivalent to {@code + * Collections.reverse(Bytes.asList(array).subList(fromIndex, toIndex))}, but is likely to be more + * efficient. + * + * @throws IndexOutOfBoundsException if {@code fromIndex < 0}, {@code toIndex > array.length}, or + * {@code toIndex > fromIndex} + * @since 23.1 + */ + public static void reverse(byte[] array, int fromIndex, int toIndex) { + checkNotNull(array); + checkPositionIndexes(fromIndex, toIndex, array.length); + for (int i = fromIndex, j = toIndex - 1; i < j; i++, j--) { + byte tmp = array[i]; + array[i] = array[j]; + array[j] = tmp; + } + } +} diff --git a/src/main/java/com/google/common/primitives/Chars.java b/src/main/java/com/google/common/primitives/Chars.java new file mode 100644 index 0000000..8a9ed55 --- /dev/null +++ b/src/main/java/com/google/common/primitives/Chars.java @@ -0,0 +1,638 @@ +/* + * Copyright (C) 2008 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.primitives; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkElementIndex; +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.base.Preconditions.checkPositionIndexes; + +import com.google.common.annotations.Beta; +import com.google.common.annotations.GwtCompatible; +import com.google.common.annotations.GwtIncompatible; +import java.io.Serializable; +import java.util.AbstractList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; +import java.util.RandomAccess; + + +/** + * Static utility methods pertaining to {@code char} primitives, that are not already found in + * either {@link Character} or {@link Arrays}. + * + *

All the operations in this class treat {@code char} values strictly numerically; they are + * neither Unicode-aware nor locale-dependent. + * + *

See the Guava User Guide article on primitive utilities. + * + * @author Kevin Bourrillion + * @since 1.0 + */ +@GwtCompatible(emulated = true) +public final class Chars { + private Chars() {} + + /** + * The number of bytes required to represent a primitive {@code char} value. + * + *

Java 8 users: use {@link Character#BYTES} instead. + */ + public static final int BYTES = Character.SIZE / Byte.SIZE; + + /** + * Returns a hash code for {@code value}; equal to the result of invoking {@code ((Character) + * value).hashCode()}. + * + *

Java 8 users: use {@link Character#hashCode(char)} instead. + * + * @param value a primitive {@code char} value + * @return a hash code for the value + */ + public static int hashCode(char value) { + return value; + } + + /** + * Returns the {@code char} value that is equal to {@code value}, if possible. + * + * @param value any value in the range of the {@code char} type + * @return the {@code char} value that equals {@code value} + * @throws IllegalArgumentException if {@code value} is greater than {@link Character#MAX_VALUE} + * or less than {@link Character#MIN_VALUE} + */ + public static char checkedCast(long value) { + char result = (char) value; + checkArgument(result == value, "Out of range: %s", value); + return result; + } + + /** + * Returns the {@code char} nearest in value to {@code value}. + * + * @param value any {@code long} value + * @return the same value cast to {@code char} if it is in the range of the {@code char} type, + * {@link Character#MAX_VALUE} if it is too large, or {@link Character#MIN_VALUE} if it is too + * small + */ + public static char saturatedCast(long value) { + if (value > Character.MAX_VALUE) { + return Character.MAX_VALUE; + } + if (value < Character.MIN_VALUE) { + return Character.MIN_VALUE; + } + return (char) value; + } + + /** + * Compares the two specified {@code char} values. The sign of the value returned is the same as + * that of {@code ((Character) a).compareTo(b)}. + * + *

Note for Java 7 and later: this method should be treated as deprecated; use the + * equivalent {@link Character#compare} method instead. + * + * @param a the first {@code char} to compare + * @param b the second {@code char} to compare + * @return a negative value if {@code a} is less than {@code b}; a positive value if {@code a} is + * greater than {@code b}; or zero if they are equal + */ + public static int compare(char a, char b) { + return a - b; // safe due to restricted range + } + + /** + * Returns {@code true} if {@code target} is present as an element anywhere in {@code array}. + * + * @param array an array of {@code char} values, possibly empty + * @param target a primitive {@code char} value + * @return {@code true} if {@code array[i] == target} for some value of {@code i} + */ + public static boolean contains(char[] array, char target) { + for (char value : array) { + if (value == target) { + return true; + } + } + return false; + } + + /** + * Returns the index of the first appearance of the value {@code target} in {@code array}. + * + * @param array an array of {@code char} values, possibly empty + * @param target a primitive {@code char} value + * @return the least index {@code i} for which {@code array[i] == target}, or {@code -1} if no + * such index exists. + */ + public static int indexOf(char[] array, char target) { + return indexOf(array, target, 0, array.length); + } + + // TODO(kevinb): consider making this public + private static int indexOf(char[] array, char target, int start, int end) { + for (int i = start; i < end; i++) { + if (array[i] == target) { + return i; + } + } + return -1; + } + + /** + * Returns the start position of the first occurrence of the specified {@code target} within + * {@code array}, or {@code -1} if there is no such occurrence. + * + *

More formally, returns the lowest index {@code i} such that {@code Arrays.copyOfRange(array, + * i, i + target.length)} contains exactly the same elements as {@code target}. + * + * @param array the array to search for the sequence {@code target} + * @param target the array to search for as a sub-sequence of {@code array} + */ + public static int indexOf(char[] array, char[] target) { + checkNotNull(array, "array"); + checkNotNull(target, "target"); + if (target.length == 0) { + return 0; + } + + outer: + for (int i = 0; i < array.length - target.length + 1; i++) { + for (int j = 0; j < target.length; j++) { + if (array[i + j] != target[j]) { + continue outer; + } + } + return i; + } + return -1; + } + + /** + * Returns the index of the last appearance of the value {@code target} in {@code array}. + * + * @param array an array of {@code char} values, possibly empty + * @param target a primitive {@code char} value + * @return the greatest index {@code i} for which {@code array[i] == target}, or {@code -1} if no + * such index exists. + */ + public static int lastIndexOf(char[] array, char target) { + return lastIndexOf(array, target, 0, array.length); + } + + // TODO(kevinb): consider making this public + private static int lastIndexOf(char[] array, char target, int start, int end) { + for (int i = end - 1; i >= start; i--) { + if (array[i] == target) { + return i; + } + } + return -1; + } + + /** + * Returns the least value present in {@code array}. + * + * @param array a nonempty array of {@code char} values + * @return the value present in {@code array} that is less than or equal to every other value in + * the array + * @throws IllegalArgumentException if {@code array} is empty + */ + public static char min(char... array) { + checkArgument(array.length > 0); + char min = array[0]; + for (int i = 1; i < array.length; i++) { + if (array[i] < min) { + min = array[i]; + } + } + return min; + } + + /** + * Returns the greatest value present in {@code array}. + * + * @param array a nonempty array of {@code char} values + * @return the value present in {@code array} that is greater than or equal to every other value + * in the array + * @throws IllegalArgumentException if {@code array} is empty + */ + public static char max(char... array) { + checkArgument(array.length > 0); + char max = array[0]; + for (int i = 1; i < array.length; i++) { + if (array[i] > max) { + max = array[i]; + } + } + return max; + } + + /** + * Returns the value nearest to {@code value} which is within the closed range {@code [min..max]}. + * + *

If {@code value} is within the range {@code [min..max]}, {@code value} is returned + * unchanged. If {@code value} is less than {@code min}, {@code min} is returned, and if {@code + * value} is greater than {@code max}, {@code max} is returned. + * + * @param value the {@code char} value to constrain + * @param min the lower bound (inclusive) of the range to constrain {@code value} to + * @param max the upper bound (inclusive) of the range to constrain {@code value} to + * @throws IllegalArgumentException if {@code min > max} + * @since 21.0 + */ + @Beta + public static char constrainToRange(char value, char min, char max) { + checkArgument(min <= max, "min (%s) must be less than or equal to max (%s)", min, max); + return value < min ? min : value < max ? value : max; + } + + /** + * Returns the values from each provided array combined into a single array. For example, {@code + * concat(new char[] {a, b}, new char[] {}, new char[] {c}} returns the array {@code {a, b, c}}. + * + * @param arrays zero or more {@code char} arrays + * @return a single array containing all the values from the source arrays, in order + */ + public static char[] concat(char[]... arrays) { + int length = 0; + for (char[] array : arrays) { + length += array.length; + } + char[] result = new char[length]; + int pos = 0; + for (char[] array : arrays) { + System.arraycopy(array, 0, result, pos, array.length); + pos += array.length; + } + return result; + } + + /** + * Returns a big-endian representation of {@code value} in a 2-element byte array; equivalent to + * {@code ByteBuffer.allocate(2).putChar(value).array()}. For example, the input value {@code + * '\\u5432'} would yield the byte array {@code {0x54, 0x32}}. + * + *

If you need to convert and concatenate several values (possibly even of different types), + * use a shared {@link java.nio.ByteBuffer} instance, or use {@link + * com.google.common.io.ByteStreams#newDataOutput()} to get a growable buffer. + */ + @GwtIncompatible // doesn't work + public static byte[] toByteArray(char value) { + return new byte[] {(byte) (value >> 8), (byte) value}; + } + + /** + * Returns the {@code char} value whose big-endian representation is stored in the first 2 bytes + * of {@code bytes}; equivalent to {@code ByteBuffer.wrap(bytes).getChar()}. For example, the + * input byte array {@code {0x54, 0x32}} would yield the {@code char} value {@code '\\u5432'}. + * + *

Arguably, it's preferable to use {@link java.nio.ByteBuffer}; that library exposes much more + * flexibility at little cost in readability. + * + * @throws IllegalArgumentException if {@code bytes} has fewer than 2 elements + */ + @GwtIncompatible // doesn't work + public static char fromByteArray(byte[] bytes) { + checkArgument(bytes.length >= BYTES, "array too small: %s < %s", bytes.length, BYTES); + return fromBytes(bytes[0], bytes[1]); + } + + /** + * Returns the {@code char} value whose byte representation is the given 2 bytes, in big-endian + * order; equivalent to {@code Chars.fromByteArray(new byte[] {b1, b2})}. + * + * @since 7.0 + */ + @GwtIncompatible // doesn't work + public static char fromBytes(byte b1, byte b2) { + return (char) ((b1 << 8) | (b2 & 0xFF)); + } + + /** + * Returns an array containing the same values as {@code array}, but guaranteed to be of a + * specified minimum length. If {@code array} already has a length of at least {@code minLength}, + * it is returned directly. Otherwise, a new array of size {@code minLength + padding} is + * returned, containing the values of {@code array}, and zeroes in the remaining places. + * + * @param array the source array + * @param minLength the minimum length the returned array must guarantee + * @param padding an extra amount to "grow" the array by if growth is necessary + * @throws IllegalArgumentException if {@code minLength} or {@code padding} is negative + * @return an array containing the values of {@code array}, with guaranteed minimum length {@code + * minLength} + */ + public static char[] ensureCapacity(char[] array, int minLength, int padding) { + checkArgument(minLength >= 0, "Invalid minLength: %s", minLength); + checkArgument(padding >= 0, "Invalid padding: %s", padding); + return (array.length < minLength) ? Arrays.copyOf(array, minLength + padding) : array; + } + + /** + * Returns a string containing the supplied {@code char} values separated by {@code separator}. + * For example, {@code join("-", '1', '2', '3')} returns the string {@code "1-2-3"}. + * + * @param separator the text that should appear between consecutive values in the resulting string + * (but not at the start or end) + * @param array an array of {@code char} values, possibly empty + */ + public static String join(String separator, char... array) { + checkNotNull(separator); + int len = array.length; + if (len == 0) { + return ""; + } + + StringBuilder builder = new StringBuilder(len + separator.length() * (len - 1)); + builder.append(array[0]); + for (int i = 1; i < len; i++) { + builder.append(separator).append(array[i]); + } + return builder.toString(); + } + + /** + * Returns a comparator that compares two {@code char} arrays lexicographically; not advisable + * for sorting user-visible strings as the ordering may not match the conventions of the user's + * locale. That is, it compares, using {@link #compare(char, char)}), the first pair of values + * that follow any common prefix, or when one array is a prefix of the other, treats the shorter + * array as the lesser. For example, {@code [] < ['a'] < ['a', 'b'] < ['b']}. + * + *

The returned comparator is inconsistent with {@link Object#equals(Object)} (since arrays + * support only identity equality), but it is consistent with {@link Arrays#equals(char[], + * char[])}. + * + * @since 2.0 + */ + public static Comparator lexicographicalComparator() { + return LexicographicalComparator.INSTANCE; + } + + private enum LexicographicalComparator implements Comparator { + INSTANCE; + + @Override + public int compare(char[] left, char[] right) { + int minLength = Math.min(left.length, right.length); + for (int i = 0; i < minLength; i++) { + int result = Chars.compare(left[i], right[i]); + if (result != 0) { + return result; + } + } + return left.length - right.length; + } + + @Override + public String toString() { + return "Chars.lexicographicalComparator()"; + } + } + + /** + * Copies a collection of {@code Character} instances into a new array of primitive {@code char} + * values. + * + *

Elements are copied from the argument collection as if by {@code collection.toArray()}. + * Calling this method is as thread-safe as calling that method. + * + * @param collection a collection of {@code Character} objects + * @return an array containing the same values as {@code collection}, in the same order, converted + * to primitives + * @throws NullPointerException if {@code collection} or any of its elements is null + */ + public static char[] toArray(Collection collection) { + if (collection instanceof CharArrayAsList) { + return ((CharArrayAsList) collection).toCharArray(); + } + + Object[] boxedArray = collection.toArray(); + int len = boxedArray.length; + char[] array = new char[len]; + for (int i = 0; i < len; i++) { + // checkNotNull for GWT (do not optimize) + array[i] = (Character) checkNotNull(boxedArray[i]); + } + return array; + } + + /** + * Sorts the elements of {@code array} in descending order. + * + * @since 23.1 + */ + public static void sortDescending(char[] array) { + checkNotNull(array); + sortDescending(array, 0, array.length); + } + + /** + * Sorts the elements of {@code array} between {@code fromIndex} inclusive and {@code toIndex} + * exclusive in descending order. + * + * @since 23.1 + */ + public static void sortDescending(char[] array, int fromIndex, int toIndex) { + checkNotNull(array); + checkPositionIndexes(fromIndex, toIndex, array.length); + Arrays.sort(array, fromIndex, toIndex); + reverse(array, fromIndex, toIndex); + } + + /** + * Reverses the elements of {@code array}. This is equivalent to {@code + * Collections.reverse(Chars.asList(array))}, but is likely to be more efficient. + * + * @since 23.1 + */ + public static void reverse(char[] array) { + checkNotNull(array); + reverse(array, 0, array.length); + } + + /** + * Reverses the elements of {@code array} between {@code fromIndex} inclusive and {@code toIndex} + * exclusive. This is equivalent to {@code + * Collections.reverse(Chars.asList(array).subList(fromIndex, toIndex))}, but is likely to be more + * efficient. + * + * @throws IndexOutOfBoundsException if {@code fromIndex < 0}, {@code toIndex > array.length}, or + * {@code toIndex > fromIndex} + * @since 23.1 + */ + public static void reverse(char[] array, int fromIndex, int toIndex) { + checkNotNull(array); + checkPositionIndexes(fromIndex, toIndex, array.length); + for (int i = fromIndex, j = toIndex - 1; i < j; i++, j--) { + char tmp = array[i]; + array[i] = array[j]; + array[j] = tmp; + } + } + + /** + * Returns a fixed-size list backed by the specified array, similar to {@link + * Arrays#asList(Object[])}. The list supports {@link List#set(int, Object)}, but any attempt to + * set a value to {@code null} will result in a {@link NullPointerException}. + * + *

The returned list maintains the values, but not the identities, of {@code Character} objects + * written to or read from it. For example, whether {@code list.get(0) == list.get(0)} is true for + * the returned list is unspecified. + * + * @param backingArray the array to back the list + * @return a list view of the array + */ + public static List asList(char... backingArray) { + if (backingArray.length == 0) { + return Collections.emptyList(); + } + return new CharArrayAsList(backingArray); + } + + @GwtCompatible + private static class CharArrayAsList extends AbstractList + implements RandomAccess, Serializable { + final char[] array; + final int start; + final int end; + + CharArrayAsList(char[] array) { + this(array, 0, array.length); + } + + CharArrayAsList(char[] array, int start, int end) { + this.array = array; + this.start = start; + this.end = end; + } + + @Override + public int size() { + return end - start; + } + + @Override + public boolean isEmpty() { + return false; + } + + @Override + public Character get(int index) { + checkElementIndex(index, size()); + return array[start + index]; + } + + @Override + public boolean contains(Object target) { + // Overridden to prevent a ton of boxing + return (target instanceof Character) + && Chars.indexOf(array, (Character) target, start, end) != -1; + } + + @Override + public int indexOf(Object target) { + // Overridden to prevent a ton of boxing + if (target instanceof Character) { + int i = Chars.indexOf(array, (Character) target, start, end); + if (i >= 0) { + return i - start; + } + } + return -1; + } + + @Override + public int lastIndexOf(Object target) { + // Overridden to prevent a ton of boxing + if (target instanceof Character) { + int i = Chars.lastIndexOf(array, (Character) target, start, end); + if (i >= 0) { + return i - start; + } + } + return -1; + } + + @Override + public Character set(int index, Character element) { + checkElementIndex(index, size()); + char oldValue = array[start + index]; + // checkNotNull for GWT (do not optimize) + array[start + index] = checkNotNull(element); + return oldValue; + } + + @Override + public List subList(int fromIndex, int toIndex) { + int size = size(); + checkPositionIndexes(fromIndex, toIndex, size); + if (fromIndex == toIndex) { + return Collections.emptyList(); + } + return new CharArrayAsList(array, start + fromIndex, start + toIndex); + } + + @Override + public boolean equals(Object object) { + if (object == this) { + return true; + } + if (object instanceof CharArrayAsList) { + CharArrayAsList that = (CharArrayAsList) object; + int size = size(); + if (that.size() != size) { + return false; + } + for (int i = 0; i < size; i++) { + if (array[start + i] != that.array[that.start + i]) { + return false; + } + } + return true; + } + return super.equals(object); + } + + @Override + public int hashCode() { + int result = 1; + for (int i = start; i < end; i++) { + result = 31 * result + Chars.hashCode(array[i]); + } + return result; + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(size() * 3); + builder.append('[').append(array[start]); + for (int i = start + 1; i < end; i++) { + builder.append(", ").append(array[i]); + } + return builder.append(']').toString(); + } + + char[] toCharArray() { + return Arrays.copyOfRange(array, start, end); + } + + private static final long serialVersionUID = 0; + } +} diff --git a/src/main/java/com/google/common/primitives/Doubles.java b/src/main/java/com/google/common/primitives/Doubles.java new file mode 100644 index 0000000..7637966 --- /dev/null +++ b/src/main/java/com/google/common/primitives/Doubles.java @@ -0,0 +1,715 @@ +/* + * Copyright (C) 2008 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.primitives; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkElementIndex; +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.base.Preconditions.checkPositionIndexes; +import static java.lang.Double.NEGATIVE_INFINITY; +import static java.lang.Double.POSITIVE_INFINITY; + +import com.google.common.annotations.Beta; +import com.google.common.annotations.GwtCompatible; +import com.google.common.annotations.GwtIncompatible; +import com.google.common.base.Converter; +import java.io.Serializable; +import java.util.AbstractList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; +import java.util.RandomAccess; +import java.util.Spliterator; +import java.util.Spliterators; + + +/** + * Static utility methods pertaining to {@code double} primitives, that are not already found in + * either {@link Double} or {@link Arrays}. + * + *

See the Guava User Guide article on primitive utilities. + * + * @author Kevin Bourrillion + * @since 1.0 + */ +@GwtCompatible(emulated = true) +public final class Doubles { + private Doubles() {} + + /** + * The number of bytes required to represent a primitive {@code double} value. + * + *

Java 8 users: use {@link Double#BYTES} instead. + * + * @since 10.0 + */ + public static final int BYTES = Double.SIZE / Byte.SIZE; + + /** + * Returns a hash code for {@code value}; equal to the result of invoking {@code ((Double) + * value).hashCode()}. + * + *

Java 8 users: use {@link Double#hashCode(double)} instead. + * + * @param value a primitive {@code double} value + * @return a hash code for the value + */ + public static int hashCode(double value) { + return ((Double) value).hashCode(); + // TODO(kevinb): do it this way when we can (GWT problem): + // long bits = Double.doubleToLongBits(value); + // return (int) (bits ^ (bits >>> 32)); + } + + /** + * Compares the two specified {@code double} values. The sign of the value returned is the same as + * that of ((Double) a).{@linkplain Double#compareTo compareTo}(b). As with that + * method, {@code NaN} is treated as greater than all other values, and {@code 0.0 > -0.0}. + * + *

Note: this method simply delegates to the JDK method {@link Double#compare}. It is + * provided for consistency with the other primitive types, whose compare methods were not added + * to the JDK until JDK 7. + * + * @param a the first {@code double} to compare + * @param b the second {@code double} to compare + * @return a negative value if {@code a} is less than {@code b}; a positive value if {@code a} is + * greater than {@code b}; or zero if they are equal + */ + public static int compare(double a, double b) { + return Double.compare(a, b); + } + + /** + * Returns {@code true} if {@code value} represents a real number. This is equivalent to, but not + * necessarily implemented as, {@code !(Double.isInfinite(value) || Double.isNaN(value))}. + * + *

Java 8 users: use {@link Double#isFinite(double)} instead. + * + * @since 10.0 + */ + public static boolean isFinite(double value) { + return NEGATIVE_INFINITY < value && value < POSITIVE_INFINITY; + } + + /** + * Returns {@code true} if {@code target} is present as an element anywhere in {@code array}. Note + * that this always returns {@code false} when {@code target} is {@code NaN}. + * + * @param array an array of {@code double} values, possibly empty + * @param target a primitive {@code double} value + * @return {@code true} if {@code array[i] == target} for some value of {@code i} + */ + public static boolean contains(double[] array, double target) { + for (double value : array) { + if (value == target) { + return true; + } + } + return false; + } + + /** + * Returns the index of the first appearance of the value {@code target} in {@code array}. Note + * that this always returns {@code -1} when {@code target} is {@code NaN}. + * + * @param array an array of {@code double} values, possibly empty + * @param target a primitive {@code double} value + * @return the least index {@code i} for which {@code array[i] == target}, or {@code -1} if no + * such index exists. + */ + public static int indexOf(double[] array, double target) { + return indexOf(array, target, 0, array.length); + } + + // TODO(kevinb): consider making this public + private static int indexOf(double[] array, double target, int start, int end) { + for (int i = start; i < end; i++) { + if (array[i] == target) { + return i; + } + } + return -1; + } + + /** + * Returns the start position of the first occurrence of the specified {@code target} within + * {@code array}, or {@code -1} if there is no such occurrence. + * + *

More formally, returns the lowest index {@code i} such that {@code Arrays.copyOfRange(array, + * i, i + target.length)} contains exactly the same elements as {@code target}. + * + *

Note that this always returns {@code -1} when {@code target} contains {@code NaN}. + * + * @param array the array to search for the sequence {@code target} + * @param target the array to search for as a sub-sequence of {@code array} + */ + public static int indexOf(double[] array, double[] target) { + checkNotNull(array, "array"); + checkNotNull(target, "target"); + if (target.length == 0) { + return 0; + } + + outer: + for (int i = 0; i < array.length - target.length + 1; i++) { + for (int j = 0; j < target.length; j++) { + if (array[i + j] != target[j]) { + continue outer; + } + } + return i; + } + return -1; + } + + /** + * Returns the index of the last appearance of the value {@code target} in {@code array}. Note + * that this always returns {@code -1} when {@code target} is {@code NaN}. + * + * @param array an array of {@code double} values, possibly empty + * @param target a primitive {@code double} value + * @return the greatest index {@code i} for which {@code array[i] == target}, or {@code -1} if no + * such index exists. + */ + public static int lastIndexOf(double[] array, double target) { + return lastIndexOf(array, target, 0, array.length); + } + + // TODO(kevinb): consider making this public + private static int lastIndexOf(double[] array, double target, int start, int end) { + for (int i = end - 1; i >= start; i--) { + if (array[i] == target) { + return i; + } + } + return -1; + } + + /** + * Returns the least value present in {@code array}, using the same rules of comparison as {@link + * Math#min(double, double)}. + * + * @param array a nonempty array of {@code double} values + * @return the value present in {@code array} that is less than or equal to every other value in + * the array + * @throws IllegalArgumentException if {@code array} is empty + */ + public static double min(double... array) { + checkArgument(array.length > 0); + double min = array[0]; + for (int i = 1; i < array.length; i++) { + min = Math.min(min, array[i]); + } + return min; + } + + /** + * Returns the greatest value present in {@code array}, using the same rules of comparison as + * {@link Math#max(double, double)}. + * + * @param array a nonempty array of {@code double} values + * @return the value present in {@code array} that is greater than or equal to every other value + * in the array + * @throws IllegalArgumentException if {@code array} is empty + */ + public static double max(double... array) { + checkArgument(array.length > 0); + double max = array[0]; + for (int i = 1; i < array.length; i++) { + max = Math.max(max, array[i]); + } + return max; + } + + /** + * Returns the value nearest to {@code value} which is within the closed range {@code [min..max]}. + * + *

If {@code value} is within the range {@code [min..max]}, {@code value} is returned + * unchanged. If {@code value} is less than {@code min}, {@code min} is returned, and if {@code + * value} is greater than {@code max}, {@code max} is returned. + * + * @param value the {@code double} value to constrain + * @param min the lower bound (inclusive) of the range to constrain {@code value} to + * @param max the upper bound (inclusive) of the range to constrain {@code value} to + * @throws IllegalArgumentException if {@code min > max} + * @since 21.0 + */ + @Beta + public static double constrainToRange(double value, double min, double max) { + checkArgument(min <= max, "min (%s) must be less than or equal to max (%s)", min, max); + return Math.min(Math.max(value, min), max); + } + + /** + * Returns the values from each provided array combined into a single array. For example, {@code + * concat(new double[] {a, b}, new double[] {}, new double[] {c}} returns the array {@code {a, b, + * c}}. + * + * @param arrays zero or more {@code double} arrays + * @return a single array containing all the values from the source arrays, in order + */ + public static double[] concat(double[]... arrays) { + int length = 0; + for (double[] array : arrays) { + length += array.length; + } + double[] result = new double[length]; + int pos = 0; + for (double[] array : arrays) { + System.arraycopy(array, 0, result, pos, array.length); + pos += array.length; + } + return result; + } + + private static final class DoubleConverter extends Converter + implements Serializable { + static final DoubleConverter INSTANCE = new DoubleConverter(); + + @Override + protected Double doForward(String value) { + return Double.valueOf(value); + } + + @Override + protected String doBackward(Double value) { + return value.toString(); + } + + @Override + public String toString() { + return "Doubles.stringConverter()"; + } + + private Object readResolve() { + return INSTANCE; + } + + private static final long serialVersionUID = 1; + } + + /** + * Returns a serializable converter object that converts between strings and doubles using {@link + * Double#valueOf} and {@link Double#toString()}. + * + * @since 16.0 + */ + @Beta + public static Converter stringConverter() { + return DoubleConverter.INSTANCE; + } + + /** + * Returns an array containing the same values as {@code array}, but guaranteed to be of a + * specified minimum length. If {@code array} already has a length of at least {@code minLength}, + * it is returned directly. Otherwise, a new array of size {@code minLength + padding} is + * returned, containing the values of {@code array}, and zeroes in the remaining places. + * + * @param array the source array + * @param minLength the minimum length the returned array must guarantee + * @param padding an extra amount to "grow" the array by if growth is necessary + * @throws IllegalArgumentException if {@code minLength} or {@code padding} is negative + * @return an array containing the values of {@code array}, with guaranteed minimum length {@code + * minLength} + */ + public static double[] ensureCapacity(double[] array, int minLength, int padding) { + checkArgument(minLength >= 0, "Invalid minLength: %s", minLength); + checkArgument(padding >= 0, "Invalid padding: %s", padding); + return (array.length < minLength) ? Arrays.copyOf(array, minLength + padding) : array; + } + + /** + * Returns a string containing the supplied {@code double} values, converted to strings as + * specified by {@link Double#toString(double)}, and separated by {@code separator}. For example, + * {@code join("-", 1.0, 2.0, 3.0)} returns the string {@code "1.0-2.0-3.0"}. + * + *

Note that {@link Double#toString(double)} formats {@code double} differently in GWT + * sometimes. In the previous example, it returns the string {@code "1-2-3"}. + * + * @param separator the text that should appear between consecutive values in the resulting string + * (but not at the start or end) + * @param array an array of {@code double} values, possibly empty + */ + public static String join(String separator, double... array) { + checkNotNull(separator); + if (array.length == 0) { + return ""; + } + + // For pre-sizing a builder, just get the right order of magnitude + StringBuilder builder = new StringBuilder(array.length * 12); + builder.append(array[0]); + for (int i = 1; i < array.length; i++) { + builder.append(separator).append(array[i]); + } + return builder.toString(); + } + + /** + * Returns a comparator that compares two {@code double} arrays lexicographically. That is, it + * compares, using {@link #compare(double, double)}), the first pair of values that follow any + * common prefix, or when one array is a prefix of the other, treats the shorter array as the + * lesser. For example, {@code [] < [1.0] < [1.0, 2.0] < [2.0]}. + * + *

The returned comparator is inconsistent with {@link Object#equals(Object)} (since arrays + * support only identity equality), but it is consistent with {@link Arrays#equals(double[], + * double[])}. + * + * @since 2.0 + */ + public static Comparator lexicographicalComparator() { + return LexicographicalComparator.INSTANCE; + } + + private enum LexicographicalComparator implements Comparator { + INSTANCE; + + @Override + public int compare(double[] left, double[] right) { + int minLength = Math.min(left.length, right.length); + for (int i = 0; i < minLength; i++) { + int result = Double.compare(left[i], right[i]); + if (result != 0) { + return result; + } + } + return left.length - right.length; + } + + @Override + public String toString() { + return "Doubles.lexicographicalComparator()"; + } + } + + /** + * Sorts the elements of {@code array} in descending order. + * + *

Note that this method uses the total order imposed by {@link Double#compare}, which treats + * all NaN values as equal and 0.0 as greater than -0.0. + * + * @since 23.1 + */ + public static void sortDescending(double[] array) { + checkNotNull(array); + sortDescending(array, 0, array.length); + } + + /** + * Sorts the elements of {@code array} between {@code fromIndex} inclusive and {@code toIndex} + * exclusive in descending order. + * + *

Note that this method uses the total order imposed by {@link Double#compare}, which treats + * all NaN values as equal and 0.0 as greater than -0.0. + * + * @since 23.1 + */ + public static void sortDescending(double[] array, int fromIndex, int toIndex) { + checkNotNull(array); + checkPositionIndexes(fromIndex, toIndex, array.length); + Arrays.sort(array, fromIndex, toIndex); + reverse(array, fromIndex, toIndex); + } + + /** + * Reverses the elements of {@code array}. This is equivalent to {@code + * Collections.reverse(Doubles.asList(array))}, but is likely to be more efficient. + * + * @since 23.1 + */ + public static void reverse(double[] array) { + checkNotNull(array); + reverse(array, 0, array.length); + } + + /** + * Reverses the elements of {@code array} between {@code fromIndex} inclusive and {@code toIndex} + * exclusive. This is equivalent to {@code + * Collections.reverse(Doubles.asList(array).subList(fromIndex, toIndex))}, but is likely to be + * more efficient. + * + * @throws IndexOutOfBoundsException if {@code fromIndex < 0}, {@code toIndex > array.length}, or + * {@code toIndex > fromIndex} + * @since 23.1 + */ + public static void reverse(double[] array, int fromIndex, int toIndex) { + checkNotNull(array); + checkPositionIndexes(fromIndex, toIndex, array.length); + for (int i = fromIndex, j = toIndex - 1; i < j; i++, j--) { + double tmp = array[i]; + array[i] = array[j]; + array[j] = tmp; + } + } + + /** + * Returns an array containing each value of {@code collection}, converted to a {@code double} + * value in the manner of {@link Number#doubleValue}. + * + *

Elements are copied from the argument collection as if by {@code collection.toArray()}. + * Calling this method is as thread-safe as calling that method. + * + * @param collection a collection of {@code Number} instances + * @return an array containing the same values as {@code collection}, in the same order, converted + * to primitives + * @throws NullPointerException if {@code collection} or any of its elements is null + * @since 1.0 (parameter was {@code Collection} before 12.0) + */ + public static double[] toArray(Collection collection) { + if (collection instanceof DoubleArrayAsList) { + return ((DoubleArrayAsList) collection).toDoubleArray(); + } + + Object[] boxedArray = collection.toArray(); + int len = boxedArray.length; + double[] array = new double[len]; + for (int i = 0; i < len; i++) { + // checkNotNull for GWT (do not optimize) + array[i] = ((Number) checkNotNull(boxedArray[i])).doubleValue(); + } + return array; + } + + /** + * Returns a fixed-size list backed by the specified array, similar to {@link + * Arrays#asList(Object[])}. The list supports {@link List#set(int, Object)}, but any attempt to + * set a value to {@code null} will result in a {@link NullPointerException}. + * + *

The returned list maintains the values, but not the identities, of {@code Double} objects + * written to or read from it. For example, whether {@code list.get(0) == list.get(0)} is true for + * the returned list is unspecified. + * + *

The returned list may have unexpected behavior if it contains {@code NaN}, or if {@code NaN} + * is used as a parameter to any of its methods. + * + *

Note: when possible, you should represent your data as an {@link + * ImmutableDoubleArray} instead, which has an {@link ImmutableDoubleArray#asList asList} view. + * + * @param backingArray the array to back the list + * @return a list view of the array + */ + public static List asList(double... backingArray) { + if (backingArray.length == 0) { + return Collections.emptyList(); + } + return new DoubleArrayAsList(backingArray); + } + + @GwtCompatible + private static class DoubleArrayAsList extends AbstractList + implements RandomAccess, Serializable { + final double[] array; + final int start; + final int end; + + DoubleArrayAsList(double[] array) { + this(array, 0, array.length); + } + + DoubleArrayAsList(double[] array, int start, int end) { + this.array = array; + this.start = start; + this.end = end; + } + + @Override + public int size() { + return end - start; + } + + @Override + public boolean isEmpty() { + return false; + } + + @Override + public Double get(int index) { + checkElementIndex(index, size()); + return array[start + index]; + } + + @Override + public Spliterator.OfDouble spliterator() { + return Spliterators.spliterator(array, start, end, 0); + } + + @Override + public boolean contains(Object target) { + // Overridden to prevent a ton of boxing + return (target instanceof Double) + && Doubles.indexOf(array, (Double) target, start, end) != -1; + } + + @Override + public int indexOf(Object target) { + // Overridden to prevent a ton of boxing + if (target instanceof Double) { + int i = Doubles.indexOf(array, (Double) target, start, end); + if (i >= 0) { + return i - start; + } + } + return -1; + } + + @Override + public int lastIndexOf(Object target) { + // Overridden to prevent a ton of boxing + if (target instanceof Double) { + int i = Doubles.lastIndexOf(array, (Double) target, start, end); + if (i >= 0) { + return i - start; + } + } + return -1; + } + + @Override + public Double set(int index, Double element) { + checkElementIndex(index, size()); + double oldValue = array[start + index]; + // checkNotNull for GWT (do not optimize) + array[start + index] = checkNotNull(element); + return oldValue; + } + + @Override + public List subList(int fromIndex, int toIndex) { + int size = size(); + checkPositionIndexes(fromIndex, toIndex, size); + if (fromIndex == toIndex) { + return Collections.emptyList(); + } + return new DoubleArrayAsList(array, start + fromIndex, start + toIndex); + } + + @Override + public boolean equals(Object object) { + if (object == this) { + return true; + } + if (object instanceof DoubleArrayAsList) { + DoubleArrayAsList that = (DoubleArrayAsList) object; + int size = size(); + if (that.size() != size) { + return false; + } + for (int i = 0; i < size; i++) { + if (array[start + i] != that.array[that.start + i]) { + return false; + } + } + return true; + } + return super.equals(object); + } + + @Override + public int hashCode() { + int result = 1; + for (int i = start; i < end; i++) { + result = 31 * result + Doubles.hashCode(array[i]); + } + return result; + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(size() * 12); + builder.append('[').append(array[start]); + for (int i = start + 1; i < end; i++) { + builder.append(", ").append(array[i]); + } + return builder.append(']').toString(); + } + + double[] toDoubleArray() { + return Arrays.copyOfRange(array, start, end); + } + + private static final long serialVersionUID = 0; + } + + /** + * This is adapted from the regex suggested by {@link Double#valueOf(String)} for prevalidating + * inputs. All valid inputs must pass this regex, but it's semantically fine if not all inputs + * that pass this regex are valid -- only a performance hit is incurred, not a semantics bug. + */ + @GwtIncompatible // regular expressions + static final + java.util.regex.Pattern + FLOATING_POINT_PATTERN = fpPattern(); + + @GwtIncompatible // regular expressions + private static + java.util.regex.Pattern + fpPattern() { + /* + * We use # instead of * for possessive quantifiers. This lets us strip them out when building + * the regex for RE2 (which doesn't support them) but leave them in when building it for + * java.util.regex (where we want them in order to avoid catastrophic backtracking). + */ + String decimal = "(?:\\d+#(?:\\.\\d*#)?|\\.\\d+#)"; + String completeDec = decimal + "(?:[eE][+-]?\\d+#)?[fFdD]?"; + String hex = "(?:[0-9a-fA-F]+#(?:\\.[0-9a-fA-F]*#)?|\\.[0-9a-fA-F]+#)"; + String completeHex = "0[xX]" + hex + "[pP][+-]?\\d+#[fFdD]?"; + String fpPattern = "[+-]?(?:NaN|Infinity|" + completeDec + "|" + completeHex + ")"; + fpPattern = + fpPattern.replace( + "#", + "+" + ); + return + java.util.regex.Pattern + .compile(fpPattern); + } + + /** + * Parses the specified string as a double-precision floating point value. The ASCII character + * {@code '-'} ('\u002D') is recognized as the minus sign. + * + *

Unlike {@link Double#parseDouble(String)}, this method returns {@code null} instead of + * throwing an exception if parsing fails. Valid inputs are exactly those accepted by {@link + * Double#valueOf(String)}, except that leading and trailing whitespace is not permitted. + * + *

This implementation is likely to be faster than {@code Double.parseDouble} if many failures + * are expected. + * + * @param string the string representation of a {@code double} value + * @return the floating point value represented by {@code string}, or {@code null} if {@code + * string} has a length of zero or cannot be parsed as a {@code double} value + * @throws NullPointerException if {@code string} is {@code null} + * @since 14.0 + */ + @Beta + @GwtIncompatible // regular expressions + public static Double tryParse(String string) { + if (FLOATING_POINT_PATTERN.matcher(string).matches()) { + // TODO(lowasser): could be potentially optimized, but only with + // extensive testing + try { + return Double.parseDouble(string); + } catch (NumberFormatException e) { + // Double.parseDouble has changed specs several times, so fall through + // gracefully + } + } + return null; + } +} diff --git a/src/main/java/com/google/common/primitives/Floats.java b/src/main/java/com/google/common/primitives/Floats.java new file mode 100644 index 0000000..d5e136e --- /dev/null +++ b/src/main/java/com/google/common/primitives/Floats.java @@ -0,0 +1,667 @@ +/* + * Copyright (C) 2008 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.primitives; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkElementIndex; +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.base.Preconditions.checkPositionIndexes; +import static java.lang.Float.NEGATIVE_INFINITY; +import static java.lang.Float.POSITIVE_INFINITY; + +import com.google.common.annotations.Beta; +import com.google.common.annotations.GwtCompatible; +import com.google.common.annotations.GwtIncompatible; +import com.google.common.base.Converter; +import java.io.Serializable; +import java.util.AbstractList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; +import java.util.RandomAccess; + + +/** + * Static utility methods pertaining to {@code float} primitives, that are not already found in + * either {@link Float} or {@link Arrays}. + * + *

See the Guava User Guide article on primitive utilities. + * + * @author Kevin Bourrillion + * @since 1.0 + */ +@GwtCompatible(emulated = true) +public final class Floats { + private Floats() {} + + /** + * The number of bytes required to represent a primitive {@code float} value. + * + *

Java 8 users: use {@link Float#BYTES} instead. + * + * @since 10.0 + */ + public static final int BYTES = Float.SIZE / Byte.SIZE; + + /** + * Returns a hash code for {@code value}; equal to the result of invoking {@code ((Float) + * value).hashCode()}. + * + *

Java 8 users: use {@link Float#hashCode(float)} instead. + * + * @param value a primitive {@code float} value + * @return a hash code for the value + */ + public static int hashCode(float value) { + // TODO(kevinb): is there a better way, that's still gwt-safe? + return ((Float) value).hashCode(); + } + + /** + * Compares the two specified {@code float} values using {@link Float#compare(float, float)}. You + * may prefer to invoke that method directly; this method exists only for consistency with the + * other utilities in this package. + * + *

Note: this method simply delegates to the JDK method {@link Float#compare}. It is + * provided for consistency with the other primitive types, whose compare methods were not added + * to the JDK until JDK 7. + * + * @param a the first {@code float} to compare + * @param b the second {@code float} to compare + * @return the result of invoking {@link Float#compare(float, float)} + */ + public static int compare(float a, float b) { + return Float.compare(a, b); + } + + /** + * Returns {@code true} if {@code value} represents a real number. This is equivalent to, but not + * necessarily implemented as, {@code !(Float.isInfinite(value) || Float.isNaN(value))}. + * + *

Java 8 users: use {@link Float#isFinite(float)} instead. + * + * @since 10.0 + */ + public static boolean isFinite(float value) { + return NEGATIVE_INFINITY < value && value < POSITIVE_INFINITY; + } + + /** + * Returns {@code true} if {@code target} is present as an element anywhere in {@code array}. Note + * that this always returns {@code false} when {@code target} is {@code NaN}. + * + * @param array an array of {@code float} values, possibly empty + * @param target a primitive {@code float} value + * @return {@code true} if {@code array[i] == target} for some value of {@code i} + */ + public static boolean contains(float[] array, float target) { + for (float value : array) { + if (value == target) { + return true; + } + } + return false; + } + + /** + * Returns the index of the first appearance of the value {@code target} in {@code array}. Note + * that this always returns {@code -1} when {@code target} is {@code NaN}. + * + * @param array an array of {@code float} values, possibly empty + * @param target a primitive {@code float} value + * @return the least index {@code i} for which {@code array[i] == target}, or {@code -1} if no + * such index exists. + */ + public static int indexOf(float[] array, float target) { + return indexOf(array, target, 0, array.length); + } + + // TODO(kevinb): consider making this public + private static int indexOf(float[] array, float target, int start, int end) { + for (int i = start; i < end; i++) { + if (array[i] == target) { + return i; + } + } + return -1; + } + + /** + * Returns the start position of the first occurrence of the specified {@code target} within + * {@code array}, or {@code -1} if there is no such occurrence. + * + *

More formally, returns the lowest index {@code i} such that {@code Arrays.copyOfRange(array, + * i, i + target.length)} contains exactly the same elements as {@code target}. + * + *

Note that this always returns {@code -1} when {@code target} contains {@code NaN}. + * + * @param array the array to search for the sequence {@code target} + * @param target the array to search for as a sub-sequence of {@code array} + */ + public static int indexOf(float[] array, float[] target) { + checkNotNull(array, "array"); + checkNotNull(target, "target"); + if (target.length == 0) { + return 0; + } + + outer: + for (int i = 0; i < array.length - target.length + 1; i++) { + for (int j = 0; j < target.length; j++) { + if (array[i + j] != target[j]) { + continue outer; + } + } + return i; + } + return -1; + } + + /** + * Returns the index of the last appearance of the value {@code target} in {@code array}. Note + * that this always returns {@code -1} when {@code target} is {@code NaN}. + * + * @param array an array of {@code float} values, possibly empty + * @param target a primitive {@code float} value + * @return the greatest index {@code i} for which {@code array[i] == target}, or {@code -1} if no + * such index exists. + */ + public static int lastIndexOf(float[] array, float target) { + return lastIndexOf(array, target, 0, array.length); + } + + // TODO(kevinb): consider making this public + private static int lastIndexOf(float[] array, float target, int start, int end) { + for (int i = end - 1; i >= start; i--) { + if (array[i] == target) { + return i; + } + } + return -1; + } + + /** + * Returns the least value present in {@code array}, using the same rules of comparison as {@link + * Math#min(float, float)}. + * + * @param array a nonempty array of {@code float} values + * @return the value present in {@code array} that is less than or equal to every other value in + * the array + * @throws IllegalArgumentException if {@code array} is empty + */ + public static float min(float... array) { + checkArgument(array.length > 0); + float min = array[0]; + for (int i = 1; i < array.length; i++) { + min = Math.min(min, array[i]); + } + return min; + } + + /** + * Returns the greatest value present in {@code array}, using the same rules of comparison as + * {@link Math#max(float, float)}. + * + * @param array a nonempty array of {@code float} values + * @return the value present in {@code array} that is greater than or equal to every other value + * in the array + * @throws IllegalArgumentException if {@code array} is empty + */ + public static float max(float... array) { + checkArgument(array.length > 0); + float max = array[0]; + for (int i = 1; i < array.length; i++) { + max = Math.max(max, array[i]); + } + return max; + } + + /** + * Returns the value nearest to {@code value} which is within the closed range {@code [min..max]}. + * + *

If {@code value} is within the range {@code [min..max]}, {@code value} is returned + * unchanged. If {@code value} is less than {@code min}, {@code min} is returned, and if {@code + * value} is greater than {@code max}, {@code max} is returned. + * + * @param value the {@code float} value to constrain + * @param min the lower bound (inclusive) of the range to constrain {@code value} to + * @param max the upper bound (inclusive) of the range to constrain {@code value} to + * @throws IllegalArgumentException if {@code min > max} + * @since 21.0 + */ + @Beta + public static float constrainToRange(float value, float min, float max) { + checkArgument(min <= max, "min (%s) must be less than or equal to max (%s)", min, max); + return Math.min(Math.max(value, min), max); + } + + /** + * Returns the values from each provided array combined into a single array. For example, {@code + * concat(new float[] {a, b}, new float[] {}, new float[] {c}} returns the array {@code {a, b, + * c}}. + * + * @param arrays zero or more {@code float} arrays + * @return a single array containing all the values from the source arrays, in order + */ + public static float[] concat(float[]... arrays) { + int length = 0; + for (float[] array : arrays) { + length += array.length; + } + float[] result = new float[length]; + int pos = 0; + for (float[] array : arrays) { + System.arraycopy(array, 0, result, pos, array.length); + pos += array.length; + } + return result; + } + + private static final class FloatConverter extends Converter + implements Serializable { + static final FloatConverter INSTANCE = new FloatConverter(); + + @Override + protected Float doForward(String value) { + return Float.valueOf(value); + } + + @Override + protected String doBackward(Float value) { + return value.toString(); + } + + @Override + public String toString() { + return "Floats.stringConverter()"; + } + + private Object readResolve() { + return INSTANCE; + } + + private static final long serialVersionUID = 1; + } + + /** + * Returns a serializable converter object that converts between strings and floats using {@link + * Float#valueOf} and {@link Float#toString()}. + * + * @since 16.0 + */ + @Beta + public static Converter stringConverter() { + return FloatConverter.INSTANCE; + } + + /** + * Returns an array containing the same values as {@code array}, but guaranteed to be of a + * specified minimum length. If {@code array} already has a length of at least {@code minLength}, + * it is returned directly. Otherwise, a new array of size {@code minLength + padding} is + * returned, containing the values of {@code array}, and zeroes in the remaining places. + * + * @param array the source array + * @param minLength the minimum length the returned array must guarantee + * @param padding an extra amount to "grow" the array by if growth is necessary + * @throws IllegalArgumentException if {@code minLength} or {@code padding} is negative + * @return an array containing the values of {@code array}, with guaranteed minimum length {@code + * minLength} + */ + public static float[] ensureCapacity(float[] array, int minLength, int padding) { + checkArgument(minLength >= 0, "Invalid minLength: %s", minLength); + checkArgument(padding >= 0, "Invalid padding: %s", padding); + return (array.length < minLength) ? Arrays.copyOf(array, minLength + padding) : array; + } + + /** + * Returns a string containing the supplied {@code float} values, converted to strings as + * specified by {@link Float#toString(float)}, and separated by {@code separator}. For example, + * {@code join("-", 1.0f, 2.0f, 3.0f)} returns the string {@code "1.0-2.0-3.0"}. + * + *

Note that {@link Float#toString(float)} formats {@code float} differently in GWT. In the + * previous example, it returns the string {@code "1-2-3"}. + * + * @param separator the text that should appear between consecutive values in the resulting string + * (but not at the start or end) + * @param array an array of {@code float} values, possibly empty + */ + public static String join(String separator, float... array) { + checkNotNull(separator); + if (array.length == 0) { + return ""; + } + + // For pre-sizing a builder, just get the right order of magnitude + StringBuilder builder = new StringBuilder(array.length * 12); + builder.append(array[0]); + for (int i = 1; i < array.length; i++) { + builder.append(separator).append(array[i]); + } + return builder.toString(); + } + + /** + * Returns a comparator that compares two {@code float} arrays lexicographically. That is, it + * compares, using {@link #compare(float, float)}), the first pair of values that follow any + * common prefix, or when one array is a prefix of the other, treats the shorter array as the + * lesser. For example, {@code [] < [1.0f] < [1.0f, 2.0f] < [2.0f]}. + * + *

The returned comparator is inconsistent with {@link Object#equals(Object)} (since arrays + * support only identity equality), but it is consistent with {@link Arrays#equals(float[], + * float[])}. + * + * @since 2.0 + */ + public static Comparator lexicographicalComparator() { + return LexicographicalComparator.INSTANCE; + } + + private enum LexicographicalComparator implements Comparator { + INSTANCE; + + @Override + public int compare(float[] left, float[] right) { + int minLength = Math.min(left.length, right.length); + for (int i = 0; i < minLength; i++) { + int result = Float.compare(left[i], right[i]); + if (result != 0) { + return result; + } + } + return left.length - right.length; + } + + @Override + public String toString() { + return "Floats.lexicographicalComparator()"; + } + } + + /** + * Sorts the elements of {@code array} in descending order. + * + *

Note that this method uses the total order imposed by {@link Float#compare}, which treats + * all NaN values as equal and 0.0 as greater than -0.0. + * + * @since 23.1 + */ + public static void sortDescending(float[] array) { + checkNotNull(array); + sortDescending(array, 0, array.length); + } + + /** + * Sorts the elements of {@code array} between {@code fromIndex} inclusive and {@code toIndex} + * exclusive in descending order. + * + *

Note that this method uses the total order imposed by {@link Float#compare}, which treats + * all NaN values as equal and 0.0 as greater than -0.0. + * + * @since 23.1 + */ + public static void sortDescending(float[] array, int fromIndex, int toIndex) { + checkNotNull(array); + checkPositionIndexes(fromIndex, toIndex, array.length); + Arrays.sort(array, fromIndex, toIndex); + reverse(array, fromIndex, toIndex); + } + + /** + * Reverses the elements of {@code array}. This is equivalent to {@code + * Collections.reverse(Floats.asList(array))}, but is likely to be more efficient. + * + * @since 23.1 + */ + public static void reverse(float[] array) { + checkNotNull(array); + reverse(array, 0, array.length); + } + + /** + * Reverses the elements of {@code array} between {@code fromIndex} inclusive and {@code toIndex} + * exclusive. This is equivalent to {@code + * Collections.reverse(Floats.asList(array).subList(fromIndex, toIndex))}, but is likely to be + * more efficient. + * + * @throws IndexOutOfBoundsException if {@code fromIndex < 0}, {@code toIndex > array.length}, or + * {@code toIndex > fromIndex} + * @since 23.1 + */ + public static void reverse(float[] array, int fromIndex, int toIndex) { + checkNotNull(array); + checkPositionIndexes(fromIndex, toIndex, array.length); + for (int i = fromIndex, j = toIndex - 1; i < j; i++, j--) { + float tmp = array[i]; + array[i] = array[j]; + array[j] = tmp; + } + } + + /** + * Returns an array containing each value of {@code collection}, converted to a {@code float} + * value in the manner of {@link Number#floatValue}. + * + *

Elements are copied from the argument collection as if by {@code collection.toArray()}. + * Calling this method is as thread-safe as calling that method. + * + * @param collection a collection of {@code Number} instances + * @return an array containing the same values as {@code collection}, in the same order, converted + * to primitives + * @throws NullPointerException if {@code collection} or any of its elements is null + * @since 1.0 (parameter was {@code Collection} before 12.0) + */ + public static float[] toArray(Collection collection) { + if (collection instanceof FloatArrayAsList) { + return ((FloatArrayAsList) collection).toFloatArray(); + } + + Object[] boxedArray = collection.toArray(); + int len = boxedArray.length; + float[] array = new float[len]; + for (int i = 0; i < len; i++) { + // checkNotNull for GWT (do not optimize) + array[i] = ((Number) checkNotNull(boxedArray[i])).floatValue(); + } + return array; + } + + /** + * Returns a fixed-size list backed by the specified array, similar to {@link + * Arrays#asList(Object[])}. The list supports {@link List#set(int, Object)}, but any attempt to + * set a value to {@code null} will result in a {@link NullPointerException}. + * + *

The returned list maintains the values, but not the identities, of {@code Float} objects + * written to or read from it. For example, whether {@code list.get(0) == list.get(0)} is true for + * the returned list is unspecified. + * + *

The returned list may have unexpected behavior if it contains {@code NaN}, or if {@code NaN} + * is used as a parameter to any of its methods. + * + * @param backingArray the array to back the list + * @return a list view of the array + */ + public static List asList(float... backingArray) { + if (backingArray.length == 0) { + return Collections.emptyList(); + } + return new FloatArrayAsList(backingArray); + } + + @GwtCompatible + private static class FloatArrayAsList extends AbstractList + implements RandomAccess, Serializable { + final float[] array; + final int start; + final int end; + + FloatArrayAsList(float[] array) { + this(array, 0, array.length); + } + + FloatArrayAsList(float[] array, int start, int end) { + this.array = array; + this.start = start; + this.end = end; + } + + @Override + public int size() { + return end - start; + } + + @Override + public boolean isEmpty() { + return false; + } + + @Override + public Float get(int index) { + checkElementIndex(index, size()); + return array[start + index]; + } + + @Override + public boolean contains(Object target) { + // Overridden to prevent a ton of boxing + return (target instanceof Float) && Floats.indexOf(array, (Float) target, start, end) != -1; + } + + @Override + public int indexOf(Object target) { + // Overridden to prevent a ton of boxing + if (target instanceof Float) { + int i = Floats.indexOf(array, (Float) target, start, end); + if (i >= 0) { + return i - start; + } + } + return -1; + } + + @Override + public int lastIndexOf(Object target) { + // Overridden to prevent a ton of boxing + if (target instanceof Float) { + int i = Floats.lastIndexOf(array, (Float) target, start, end); + if (i >= 0) { + return i - start; + } + } + return -1; + } + + @Override + public Float set(int index, Float element) { + checkElementIndex(index, size()); + float oldValue = array[start + index]; + // checkNotNull for GWT (do not optimize) + array[start + index] = checkNotNull(element); + return oldValue; + } + + @Override + public List subList(int fromIndex, int toIndex) { + int size = size(); + checkPositionIndexes(fromIndex, toIndex, size); + if (fromIndex == toIndex) { + return Collections.emptyList(); + } + return new FloatArrayAsList(array, start + fromIndex, start + toIndex); + } + + @Override + public boolean equals(Object object) { + if (object == this) { + return true; + } + if (object instanceof FloatArrayAsList) { + FloatArrayAsList that = (FloatArrayAsList) object; + int size = size(); + if (that.size() != size) { + return false; + } + for (int i = 0; i < size; i++) { + if (array[start + i] != that.array[that.start + i]) { + return false; + } + } + return true; + } + return super.equals(object); + } + + @Override + public int hashCode() { + int result = 1; + for (int i = start; i < end; i++) { + result = 31 * result + Floats.hashCode(array[i]); + } + return result; + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(size() * 12); + builder.append('[').append(array[start]); + for (int i = start + 1; i < end; i++) { + builder.append(", ").append(array[i]); + } + return builder.append(']').toString(); + } + + float[] toFloatArray() { + return Arrays.copyOfRange(array, start, end); + } + + private static final long serialVersionUID = 0; + } + + /** + * Parses the specified string as a single-precision floating point value. The ASCII character + * {@code '-'} ('\u002D') is recognized as the minus sign. + * + *

Unlike {@link Float#parseFloat(String)}, this method returns {@code null} instead of + * throwing an exception if parsing fails. Valid inputs are exactly those accepted by {@link + * Float#valueOf(String)}, except that leading and trailing whitespace is not permitted. + * + *

This implementation is likely to be faster than {@code Float.parseFloat} if many failures + * are expected. + * + * @param string the string representation of a {@code float} value + * @return the floating point value represented by {@code string}, or {@code null} if {@code + * string} has a length of zero or cannot be parsed as a {@code float} value + * @throws NullPointerException if {@code string} is {@code null} + * @since 14.0 + */ + @Beta + @GwtIncompatible // regular expressions + public static Float tryParse(String string) { + if (Doubles.FLOATING_POINT_PATTERN.matcher(string).matches()) { + // TODO(lowasser): could be potentially optimized, but only with + // extensive testing + try { + return Float.parseFloat(string); + } catch (NumberFormatException e) { + // Float.parseFloat has changed specs several times, so fall through + // gracefully + } + } + return null; + } +} diff --git a/src/main/java/com/google/common/primitives/ImmutableDoubleArray.java b/src/main/java/com/google/common/primitives/ImmutableDoubleArray.java new file mode 100644 index 0000000..b52c6d6 --- /dev/null +++ b/src/main/java/com/google/common/primitives/ImmutableDoubleArray.java @@ -0,0 +1,629 @@ +/* + * Copyright (C) 2017 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.primitives; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.annotations.Beta; +import com.google.common.annotations.GwtCompatible; +import com.google.common.base.Preconditions; + + + +import java.io.Serializable; +import java.util.AbstractList; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.RandomAccess; +import java.util.Spliterator; +import java.util.Spliterators; +import java.util.function.DoubleConsumer; +import java.util.stream.DoubleStream; + + +/** + * An immutable array of {@code double} values, with an API resembling {@link List}. + * + *

Advantages compared to {@code double[]}: + * + *

    + *
  • All the many well-known advantages of immutability (read Effective Java, third + * edition, Item 17). + *
  • Has the value-based (not identity-based) {@link #equals}, {@link #hashCode}, and {@link + * #toString} behavior you expect. + *
  • Offers useful operations beyond just {@code get} and {@code length}, so you don't have to + * hunt through classes like {@link Arrays} and {@link Doubles} for them. + *
  • Supports a copy-free {@link #subArray} view, so methods that accept this type don't need to + * add overloads that accept start and end indexes. + *
  • Can be streamed without "breaking the chain": {@code foo.getBarDoubles().stream()...}. + *
  • Access to all collection-based utilities via {@link #asList} (though at the cost of + * allocating garbage). + *
+ * + *

Disadvantages compared to {@code double[]}: + * + *

    + *
  • Memory footprint has a fixed overhead (about 24 bytes per instance). + *
  • Some construction use cases force the data to be copied (though several construction + * APIs are offered that don't). + *
  • Can't be passed directly to methods that expect {@code double[]} (though the most common + * utilities do have replacements here). + *
  • Dependency on {@code com.google.common} / Guava. + *
+ * + *

Advantages compared to {@link com.google.common.collect.ImmutableList ImmutableList}{@code + * }: + * + *

    + *
  • Improved memory compactness and locality. + *
  • Can be queried without allocating garbage. + *
  • Access to {@code DoubleStream} features (like {@link DoubleStream#sum}) using {@code + * stream()} instead of the awkward {@code stream().mapToDouble(v -> v)}. + *
+ * + *

Disadvantages compared to {@code ImmutableList}: + * + *

    + *
  • Can't be passed directly to methods that expect {@code Iterable}, {@code Collection}, or + * {@code List} (though the most common utilities do have replacements here, and there is a + * lazy {@link #asList} view). + *
+ * + * @since 22.0 + */ +@Beta +@GwtCompatible + +public final class ImmutableDoubleArray implements Serializable { + private static final ImmutableDoubleArray EMPTY = new ImmutableDoubleArray(new double[0]); + + /** Returns the empty array. */ + public static ImmutableDoubleArray of() { + return EMPTY; + } + + /** Returns an immutable array containing a single value. */ + public static ImmutableDoubleArray of(double e0) { + return new ImmutableDoubleArray(new double[] {e0}); + } + + /** Returns an immutable array containing the given values, in order. */ + public static ImmutableDoubleArray of(double e0, double e1) { + return new ImmutableDoubleArray(new double[] {e0, e1}); + } + + /** Returns an immutable array containing the given values, in order. */ + public static ImmutableDoubleArray of(double e0, double e1, double e2) { + return new ImmutableDoubleArray(new double[] {e0, e1, e2}); + } + + /** Returns an immutable array containing the given values, in order. */ + public static ImmutableDoubleArray of(double e0, double e1, double e2, double e3) { + return new ImmutableDoubleArray(new double[] {e0, e1, e2, e3}); + } + + /** Returns an immutable array containing the given values, in order. */ + public static ImmutableDoubleArray of(double e0, double e1, double e2, double e3, double e4) { + return new ImmutableDoubleArray(new double[] {e0, e1, e2, e3, e4}); + } + + /** Returns an immutable array containing the given values, in order. */ + public static ImmutableDoubleArray of( + double e0, double e1, double e2, double e3, double e4, double e5) { + return new ImmutableDoubleArray(new double[] {e0, e1, e2, e3, e4, e5}); + } + + // TODO(kevinb): go up to 11? + + /** + * Returns an immutable array containing the given values, in order. + * + *

The array {@code rest} must not be longer than {@code Integer.MAX_VALUE - 1}. + */ + // Use (first, rest) so that `of(someDoubleArray)` won't compile (they should use copyOf), which + // is okay since we have to copy the just-created array anyway. + public static ImmutableDoubleArray of(double first, double... rest) { + checkArgument( + rest.length <= Integer.MAX_VALUE - 1, "the total number of elements must fit in an int"); + double[] array = new double[rest.length + 1]; + array[0] = first; + System.arraycopy(rest, 0, array, 1, rest.length); + return new ImmutableDoubleArray(array); + } + + /** Returns an immutable array containing the given values, in order. */ + public static ImmutableDoubleArray copyOf(double[] values) { + return values.length == 0 + ? EMPTY + : new ImmutableDoubleArray(Arrays.copyOf(values, values.length)); + } + + /** Returns an immutable array containing the given values, in order. */ + public static ImmutableDoubleArray copyOf(Collection values) { + return values.isEmpty() ? EMPTY : new ImmutableDoubleArray(Doubles.toArray(values)); + } + + /** + * Returns an immutable array containing the given values, in order. + * + *

Performance note: this method delegates to {@link #copyOf(Collection)} if {@code + * values} is a {@link Collection}. Otherwise it creates a {@link #builder} and uses {@link + * Builder#addAll(Iterable)}, with all the performance implications associated with that. + */ + public static ImmutableDoubleArray copyOf(Iterable values) { + if (values instanceof Collection) { + return copyOf((Collection) values); + } + return builder().addAll(values).build(); + } + + /** Returns an immutable array containing all the values from {@code stream}, in order. */ + public static ImmutableDoubleArray copyOf(DoubleStream stream) { + // Note this uses very different growth behavior from copyOf(Iterable) and the builder. + double[] array = stream.toArray(); + return (array.length == 0) ? EMPTY : new ImmutableDoubleArray(array); + } + + /** + * Returns a new, empty builder for {@link ImmutableDoubleArray} instances, sized to hold up to + * {@code initialCapacity} values without resizing. The returned builder is not thread-safe. + * + *

Performance note: When feasible, {@code initialCapacity} should be the exact number + * of values that will be added, if that knowledge is readily available. It is better to guess a + * value slightly too high than slightly too low. If the value is not exact, the {@link + * ImmutableDoubleArray} that is built will very likely occupy more memory than strictly + * necessary; to trim memory usage, build using {@code builder.build().trimmed()}. + */ + public static Builder builder(int initialCapacity) { + checkArgument(initialCapacity >= 0, "Invalid initialCapacity: %s", initialCapacity); + return new Builder(initialCapacity); + } + + /** + * Returns a new, empty builder for {@link ImmutableDoubleArray} instances, with a default initial + * capacity. The returned builder is not thread-safe. + * + *

Performance note: The {@link ImmutableDoubleArray} that is built will very likely + * occupy more memory than necessary; to trim memory usage, build using {@code + * builder.build().trimmed()}. + */ + public static Builder builder() { + return new Builder(10); + } + + /** + * A builder for {@link ImmutableDoubleArray} instances; obtained using {@link + * ImmutableDoubleArray#builder}. + */ + + public static final class Builder { + private double[] array; + private int count = 0; // <= array.length + + Builder(int initialCapacity) { + array = new double[initialCapacity]; + } + + /** + * Appends {@code value} to the end of the values the built {@link ImmutableDoubleArray} will + * contain. + */ + public Builder add(double value) { + ensureRoomFor(1); + array[count] = value; + count += 1; + return this; + } + + /** + * Appends {@code values}, in order, to the end of the values the built {@link + * ImmutableDoubleArray} will contain. + */ + public Builder addAll(double[] values) { + ensureRoomFor(values.length); + System.arraycopy(values, 0, array, count, values.length); + count += values.length; + return this; + } + + /** + * Appends {@code values}, in order, to the end of the values the built {@link + * ImmutableDoubleArray} will contain. + */ + public Builder addAll(Iterable values) { + if (values instanceof Collection) { + return addAll((Collection) values); + } + for (Double value : values) { + add(value); + } + return this; + } + + /** + * Appends {@code values}, in order, to the end of the values the built {@link + * ImmutableDoubleArray} will contain. + */ + public Builder addAll(Collection values) { + ensureRoomFor(values.size()); + for (Double value : values) { + array[count++] = value; + } + return this; + } + + /** + * Appends all values from {@code stream}, in order, to the end of the values the built {@link + * ImmutableDoubleArray} will contain. + */ + public Builder addAll(DoubleStream stream) { + Spliterator.OfDouble spliterator = stream.spliterator(); + long size = spliterator.getExactSizeIfKnown(); + if (size > 0) { // known *and* nonempty + ensureRoomFor(Ints.saturatedCast(size)); + } + spliterator.forEachRemaining((DoubleConsumer) this::add); + return this; + } + + /** + * Appends {@code values}, in order, to the end of the values the built {@link + * ImmutableDoubleArray} will contain. + */ + public Builder addAll(ImmutableDoubleArray values) { + ensureRoomFor(values.length()); + System.arraycopy(values.array, values.start, array, count, values.length()); + count += values.length(); + return this; + } + + private void ensureRoomFor(int numberToAdd) { + int newCount = count + numberToAdd; // TODO(kevinb): check overflow now? + if (newCount > array.length) { + double[] newArray = new double[expandedCapacity(array.length, newCount)]; + System.arraycopy(array, 0, newArray, 0, count); + this.array = newArray; + } + } + + // Unfortunately this is pasted from ImmutableCollection.Builder. + private static int expandedCapacity(int oldCapacity, int minCapacity) { + if (minCapacity < 0) { + throw new AssertionError("cannot store more than MAX_VALUE elements"); + } + // careful of overflow! + int newCapacity = oldCapacity + (oldCapacity >> 1) + 1; + if (newCapacity < minCapacity) { + newCapacity = Integer.highestOneBit(minCapacity - 1) << 1; + } + if (newCapacity < 0) { + newCapacity = Integer.MAX_VALUE; // guaranteed to be >= newCapacity + } + return newCapacity; + } + + /** + * Returns a new immutable array. The builder can continue to be used after this call, to append + * more values and build again. + * + *

Performance note: the returned array is backed by the same array as the builder, so + * no data is copied as part of this step, but this may occupy more memory than strictly + * necessary. To copy the data to a right-sized backing array, use {@code .build().trimmed()}. + */ + + public ImmutableDoubleArray build() { + return count == 0 ? EMPTY : new ImmutableDoubleArray(array, 0, count); + } + } + + // Instance stuff here + + // The array is never mutated after storing in this field and the construction strategies ensure + // it doesn't escape this class + @SuppressWarnings("Immutable") + private final double[] array; + + /* + * TODO(kevinb): evaluate the trade-offs of going bimorphic to save these two fields from most + * instances. Note that the instances that would get smaller are the right set to care about + * optimizing, because the rest have the option of calling `trimmed`. + */ + + private final transient int start; // it happens that we only serialize instances where this is 0 + private final int end; // exclusive + + private ImmutableDoubleArray(double[] array) { + this(array, 0, array.length); + } + + private ImmutableDoubleArray(double[] array, int start, int end) { + this.array = array; + this.start = start; + this.end = end; + } + + /** Returns the number of values in this array. */ + public int length() { + return end - start; + } + + /** Returns {@code true} if there are no values in this array ({@link #length} is zero). */ + public boolean isEmpty() { + return end == start; + } + + /** + * Returns the {@code double} value present at the given index. + * + * @throws IndexOutOfBoundsException if {@code index} is negative, or greater than or equal to + * {@link #length} + */ + public double get(int index) { + Preconditions.checkElementIndex(index, length()); + return array[start + index]; + } + + /** + * Returns the smallest index for which {@link #get} returns {@code target}, or {@code -1} if no + * such index exists. Values are compared as if by {@link Double#equals}. Equivalent to {@code + * asList().indexOf(target)}. + */ + public int indexOf(double target) { + for (int i = start; i < end; i++) { + if (areEqual(array[i], target)) { + return i - start; + } + } + return -1; + } + + /** + * Returns the largest index for which {@link #get} returns {@code target}, or {@code -1} if no + * such index exists. Values are compared as if by {@link Double#equals}. Equivalent to {@code + * asList().lastIndexOf(target)}. + */ + public int lastIndexOf(double target) { + for (int i = end - 1; i >= start; i--) { + if (areEqual(array[i], target)) { + return i - start; + } + } + return -1; + } + + /** + * Returns {@code true} if {@code target} is present at any index in this array. Values are + * compared as if by {@link Double#equals}. Equivalent to {@code asList().contains(target)}. + */ + public boolean contains(double target) { + return indexOf(target) >= 0; + } + + /** Invokes {@code consumer} for each value contained in this array, in order. */ + public void forEach(DoubleConsumer consumer) { + checkNotNull(consumer); + for (int i = start; i < end; i++) { + consumer.accept(array[i]); + } + } + + /** Returns a stream over the values in this array, in order. */ + public DoubleStream stream() { + return Arrays.stream(array, start, end); + } + + /** Returns a new, mutable copy of this array's values, as a primitive {@code double[]}. */ + public double[] toArray() { + return Arrays.copyOfRange(array, start, end); + } + + /** + * Returns a new immutable array containing the values in the specified range. + * + *

Performance note: The returned array has the same full memory footprint as this one + * does (no actual copying is performed). To reduce memory usage, use {@code subArray(start, + * end).trimmed()}. + */ + public ImmutableDoubleArray subArray(int startIndex, int endIndex) { + Preconditions.checkPositionIndexes(startIndex, endIndex, length()); + return startIndex == endIndex + ? EMPTY + : new ImmutableDoubleArray(array, start + startIndex, start + endIndex); + } + + private Spliterator.OfDouble spliterator() { + return Spliterators.spliterator(array, start, end, Spliterator.IMMUTABLE | Spliterator.ORDERED); + } + + /** + * Returns an immutable view of this array's values as a {@code List}; note that {@code + * double} values are boxed into {@link Double} instances on demand, which can be very expensive. + * The returned list should be used once and discarded. For any usages beyond that, pass the + * returned list to {@link com.google.common.collect.ImmutableList#copyOf(Collection) + * ImmutableList.copyOf} and use that list instead. + */ + public List asList() { + /* + * Typically we cache this kind of thing, but much repeated use of this view is a performance + * anti-pattern anyway. If we cache, then everyone pays a price in memory footprint even if + * they never use this method. + */ + return new AsList(this); + } + + static class AsList extends AbstractList implements RandomAccess, Serializable { + private final ImmutableDoubleArray parent; + + private AsList(ImmutableDoubleArray parent) { + this.parent = parent; + } + + // inherit: isEmpty, containsAll, toArray x2, iterator, listIterator, stream, forEach, mutations + + @Override + public int size() { + return parent.length(); + } + + @Override + public Double get(int index) { + return parent.get(index); + } + + @Override + public boolean contains(Object target) { + return indexOf(target) >= 0; + } + + @Override + public int indexOf(Object target) { + return target instanceof Double ? parent.indexOf((Double) target) : -1; + } + + @Override + public int lastIndexOf(Object target) { + return target instanceof Double ? parent.lastIndexOf((Double) target) : -1; + } + + @Override + public List subList(int fromIndex, int toIndex) { + return parent.subArray(fromIndex, toIndex).asList(); + } + + // The default List spliterator is not efficiently splittable + @Override + public Spliterator spliterator() { + return parent.spliterator(); + } + + @Override + public boolean equals(Object object) { + if (object instanceof AsList) { + AsList that = (AsList) object; + return this.parent.equals(that.parent); + } + // We could delegate to super now but it would still box too much + if (!(object instanceof List)) { + return false; + } + List that = (List) object; + if (this.size() != that.size()) { + return false; + } + int i = parent.start; + // Since `that` is very likely RandomAccess we could avoid allocating this iterator... + for (Object element : that) { + if (!(element instanceof Double) || !areEqual(parent.array[i++], (Double) element)) { + return false; + } + } + return true; + } + + // Because we happen to use the same formula. If that changes, just don't override this. + @Override + public int hashCode() { + return parent.hashCode(); + } + + @Override + public String toString() { + return parent.toString(); + } + } + + /** + * Returns {@code true} if {@code object} is an {@code ImmutableDoubleArray} containing the same + * values as this one, in the same order. Values are compared as if by {@link Double#equals}. + */ + @Override + public boolean equals(Object object) { + if (object == this) { + return true; + } + if (!(object instanceof ImmutableDoubleArray)) { + return false; + } + ImmutableDoubleArray that = (ImmutableDoubleArray) object; + if (this.length() != that.length()) { + return false; + } + for (int i = 0; i < length(); i++) { + if (!areEqual(this.get(i), that.get(i))) { + return false; + } + } + return true; + } + + // Match the behavior of Double.equals() + private static boolean areEqual(double a, double b) { + return Double.doubleToLongBits(a) == Double.doubleToLongBits(b); + } + + /** Returns an unspecified hash code for the contents of this immutable array. */ + @Override + public int hashCode() { + int hash = 1; + for (int i = start; i < end; i++) { + hash *= 31; + hash += Doubles.hashCode(array[i]); + } + return hash; + } + + /** + * Returns a string representation of this array in the same form as {@link + * Arrays#toString(double[])}, for example {@code "[1, 2, 3]"}. + */ + @Override + public String toString() { + if (isEmpty()) { + return "[]"; + } + StringBuilder builder = new StringBuilder(length() * 5); // rough estimate is fine + builder.append('[').append(array[start]); + + for (int i = start + 1; i < end; i++) { + builder.append(", ").append(array[i]); + } + builder.append(']'); + return builder.toString(); + } + + /** + * Returns an immutable array containing the same values as {@code this} array. This is logically + * a no-op, and in some circumstances {@code this} itself is returned. However, if this instance + * is a {@link #subArray} view of a larger array, this method will copy only the appropriate range + * of values, resulting in an equivalent array with a smaller memory footprint. + */ + public ImmutableDoubleArray trimmed() { + return isPartialView() ? new ImmutableDoubleArray(toArray()) : this; + } + + private boolean isPartialView() { + return start > 0 || end < array.length; + } + + Object writeReplace() { + return trimmed(); + } + + Object readResolve() { + return isEmpty() ? EMPTY : this; + } +} diff --git a/src/main/java/com/google/common/primitives/ImmutableIntArray.java b/src/main/java/com/google/common/primitives/ImmutableIntArray.java new file mode 100644 index 0000000..fde1204 --- /dev/null +++ b/src/main/java/com/google/common/primitives/ImmutableIntArray.java @@ -0,0 +1,619 @@ +/* + * Copyright (C) 2017 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.primitives; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.annotations.Beta; +import com.google.common.annotations.GwtCompatible; +import com.google.common.base.Preconditions; + + + +import java.io.Serializable; +import java.util.AbstractList; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.RandomAccess; +import java.util.Spliterator; +import java.util.Spliterators; +import java.util.function.IntConsumer; +import java.util.stream.IntStream; + + +/** + * An immutable array of {@code int} values, with an API resembling {@link List}. + * + *

Advantages compared to {@code int[]}: + * + *

    + *
  • All the many well-known advantages of immutability (read Effective Java, third + * edition, Item 17). + *
  • Has the value-based (not identity-based) {@link #equals}, {@link #hashCode}, and {@link + * #toString} behavior you expect. + *
  • Offers useful operations beyond just {@code get} and {@code length}, so you don't have to + * hunt through classes like {@link Arrays} and {@link Ints} for them. + *
  • Supports a copy-free {@link #subArray} view, so methods that accept this type don't need to + * add overloads that accept start and end indexes. + *
  • Can be streamed without "breaking the chain": {@code foo.getBarInts().stream()...}. + *
  • Access to all collection-based utilities via {@link #asList} (though at the cost of + * allocating garbage). + *
+ * + *

Disadvantages compared to {@code int[]}: + * + *

    + *
  • Memory footprint has a fixed overhead (about 24 bytes per instance). + *
  • Some construction use cases force the data to be copied (though several construction + * APIs are offered that don't). + *
  • Can't be passed directly to methods that expect {@code int[]} (though the most common + * utilities do have replacements here). + *
  • Dependency on {@code com.google.common} / Guava. + *
+ * + *

Advantages compared to {@link com.google.common.collect.ImmutableList ImmutableList}{@code + * }: + * + *

    + *
  • Improved memory compactness and locality. + *
  • Can be queried without allocating garbage. + *
  • Access to {@code IntStream} features (like {@link IntStream#sum}) using {@code stream()} + * instead of the awkward {@code stream().mapToInt(v -> v)}. + *
+ * + *

Disadvantages compared to {@code ImmutableList}: + * + *

    + *
  • Can't be passed directly to methods that expect {@code Iterable}, {@code Collection}, or + * {@code List} (though the most common utilities do have replacements here, and there is a + * lazy {@link #asList} view). + *
+ * + * @since 22.0 + */ +@Beta +@GwtCompatible + +public final class ImmutableIntArray implements Serializable { + private static final ImmutableIntArray EMPTY = new ImmutableIntArray(new int[0]); + + /** Returns the empty array. */ + public static ImmutableIntArray of() { + return EMPTY; + } + + /** Returns an immutable array containing a single value. */ + public static ImmutableIntArray of(int e0) { + return new ImmutableIntArray(new int[] {e0}); + } + + /** Returns an immutable array containing the given values, in order. */ + public static ImmutableIntArray of(int e0, int e1) { + return new ImmutableIntArray(new int[] {e0, e1}); + } + + /** Returns an immutable array containing the given values, in order. */ + public static ImmutableIntArray of(int e0, int e1, int e2) { + return new ImmutableIntArray(new int[] {e0, e1, e2}); + } + + /** Returns an immutable array containing the given values, in order. */ + public static ImmutableIntArray of(int e0, int e1, int e2, int e3) { + return new ImmutableIntArray(new int[] {e0, e1, e2, e3}); + } + + /** Returns an immutable array containing the given values, in order. */ + public static ImmutableIntArray of(int e0, int e1, int e2, int e3, int e4) { + return new ImmutableIntArray(new int[] {e0, e1, e2, e3, e4}); + } + + /** Returns an immutable array containing the given values, in order. */ + public static ImmutableIntArray of(int e0, int e1, int e2, int e3, int e4, int e5) { + return new ImmutableIntArray(new int[] {e0, e1, e2, e3, e4, e5}); + } + + // TODO(kevinb): go up to 11? + + /** + * Returns an immutable array containing the given values, in order. + * + *

The array {@code rest} must not be longer than {@code Integer.MAX_VALUE - 1}. + */ + // Use (first, rest) so that `of(someIntArray)` won't compile (they should use copyOf), which is + // okay since we have to copy the just-created array anyway. + public static ImmutableIntArray of(int first, int... rest) { + checkArgument( + rest.length <= Integer.MAX_VALUE - 1, "the total number of elements must fit in an int"); + int[] array = new int[rest.length + 1]; + array[0] = first; + System.arraycopy(rest, 0, array, 1, rest.length); + return new ImmutableIntArray(array); + } + + /** Returns an immutable array containing the given values, in order. */ + public static ImmutableIntArray copyOf(int[] values) { + return values.length == 0 ? EMPTY : new ImmutableIntArray(Arrays.copyOf(values, values.length)); + } + + /** Returns an immutable array containing the given values, in order. */ + public static ImmutableIntArray copyOf(Collection values) { + return values.isEmpty() ? EMPTY : new ImmutableIntArray(Ints.toArray(values)); + } + + /** + * Returns an immutable array containing the given values, in order. + * + *

Performance note: this method delegates to {@link #copyOf(Collection)} if {@code + * values} is a {@link Collection}. Otherwise it creates a {@link #builder} and uses {@link + * Builder#addAll(Iterable)}, with all the performance implications associated with that. + */ + public static ImmutableIntArray copyOf(Iterable values) { + if (values instanceof Collection) { + return copyOf((Collection) values); + } + return builder().addAll(values).build(); + } + + /** Returns an immutable array containing all the values from {@code stream}, in order. */ + public static ImmutableIntArray copyOf(IntStream stream) { + // Note this uses very different growth behavior from copyOf(Iterable) and the builder. + int[] array = stream.toArray(); + return (array.length == 0) ? EMPTY : new ImmutableIntArray(array); + } + + /** + * Returns a new, empty builder for {@link ImmutableIntArray} instances, sized to hold up to + * {@code initialCapacity} values without resizing. The returned builder is not thread-safe. + * + *

Performance note: When feasible, {@code initialCapacity} should be the exact number + * of values that will be added, if that knowledge is readily available. It is better to guess a + * value slightly too high than slightly too low. If the value is not exact, the {@link + * ImmutableIntArray} that is built will very likely occupy more memory than strictly necessary; + * to trim memory usage, build using {@code builder.build().trimmed()}. + */ + public static Builder builder(int initialCapacity) { + checkArgument(initialCapacity >= 0, "Invalid initialCapacity: %s", initialCapacity); + return new Builder(initialCapacity); + } + + /** + * Returns a new, empty builder for {@link ImmutableIntArray} instances, with a default initial + * capacity. The returned builder is not thread-safe. + * + *

Performance note: The {@link ImmutableIntArray} that is built will very likely occupy + * more memory than necessary; to trim memory usage, build using {@code + * builder.build().trimmed()}. + */ + public static Builder builder() { + return new Builder(10); + } + + /** + * A builder for {@link ImmutableIntArray} instances; obtained using {@link + * ImmutableIntArray#builder}. + */ + + public static final class Builder { + private int[] array; + private int count = 0; // <= array.length + + Builder(int initialCapacity) { + array = new int[initialCapacity]; + } + + /** + * Appends {@code value} to the end of the values the built {@link ImmutableIntArray} will + * contain. + */ + public Builder add(int value) { + ensureRoomFor(1); + array[count] = value; + count += 1; + return this; + } + + /** + * Appends {@code values}, in order, to the end of the values the built {@link + * ImmutableIntArray} will contain. + */ + public Builder addAll(int[] values) { + ensureRoomFor(values.length); + System.arraycopy(values, 0, array, count, values.length); + count += values.length; + return this; + } + + /** + * Appends {@code values}, in order, to the end of the values the built {@link + * ImmutableIntArray} will contain. + */ + public Builder addAll(Iterable values) { + if (values instanceof Collection) { + return addAll((Collection) values); + } + for (Integer value : values) { + add(value); + } + return this; + } + + /** + * Appends {@code values}, in order, to the end of the values the built {@link + * ImmutableIntArray} will contain. + */ + public Builder addAll(Collection values) { + ensureRoomFor(values.size()); + for (Integer value : values) { + array[count++] = value; + } + return this; + } + + /** + * Appends all values from {@code stream}, in order, to the end of the values the built {@link + * ImmutableIntArray} will contain. + */ + public Builder addAll(IntStream stream) { + Spliterator.OfInt spliterator = stream.spliterator(); + long size = spliterator.getExactSizeIfKnown(); + if (size > 0) { // known *and* nonempty + ensureRoomFor(Ints.saturatedCast(size)); + } + spliterator.forEachRemaining((IntConsumer) this::add); + return this; + } + + /** + * Appends {@code values}, in order, to the end of the values the built {@link + * ImmutableIntArray} will contain. + */ + public Builder addAll(ImmutableIntArray values) { + ensureRoomFor(values.length()); + System.arraycopy(values.array, values.start, array, count, values.length()); + count += values.length(); + return this; + } + + private void ensureRoomFor(int numberToAdd) { + int newCount = count + numberToAdd; // TODO(kevinb): check overflow now? + if (newCount > array.length) { + int[] newArray = new int[expandedCapacity(array.length, newCount)]; + System.arraycopy(array, 0, newArray, 0, count); + this.array = newArray; + } + } + + // Unfortunately this is pasted from ImmutableCollection.Builder. + private static int expandedCapacity(int oldCapacity, int minCapacity) { + if (minCapacity < 0) { + throw new AssertionError("cannot store more than MAX_VALUE elements"); + } + // careful of overflow! + int newCapacity = oldCapacity + (oldCapacity >> 1) + 1; + if (newCapacity < minCapacity) { + newCapacity = Integer.highestOneBit(minCapacity - 1) << 1; + } + if (newCapacity < 0) { + newCapacity = Integer.MAX_VALUE; // guaranteed to be >= newCapacity + } + return newCapacity; + } + + /** + * Returns a new immutable array. The builder can continue to be used after this call, to append + * more values and build again. + * + *

Performance note: the returned array is backed by the same array as the builder, so + * no data is copied as part of this step, but this may occupy more memory than strictly + * necessary. To copy the data to a right-sized backing array, use {@code .build().trimmed()}. + */ + + public ImmutableIntArray build() { + return count == 0 ? EMPTY : new ImmutableIntArray(array, 0, count); + } + } + + // Instance stuff here + + // The array is never mutated after storing in this field and the construction strategies ensure + // it doesn't escape this class + @SuppressWarnings("Immutable") + private final int[] array; + + /* + * TODO(kevinb): evaluate the trade-offs of going bimorphic to save these two fields from most + * instances. Note that the instances that would get smaller are the right set to care about + * optimizing, because the rest have the option of calling `trimmed`. + */ + + private final transient int start; // it happens that we only serialize instances where this is 0 + private final int end; // exclusive + + private ImmutableIntArray(int[] array) { + this(array, 0, array.length); + } + + private ImmutableIntArray(int[] array, int start, int end) { + this.array = array; + this.start = start; + this.end = end; + } + + /** Returns the number of values in this array. */ + public int length() { + return end - start; + } + + /** Returns {@code true} if there are no values in this array ({@link #length} is zero). */ + public boolean isEmpty() { + return end == start; + } + + /** + * Returns the {@code int} value present at the given index. + * + * @throws IndexOutOfBoundsException if {@code index} is negative, or greater than or equal to + * {@link #length} + */ + public int get(int index) { + Preconditions.checkElementIndex(index, length()); + return array[start + index]; + } + + /** + * Returns the smallest index for which {@link #get} returns {@code target}, or {@code -1} if no + * such index exists. Equivalent to {@code asList().indexOf(target)}. + */ + public int indexOf(int target) { + for (int i = start; i < end; i++) { + if (array[i] == target) { + return i - start; + } + } + return -1; + } + + /** + * Returns the largest index for which {@link #get} returns {@code target}, or {@code -1} if no + * such index exists. Equivalent to {@code asList().lastIndexOf(target)}. + */ + public int lastIndexOf(int target) { + for (int i = end - 1; i >= start; i--) { + if (array[i] == target) { + return i - start; + } + } + return -1; + } + + /** + * Returns {@code true} if {@code target} is present at any index in this array. Equivalent to + * {@code asList().contains(target)}. + */ + public boolean contains(int target) { + return indexOf(target) >= 0; + } + + /** Invokes {@code consumer} for each value contained in this array, in order. */ + public void forEach(IntConsumer consumer) { + checkNotNull(consumer); + for (int i = start; i < end; i++) { + consumer.accept(array[i]); + } + } + + /** Returns a stream over the values in this array, in order. */ + public IntStream stream() { + return Arrays.stream(array, start, end); + } + + /** Returns a new, mutable copy of this array's values, as a primitive {@code int[]}. */ + public int[] toArray() { + return Arrays.copyOfRange(array, start, end); + } + + /** + * Returns a new immutable array containing the values in the specified range. + * + *

Performance note: The returned array has the same full memory footprint as this one + * does (no actual copying is performed). To reduce memory usage, use {@code subArray(start, + * end).trimmed()}. + */ + public ImmutableIntArray subArray(int startIndex, int endIndex) { + Preconditions.checkPositionIndexes(startIndex, endIndex, length()); + return startIndex == endIndex + ? EMPTY + : new ImmutableIntArray(array, start + startIndex, start + endIndex); + } + + private Spliterator.OfInt spliterator() { + return Spliterators.spliterator(array, start, end, Spliterator.IMMUTABLE | Spliterator.ORDERED); + } + + /** + * Returns an immutable view of this array's values as a {@code List}; note that {@code + * int} values are boxed into {@link Integer} instances on demand, which can be very expensive. + * The returned list should be used once and discarded. For any usages beyond that, pass the + * returned list to {@link com.google.common.collect.ImmutableList#copyOf(Collection) + * ImmutableList.copyOf} and use that list instead. + */ + public List asList() { + /* + * Typically we cache this kind of thing, but much repeated use of this view is a performance + * anti-pattern anyway. If we cache, then everyone pays a price in memory footprint even if + * they never use this method. + */ + return new AsList(this); + } + + static class AsList extends AbstractList implements RandomAccess, Serializable { + private final ImmutableIntArray parent; + + private AsList(ImmutableIntArray parent) { + this.parent = parent; + } + + // inherit: isEmpty, containsAll, toArray x2, iterator, listIterator, stream, forEach, mutations + + @Override + public int size() { + return parent.length(); + } + + @Override + public Integer get(int index) { + return parent.get(index); + } + + @Override + public boolean contains(Object target) { + return indexOf(target) >= 0; + } + + @Override + public int indexOf(Object target) { + return target instanceof Integer ? parent.indexOf((Integer) target) : -1; + } + + @Override + public int lastIndexOf(Object target) { + return target instanceof Integer ? parent.lastIndexOf((Integer) target) : -1; + } + + @Override + public List subList(int fromIndex, int toIndex) { + return parent.subArray(fromIndex, toIndex).asList(); + } + + // The default List spliterator is not efficiently splittable + @Override + public Spliterator spliterator() { + return parent.spliterator(); + } + + @Override + public boolean equals(Object object) { + if (object instanceof AsList) { + AsList that = (AsList) object; + return this.parent.equals(that.parent); + } + // We could delegate to super now but it would still box too much + if (!(object instanceof List)) { + return false; + } + List that = (List) object; + if (this.size() != that.size()) { + return false; + } + int i = parent.start; + // Since `that` is very likely RandomAccess we could avoid allocating this iterator... + for (Object element : that) { + if (!(element instanceof Integer) || parent.array[i++] != (Integer) element) { + return false; + } + } + return true; + } + + // Because we happen to use the same formula. If that changes, just don't override this. + @Override + public int hashCode() { + return parent.hashCode(); + } + + @Override + public String toString() { + return parent.toString(); + } + } + + /** + * Returns {@code true} if {@code object} is an {@code ImmutableIntArray} containing the same + * values as this one, in the same order. + */ + @Override + public boolean equals(Object object) { + if (object == this) { + return true; + } + if (!(object instanceof ImmutableIntArray)) { + return false; + } + ImmutableIntArray that = (ImmutableIntArray) object; + if (this.length() != that.length()) { + return false; + } + for (int i = 0; i < length(); i++) { + if (this.get(i) != that.get(i)) { + return false; + } + } + return true; + } + + /** Returns an unspecified hash code for the contents of this immutable array. */ + @Override + public int hashCode() { + int hash = 1; + for (int i = start; i < end; i++) { + hash *= 31; + hash += Ints.hashCode(array[i]); + } + return hash; + } + + /** + * Returns a string representation of this array in the same form as {@link + * Arrays#toString(int[])}, for example {@code "[1, 2, 3]"}. + */ + @Override + public String toString() { + if (isEmpty()) { + return "[]"; + } + StringBuilder builder = new StringBuilder(length() * 5); // rough estimate is fine + builder.append('[').append(array[start]); + + for (int i = start + 1; i < end; i++) { + builder.append(", ").append(array[i]); + } + builder.append(']'); + return builder.toString(); + } + + /** + * Returns an immutable array containing the same values as {@code this} array. This is logically + * a no-op, and in some circumstances {@code this} itself is returned. However, if this instance + * is a {@link #subArray} view of a larger array, this method will copy only the appropriate range + * of values, resulting in an equivalent array with a smaller memory footprint. + */ + public ImmutableIntArray trimmed() { + return isPartialView() ? new ImmutableIntArray(toArray()) : this; + } + + private boolean isPartialView() { + return start > 0 || end < array.length; + } + + Object writeReplace() { + return trimmed(); + } + + Object readResolve() { + return isEmpty() ? EMPTY : this; + } +} diff --git a/src/main/java/com/google/common/primitives/ImmutableLongArray.java b/src/main/java/com/google/common/primitives/ImmutableLongArray.java new file mode 100644 index 0000000..5a86f74 --- /dev/null +++ b/src/main/java/com/google/common/primitives/ImmutableLongArray.java @@ -0,0 +1,621 @@ +/* + * Copyright (C) 2017 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.primitives; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.annotations.Beta; +import com.google.common.annotations.GwtCompatible; +import com.google.common.base.Preconditions; + + + +import java.io.Serializable; +import java.util.AbstractList; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.RandomAccess; +import java.util.Spliterator; +import java.util.Spliterators; +import java.util.function.LongConsumer; +import java.util.stream.LongStream; + + +/** + * An immutable array of {@code long} values, with an API resembling {@link List}. + * + *

Advantages compared to {@code long[]}: + * + *

    + *
  • All the many well-known advantages of immutability (read Effective Java, third + * edition, Item 17). + *
  • Has the value-based (not identity-based) {@link #equals}, {@link #hashCode}, and {@link + * #toString} behavior you expect. + *
  • Offers useful operations beyond just {@code get} and {@code length}, so you don't have to + * hunt through classes like {@link Arrays} and {@link Longs} for them. + *
  • Supports a copy-free {@link #subArray} view, so methods that accept this type don't need to + * add overloads that accept start and end indexes. + *
  • Can be streamed without "breaking the chain": {@code foo.getBarLongs().stream()...}. + *
  • Access to all collection-based utilities via {@link #asList} (though at the cost of + * allocating garbage). + *
+ * + *

Disadvantages compared to {@code long[]}: + * + *

    + *
  • Memory footprint has a fixed overhead (about 24 bytes per instance). + *
  • Some construction use cases force the data to be copied (though several construction + * APIs are offered that don't). + *
  • Can't be passed directly to methods that expect {@code long[]} (though the most common + * utilities do have replacements here). + *
  • Dependency on {@code com.google.common} / Guava. + *
+ * + *

Advantages compared to {@link com.google.common.collect.ImmutableList ImmutableList}{@code + * }: + * + *

    + *
  • Improved memory compactness and locality. + *
  • Can be queried without allocating garbage. + *
  • Access to {@code LongStream} features (like {@link LongStream#sum}) using {@code stream()} + * instead of the awkward {@code stream().mapToLong(v -> v)}. + *
+ * + *

Disadvantages compared to {@code ImmutableList}: + * + *

    + *
  • Can't be passed directly to methods that expect {@code Iterable}, {@code Collection}, or + * {@code List} (though the most common utilities do have replacements here, and there is a + * lazy {@link #asList} view). + *
+ * + * @since 22.0 + */ +@Beta +@GwtCompatible + +public final class ImmutableLongArray implements Serializable { + private static final ImmutableLongArray EMPTY = new ImmutableLongArray(new long[0]); + + /** Returns the empty array. */ + public static ImmutableLongArray of() { + return EMPTY; + } + + /** Returns an immutable array containing a single value. */ + public static ImmutableLongArray of(long e0) { + return new ImmutableLongArray(new long[] {e0}); + } + + /** Returns an immutable array containing the given values, in order. */ + public static ImmutableLongArray of(long e0, long e1) { + return new ImmutableLongArray(new long[] {e0, e1}); + } + + /** Returns an immutable array containing the given values, in order. */ + public static ImmutableLongArray of(long e0, long e1, long e2) { + return new ImmutableLongArray(new long[] {e0, e1, e2}); + } + + /** Returns an immutable array containing the given values, in order. */ + public static ImmutableLongArray of(long e0, long e1, long e2, long e3) { + return new ImmutableLongArray(new long[] {e0, e1, e2, e3}); + } + + /** Returns an immutable array containing the given values, in order. */ + public static ImmutableLongArray of(long e0, long e1, long e2, long e3, long e4) { + return new ImmutableLongArray(new long[] {e0, e1, e2, e3, e4}); + } + + /** Returns an immutable array containing the given values, in order. */ + public static ImmutableLongArray of(long e0, long e1, long e2, long e3, long e4, long e5) { + return new ImmutableLongArray(new long[] {e0, e1, e2, e3, e4, e5}); + } + + // TODO(kevinb): go up to 11? + + /** + * Returns an immutable array containing the given values, in order. + * + *

The array {@code rest} must not be longer than {@code Integer.MAX_VALUE - 1}. + */ + // Use (first, rest) so that `of(someLongArray)` won't compile (they should use copyOf), which is + // okay since we have to copy the just-created array anyway. + public static ImmutableLongArray of(long first, long... rest) { + checkArgument( + rest.length <= Integer.MAX_VALUE - 1, "the total number of elements must fit in an int"); + long[] array = new long[rest.length + 1]; + array[0] = first; + System.arraycopy(rest, 0, array, 1, rest.length); + return new ImmutableLongArray(array); + } + + /** Returns an immutable array containing the given values, in order. */ + public static ImmutableLongArray copyOf(long[] values) { + return values.length == 0 + ? EMPTY + : new ImmutableLongArray(Arrays.copyOf(values, values.length)); + } + + /** Returns an immutable array containing the given values, in order. */ + public static ImmutableLongArray copyOf(Collection values) { + return values.isEmpty() ? EMPTY : new ImmutableLongArray(Longs.toArray(values)); + } + + /** + * Returns an immutable array containing the given values, in order. + * + *

Performance note: this method delegates to {@link #copyOf(Collection)} if {@code + * values} is a {@link Collection}. Otherwise it creates a {@link #builder} and uses {@link + * Builder#addAll(Iterable)}, with all the performance implications associated with that. + */ + public static ImmutableLongArray copyOf(Iterable values) { + if (values instanceof Collection) { + return copyOf((Collection) values); + } + return builder().addAll(values).build(); + } + + /** Returns an immutable array containing all the values from {@code stream}, in order. */ + public static ImmutableLongArray copyOf(LongStream stream) { + // Note this uses very different growth behavior from copyOf(Iterable) and the builder. + long[] array = stream.toArray(); + return (array.length == 0) ? EMPTY : new ImmutableLongArray(array); + } + + /** + * Returns a new, empty builder for {@link ImmutableLongArray} instances, sized to hold up to + * {@code initialCapacity} values without resizing. The returned builder is not thread-safe. + * + *

Performance note: When feasible, {@code initialCapacity} should be the exact number + * of values that will be added, if that knowledge is readily available. It is better to guess a + * value slightly too high than slightly too low. If the value is not exact, the {@link + * ImmutableLongArray} that is built will very likely occupy more memory than strictly necessary; + * to trim memory usage, build using {@code builder.build().trimmed()}. + */ + public static Builder builder(int initialCapacity) { + checkArgument(initialCapacity >= 0, "Invalid initialCapacity: %s", initialCapacity); + return new Builder(initialCapacity); + } + + /** + * Returns a new, empty builder for {@link ImmutableLongArray} instances, with a default initial + * capacity. The returned builder is not thread-safe. + * + *

Performance note: The {@link ImmutableLongArray} that is built will very likely + * occupy more memory than necessary; to trim memory usage, build using {@code + * builder.build().trimmed()}. + */ + public static Builder builder() { + return new Builder(10); + } + + /** + * A builder for {@link ImmutableLongArray} instances; obtained using {@link + * ImmutableLongArray#builder}. + */ + + public static final class Builder { + private long[] array; + private int count = 0; // <= array.length + + Builder(int initialCapacity) { + array = new long[initialCapacity]; + } + + /** + * Appends {@code value} to the end of the values the built {@link ImmutableLongArray} will + * contain. + */ + public Builder add(long value) { + ensureRoomFor(1); + array[count] = value; + count += 1; + return this; + } + + /** + * Appends {@code values}, in order, to the end of the values the built {@link + * ImmutableLongArray} will contain. + */ + public Builder addAll(long[] values) { + ensureRoomFor(values.length); + System.arraycopy(values, 0, array, count, values.length); + count += values.length; + return this; + } + + /** + * Appends {@code values}, in order, to the end of the values the built {@link + * ImmutableLongArray} will contain. + */ + public Builder addAll(Iterable values) { + if (values instanceof Collection) { + return addAll((Collection) values); + } + for (Long value : values) { + add(value); + } + return this; + } + + /** + * Appends {@code values}, in order, to the end of the values the built {@link + * ImmutableLongArray} will contain. + */ + public Builder addAll(Collection values) { + ensureRoomFor(values.size()); + for (Long value : values) { + array[count++] = value; + } + return this; + } + + /** + * Appends all values from {@code stream}, in order, to the end of the values the built {@link + * ImmutableLongArray} will contain. + */ + public Builder addAll(LongStream stream) { + Spliterator.OfLong spliterator = stream.spliterator(); + long size = spliterator.getExactSizeIfKnown(); + if (size > 0) { // known *and* nonempty + ensureRoomFor(Ints.saturatedCast(size)); + } + spliterator.forEachRemaining((LongConsumer) this::add); + return this; + } + + /** + * Appends {@code values}, in order, to the end of the values the built {@link + * ImmutableLongArray} will contain. + */ + public Builder addAll(ImmutableLongArray values) { + ensureRoomFor(values.length()); + System.arraycopy(values.array, values.start, array, count, values.length()); + count += values.length(); + return this; + } + + private void ensureRoomFor(int numberToAdd) { + int newCount = count + numberToAdd; // TODO(kevinb): check overflow now? + if (newCount > array.length) { + long[] newArray = new long[expandedCapacity(array.length, newCount)]; + System.arraycopy(array, 0, newArray, 0, count); + this.array = newArray; + } + } + + // Unfortunately this is pasted from ImmutableCollection.Builder. + private static int expandedCapacity(int oldCapacity, int minCapacity) { + if (minCapacity < 0) { + throw new AssertionError("cannot store more than MAX_VALUE elements"); + } + // careful of overflow! + int newCapacity = oldCapacity + (oldCapacity >> 1) + 1; + if (newCapacity < minCapacity) { + newCapacity = Integer.highestOneBit(minCapacity - 1) << 1; + } + if (newCapacity < 0) { + newCapacity = Integer.MAX_VALUE; // guaranteed to be >= newCapacity + } + return newCapacity; + } + + /** + * Returns a new immutable array. The builder can continue to be used after this call, to append + * more values and build again. + * + *

Performance note: the returned array is backed by the same array as the builder, so + * no data is copied as part of this step, but this may occupy more memory than strictly + * necessary. To copy the data to a right-sized backing array, use {@code .build().trimmed()}. + */ + + public ImmutableLongArray build() { + return count == 0 ? EMPTY : new ImmutableLongArray(array, 0, count); + } + } + + // Instance stuff here + + // The array is never mutated after storing in this field and the construction strategies ensure + // it doesn't escape this class + @SuppressWarnings("Immutable") + private final long[] array; + + /* + * TODO(kevinb): evaluate the trade-offs of going bimorphic to save these two fields from most + * instances. Note that the instances that would get smaller are the right set to care about + * optimizing, because the rest have the option of calling `trimmed`. + */ + + private final transient int start; // it happens that we only serialize instances where this is 0 + private final int end; // exclusive + + private ImmutableLongArray(long[] array) { + this(array, 0, array.length); + } + + private ImmutableLongArray(long[] array, int start, int end) { + this.array = array; + this.start = start; + this.end = end; + } + + /** Returns the number of values in this array. */ + public int length() { + return end - start; + } + + /** Returns {@code true} if there are no values in this array ({@link #length} is zero). */ + public boolean isEmpty() { + return end == start; + } + + /** + * Returns the {@code long} value present at the given index. + * + * @throws IndexOutOfBoundsException if {@code index} is negative, or greater than or equal to + * {@link #length} + */ + public long get(int index) { + Preconditions.checkElementIndex(index, length()); + return array[start + index]; + } + + /** + * Returns the smallest index for which {@link #get} returns {@code target}, or {@code -1} if no + * such index exists. Equivalent to {@code asList().indexOf(target)}. + */ + public int indexOf(long target) { + for (int i = start; i < end; i++) { + if (array[i] == target) { + return i - start; + } + } + return -1; + } + + /** + * Returns the largest index for which {@link #get} returns {@code target}, or {@code -1} if no + * such index exists. Equivalent to {@code asList().lastIndexOf(target)}. + */ + public int lastIndexOf(long target) { + for (int i = end - 1; i >= start; i--) { + if (array[i] == target) { + return i - start; + } + } + return -1; + } + + /** + * Returns {@code true} if {@code target} is present at any index in this array. Equivalent to + * {@code asList().contains(target)}. + */ + public boolean contains(long target) { + return indexOf(target) >= 0; + } + + /** Invokes {@code consumer} for each value contained in this array, in order. */ + public void forEach(LongConsumer consumer) { + checkNotNull(consumer); + for (int i = start; i < end; i++) { + consumer.accept(array[i]); + } + } + + /** Returns a stream over the values in this array, in order. */ + public LongStream stream() { + return Arrays.stream(array, start, end); + } + + /** Returns a new, mutable copy of this array's values, as a primitive {@code long[]}. */ + public long[] toArray() { + return Arrays.copyOfRange(array, start, end); + } + + /** + * Returns a new immutable array containing the values in the specified range. + * + *

Performance note: The returned array has the same full memory footprint as this one + * does (no actual copying is performed). To reduce memory usage, use {@code subArray(start, + * end).trimmed()}. + */ + public ImmutableLongArray subArray(int startIndex, int endIndex) { + Preconditions.checkPositionIndexes(startIndex, endIndex, length()); + return startIndex == endIndex + ? EMPTY + : new ImmutableLongArray(array, start + startIndex, start + endIndex); + } + + private Spliterator.OfLong spliterator() { + return Spliterators.spliterator(array, start, end, Spliterator.IMMUTABLE | Spliterator.ORDERED); + } + + /** + * Returns an immutable view of this array's values as a {@code List}; note that {@code + * long} values are boxed into {@link Long} instances on demand, which can be very expensive. The + * returned list should be used once and discarded. For any usages beyond that, pass the returned + * list to {@link com.google.common.collect.ImmutableList#copyOf(Collection) ImmutableList.copyOf} + * and use that list instead. + */ + public List asList() { + /* + * Typically we cache this kind of thing, but much repeated use of this view is a performance + * anti-pattern anyway. If we cache, then everyone pays a price in memory footprint even if + * they never use this method. + */ + return new AsList(this); + } + + static class AsList extends AbstractList implements RandomAccess, Serializable { + private final ImmutableLongArray parent; + + private AsList(ImmutableLongArray parent) { + this.parent = parent; + } + + // inherit: isEmpty, containsAll, toArray x2, iterator, listIterator, stream, forEach, mutations + + @Override + public int size() { + return parent.length(); + } + + @Override + public Long get(int index) { + return parent.get(index); + } + + @Override + public boolean contains(Object target) { + return indexOf(target) >= 0; + } + + @Override + public int indexOf(Object target) { + return target instanceof Long ? parent.indexOf((Long) target) : -1; + } + + @Override + public int lastIndexOf(Object target) { + return target instanceof Long ? parent.lastIndexOf((Long) target) : -1; + } + + @Override + public List subList(int fromIndex, int toIndex) { + return parent.subArray(fromIndex, toIndex).asList(); + } + + // The default List spliterator is not efficiently splittable + @Override + public Spliterator spliterator() { + return parent.spliterator(); + } + + @Override + public boolean equals(Object object) { + if (object instanceof AsList) { + AsList that = (AsList) object; + return this.parent.equals(that.parent); + } + // We could delegate to super now but it would still box too much + if (!(object instanceof List)) { + return false; + } + List that = (List) object; + if (this.size() != that.size()) { + return false; + } + int i = parent.start; + // Since `that` is very likely RandomAccess we could avoid allocating this iterator... + for (Object element : that) { + if (!(element instanceof Long) || parent.array[i++] != (Long) element) { + return false; + } + } + return true; + } + + // Because we happen to use the same formula. If that changes, just don't override this. + @Override + public int hashCode() { + return parent.hashCode(); + } + + @Override + public String toString() { + return parent.toString(); + } + } + + /** + * Returns {@code true} if {@code object} is an {@code ImmutableLongArray} containing the same + * values as this one, in the same order. + */ + @Override + public boolean equals(Object object) { + if (object == this) { + return true; + } + if (!(object instanceof ImmutableLongArray)) { + return false; + } + ImmutableLongArray that = (ImmutableLongArray) object; + if (this.length() != that.length()) { + return false; + } + for (int i = 0; i < length(); i++) { + if (this.get(i) != that.get(i)) { + return false; + } + } + return true; + } + + /** Returns an unspecified hash code for the contents of this immutable array. */ + @Override + public int hashCode() { + int hash = 1; + for (int i = start; i < end; i++) { + hash *= 31; + hash += Longs.hashCode(array[i]); + } + return hash; + } + + /** + * Returns a string representation of this array in the same form as {@link + * Arrays#toString(long[])}, for example {@code "[1, 2, 3]"}. + */ + @Override + public String toString() { + if (isEmpty()) { + return "[]"; + } + StringBuilder builder = new StringBuilder(length() * 5); // rough estimate is fine + builder.append('[').append(array[start]); + + for (int i = start + 1; i < end; i++) { + builder.append(", ").append(array[i]); + } + builder.append(']'); + return builder.toString(); + } + + /** + * Returns an immutable array containing the same values as {@code this} array. This is logically + * a no-op, and in some circumstances {@code this} itself is returned. However, if this instance + * is a {@link #subArray} view of a larger array, this method will copy only the appropriate range + * of values, resulting in an equivalent array with a smaller memory footprint. + */ + public ImmutableLongArray trimmed() { + return isPartialView() ? new ImmutableLongArray(toArray()) : this; + } + + private boolean isPartialView() { + return start > 0 || end < array.length; + } + + Object writeReplace() { + return trimmed(); + } + + Object readResolve() { + return isEmpty() ? EMPTY : this; + } +} diff --git a/src/main/java/com/google/common/primitives/Ints.java b/src/main/java/com/google/common/primitives/Ints.java new file mode 100644 index 0000000..e39bec7 --- /dev/null +++ b/src/main/java/com/google/common/primitives/Ints.java @@ -0,0 +1,744 @@ +/* + * Copyright (C) 2008 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.primitives; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkElementIndex; +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.base.Preconditions.checkPositionIndexes; + +import com.google.common.annotations.Beta; +import com.google.common.annotations.GwtCompatible; +import com.google.common.base.Converter; +import java.io.Serializable; +import java.util.AbstractList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; +import java.util.RandomAccess; +import java.util.Spliterator; +import java.util.Spliterators; + + +/** + * Static utility methods pertaining to {@code int} primitives, that are not already found in either + * {@link Integer} or {@link Arrays}. + * + *

See the Guava User Guide article on primitive utilities. + * + * @author Kevin Bourrillion + * @since 1.0 + */ +@GwtCompatible +public final class Ints { + private Ints() {} + + /** + * The number of bytes required to represent a primitive {@code int} value. + * + *

Java 8 users: use {@link Integer#BYTES} instead. + */ + public static final int BYTES = Integer.SIZE / Byte.SIZE; + + /** + * The largest power of two that can be represented as an {@code int}. + * + * @since 10.0 + */ + public static final int MAX_POWER_OF_TWO = 1 << (Integer.SIZE - 2); + + /** + * Returns a hash code for {@code value}; equal to the result of invoking {@code ((Integer) + * value).hashCode()}. + * + *

Java 8 users: use {@link Integer#hashCode(int)} instead. + * + * @param value a primitive {@code int} value + * @return a hash code for the value + */ + public static int hashCode(int value) { + return value; + } + + /** + * Returns the {@code int} value that is equal to {@code value}, if possible. + * + * @param value any value in the range of the {@code int} type + * @return the {@code int} value that equals {@code value} + * @throws IllegalArgumentException if {@code value} is greater than {@link Integer#MAX_VALUE} or + * less than {@link Integer#MIN_VALUE} + */ + public static int checkedCast(long value) { + int result = (int) value; + checkArgument(result == value, "Out of range: %s", value); + return result; + } + + /** + * Returns the {@code int} nearest in value to {@code value}. + * + * @param value any {@code long} value + * @return the same value cast to {@code int} if it is in the range of the {@code int} type, + * {@link Integer#MAX_VALUE} if it is too large, or {@link Integer#MIN_VALUE} if it is too + * small + */ + public static int saturatedCast(long value) { + if (value > Integer.MAX_VALUE) { + return Integer.MAX_VALUE; + } + if (value < Integer.MIN_VALUE) { + return Integer.MIN_VALUE; + } + return (int) value; + } + + /** + * Compares the two specified {@code int} values. The sign of the value returned is the same as + * that of {@code ((Integer) a).compareTo(b)}. + * + *

Note for Java 7 and later: this method should be treated as deprecated; use the + * equivalent {@link Integer#compare} method instead. + * + * @param a the first {@code int} to compare + * @param b the second {@code int} to compare + * @return a negative value if {@code a} is less than {@code b}; a positive value if {@code a} is + * greater than {@code b}; or zero if they are equal + */ + public static int compare(int a, int b) { + return (a < b) ? -1 : ((a > b) ? 1 : 0); + } + + /** + * Returns {@code true} if {@code target} is present as an element anywhere in {@code array}. + * + * @param array an array of {@code int} values, possibly empty + * @param target a primitive {@code int} value + * @return {@code true} if {@code array[i] == target} for some value of {@code i} + */ + public static boolean contains(int[] array, int target) { + for (int value : array) { + if (value == target) { + return true; + } + } + return false; + } + + /** + * Returns the index of the first appearance of the value {@code target} in {@code array}. + * + * @param array an array of {@code int} values, possibly empty + * @param target a primitive {@code int} value + * @return the least index {@code i} for which {@code array[i] == target}, or {@code -1} if no + * such index exists. + */ + public static int indexOf(int[] array, int target) { + return indexOf(array, target, 0, array.length); + } + + // TODO(kevinb): consider making this public + private static int indexOf(int[] array, int target, int start, int end) { + for (int i = start; i < end; i++) { + if (array[i] == target) { + return i; + } + } + return -1; + } + + /** + * Returns the start position of the first occurrence of the specified {@code target} within + * {@code array}, or {@code -1} if there is no such occurrence. + * + *

More formally, returns the lowest index {@code i} such that {@code Arrays.copyOfRange(array, + * i, i + target.length)} contains exactly the same elements as {@code target}. + * + * @param array the array to search for the sequence {@code target} + * @param target the array to search for as a sub-sequence of {@code array} + */ + public static int indexOf(int[] array, int[] target) { + checkNotNull(array, "array"); + checkNotNull(target, "target"); + if (target.length == 0) { + return 0; + } + + outer: + for (int i = 0; i < array.length - target.length + 1; i++) { + for (int j = 0; j < target.length; j++) { + if (array[i + j] != target[j]) { + continue outer; + } + } + return i; + } + return -1; + } + + /** + * Returns the index of the last appearance of the value {@code target} in {@code array}. + * + * @param array an array of {@code int} values, possibly empty + * @param target a primitive {@code int} value + * @return the greatest index {@code i} for which {@code array[i] == target}, or {@code -1} if no + * such index exists. + */ + public static int lastIndexOf(int[] array, int target) { + return lastIndexOf(array, target, 0, array.length); + } + + // TODO(kevinb): consider making this public + private static int lastIndexOf(int[] array, int target, int start, int end) { + for (int i = end - 1; i >= start; i--) { + if (array[i] == target) { + return i; + } + } + return -1; + } + + /** + * Returns the least value present in {@code array}. + * + * @param array a nonempty array of {@code int} values + * @return the value present in {@code array} that is less than or equal to every other value in + * the array + * @throws IllegalArgumentException if {@code array} is empty + */ + public static int min(int... array) { + checkArgument(array.length > 0); + int min = array[0]; + for (int i = 1; i < array.length; i++) { + if (array[i] < min) { + min = array[i]; + } + } + return min; + } + + /** + * Returns the greatest value present in {@code array}. + * + * @param array a nonempty array of {@code int} values + * @return the value present in {@code array} that is greater than or equal to every other value + * in the array + * @throws IllegalArgumentException if {@code array} is empty + */ + public static int max(int... array) { + checkArgument(array.length > 0); + int max = array[0]; + for (int i = 1; i < array.length; i++) { + if (array[i] > max) { + max = array[i]; + } + } + return max; + } + + /** + * Returns the value nearest to {@code value} which is within the closed range {@code [min..max]}. + * + *

If {@code value} is within the range {@code [min..max]}, {@code value} is returned + * unchanged. If {@code value} is less than {@code min}, {@code min} is returned, and if {@code + * value} is greater than {@code max}, {@code max} is returned. + * + * @param value the {@code int} value to constrain + * @param min the lower bound (inclusive) of the range to constrain {@code value} to + * @param max the upper bound (inclusive) of the range to constrain {@code value} to + * @throws IllegalArgumentException if {@code min > max} + * @since 21.0 + */ + @Beta + public static int constrainToRange(int value, int min, int max) { + checkArgument(min <= max, "min (%s) must be less than or equal to max (%s)", min, max); + return Math.min(Math.max(value, min), max); + } + + /** + * Returns the values from each provided array combined into a single array. For example, {@code + * concat(new int[] {a, b}, new int[] {}, new int[] {c}} returns the array {@code {a, b, c}}. + * + * @param arrays zero or more {@code int} arrays + * @return a single array containing all the values from the source arrays, in order + */ + public static int[] concat(int[]... arrays) { + int length = 0; + for (int[] array : arrays) { + length += array.length; + } + int[] result = new int[length]; + int pos = 0; + for (int[] array : arrays) { + System.arraycopy(array, 0, result, pos, array.length); + pos += array.length; + } + return result; + } + + /** + * Returns a big-endian representation of {@code value} in a 4-element byte array; equivalent to + * {@code ByteBuffer.allocate(4).putInt(value).array()}. For example, the input value {@code + * 0x12131415} would yield the byte array {@code {0x12, 0x13, 0x14, 0x15}}. + * + *

If you need to convert and concatenate several values (possibly even of different types), + * use a shared {@link java.nio.ByteBuffer} instance, or use {@link + * com.google.common.io.ByteStreams#newDataOutput()} to get a growable buffer. + */ + public static byte[] toByteArray(int value) { + return new byte[] { + (byte) (value >> 24), (byte) (value >> 16), (byte) (value >> 8), (byte) value + }; + } + + /** + * Returns the {@code int} value whose big-endian representation is stored in the first 4 bytes of + * {@code bytes}; equivalent to {@code ByteBuffer.wrap(bytes).getInt()}. For example, the input + * byte array {@code {0x12, 0x13, 0x14, 0x15, 0x33}} would yield the {@code int} value {@code + * 0x12131415}. + * + *

Arguably, it's preferable to use {@link java.nio.ByteBuffer}; that library exposes much more + * flexibility at little cost in readability. + * + * @throws IllegalArgumentException if {@code bytes} has fewer than 4 elements + */ + public static int fromByteArray(byte[] bytes) { + checkArgument(bytes.length >= BYTES, "array too small: %s < %s", bytes.length, BYTES); + return fromBytes(bytes[0], bytes[1], bytes[2], bytes[3]); + } + + /** + * Returns the {@code int} value whose byte representation is the given 4 bytes, in big-endian + * order; equivalent to {@code Ints.fromByteArray(new byte[] {b1, b2, b3, b4})}. + * + * @since 7.0 + */ + public static int fromBytes(byte b1, byte b2, byte b3, byte b4) { + return b1 << 24 | (b2 & 0xFF) << 16 | (b3 & 0xFF) << 8 | (b4 & 0xFF); + } + + private static final class IntConverter extends Converter + implements Serializable { + static final IntConverter INSTANCE = new IntConverter(); + + @Override + protected Integer doForward(String value) { + return Integer.decode(value); + } + + @Override + protected String doBackward(Integer value) { + return value.toString(); + } + + @Override + public String toString() { + return "Ints.stringConverter()"; + } + + private Object readResolve() { + return INSTANCE; + } + + private static final long serialVersionUID = 1; + } + + /** + * Returns a serializable converter object that converts between strings and integers using {@link + * Integer#decode} and {@link Integer#toString()}. The returned converter throws {@link + * NumberFormatException} if the input string is invalid. + * + *

Warning: please see {@link Integer#decode} to understand exactly how strings are + * parsed. For example, the string {@code "0123"} is treated as octal and converted to the + * value {@code 83}. + * + * @since 16.0 + */ + @Beta + public static Converter stringConverter() { + return IntConverter.INSTANCE; + } + + /** + * Returns an array containing the same values as {@code array}, but guaranteed to be of a + * specified minimum length. If {@code array} already has a length of at least {@code minLength}, + * it is returned directly. Otherwise, a new array of size {@code minLength + padding} is + * returned, containing the values of {@code array}, and zeroes in the remaining places. + * + * @param array the source array + * @param minLength the minimum length the returned array must guarantee + * @param padding an extra amount to "grow" the array by if growth is necessary + * @throws IllegalArgumentException if {@code minLength} or {@code padding} is negative + * @return an array containing the values of {@code array}, with guaranteed minimum length {@code + * minLength} + */ + public static int[] ensureCapacity(int[] array, int minLength, int padding) { + checkArgument(minLength >= 0, "Invalid minLength: %s", minLength); + checkArgument(padding >= 0, "Invalid padding: %s", padding); + return (array.length < minLength) ? Arrays.copyOf(array, minLength + padding) : array; + } + + /** + * Returns a string containing the supplied {@code int} values separated by {@code separator}. For + * example, {@code join("-", 1, 2, 3)} returns the string {@code "1-2-3"}. + * + * @param separator the text that should appear between consecutive values in the resulting string + * (but not at the start or end) + * @param array an array of {@code int} values, possibly empty + */ + public static String join(String separator, int... array) { + checkNotNull(separator); + if (array.length == 0) { + return ""; + } + + // For pre-sizing a builder, just get the right order of magnitude + StringBuilder builder = new StringBuilder(array.length * 5); + builder.append(array[0]); + for (int i = 1; i < array.length; i++) { + builder.append(separator).append(array[i]); + } + return builder.toString(); + } + + /** + * Returns a comparator that compares two {@code int} arrays lexicographically. That is, it + * compares, using {@link #compare(int, int)}), the first pair of values that follow any common + * prefix, or when one array is a prefix of the other, treats the shorter array as the lesser. For + * example, {@code [] < [1] < [1, 2] < [2]}. + * + *

The returned comparator is inconsistent with {@link Object#equals(Object)} (since arrays + * support only identity equality), but it is consistent with {@link Arrays#equals(int[], int[])}. + * + * @since 2.0 + */ + public static Comparator lexicographicalComparator() { + return LexicographicalComparator.INSTANCE; + } + + private enum LexicographicalComparator implements Comparator { + INSTANCE; + + @Override + public int compare(int[] left, int[] right) { + int minLength = Math.min(left.length, right.length); + for (int i = 0; i < minLength; i++) { + int result = Ints.compare(left[i], right[i]); + if (result != 0) { + return result; + } + } + return left.length - right.length; + } + + @Override + public String toString() { + return "Ints.lexicographicalComparator()"; + } + } + + /** + * Sorts the elements of {@code array} in descending order. + * + * @since 23.1 + */ + public static void sortDescending(int[] array) { + checkNotNull(array); + sortDescending(array, 0, array.length); + } + + /** + * Sorts the elements of {@code array} between {@code fromIndex} inclusive and {@code toIndex} + * exclusive in descending order. + * + * @since 23.1 + */ + public static void sortDescending(int[] array, int fromIndex, int toIndex) { + checkNotNull(array); + checkPositionIndexes(fromIndex, toIndex, array.length); + Arrays.sort(array, fromIndex, toIndex); + reverse(array, fromIndex, toIndex); + } + + /** + * Reverses the elements of {@code array}. This is equivalent to {@code + * Collections.reverse(Ints.asList(array))}, but is likely to be more efficient. + * + * @since 23.1 + */ + public static void reverse(int[] array) { + checkNotNull(array); + reverse(array, 0, array.length); + } + + /** + * Reverses the elements of {@code array} between {@code fromIndex} inclusive and {@code toIndex} + * exclusive. This is equivalent to {@code + * Collections.reverse(Ints.asList(array).subList(fromIndex, toIndex))}, but is likely to be more + * efficient. + * + * @throws IndexOutOfBoundsException if {@code fromIndex < 0}, {@code toIndex > array.length}, or + * {@code toIndex > fromIndex} + * @since 23.1 + */ + public static void reverse(int[] array, int fromIndex, int toIndex) { + checkNotNull(array); + checkPositionIndexes(fromIndex, toIndex, array.length); + for (int i = fromIndex, j = toIndex - 1; i < j; i++, j--) { + int tmp = array[i]; + array[i] = array[j]; + array[j] = tmp; + } + } + + /** + * Returns an array containing each value of {@code collection}, converted to a {@code int} value + * in the manner of {@link Number#intValue}. + * + *

Elements are copied from the argument collection as if by {@code collection.toArray()}. + * Calling this method is as thread-safe as calling that method. + * + * @param collection a collection of {@code Number} instances + * @return an array containing the same values as {@code collection}, in the same order, converted + * to primitives + * @throws NullPointerException if {@code collection} or any of its elements is null + * @since 1.0 (parameter was {@code Collection} before 12.0) + */ + public static int[] toArray(Collection collection) { + if (collection instanceof IntArrayAsList) { + return ((IntArrayAsList) collection).toIntArray(); + } + + Object[] boxedArray = collection.toArray(); + int len = boxedArray.length; + int[] array = new int[len]; + for (int i = 0; i < len; i++) { + // checkNotNull for GWT (do not optimize) + array[i] = ((Number) checkNotNull(boxedArray[i])).intValue(); + } + return array; + } + + /** + * Returns a fixed-size list backed by the specified array, similar to {@link + * Arrays#asList(Object[])}. The list supports {@link List#set(int, Object)}, but any attempt to + * set a value to {@code null} will result in a {@link NullPointerException}. + * + *

The returned list maintains the values, but not the identities, of {@code Integer} objects + * written to or read from it. For example, whether {@code list.get(0) == list.get(0)} is true for + * the returned list is unspecified. + * + *

Note: when possible, you should represent your data as an {@link ImmutableIntArray} + * instead, which has an {@link ImmutableIntArray#asList asList} view. + * + * @param backingArray the array to back the list + * @return a list view of the array + */ + public static List asList(int... backingArray) { + if (backingArray.length == 0) { + return Collections.emptyList(); + } + return new IntArrayAsList(backingArray); + } + + @GwtCompatible + private static class IntArrayAsList extends AbstractList + implements RandomAccess, Serializable { + final int[] array; + final int start; + final int end; + + IntArrayAsList(int[] array) { + this(array, 0, array.length); + } + + IntArrayAsList(int[] array, int start, int end) { + this.array = array; + this.start = start; + this.end = end; + } + + @Override + public int size() { + return end - start; + } + + @Override + public boolean isEmpty() { + return false; + } + + @Override + public Integer get(int index) { + checkElementIndex(index, size()); + return array[start + index]; + } + + @Override + public Spliterator.OfInt spliterator() { + return Spliterators.spliterator(array, start, end, 0); + } + + @Override + public boolean contains(Object target) { + // Overridden to prevent a ton of boxing + return (target instanceof Integer) && Ints.indexOf(array, (Integer) target, start, end) != -1; + } + + @Override + public int indexOf(Object target) { + // Overridden to prevent a ton of boxing + if (target instanceof Integer) { + int i = Ints.indexOf(array, (Integer) target, start, end); + if (i >= 0) { + return i - start; + } + } + return -1; + } + + @Override + public int lastIndexOf(Object target) { + // Overridden to prevent a ton of boxing + if (target instanceof Integer) { + int i = Ints.lastIndexOf(array, (Integer) target, start, end); + if (i >= 0) { + return i - start; + } + } + return -1; + } + + @Override + public Integer set(int index, Integer element) { + checkElementIndex(index, size()); + int oldValue = array[start + index]; + // checkNotNull for GWT (do not optimize) + array[start + index] = checkNotNull(element); + return oldValue; + } + + @Override + public List subList(int fromIndex, int toIndex) { + int size = size(); + checkPositionIndexes(fromIndex, toIndex, size); + if (fromIndex == toIndex) { + return Collections.emptyList(); + } + return new IntArrayAsList(array, start + fromIndex, start + toIndex); + } + + @Override + public boolean equals(Object object) { + if (object == this) { + return true; + } + if (object instanceof IntArrayAsList) { + IntArrayAsList that = (IntArrayAsList) object; + int size = size(); + if (that.size() != size) { + return false; + } + for (int i = 0; i < size; i++) { + if (array[start + i] != that.array[that.start + i]) { + return false; + } + } + return true; + } + return super.equals(object); + } + + @Override + public int hashCode() { + int result = 1; + for (int i = start; i < end; i++) { + result = 31 * result + Ints.hashCode(array[i]); + } + return result; + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(size() * 5); + builder.append('[').append(array[start]); + for (int i = start + 1; i < end; i++) { + builder.append(", ").append(array[i]); + } + return builder.append(']').toString(); + } + + int[] toIntArray() { + return Arrays.copyOfRange(array, start, end); + } + + private static final long serialVersionUID = 0; + } + + /** + * Parses the specified string as a signed decimal integer value. The ASCII character {@code '-'} + * ('\u002D') is recognized as the minus sign. + * + *

Unlike {@link Integer#parseInt(String)}, this method returns {@code null} instead of + * throwing an exception if parsing fails. Additionally, this method only accepts ASCII digits, + * and returns {@code null} if non-ASCII digits are present in the string. + * + *

Note that strings prefixed with ASCII {@code '+'} are rejected, even under JDK 7, despite + * the change to {@link Integer#parseInt(String)} for that version. + * + * @param string the string representation of an integer value + * @return the integer value represented by {@code string}, or {@code null} if {@code string} has + * a length of zero or cannot be parsed as an integer value + * @throws NullPointerException if {@code string} is {@code null} + * @since 11.0 + */ + @Beta + public static Integer tryParse(String string) { + return tryParse(string, 10); + } + + /** + * Parses the specified string as a signed integer value using the specified radix. The ASCII + * character {@code '-'} ('\u002D') is recognized as the minus sign. + * + *

Unlike {@link Integer#parseInt(String, int)}, this method returns {@code null} instead of + * throwing an exception if parsing fails. Additionally, this method only accepts ASCII digits, + * and returns {@code null} if non-ASCII digits are present in the string. + * + *

Note that strings prefixed with ASCII {@code '+'} are rejected, even under JDK 7, despite + * the change to {@link Integer#parseInt(String, int)} for that version. + * + * @param string the string representation of an integer value + * @param radix the radix to use when parsing + * @return the integer value represented by {@code string} using {@code radix}, or {@code null} if + * {@code string} has a length of zero or cannot be parsed as an integer value + * @throws IllegalArgumentException if {@code radix < Character.MIN_RADIX} or {@code radix > + * Character.MAX_RADIX} + * @throws NullPointerException if {@code string} is {@code null} + * @since 19.0 + */ + @Beta + public static Integer tryParse(String string, int radix) { + Long result = Longs.tryParse(string, radix); + if (result == null || result.longValue() != result.intValue()) { + return null; + } else { + return result.intValue(); + } + } +} diff --git a/src/main/java/com/google/common/primitives/Longs.java b/src/main/java/com/google/common/primitives/Longs.java new file mode 100644 index 0000000..6afe847 --- /dev/null +++ b/src/main/java/com/google/common/primitives/Longs.java @@ -0,0 +1,791 @@ +/* + * Copyright (C) 2008 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.primitives; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkElementIndex; +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.base.Preconditions.checkPositionIndexes; + +import com.google.common.annotations.Beta; +import com.google.common.annotations.GwtCompatible; +import com.google.common.base.Converter; +import java.io.Serializable; +import java.util.AbstractList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; +import java.util.RandomAccess; +import java.util.Spliterator; +import java.util.Spliterators; + + +/** + * Static utility methods pertaining to {@code long} primitives, that are not already found in + * either {@link Long} or {@link Arrays}. + * + *

See the Guava User Guide article on primitive utilities. + * + * @author Kevin Bourrillion + * @since 1.0 + */ +@GwtCompatible +public final class Longs { + private Longs() {} + + /** + * The number of bytes required to represent a primitive {@code long} value. + * + *

Java 8 users: use {@link Long#BYTES} instead. + */ + public static final int BYTES = Long.SIZE / Byte.SIZE; + + /** + * The largest power of two that can be represented as a {@code long}. + * + * @since 10.0 + */ + public static final long MAX_POWER_OF_TWO = 1L << (Long.SIZE - 2); + + /** + * Returns a hash code for {@code value}; equal to the result of invoking {@code ((Long) + * value).hashCode()}. + * + *

This method always return the value specified by {@link Long#hashCode()} in java, which + * might be different from {@code ((Long) value).hashCode()} in GWT because {@link + * Long#hashCode()} in GWT does not obey the JRE contract. + * + *

Java 8 users: use {@link Long#hashCode(long)} instead. + * + * @param value a primitive {@code long} value + * @return a hash code for the value + */ + public static int hashCode(long value) { + return (int) (value ^ (value >>> 32)); + } + + /** + * Compares the two specified {@code long} values. The sign of the value returned is the same as + * that of {@code ((Long) a).compareTo(b)}. + * + *

Note for Java 7 and later: this method should be treated as deprecated; use the + * equivalent {@link Long#compare} method instead. + * + * @param a the first {@code long} to compare + * @param b the second {@code long} to compare + * @return a negative value if {@code a} is less than {@code b}; a positive value if {@code a} is + * greater than {@code b}; or zero if they are equal + */ + public static int compare(long a, long b) { + return (a < b) ? -1 : ((a > b) ? 1 : 0); + } + + /** + * Returns {@code true} if {@code target} is present as an element anywhere in {@code array}. + * + * @param array an array of {@code long} values, possibly empty + * @param target a primitive {@code long} value + * @return {@code true} if {@code array[i] == target} for some value of {@code i} + */ + public static boolean contains(long[] array, long target) { + for (long value : array) { + if (value == target) { + return true; + } + } + return false; + } + + /** + * Returns the index of the first appearance of the value {@code target} in {@code array}. + * + * @param array an array of {@code long} values, possibly empty + * @param target a primitive {@code long} value + * @return the least index {@code i} for which {@code array[i] == target}, or {@code -1} if no + * such index exists. + */ + public static int indexOf(long[] array, long target) { + return indexOf(array, target, 0, array.length); + } + + // TODO(kevinb): consider making this public + private static int indexOf(long[] array, long target, int start, int end) { + for (int i = start; i < end; i++) { + if (array[i] == target) { + return i; + } + } + return -1; + } + + /** + * Returns the start position of the first occurrence of the specified {@code target} within + * {@code array}, or {@code -1} if there is no such occurrence. + * + *

More formally, returns the lowest index {@code i} such that {@code Arrays.copyOfRange(array, + * i, i + target.length)} contains exactly the same elements as {@code target}. + * + * @param array the array to search for the sequence {@code target} + * @param target the array to search for as a sub-sequence of {@code array} + */ + public static int indexOf(long[] array, long[] target) { + checkNotNull(array, "array"); + checkNotNull(target, "target"); + if (target.length == 0) { + return 0; + } + + outer: + for (int i = 0; i < array.length - target.length + 1; i++) { + for (int j = 0; j < target.length; j++) { + if (array[i + j] != target[j]) { + continue outer; + } + } + return i; + } + return -1; + } + + /** + * Returns the index of the last appearance of the value {@code target} in {@code array}. + * + * @param array an array of {@code long} values, possibly empty + * @param target a primitive {@code long} value + * @return the greatest index {@code i} for which {@code array[i] == target}, or {@code -1} if no + * such index exists. + */ + public static int lastIndexOf(long[] array, long target) { + return lastIndexOf(array, target, 0, array.length); + } + + // TODO(kevinb): consider making this public + private static int lastIndexOf(long[] array, long target, int start, int end) { + for (int i = end - 1; i >= start; i--) { + if (array[i] == target) { + return i; + } + } + return -1; + } + + /** + * Returns the least value present in {@code array}. + * + * @param array a nonempty array of {@code long} values + * @return the value present in {@code array} that is less than or equal to every other value in + * the array + * @throws IllegalArgumentException if {@code array} is empty + */ + public static long min(long... array) { + checkArgument(array.length > 0); + long min = array[0]; + for (int i = 1; i < array.length; i++) { + if (array[i] < min) { + min = array[i]; + } + } + return min; + } + + /** + * Returns the greatest value present in {@code array}. + * + * @param array a nonempty array of {@code long} values + * @return the value present in {@code array} that is greater than or equal to every other value + * in the array + * @throws IllegalArgumentException if {@code array} is empty + */ + public static long max(long... array) { + checkArgument(array.length > 0); + long max = array[0]; + for (int i = 1; i < array.length; i++) { + if (array[i] > max) { + max = array[i]; + } + } + return max; + } + + /** + * Returns the value nearest to {@code value} which is within the closed range {@code [min..max]}. + * + *

If {@code value} is within the range {@code [min..max]}, {@code value} is returned + * unchanged. If {@code value} is less than {@code min}, {@code min} is returned, and if {@code + * value} is greater than {@code max}, {@code max} is returned. + * + * @param value the {@code long} value to constrain + * @param min the lower bound (inclusive) of the range to constrain {@code value} to + * @param max the upper bound (inclusive) of the range to constrain {@code value} to + * @throws IllegalArgumentException if {@code min > max} + * @since 21.0 + */ + @Beta + public static long constrainToRange(long value, long min, long max) { + checkArgument(min <= max, "min (%s) must be less than or equal to max (%s)", min, max); + return Math.min(Math.max(value, min), max); + } + + /** + * Returns the values from each provided array combined into a single array. For example, {@code + * concat(new long[] {a, b}, new long[] {}, new long[] {c}} returns the array {@code {a, b, c}}. + * + * @param arrays zero or more {@code long} arrays + * @return a single array containing all the values from the source arrays, in order + */ + public static long[] concat(long[]... arrays) { + int length = 0; + for (long[] array : arrays) { + length += array.length; + } + long[] result = new long[length]; + int pos = 0; + for (long[] array : arrays) { + System.arraycopy(array, 0, result, pos, array.length); + pos += array.length; + } + return result; + } + + /** + * Returns a big-endian representation of {@code value} in an 8-element byte array; equivalent to + * {@code ByteBuffer.allocate(8).putLong(value).array()}. For example, the input value {@code + * 0x1213141516171819L} would yield the byte array {@code {0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + * 0x18, 0x19}}. + * + *

If you need to convert and concatenate several values (possibly even of different types), + * use a shared {@link java.nio.ByteBuffer} instance, or use {@link + * com.google.common.io.ByteStreams#newDataOutput()} to get a growable buffer. + */ + public static byte[] toByteArray(long value) { + // Note that this code needs to stay compatible with GWT, which has known + // bugs when narrowing byte casts of long values occur. + byte[] result = new byte[8]; + for (int i = 7; i >= 0; i--) { + result[i] = (byte) (value & 0xffL); + value >>= 8; + } + return result; + } + + /** + * Returns the {@code long} value whose big-endian representation is stored in the first 8 bytes + * of {@code bytes}; equivalent to {@code ByteBuffer.wrap(bytes).getLong()}. For example, the + * input byte array {@code {0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19}} would yield the + * {@code long} value {@code 0x1213141516171819L}. + * + *

Arguably, it's preferable to use {@link java.nio.ByteBuffer}; that library exposes much more + * flexibility at little cost in readability. + * + * @throws IllegalArgumentException if {@code bytes} has fewer than 8 elements + */ + public static long fromByteArray(byte[] bytes) { + checkArgument(bytes.length >= BYTES, "array too small: %s < %s", bytes.length, BYTES); + return fromBytes( + bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5], bytes[6], bytes[7]); + } + + /** + * Returns the {@code long} value whose byte representation is the given 8 bytes, in big-endian + * order; equivalent to {@code Longs.fromByteArray(new byte[] {b1, b2, b3, b4, b5, b6, b7, b8})}. + * + * @since 7.0 + */ + public static long fromBytes( + byte b1, byte b2, byte b3, byte b4, byte b5, byte b6, byte b7, byte b8) { + return (b1 & 0xFFL) << 56 + | (b2 & 0xFFL) << 48 + | (b3 & 0xFFL) << 40 + | (b4 & 0xFFL) << 32 + | (b5 & 0xFFL) << 24 + | (b6 & 0xFFL) << 16 + | (b7 & 0xFFL) << 8 + | (b8 & 0xFFL); + } + + /* + * Moving asciiDigits into this static holder class lets ProGuard eliminate and inline the Longs + * class. + */ + static final class AsciiDigits { + private AsciiDigits() {} + + private static final byte[] asciiDigits; + + static { + byte[] result = new byte[128]; + Arrays.fill(result, (byte) -1); + for (int i = 0; i <= 9; i++) { + result['0' + i] = (byte) i; + } + for (int i = 0; i <= 26; i++) { + result['A' + i] = (byte) (10 + i); + result['a' + i] = (byte) (10 + i); + } + asciiDigits = result; + } + + static int digit(char c) { + return (c < 128) ? asciiDigits[c] : -1; + } + } + + /** + * Parses the specified string as a signed decimal long value. The ASCII character {@code '-'} ( + * '\u002D') is recognized as the minus sign. + * + *

Unlike {@link Long#parseLong(String)}, this method returns {@code null} instead of throwing + * an exception if parsing fails. Additionally, this method only accepts ASCII digits, and returns + * {@code null} if non-ASCII digits are present in the string. + * + *

Note that strings prefixed with ASCII {@code '+'} are rejected, even under JDK 7, despite + * the change to {@link Long#parseLong(String)} for that version. + * + * @param string the string representation of a long value + * @return the long value represented by {@code string}, or {@code null} if {@code string} has a + * length of zero or cannot be parsed as a long value + * @throws NullPointerException if {@code string} is {@code null} + * @since 14.0 + */ + @Beta + public static Long tryParse(String string) { + return tryParse(string, 10); + } + + /** + * Parses the specified string as a signed long value using the specified radix. The ASCII + * character {@code '-'} ('\u002D') is recognized as the minus sign. + * + *

Unlike {@link Long#parseLong(String, int)}, this method returns {@code null} instead of + * throwing an exception if parsing fails. Additionally, this method only accepts ASCII digits, + * and returns {@code null} if non-ASCII digits are present in the string. + * + *

Note that strings prefixed with ASCII {@code '+'} are rejected, even under JDK 7, despite + * the change to {@link Long#parseLong(String, int)} for that version. + * + * @param string the string representation of an long value + * @param radix the radix to use when parsing + * @return the long value represented by {@code string} using {@code radix}, or {@code null} if + * {@code string} has a length of zero or cannot be parsed as a long value + * @throws IllegalArgumentException if {@code radix < Character.MIN_RADIX} or {@code radix > + * Character.MAX_RADIX} + * @throws NullPointerException if {@code string} is {@code null} + * @since 19.0 + */ + @Beta + public static Long tryParse(String string, int radix) { + if (checkNotNull(string).isEmpty()) { + return null; + } + if (radix < Character.MIN_RADIX || radix > Character.MAX_RADIX) { + throw new IllegalArgumentException( + "radix must be between MIN_RADIX and MAX_RADIX but was " + radix); + } + boolean negative = string.charAt(0) == '-'; + int index = negative ? 1 : 0; + if (index == string.length()) { + return null; + } + int digit = AsciiDigits.digit(string.charAt(index++)); + if (digit < 0 || digit >= radix) { + return null; + } + long accum = -digit; + + long cap = Long.MIN_VALUE / radix; + + while (index < string.length()) { + digit = AsciiDigits.digit(string.charAt(index++)); + if (digit < 0 || digit >= radix || accum < cap) { + return null; + } + accum *= radix; + if (accum < Long.MIN_VALUE + digit) { + return null; + } + accum -= digit; + } + + if (negative) { + return accum; + } else if (accum == Long.MIN_VALUE) { + return null; + } else { + return -accum; + } + } + + private static final class LongConverter extends Converter implements Serializable { + static final LongConverter INSTANCE = new LongConverter(); + + @Override + protected Long doForward(String value) { + return Long.decode(value); + } + + @Override + protected String doBackward(Long value) { + return value.toString(); + } + + @Override + public String toString() { + return "Longs.stringConverter()"; + } + + private Object readResolve() { + return INSTANCE; + } + + private static final long serialVersionUID = 1; + } + + /** + * Returns a serializable converter object that converts between strings and longs using {@link + * Long#decode} and {@link Long#toString()}. The returned converter throws {@link + * NumberFormatException} if the input string is invalid. + * + *

Warning: please see {@link Long#decode} to understand exactly how strings are parsed. + * For example, the string {@code "0123"} is treated as octal and converted to the value + * {@code 83L}. + * + * @since 16.0 + */ + @Beta + public static Converter stringConverter() { + return LongConverter.INSTANCE; + } + + /** + * Returns an array containing the same values as {@code array}, but guaranteed to be of a + * specified minimum length. If {@code array} already has a length of at least {@code minLength}, + * it is returned directly. Otherwise, a new array of size {@code minLength + padding} is + * returned, containing the values of {@code array}, and zeroes in the remaining places. + * + * @param array the source array + * @param minLength the minimum length the returned array must guarantee + * @param padding an extra amount to "grow" the array by if growth is necessary + * @throws IllegalArgumentException if {@code minLength} or {@code padding} is negative + * @return an array containing the values of {@code array}, with guaranteed minimum length {@code + * minLength} + */ + public static long[] ensureCapacity(long[] array, int minLength, int padding) { + checkArgument(minLength >= 0, "Invalid minLength: %s", minLength); + checkArgument(padding >= 0, "Invalid padding: %s", padding); + return (array.length < minLength) ? Arrays.copyOf(array, minLength + padding) : array; + } + + /** + * Returns a string containing the supplied {@code long} values separated by {@code separator}. + * For example, {@code join("-", 1L, 2L, 3L)} returns the string {@code "1-2-3"}. + * + * @param separator the text that should appear between consecutive values in the resulting string + * (but not at the start or end) + * @param array an array of {@code long} values, possibly empty + */ + public static String join(String separator, long... array) { + checkNotNull(separator); + if (array.length == 0) { + return ""; + } + + // For pre-sizing a builder, just get the right order of magnitude + StringBuilder builder = new StringBuilder(array.length * 10); + builder.append(array[0]); + for (int i = 1; i < array.length; i++) { + builder.append(separator).append(array[i]); + } + return builder.toString(); + } + + /** + * Returns a comparator that compares two {@code long} arrays lexicographically. That is, it + * compares, using {@link #compare(long, long)}), the first pair of values that follow any common + * prefix, or when one array is a prefix of the other, treats the shorter array as the lesser. For + * example, {@code [] < [1L] < [1L, 2L] < [2L]}. + * + *

The returned comparator is inconsistent with {@link Object#equals(Object)} (since arrays + * support only identity equality), but it is consistent with {@link Arrays#equals(long[], + * long[])}. + * + * @since 2.0 + */ + public static Comparator lexicographicalComparator() { + return LexicographicalComparator.INSTANCE; + } + + private enum LexicographicalComparator implements Comparator { + INSTANCE; + + @Override + public int compare(long[] left, long[] right) { + int minLength = Math.min(left.length, right.length); + for (int i = 0; i < minLength; i++) { + int result = Longs.compare(left[i], right[i]); + if (result != 0) { + return result; + } + } + return left.length - right.length; + } + + @Override + public String toString() { + return "Longs.lexicographicalComparator()"; + } + } + + /** + * Sorts the elements of {@code array} in descending order. + * + * @since 23.1 + */ + public static void sortDescending(long[] array) { + checkNotNull(array); + sortDescending(array, 0, array.length); + } + + /** + * Sorts the elements of {@code array} between {@code fromIndex} inclusive and {@code toIndex} + * exclusive in descending order. + * + * @since 23.1 + */ + public static void sortDescending(long[] array, int fromIndex, int toIndex) { + checkNotNull(array); + checkPositionIndexes(fromIndex, toIndex, array.length); + Arrays.sort(array, fromIndex, toIndex); + reverse(array, fromIndex, toIndex); + } + + /** + * Reverses the elements of {@code array}. This is equivalent to {@code + * Collections.reverse(Longs.asList(array))}, but is likely to be more efficient. + * + * @since 23.1 + */ + public static void reverse(long[] array) { + checkNotNull(array); + reverse(array, 0, array.length); + } + + /** + * Reverses the elements of {@code array} between {@code fromIndex} inclusive and {@code toIndex} + * exclusive. This is equivalent to {@code + * Collections.reverse(Longs.asList(array).subList(fromIndex, toIndex))}, but is likely to be more + * efficient. + * + * @throws IndexOutOfBoundsException if {@code fromIndex < 0}, {@code toIndex > array.length}, or + * {@code toIndex > fromIndex} + * @since 23.1 + */ + public static void reverse(long[] array, int fromIndex, int toIndex) { + checkNotNull(array); + checkPositionIndexes(fromIndex, toIndex, array.length); + for (int i = fromIndex, j = toIndex - 1; i < j; i++, j--) { + long tmp = array[i]; + array[i] = array[j]; + array[j] = tmp; + } + } + + /** + * Returns an array containing each value of {@code collection}, converted to a {@code long} value + * in the manner of {@link Number#longValue}. + * + *

Elements are copied from the argument collection as if by {@code collection.toArray()}. + * Calling this method is as thread-safe as calling that method. + * + * @param collection a collection of {@code Number} instances + * @return an array containing the same values as {@code collection}, in the same order, converted + * to primitives + * @throws NullPointerException if {@code collection} or any of its elements is null + * @since 1.0 (parameter was {@code Collection} before 12.0) + */ + public static long[] toArray(Collection collection) { + if (collection instanceof LongArrayAsList) { + return ((LongArrayAsList) collection).toLongArray(); + } + + Object[] boxedArray = collection.toArray(); + int len = boxedArray.length; + long[] array = new long[len]; + for (int i = 0; i < len; i++) { + // checkNotNull for GWT (do not optimize) + array[i] = ((Number) checkNotNull(boxedArray[i])).longValue(); + } + return array; + } + + /** + * Returns a fixed-size list backed by the specified array, similar to {@link + * Arrays#asList(Object[])}. The list supports {@link List#set(int, Object)}, but any attempt to + * set a value to {@code null} will result in a {@link NullPointerException}. + * + *

The returned list maintains the values, but not the identities, of {@code Long} objects + * written to or read from it. For example, whether {@code list.get(0) == list.get(0)} is true for + * the returned list is unspecified. + * + *

Note: when possible, you should represent your data as an {@link ImmutableLongArray} + * instead, which has an {@link ImmutableLongArray#asList asList} view. + * + * @param backingArray the array to back the list + * @return a list view of the array + */ + public static List asList(long... backingArray) { + if (backingArray.length == 0) { + return Collections.emptyList(); + } + return new LongArrayAsList(backingArray); + } + + @GwtCompatible + private static class LongArrayAsList extends AbstractList + implements RandomAccess, Serializable { + final long[] array; + final int start; + final int end; + + LongArrayAsList(long[] array) { + this(array, 0, array.length); + } + + LongArrayAsList(long[] array, int start, int end) { + this.array = array; + this.start = start; + this.end = end; + } + + @Override + public int size() { + return end - start; + } + + @Override + public boolean isEmpty() { + return false; + } + + @Override + public Long get(int index) { + checkElementIndex(index, size()); + return array[start + index]; + } + + @Override + public Spliterator.OfLong spliterator() { + return Spliterators.spliterator(array, start, end, 0); + } + + @Override + public boolean contains(Object target) { + // Overridden to prevent a ton of boxing + return (target instanceof Long) && Longs.indexOf(array, (Long) target, start, end) != -1; + } + + @Override + public int indexOf(Object target) { + // Overridden to prevent a ton of boxing + if (target instanceof Long) { + int i = Longs.indexOf(array, (Long) target, start, end); + if (i >= 0) { + return i - start; + } + } + return -1; + } + + @Override + public int lastIndexOf(Object target) { + // Overridden to prevent a ton of boxing + if (target instanceof Long) { + int i = Longs.lastIndexOf(array, (Long) target, start, end); + if (i >= 0) { + return i - start; + } + } + return -1; + } + + @Override + public Long set(int index, Long element) { + checkElementIndex(index, size()); + long oldValue = array[start + index]; + // checkNotNull for GWT (do not optimize) + array[start + index] = checkNotNull(element); + return oldValue; + } + + @Override + public List subList(int fromIndex, int toIndex) { + int size = size(); + checkPositionIndexes(fromIndex, toIndex, size); + if (fromIndex == toIndex) { + return Collections.emptyList(); + } + return new LongArrayAsList(array, start + fromIndex, start + toIndex); + } + + @Override + public boolean equals(Object object) { + if (object == this) { + return true; + } + if (object instanceof LongArrayAsList) { + LongArrayAsList that = (LongArrayAsList) object; + int size = size(); + if (that.size() != size) { + return false; + } + for (int i = 0; i < size; i++) { + if (array[start + i] != that.array[that.start + i]) { + return false; + } + } + return true; + } + return super.equals(object); + } + + @Override + public int hashCode() { + int result = 1; + for (int i = start; i < end; i++) { + result = 31 * result + Longs.hashCode(array[i]); + } + return result; + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(size() * 10); + builder.append('[').append(array[start]); + for (int i = start + 1; i < end; i++) { + builder.append(", ").append(array[i]); + } + return builder.append(']').toString(); + } + + long[] toLongArray() { + return Arrays.copyOfRange(array, start, end); + } + + private static final long serialVersionUID = 0; + } +} diff --git a/src/main/java/com/google/common/primitives/ParseRequest.java b/src/main/java/com/google/common/primitives/ParseRequest.java new file mode 100644 index 0000000..97b0f1b --- /dev/null +++ b/src/main/java/com/google/common/primitives/ParseRequest.java @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2011 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.primitives; + +import com.google.common.annotations.GwtCompatible; + +/** A string to be parsed as a number and the radix to interpret it in. */ +@GwtCompatible +final class ParseRequest { + final String rawValue; + final int radix; + + private ParseRequest(String rawValue, int radix) { + this.rawValue = rawValue; + this.radix = radix; + } + + static ParseRequest fromString(String stringValue) { + if (stringValue.length() == 0) { + throw new NumberFormatException("empty string"); + } + + // Handle radix specifier if present + String rawValue; + int radix; + char firstChar = stringValue.charAt(0); + if (stringValue.startsWith("0x") || stringValue.startsWith("0X")) { + rawValue = stringValue.substring(2); + radix = 16; + } else if (firstChar == '#') { + rawValue = stringValue.substring(1); + radix = 16; + } else if (firstChar == '0' && stringValue.length() > 1) { + rawValue = stringValue.substring(1); + radix = 8; + } else { + rawValue = stringValue; + radix = 10; + } + + return new ParseRequest(rawValue, radix); + } +} diff --git a/src/main/java/com/google/common/primitives/Platform.java b/src/main/java/com/google/common/primitives/Platform.java new file mode 100644 index 0000000..7006baf --- /dev/null +++ b/src/main/java/com/google/common/primitives/Platform.java @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2019 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.primitives; + +import static com.google.common.base.Strings.lenientFormat; +import static java.lang.Boolean.parseBoolean; + +import com.google.common.annotations.GwtCompatible; + +/** Methods factored out so that they can be emulated differently in GWT. */ +@GwtCompatible(emulated = true) +final class Platform { + private static final java.util.logging.Logger logger = + java.util.logging.Logger.getLogger(Platform.class.getName()); + + private static final String GWT_RPC_PROPERTY_NAME = "guava.gwt.emergency_reenable_rpc"; + + static void checkGwtRpcEnabled() { + if (!parseBoolean(System.getProperty(GWT_RPC_PROPERTY_NAME, "true"))) { + throw new UnsupportedOperationException( + lenientFormat( + "We are removing GWT-RPC support for Guava types. You can temporarily reenable" + + " support by setting the system property %s to true. For more about system" + + " properties, see %s. For more about Guava's GWT-RPC support, see %s.", + GWT_RPC_PROPERTY_NAME, + "https://stackoverflow.com/q/5189914/28465", + "https://groups.google.com/d/msg/guava-announce/zHZTFg7YF3o/rQNnwdHeEwAJ")); + } + logger.log( + java.util.logging.Level.WARNING, + "In January 2020, we will remove GWT-RPC support for Guava types. You are seeing this" + + " warning because you are sending a Guava type over GWT-RPC, which will break. You" + + " can identify which type by looking at the class name in the attached stack trace.", + new Throwable()); + + } + + private Platform() {} +} diff --git a/src/main/java/com/google/common/primitives/Primitives.java b/src/main/java/com/google/common/primitives/Primitives.java new file mode 100644 index 0000000..1bdc740 --- /dev/null +++ b/src/main/java/com/google/common/primitives/Primitives.java @@ -0,0 +1,138 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.primitives; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.annotations.GwtIncompatible; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Set; + +/** + * Contains static utility methods pertaining to primitive types and their corresponding wrapper + * types. + * + * @author Kevin Bourrillion + * @since 1.0 + */ +@GwtIncompatible +public final class Primitives { + private Primitives() {} + + /** A map from primitive types to their corresponding wrapper types. */ + private static final Map, Class> PRIMITIVE_TO_WRAPPER_TYPE; + + /** A map from wrapper types to their corresponding primitive types. */ + private static final Map, Class> WRAPPER_TO_PRIMITIVE_TYPE; + + // Sad that we can't use a BiMap. :( + + static { + Map, Class> primToWrap = new LinkedHashMap<>(16); + Map, Class> wrapToPrim = new LinkedHashMap<>(16); + + add(primToWrap, wrapToPrim, boolean.class, Boolean.class); + add(primToWrap, wrapToPrim, byte.class, Byte.class); + add(primToWrap, wrapToPrim, char.class, Character.class); + add(primToWrap, wrapToPrim, double.class, Double.class); + add(primToWrap, wrapToPrim, float.class, Float.class); + add(primToWrap, wrapToPrim, int.class, Integer.class); + add(primToWrap, wrapToPrim, long.class, Long.class); + add(primToWrap, wrapToPrim, short.class, Short.class); + add(primToWrap, wrapToPrim, void.class, Void.class); + + PRIMITIVE_TO_WRAPPER_TYPE = Collections.unmodifiableMap(primToWrap); + WRAPPER_TO_PRIMITIVE_TYPE = Collections.unmodifiableMap(wrapToPrim); + } + + private static void add( + Map, Class> forward, + Map, Class> backward, + Class key, + Class value) { + forward.put(key, value); + backward.put(value, key); + } + + /** + * Returns an immutable set of all nine primitive types (including {@code void}). Note that a + * simpler way to test whether a {@code Class} instance is a member of this set is to call {@link + * Class#isPrimitive}. + * + * @since 3.0 + */ + public static Set> allPrimitiveTypes() { + return PRIMITIVE_TO_WRAPPER_TYPE.keySet(); + } + + /** + * Returns an immutable set of all nine primitive-wrapper types (including {@link Void}). + * + * @since 3.0 + */ + public static Set> allWrapperTypes() { + return WRAPPER_TO_PRIMITIVE_TYPE.keySet(); + } + + /** + * Returns {@code true} if {@code type} is one of the nine primitive-wrapper types, such as {@link + * Integer}. + * + * @see Class#isPrimitive + */ + public static boolean isWrapperType(Class type) { + return WRAPPER_TO_PRIMITIVE_TYPE.containsKey(checkNotNull(type)); + } + + /** + * Returns the corresponding wrapper type of {@code type} if it is a primitive type; otherwise + * returns {@code type} itself. Idempotent. + * + *

+   *     wrap(int.class) == Integer.class
+   *     wrap(Integer.class) == Integer.class
+   *     wrap(String.class) == String.class
+   * 
+ */ + public static Class wrap(Class type) { + checkNotNull(type); + + // cast is safe: long.class and Long.class are both of type Class + @SuppressWarnings("unchecked") + Class wrapped = (Class) PRIMITIVE_TO_WRAPPER_TYPE.get(type); + return (wrapped == null) ? type : wrapped; + } + + /** + * Returns the corresponding primitive type of {@code type} if it is a wrapper type; otherwise + * returns {@code type} itself. Idempotent. + * + *
+   *     unwrap(Integer.class) == int.class
+   *     unwrap(int.class) == int.class
+   *     unwrap(String.class) == String.class
+   * 
+ */ + public static Class unwrap(Class type) { + checkNotNull(type); + + // cast is safe: long.class and Long.class are both of type Class + @SuppressWarnings("unchecked") + Class unwrapped = (Class) WRAPPER_TO_PRIMITIVE_TYPE.get(type); + return (unwrapped == null) ? type : unwrapped; + } +} diff --git a/src/main/java/com/google/common/primitives/Shorts.java b/src/main/java/com/google/common/primitives/Shorts.java new file mode 100644 index 0000000..1f91edc --- /dev/null +++ b/src/main/java/com/google/common/primitives/Shorts.java @@ -0,0 +1,685 @@ +/* + * Copyright (C) 2008 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.primitives; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkElementIndex; +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.base.Preconditions.checkPositionIndexes; + +import com.google.common.annotations.Beta; +import com.google.common.annotations.GwtCompatible; +import com.google.common.annotations.GwtIncompatible; +import com.google.common.base.Converter; +import java.io.Serializable; +import java.util.AbstractList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; +import java.util.RandomAccess; + + +/** + * Static utility methods pertaining to {@code short} primitives, that are not already found in + * either {@link Short} or {@link Arrays}. + * + *

See the Guava User Guide article on primitive utilities. + * + * @author Kevin Bourrillion + * @since 1.0 + */ +@GwtCompatible(emulated = true) +public final class Shorts { + private Shorts() {} + + /** + * The number of bytes required to represent a primitive {@code short} value. + * + *

Java 8 users: use {@link Short#BYTES} instead. + */ + public static final int BYTES = Short.SIZE / Byte.SIZE; + + /** + * The largest power of two that can be represented as a {@code short}. + * + * @since 10.0 + */ + public static final short MAX_POWER_OF_TWO = 1 << (Short.SIZE - 2); + + /** + * Returns a hash code for {@code value}; equal to the result of invoking {@code ((Short) + * value).hashCode()}. + * + *

Java 8 users: use {@link Short#hashCode(short)} instead. + * + * @param value a primitive {@code short} value + * @return a hash code for the value + */ + public static int hashCode(short value) { + return value; + } + + /** + * Returns the {@code short} value that is equal to {@code value}, if possible. + * + * @param value any value in the range of the {@code short} type + * @return the {@code short} value that equals {@code value} + * @throws IllegalArgumentException if {@code value} is greater than {@link Short#MAX_VALUE} or + * less than {@link Short#MIN_VALUE} + */ + public static short checkedCast(long value) { + short result = (short) value; + checkArgument(result == value, "Out of range: %s", value); + return result; + } + + /** + * Returns the {@code short} nearest in value to {@code value}. + * + * @param value any {@code long} value + * @return the same value cast to {@code short} if it is in the range of the {@code short} type, + * {@link Short#MAX_VALUE} if it is too large, or {@link Short#MIN_VALUE} if it is too small + */ + public static short saturatedCast(long value) { + if (value > Short.MAX_VALUE) { + return Short.MAX_VALUE; + } + if (value < Short.MIN_VALUE) { + return Short.MIN_VALUE; + } + return (short) value; + } + + /** + * Compares the two specified {@code short} values. The sign of the value returned is the same as + * that of {@code ((Short) a).compareTo(b)}. + * + *

Note for Java 7 and later: this method should be treated as deprecated; use the + * equivalent {@link Short#compare} method instead. + * + * @param a the first {@code short} to compare + * @param b the second {@code short} to compare + * @return a negative value if {@code a} is less than {@code b}; a positive value if {@code a} is + * greater than {@code b}; or zero if they are equal + */ + public static int compare(short a, short b) { + return a - b; // safe due to restricted range + } + + /** + * Returns {@code true} if {@code target} is present as an element anywhere in {@code array}. + * + * @param array an array of {@code short} values, possibly empty + * @param target a primitive {@code short} value + * @return {@code true} if {@code array[i] == target} for some value of {@code i} + */ + public static boolean contains(short[] array, short target) { + for (short value : array) { + if (value == target) { + return true; + } + } + return false; + } + + /** + * Returns the index of the first appearance of the value {@code target} in {@code array}. + * + * @param array an array of {@code short} values, possibly empty + * @param target a primitive {@code short} value + * @return the least index {@code i} for which {@code array[i] == target}, or {@code -1} if no + * such index exists. + */ + public static int indexOf(short[] array, short target) { + return indexOf(array, target, 0, array.length); + } + + // TODO(kevinb): consider making this public + private static int indexOf(short[] array, short target, int start, int end) { + for (int i = start; i < end; i++) { + if (array[i] == target) { + return i; + } + } + return -1; + } + + /** + * Returns the start position of the first occurrence of the specified {@code target} within + * {@code array}, or {@code -1} if there is no such occurrence. + * + *

More formally, returns the lowest index {@code i} such that {@code Arrays.copyOfRange(array, + * i, i + target.length)} contains exactly the same elements as {@code target}. + * + * @param array the array to search for the sequence {@code target} + * @param target the array to search for as a sub-sequence of {@code array} + */ + public static int indexOf(short[] array, short[] target) { + checkNotNull(array, "array"); + checkNotNull(target, "target"); + if (target.length == 0) { + return 0; + } + + outer: + for (int i = 0; i < array.length - target.length + 1; i++) { + for (int j = 0; j < target.length; j++) { + if (array[i + j] != target[j]) { + continue outer; + } + } + return i; + } + return -1; + } + + /** + * Returns the index of the last appearance of the value {@code target} in {@code array}. + * + * @param array an array of {@code short} values, possibly empty + * @param target a primitive {@code short} value + * @return the greatest index {@code i} for which {@code array[i] == target}, or {@code -1} if no + * such index exists. + */ + public static int lastIndexOf(short[] array, short target) { + return lastIndexOf(array, target, 0, array.length); + } + + // TODO(kevinb): consider making this public + private static int lastIndexOf(short[] array, short target, int start, int end) { + for (int i = end - 1; i >= start; i--) { + if (array[i] == target) { + return i; + } + } + return -1; + } + + /** + * Returns the least value present in {@code array}. + * + * @param array a nonempty array of {@code short} values + * @return the value present in {@code array} that is less than or equal to every other value in + * the array + * @throws IllegalArgumentException if {@code array} is empty + */ + public static short min(short... array) { + checkArgument(array.length > 0); + short min = array[0]; + for (int i = 1; i < array.length; i++) { + if (array[i] < min) { + min = array[i]; + } + } + return min; + } + + /** + * Returns the greatest value present in {@code array}. + * + * @param array a nonempty array of {@code short} values + * @return the value present in {@code array} that is greater than or equal to every other value + * in the array + * @throws IllegalArgumentException if {@code array} is empty + */ + public static short max(short... array) { + checkArgument(array.length > 0); + short max = array[0]; + for (int i = 1; i < array.length; i++) { + if (array[i] > max) { + max = array[i]; + } + } + return max; + } + + /** + * Returns the value nearest to {@code value} which is within the closed range {@code [min..max]}. + * + *

If {@code value} is within the range {@code [min..max]}, {@code value} is returned + * unchanged. If {@code value} is less than {@code min}, {@code min} is returned, and if {@code + * value} is greater than {@code max}, {@code max} is returned. + * + * @param value the {@code short} value to constrain + * @param min the lower bound (inclusive) of the range to constrain {@code value} to + * @param max the upper bound (inclusive) of the range to constrain {@code value} to + * @throws IllegalArgumentException if {@code min > max} + * @since 21.0 + */ + @Beta + public static short constrainToRange(short value, short min, short max) { + checkArgument(min <= max, "min (%s) must be less than or equal to max (%s)", min, max); + return value < min ? min : value < max ? value : max; + } + + /** + * Returns the values from each provided array combined into a single array. For example, {@code + * concat(new short[] {a, b}, new short[] {}, new short[] {c}} returns the array {@code {a, b, + * c}}. + * + * @param arrays zero or more {@code short} arrays + * @return a single array containing all the values from the source arrays, in order + */ + public static short[] concat(short[]... arrays) { + int length = 0; + for (short[] array : arrays) { + length += array.length; + } + short[] result = new short[length]; + int pos = 0; + for (short[] array : arrays) { + System.arraycopy(array, 0, result, pos, array.length); + pos += array.length; + } + return result; + } + + /** + * Returns a big-endian representation of {@code value} in a 2-element byte array; equivalent to + * {@code ByteBuffer.allocate(2).putShort(value).array()}. For example, the input value {@code + * (short) 0x1234} would yield the byte array {@code {0x12, 0x34}}. + * + *

If you need to convert and concatenate several values (possibly even of different types), + * use a shared {@link java.nio.ByteBuffer} instance, or use {@link + * com.google.common.io.ByteStreams#newDataOutput()} to get a growable buffer. + */ + @GwtIncompatible // doesn't work + public static byte[] toByteArray(short value) { + return new byte[] {(byte) (value >> 8), (byte) value}; + } + + /** + * Returns the {@code short} value whose big-endian representation is stored in the first 2 bytes + * of {@code bytes}; equivalent to {@code ByteBuffer.wrap(bytes).getShort()}. For example, the + * input byte array {@code {0x54, 0x32}} would yield the {@code short} value {@code 0x5432}. + * + *

Arguably, it's preferable to use {@link java.nio.ByteBuffer}; that library exposes much more + * flexibility at little cost in readability. + * + * @throws IllegalArgumentException if {@code bytes} has fewer than 2 elements + */ + @GwtIncompatible // doesn't work + public static short fromByteArray(byte[] bytes) { + checkArgument(bytes.length >= BYTES, "array too small: %s < %s", bytes.length, BYTES); + return fromBytes(bytes[0], bytes[1]); + } + + /** + * Returns the {@code short} value whose byte representation is the given 2 bytes, in big-endian + * order; equivalent to {@code Shorts.fromByteArray(new byte[] {b1, b2})}. + * + * @since 7.0 + */ + @GwtIncompatible // doesn't work + public static short fromBytes(byte b1, byte b2) { + return (short) ((b1 << 8) | (b2 & 0xFF)); + } + + private static final class ShortConverter extends Converter + implements Serializable { + static final ShortConverter INSTANCE = new ShortConverter(); + + @Override + protected Short doForward(String value) { + return Short.decode(value); + } + + @Override + protected String doBackward(Short value) { + return value.toString(); + } + + @Override + public String toString() { + return "Shorts.stringConverter()"; + } + + private Object readResolve() { + return INSTANCE; + } + + private static final long serialVersionUID = 1; + } + + /** + * Returns a serializable converter object that converts between strings and shorts using {@link + * Short#decode} and {@link Short#toString()}. The returned converter throws {@link + * NumberFormatException} if the input string is invalid. + * + *

Warning: please see {@link Short#decode} to understand exactly how strings are + * parsed. For example, the string {@code "0123"} is treated as octal and converted to the + * value {@code 83}. + * + * @since 16.0 + */ + @Beta + public static Converter stringConverter() { + return ShortConverter.INSTANCE; + } + + /** + * Returns an array containing the same values as {@code array}, but guaranteed to be of a + * specified minimum length. If {@code array} already has a length of at least {@code minLength}, + * it is returned directly. Otherwise, a new array of size {@code minLength + padding} is + * returned, containing the values of {@code array}, and zeroes in the remaining places. + * + * @param array the source array + * @param minLength the minimum length the returned array must guarantee + * @param padding an extra amount to "grow" the array by if growth is necessary + * @throws IllegalArgumentException if {@code minLength} or {@code padding} is negative + * @return an array containing the values of {@code array}, with guaranteed minimum length {@code + * minLength} + */ + public static short[] ensureCapacity(short[] array, int minLength, int padding) { + checkArgument(minLength >= 0, "Invalid minLength: %s", minLength); + checkArgument(padding >= 0, "Invalid padding: %s", padding); + return (array.length < minLength) ? Arrays.copyOf(array, minLength + padding) : array; + } + + /** + * Returns a string containing the supplied {@code short} values separated by {@code separator}. + * For example, {@code join("-", (short) 1, (short) 2, (short) 3)} returns the string {@code + * "1-2-3"}. + * + * @param separator the text that should appear between consecutive values in the resulting string + * (but not at the start or end) + * @param array an array of {@code short} values, possibly empty + */ + public static String join(String separator, short... array) { + checkNotNull(separator); + if (array.length == 0) { + return ""; + } + + // For pre-sizing a builder, just get the right order of magnitude + StringBuilder builder = new StringBuilder(array.length * 6); + builder.append(array[0]); + for (int i = 1; i < array.length; i++) { + builder.append(separator).append(array[i]); + } + return builder.toString(); + } + + /** + * Returns a comparator that compares two {@code short} arrays lexicographically. That is, it + * compares, using {@link #compare(short, short)}), the first pair of values that follow any + * common prefix, or when one array is a prefix of the other, treats the shorter array as the + * lesser. For example, {@code [] < [(short) 1] < [(short) 1, (short) 2] < [(short) 2]}. + * + *

The returned comparator is inconsistent with {@link Object#equals(Object)} (since arrays + * support only identity equality), but it is consistent with {@link Arrays#equals(short[], + * short[])}. + * + * @since 2.0 + */ + public static Comparator lexicographicalComparator() { + return LexicographicalComparator.INSTANCE; + } + + private enum LexicographicalComparator implements Comparator { + INSTANCE; + + @Override + public int compare(short[] left, short[] right) { + int minLength = Math.min(left.length, right.length); + for (int i = 0; i < minLength; i++) { + int result = Shorts.compare(left[i], right[i]); + if (result != 0) { + return result; + } + } + return left.length - right.length; + } + + @Override + public String toString() { + return "Shorts.lexicographicalComparator()"; + } + } + + /** + * Sorts the elements of {@code array} in descending order. + * + * @since 23.1 + */ + public static void sortDescending(short[] array) { + checkNotNull(array); + sortDescending(array, 0, array.length); + } + + /** + * Sorts the elements of {@code array} between {@code fromIndex} inclusive and {@code toIndex} + * exclusive in descending order. + * + * @since 23.1 + */ + public static void sortDescending(short[] array, int fromIndex, int toIndex) { + checkNotNull(array); + checkPositionIndexes(fromIndex, toIndex, array.length); + Arrays.sort(array, fromIndex, toIndex); + reverse(array, fromIndex, toIndex); + } + + /** + * Reverses the elements of {@code array}. This is equivalent to {@code + * Collections.reverse(Shorts.asList(array))}, but is likely to be more efficient. + * + * @since 23.1 + */ + public static void reverse(short[] array) { + checkNotNull(array); + reverse(array, 0, array.length); + } + + /** + * Reverses the elements of {@code array} between {@code fromIndex} inclusive and {@code toIndex} + * exclusive. This is equivalent to {@code + * Collections.reverse(Shorts.asList(array).subList(fromIndex, toIndex))}, but is likely to be + * more efficient. + * + * @throws IndexOutOfBoundsException if {@code fromIndex < 0}, {@code toIndex > array.length}, or + * {@code toIndex > fromIndex} + * @since 23.1 + */ + public static void reverse(short[] array, int fromIndex, int toIndex) { + checkNotNull(array); + checkPositionIndexes(fromIndex, toIndex, array.length); + for (int i = fromIndex, j = toIndex - 1; i < j; i++, j--) { + short tmp = array[i]; + array[i] = array[j]; + array[j] = tmp; + } + } + + /** + * Returns an array containing each value of {@code collection}, converted to a {@code short} + * value in the manner of {@link Number#shortValue}. + * + *

Elements are copied from the argument collection as if by {@code collection.toArray()}. + * Calling this method is as thread-safe as calling that method. + * + * @param collection a collection of {@code Number} instances + * @return an array containing the same values as {@code collection}, in the same order, converted + * to primitives + * @throws NullPointerException if {@code collection} or any of its elements is null + * @since 1.0 (parameter was {@code Collection} before 12.0) + */ + public static short[] toArray(Collection collection) { + if (collection instanceof ShortArrayAsList) { + return ((ShortArrayAsList) collection).toShortArray(); + } + + Object[] boxedArray = collection.toArray(); + int len = boxedArray.length; + short[] array = new short[len]; + for (int i = 0; i < len; i++) { + // checkNotNull for GWT (do not optimize) + array[i] = ((Number) checkNotNull(boxedArray[i])).shortValue(); + } + return array; + } + + /** + * Returns a fixed-size list backed by the specified array, similar to {@link + * Arrays#asList(Object[])}. The list supports {@link List#set(int, Object)}, but any attempt to + * set a value to {@code null} will result in a {@link NullPointerException}. + * + *

The returned list maintains the values, but not the identities, of {@code Short} objects + * written to or read from it. For example, whether {@code list.get(0) == list.get(0)} is true for + * the returned list is unspecified. + * + * @param backingArray the array to back the list + * @return a list view of the array + */ + public static List asList(short... backingArray) { + if (backingArray.length == 0) { + return Collections.emptyList(); + } + return new ShortArrayAsList(backingArray); + } + + @GwtCompatible + private static class ShortArrayAsList extends AbstractList + implements RandomAccess, Serializable { + final short[] array; + final int start; + final int end; + + ShortArrayAsList(short[] array) { + this(array, 0, array.length); + } + + ShortArrayAsList(short[] array, int start, int end) { + this.array = array; + this.start = start; + this.end = end; + } + + @Override + public int size() { + return end - start; + } + + @Override + public boolean isEmpty() { + return false; + } + + @Override + public Short get(int index) { + checkElementIndex(index, size()); + return array[start + index]; + } + + @Override + public boolean contains(Object target) { + // Overridden to prevent a ton of boxing + return (target instanceof Short) && Shorts.indexOf(array, (Short) target, start, end) != -1; + } + + @Override + public int indexOf(Object target) { + // Overridden to prevent a ton of boxing + if (target instanceof Short) { + int i = Shorts.indexOf(array, (Short) target, start, end); + if (i >= 0) { + return i - start; + } + } + return -1; + } + + @Override + public int lastIndexOf(Object target) { + // Overridden to prevent a ton of boxing + if (target instanceof Short) { + int i = Shorts.lastIndexOf(array, (Short) target, start, end); + if (i >= 0) { + return i - start; + } + } + return -1; + } + + @Override + public Short set(int index, Short element) { + checkElementIndex(index, size()); + short oldValue = array[start + index]; + // checkNotNull for GWT (do not optimize) + array[start + index] = checkNotNull(element); + return oldValue; + } + + @Override + public List subList(int fromIndex, int toIndex) { + int size = size(); + checkPositionIndexes(fromIndex, toIndex, size); + if (fromIndex == toIndex) { + return Collections.emptyList(); + } + return new ShortArrayAsList(array, start + fromIndex, start + toIndex); + } + + @Override + public boolean equals(Object object) { + if (object == this) { + return true; + } + if (object instanceof ShortArrayAsList) { + ShortArrayAsList that = (ShortArrayAsList) object; + int size = size(); + if (that.size() != size) { + return false; + } + for (int i = 0; i < size; i++) { + if (array[start + i] != that.array[that.start + i]) { + return false; + } + } + return true; + } + return super.equals(object); + } + + @Override + public int hashCode() { + int result = 1; + for (int i = start; i < end; i++) { + result = 31 * result + Shorts.hashCode(array[i]); + } + return result; + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(size() * 6); + builder.append('[').append(array[start]); + for (int i = start + 1; i < end; i++) { + builder.append(", ").append(array[i]); + } + return builder.append(']').toString(); + } + + short[] toShortArray() { + return Arrays.copyOfRange(array, start, end); + } + + private static final long serialVersionUID = 0; + } +} diff --git a/src/main/java/com/google/common/primitives/SignedBytes.java b/src/main/java/com/google/common/primitives/SignedBytes.java new file mode 100644 index 0000000..e475c41 --- /dev/null +++ b/src/main/java/com/google/common/primitives/SignedBytes.java @@ -0,0 +1,219 @@ +/* + * Copyright (C) 2009 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.primitives; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.base.Preconditions.checkPositionIndexes; + +import com.google.common.annotations.GwtCompatible; +import java.util.Arrays; +import java.util.Comparator; + +/** + * Static utility methods pertaining to {@code byte} primitives that interpret values as signed. The + * corresponding methods that treat the values as unsigned are found in {@link UnsignedBytes}, and + * the methods for which signedness is not an issue are in {@link Bytes}. + * + *

See the Guava User Guide article on primitive utilities. + * + * @author Kevin Bourrillion + * @since 1.0 + */ +// TODO(kevinb): how to prevent warning on UnsignedBytes when building GWT +// javadoc? +@GwtCompatible +public final class SignedBytes { + private SignedBytes() {} + + /** + * The largest power of two that can be represented as a signed {@code byte}. + * + * @since 10.0 + */ + public static final byte MAX_POWER_OF_TWO = 1 << 6; + + /** + * Returns the {@code byte} value that is equal to {@code value}, if possible. + * + * @param value any value in the range of the {@code byte} type + * @return the {@code byte} value that equals {@code value} + * @throws IllegalArgumentException if {@code value} is greater than {@link Byte#MAX_VALUE} or + * less than {@link Byte#MIN_VALUE} + */ + public static byte checkedCast(long value) { + byte result = (byte) value; + checkArgument(result == value, "Out of range: %s", value); + return result; + } + + /** + * Returns the {@code byte} nearest in value to {@code value}. + * + * @param value any {@code long} value + * @return the same value cast to {@code byte} if it is in the range of the {@code byte} type, + * {@link Byte#MAX_VALUE} if it is too large, or {@link Byte#MIN_VALUE} if it is too small + */ + public static byte saturatedCast(long value) { + if (value > Byte.MAX_VALUE) { + return Byte.MAX_VALUE; + } + if (value < Byte.MIN_VALUE) { + return Byte.MIN_VALUE; + } + return (byte) value; + } + + /** + * Compares the two specified {@code byte} values. The sign of the value returned is the same as + * that of {@code ((Byte) a).compareTo(b)}. + * + *

Note: this method behaves identically to the JDK 7 method {@link Byte#compare}. + * + * @param a the first {@code byte} to compare + * @param b the second {@code byte} to compare + * @return a negative value if {@code a} is less than {@code b}; a positive value if {@code a} is + * greater than {@code b}; or zero if they are equal + */ + // TODO(kevinb): if Ints.compare etc. are ever removed, *maybe* remove this + // one too, which would leave compare methods only on the Unsigned* classes. + public static int compare(byte a, byte b) { + return a - b; // safe due to restricted range + } + + /** + * Returns the least value present in {@code array}. + * + * @param array a nonempty array of {@code byte} values + * @return the value present in {@code array} that is less than or equal to every other value in + * the array + * @throws IllegalArgumentException if {@code array} is empty + */ + public static byte min(byte... array) { + checkArgument(array.length > 0); + byte min = array[0]; + for (int i = 1; i < array.length; i++) { + if (array[i] < min) { + min = array[i]; + } + } + return min; + } + + /** + * Returns the greatest value present in {@code array}. + * + * @param array a nonempty array of {@code byte} values + * @return the value present in {@code array} that is greater than or equal to every other value + * in the array + * @throws IllegalArgumentException if {@code array} is empty + */ + public static byte max(byte... array) { + checkArgument(array.length > 0); + byte max = array[0]; + for (int i = 1; i < array.length; i++) { + if (array[i] > max) { + max = array[i]; + } + } + return max; + } + + /** + * Returns a string containing the supplied {@code byte} values separated by {@code separator}. + * For example, {@code join(":", 0x01, 0x02, -0x01)} returns the string {@code "1:2:-1"}. + * + * @param separator the text that should appear between consecutive values in the resulting string + * (but not at the start or end) + * @param array an array of {@code byte} values, possibly empty + */ + public static String join(String separator, byte... array) { + checkNotNull(separator); + if (array.length == 0) { + return ""; + } + + // For pre-sizing a builder, just get the right order of magnitude + StringBuilder builder = new StringBuilder(array.length * 5); + builder.append(array[0]); + for (int i = 1; i < array.length; i++) { + builder.append(separator).append(array[i]); + } + return builder.toString(); + } + + /** + * Returns a comparator that compares two {@code byte} arrays lexicographically. That is, it + * compares, using {@link #compare(byte, byte)}), the first pair of values that follow any common + * prefix, or when one array is a prefix of the other, treats the shorter array as the lesser. For + * example, {@code [] < [0x01] < [0x01, 0x80] < [0x01, 0x7F] < [0x02]}. Values are treated as + * signed. + * + *

The returned comparator is inconsistent with {@link Object#equals(Object)} (since arrays + * support only identity equality), but it is consistent with {@link + * java.util.Arrays#equals(byte[], byte[])}. + * + * @since 2.0 + */ + public static Comparator lexicographicalComparator() { + return LexicographicalComparator.INSTANCE; + } + + private enum LexicographicalComparator implements Comparator { + INSTANCE; + + @Override + public int compare(byte[] left, byte[] right) { + int minLength = Math.min(left.length, right.length); + for (int i = 0; i < minLength; i++) { + int result = SignedBytes.compare(left[i], right[i]); + if (result != 0) { + return result; + } + } + return left.length - right.length; + } + + @Override + public String toString() { + return "SignedBytes.lexicographicalComparator()"; + } + } + + /** + * Sorts the elements of {@code array} in descending order. + * + * @since 23.1 + */ + public static void sortDescending(byte[] array) { + checkNotNull(array); + sortDescending(array, 0, array.length); + } + + /** + * Sorts the elements of {@code array} between {@code fromIndex} inclusive and {@code toIndex} + * exclusive in descending order. + * + * @since 23.1 + */ + public static void sortDescending(byte[] array, int fromIndex, int toIndex) { + checkNotNull(array); + checkPositionIndexes(fromIndex, toIndex, array.length); + Arrays.sort(array, fromIndex, toIndex); + Bytes.reverse(array, fromIndex, toIndex); + } +} diff --git a/src/main/java/com/google/common/primitives/UnsignedBytes.java b/src/main/java/com/google/common/primitives/UnsignedBytes.java new file mode 100644 index 0000000..70b60ed --- /dev/null +++ b/src/main/java/com/google/common/primitives/UnsignedBytes.java @@ -0,0 +1,376 @@ +/* + * Copyright (C) 2009 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.primitives; + +import com.google.common.annotations.Beta; +import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.VisibleForTesting; + +import java.util.Arrays; +import java.util.Comparator; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.base.Preconditions.checkPositionIndexes; + +/** + * Static utility methods pertaining to {@code byte} primitives that interpret values as + * unsigned (that is, any negative value {@code b} is treated as the positive value {@code + * 256 + b}). The corresponding methods that treat the values as signed are found in {@link + * SignedBytes}, and the methods for which signedness is not an issue are in {@link Bytes}. + * + *

See the Guava User Guide article on primitive utilities. + * + * @author Kevin Bourrillion + * @author Martin Buchholz + * @author Hiroshi Yamauchi + * @author Louis Wasserman + * @since 1.0 + */ +@GwtIncompatible +public final class UnsignedBytes { + private UnsignedBytes() {} + + /** + * The largest power of two that can be represented as an unsigned {@code byte}. + * + * @since 10.0 + */ + public static final byte MAX_POWER_OF_TWO = (byte) 0x80; + + /** + * The largest value that fits into an unsigned byte. + * + * @since 13.0 + */ + public static final byte MAX_VALUE = (byte) 0xFF; + + private static final int UNSIGNED_MASK = 0xFF; + + /** + * Returns the value of the given byte as an integer, when treated as unsigned. That is, returns + * {@code value + 256} if {@code value} is negative; {@code value} itself otherwise. + * + *

Java 8 users: use {@link Byte#toUnsignedInt(byte)} instead. + * + * @since 6.0 + */ + public static int toInt(byte value) { + return value & UNSIGNED_MASK; + } + + /** + * Returns the {@code byte} value that, when treated as unsigned, is equal to {@code value}, if + * possible. + * + * @param value a value between 0 and 255 inclusive + * @return the {@code byte} value that, when treated as unsigned, equals {@code value} + * @throws IllegalArgumentException if {@code value} is negative or greater than 255 + */ + public static byte checkedCast(long value) { + checkArgument(value >> Byte.SIZE == 0, "out of range: %s", value); + return (byte) value; + } + + /** + * Returns the {@code byte} value that, when treated as unsigned, is nearest in value to {@code + * value}. + * + * @param value any {@code long} value + * @return {@code (byte) 255} if {@code value >= 255}, {@code (byte) 0} if {@code value <= 0}, and + * {@code value} cast to {@code byte} otherwise + */ + public static byte saturatedCast(long value) { + if (value > toInt(MAX_VALUE)) { + return MAX_VALUE; // -1 + } + if (value < 0) { + return (byte) 0; + } + return (byte) value; + } + + /** + * Compares the two specified {@code byte} values, treating them as unsigned values between 0 and + * 255 inclusive. For example, {@code (byte) -127} is considered greater than {@code (byte) 127} + * because it is seen as having the value of positive {@code 129}. + * + * @param a the first {@code byte} to compare + * @param b the second {@code byte} to compare + * @return a negative value if {@code a} is less than {@code b}; a positive value if {@code a} is + * greater than {@code b}; or zero if they are equal + */ + public static int compare(byte a, byte b) { + return toInt(a) - toInt(b); + } + + /** + * Returns the least value present in {@code array}. + * + * @param array a nonempty array of {@code byte} values + * @return the value present in {@code array} that is less than or equal to every other value in + * the array + * @throws IllegalArgumentException if {@code array} is empty + */ + public static byte min(byte... array) { + checkArgument(array.length > 0); + int min = toInt(array[0]); + for (int i = 1; i < array.length; i++) { + int next = toInt(array[i]); + if (next < min) { + min = next; + } + } + return (byte) min; + } + + /** + * Returns the greatest value present in {@code array}. + * + * @param array a nonempty array of {@code byte} values + * @return the value present in {@code array} that is greater than or equal to every other value + * in the array + * @throws IllegalArgumentException if {@code array} is empty + */ + public static byte max(byte... array) { + checkArgument(array.length > 0); + int max = toInt(array[0]); + for (int i = 1; i < array.length; i++) { + int next = toInt(array[i]); + if (next > max) { + max = next; + } + } + return (byte) max; + } + + /** + * Returns a string representation of x, where x is treated as unsigned. + * + * @since 13.0 + */ + @Beta + public static String toString(byte x) { + return toString(x, 10); + } + + /** + * Returns a string representation of {@code x} for the given radix, where {@code x} is treated as + * unsigned. + * + * @param x the value to convert to a string. + * @param radix the radix to use while working with {@code x} + * @throws IllegalArgumentException if {@code radix} is not between {@link Character#MIN_RADIX} + * and {@link Character#MAX_RADIX}. + * @since 13.0 + */ + @Beta + public static String toString(byte x, int radix) { + checkArgument( + radix >= Character.MIN_RADIX && radix <= Character.MAX_RADIX, + "radix (%s) must be between Character.MIN_RADIX and Character.MAX_RADIX", + radix); + // Benchmarks indicate this is probably not worth optimizing. + return Integer.toString(toInt(x), radix); + } + + /** + * Returns the unsigned {@code byte} value represented by the given decimal string. + * + * @throws NumberFormatException if the string does not contain a valid unsigned {@code byte} + * value + * @throws NullPointerException if {@code string} is null (in contrast to {@link + * Byte#parseByte(String)}) + * @since 13.0 + */ + @Beta + public static byte parseUnsignedByte(String string) { + return parseUnsignedByte(string, 10); + } + + /** + * Returns the unsigned {@code byte} value represented by a string with the given radix. + * + * @param string the string containing the unsigned {@code byte} representation to be parsed. + * @param radix the radix to use while parsing {@code string} + * @throws NumberFormatException if the string does not contain a valid unsigned {@code byte} with + * the given radix, or if {@code radix} is not between {@link Character#MIN_RADIX} and {@link + * Character#MAX_RADIX}. + * @throws NullPointerException if {@code string} is null (in contrast to {@link + * Byte#parseByte(String)}) + * @since 13.0 + */ + @Beta + public static byte parseUnsignedByte(String string, int radix) { + int parse = Integer.parseInt(checkNotNull(string), radix); + // We need to throw a NumberFormatException, so we have to duplicate checkedCast. =( + if (parse >> Byte.SIZE == 0) { + return (byte) parse; + } else { + throw new NumberFormatException("out of range: " + parse); + } + } + + /** + * Returns a string containing the supplied {@code byte} values separated by {@code separator}. + * For example, {@code join(":", (byte) 1, (byte) 2, (byte) 255)} returns the string {@code + * "1:2:255"}. + * + * @param separator the text that should appear between consecutive values in the resulting string + * (but not at the start or end) + * @param array an array of {@code byte} values, possibly empty + */ + public static String join(String separator, byte... array) { + checkNotNull(separator); + if (array.length == 0) { + return ""; + } + + // For pre-sizing a builder, just get the right order of magnitude + StringBuilder builder = new StringBuilder(array.length * (3 + separator.length())); + builder.append(toInt(array[0])); + for (int i = 1; i < array.length; i++) { + builder.append(separator).append(toString(array[i])); + } + return builder.toString(); + } + + /** + * Returns a comparator that compares two {@code byte} arrays lexicographically. That is, it + * compares, using {@link #compare(byte, byte)}), the first pair of values that follow any common + * prefix, or when one array is a prefix of the other, treats the shorter array as the lesser. For + * example, {@code [] < [0x01] < [0x01, 0x7F] < [0x01, 0x80] < [0x02]}. Values are treated as + * unsigned. + * + *

The returned comparator is inconsistent with {@link Object#equals(Object)} (since arrays + * support only identity equality), but it is consistent with {@link + * Arrays#equals(byte[], byte[])}. + * + * @since 2.0 + */ + public static Comparator lexicographicalComparator() { + return LexicographicalComparatorHolder.BEST_COMPARATOR; + } + + @VisibleForTesting + static Comparator lexicographicalComparatorJavaImpl() { + return LexicographicalComparatorHolder.PureJavaComparator.INSTANCE; + } + + /** + * Provides a lexicographical comparator implementation. + * + *

Uses reflection to gracefully fall back to the Java implementation if {@code Unsafe} isn't + * available. + */ + @VisibleForTesting + static class LexicographicalComparatorHolder { + + static final Comparator BEST_COMPARATOR = getBestComparator(); + + + enum PureJavaComparator implements Comparator { + INSTANCE; + + @Override + public int compare(byte[] left, byte[] right) { + int minLength = Math.min(left.length, right.length); + for (int i = 0; i < minLength; i++) { + int result = UnsignedBytes.compare(left[i], right[i]); + if (result != 0) { + return result; + } + } + return left.length - right.length; + } + + @Override + public String toString() { + return "UnsignedBytes.lexicographicalComparator() (pure Java version)"; + } + } + + /** + * Returns the Unsafe-using Comparator, or falls back to the pure-Java implementation if unable + * to do so. + */ + static Comparator getBestComparator() { + return lexicographicalComparatorJavaImpl(); + } + } + + private static byte flip(byte b) { + return (byte) (b ^ 0x80); + } + + /** + * Sorts the array, treating its elements as unsigned bytes. + * + * @since 23.1 + */ + public static void sort(byte[] array) { + checkNotNull(array); + sort(array, 0, array.length); + } + + /** + * Sorts the array between {@code fromIndex} inclusive and {@code toIndex} exclusive, treating its + * elements as unsigned bytes. + * + * @since 23.1 + */ + public static void sort(byte[] array, int fromIndex, int toIndex) { + checkNotNull(array); + checkPositionIndexes(fromIndex, toIndex, array.length); + for (int i = fromIndex; i < toIndex; i++) { + array[i] = flip(array[i]); + } + Arrays.sort(array, fromIndex, toIndex); + for (int i = fromIndex; i < toIndex; i++) { + array[i] = flip(array[i]); + } + } + + /** + * Sorts the elements of {@code array} in descending order, interpreting them as unsigned 8-bit + * integers. + * + * @since 23.1 + */ + public static void sortDescending(byte[] array) { + checkNotNull(array); + sortDescending(array, 0, array.length); + } + + /** + * Sorts the elements of {@code array} between {@code fromIndex} inclusive and {@code toIndex} + * exclusive in descending order, interpreting them as unsigned 8-bit integers. + * + * @since 23.1 + */ + public static void sortDescending(byte[] array, int fromIndex, int toIndex) { + checkNotNull(array); + checkPositionIndexes(fromIndex, toIndex, array.length); + for (int i = fromIndex; i < toIndex; i++) { + array[i] ^= Byte.MAX_VALUE; + } + Arrays.sort(array, fromIndex, toIndex); + for (int i = fromIndex; i < toIndex; i++) { + array[i] ^= Byte.MAX_VALUE; + } + } +} diff --git a/src/main/java/com/google/common/primitives/UnsignedInteger.java b/src/main/java/com/google/common/primitives/UnsignedInteger.java new file mode 100644 index 0000000..194c5e5 --- /dev/null +++ b/src/main/java/com/google/common/primitives/UnsignedInteger.java @@ -0,0 +1,251 @@ +/* + * Copyright (C) 2011 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.primitives; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.primitives.UnsignedInts.INT_MASK; +import static com.google.common.primitives.UnsignedInts.compare; +import static com.google.common.primitives.UnsignedInts.toLong; + +import com.google.common.annotations.GwtCompatible; +import com.google.common.annotations.GwtIncompatible; +import java.math.BigInteger; + + +/** + * A wrapper class for unsigned {@code int} values, supporting arithmetic operations. + * + *

In some cases, when speed is more important than code readability, it may be faster simply to + * treat primitive {@code int} values as unsigned, using the methods from {@link UnsignedInts}. + * + *

See the Guava User Guide article on unsigned + * primitive utilities. + * + * @author Louis Wasserman + * @since 11.0 + */ +@GwtCompatible(emulated = true) +public final class UnsignedInteger extends Number implements Comparable { + public static final UnsignedInteger ZERO = fromIntBits(0); + public static final UnsignedInteger ONE = fromIntBits(1); + public static final UnsignedInteger MAX_VALUE = fromIntBits(-1); + + private final int value; + + private UnsignedInteger(int value) { + // GWT doesn't consistently overflow values to make them 32-bit, so we need to force it. + this.value = value & 0xffffffff; + } + + /** + * Returns an {@code UnsignedInteger} corresponding to a given bit representation. The argument is + * interpreted as an unsigned 32-bit value. Specifically, the sign bit of {@code bits} is + * interpreted as a normal bit, and all other bits are treated as usual. + * + *

If the argument is nonnegative, the returned result will be equal to {@code bits}, + * otherwise, the result will be equal to {@code 2^32 + bits}. + * + *

To represent unsigned decimal constants, consider {@link #valueOf(long)} instead. + * + * @since 14.0 + */ + public static UnsignedInteger fromIntBits(int bits) { + return new UnsignedInteger(bits); + } + + /** + * Returns an {@code UnsignedInteger} that is equal to {@code value}, if possible. The inverse + * operation of {@link #longValue()}. + */ + public static UnsignedInteger valueOf(long value) { + checkArgument( + (value & INT_MASK) == value, + "value (%s) is outside the range for an unsigned integer value", + value); + return fromIntBits((int) value); + } + + /** + * Returns a {@code UnsignedInteger} representing the same value as the specified {@link + * BigInteger}. This is the inverse operation of {@link #bigIntegerValue()}. + * + * @throws IllegalArgumentException if {@code value} is negative or {@code value >= 2^32} + */ + public static UnsignedInteger valueOf(BigInteger value) { + checkNotNull(value); + checkArgument( + value.signum() >= 0 && value.bitLength() <= Integer.SIZE, + "value (%s) is outside the range for an unsigned integer value", + value); + return fromIntBits(value.intValue()); + } + + /** + * Returns an {@code UnsignedInteger} holding the value of the specified {@code String}, parsed as + * an unsigned {@code int} value. + * + * @throws NumberFormatException if the string does not contain a parsable unsigned {@code int} + * value + */ + public static UnsignedInteger valueOf(String string) { + return valueOf(string, 10); + } + + /** + * Returns an {@code UnsignedInteger} holding the value of the specified {@code String}, parsed as + * an unsigned {@code int} value in the specified radix. + * + * @throws NumberFormatException if the string does not contain a parsable unsigned {@code int} + * value + */ + public static UnsignedInteger valueOf(String string, int radix) { + return fromIntBits(UnsignedInts.parseUnsignedInt(string, radix)); + } + + /** + * Returns the result of adding this and {@code val}. If the result would have more than 32 bits, + * returns the low 32 bits of the result. + * + * @since 14.0 + */ + public UnsignedInteger plus(UnsignedInteger val) { + return fromIntBits(this.value + checkNotNull(val).value); + } + + /** + * Returns the result of subtracting this and {@code val}. If the result would be negative, + * returns the low 32 bits of the result. + * + * @since 14.0 + */ + public UnsignedInteger minus(UnsignedInteger val) { + return fromIntBits(value - checkNotNull(val).value); + } + + /** + * Returns the result of multiplying this and {@code val}. If the result would have more than 32 + * bits, returns the low 32 bits of the result. + * + * @since 14.0 + */ + @GwtIncompatible // Does not truncate correctly + public UnsignedInteger times(UnsignedInteger val) { + // TODO(lowasser): make this GWT-compatible + return fromIntBits(value * checkNotNull(val).value); + } + + /** + * Returns the result of dividing this by {@code val}. + * + * @throws ArithmeticException if {@code val} is zero + * @since 14.0 + */ + public UnsignedInteger dividedBy(UnsignedInteger val) { + return fromIntBits(UnsignedInts.divide(value, checkNotNull(val).value)); + } + + /** + * Returns this mod {@code val}. + * + * @throws ArithmeticException if {@code val} is zero + * @since 14.0 + */ + public UnsignedInteger mod(UnsignedInteger val) { + return fromIntBits(UnsignedInts.remainder(value, checkNotNull(val).value)); + } + + /** + * Returns the value of this {@code UnsignedInteger} as an {@code int}. This is an inverse + * operation to {@link #fromIntBits}. + * + *

Note that if this {@code UnsignedInteger} holds a value {@code >= 2^31}, the returned value + * will be equal to {@code this - 2^32}. + */ + @Override + public int intValue() { + return value; + } + + /** Returns the value of this {@code UnsignedInteger} as a {@code long}. */ + @Override + public long longValue() { + return toLong(value); + } + + /** + * Returns the value of this {@code UnsignedInteger} as a {@code float}, analogous to a widening + * primitive conversion from {@code int} to {@code float}, and correctly rounded. + */ + @Override + public float floatValue() { + return longValue(); + } + + /** + * Returns the value of this {@code UnsignedInteger} as a {@code float}, analogous to a widening + * primitive conversion from {@code int} to {@code double}, and correctly rounded. + */ + @Override + public double doubleValue() { + return longValue(); + } + + /** Returns the value of this {@code UnsignedInteger} as a {@link BigInteger}. */ + public BigInteger bigIntegerValue() { + return BigInteger.valueOf(longValue()); + } + + /** + * Compares this unsigned integer to another unsigned integer. Returns {@code 0} if they are + * equal, a negative number if {@code this < other}, and a positive number if {@code this > + * other}. + */ + @Override + public int compareTo(UnsignedInteger other) { + checkNotNull(other); + return compare(value, other.value); + } + + @Override + public int hashCode() { + return value; + } + + @Override + public boolean equals(Object obj) { + if (obj instanceof UnsignedInteger) { + UnsignedInteger other = (UnsignedInteger) obj; + return value == other.value; + } + return false; + } + + /** Returns a string representation of the {@code UnsignedInteger} value, in base 10. */ + @Override + public String toString() { + return toString(10); + } + + /** + * Returns a string representation of the {@code UnsignedInteger} value, in base {@code radix}. If + * {@code radix < Character.MIN_RADIX} or {@code radix > Character.MAX_RADIX}, the radix {@code + * 10} is used. + */ + public String toString(int radix) { + return UnsignedInts.toString(value, radix); + } +} diff --git a/src/main/java/com/google/common/primitives/UnsignedInts.java b/src/main/java/com/google/common/primitives/UnsignedInts.java new file mode 100644 index 0000000..37dc6c1 --- /dev/null +++ b/src/main/java/com/google/common/primitives/UnsignedInts.java @@ -0,0 +1,390 @@ +/* + * Copyright (C) 2011 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.primitives; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.base.Preconditions.checkPositionIndexes; + +import com.google.common.annotations.Beta; +import com.google.common.annotations.GwtCompatible; + +import java.util.Arrays; +import java.util.Comparator; + +/** + * Static utility methods pertaining to {@code int} primitives that interpret values as + * unsigned (that is, any negative value {@code x} is treated as the positive value {@code + * 2^32 + x}). The methods for which signedness is not an issue are in {@link Ints}, as well as + * signed versions of methods for which signedness is an issue. + * + *

In addition, this class provides several static methods for converting an {@code int} to a + * {@code String} and a {@code String} to an {@code int} that treat the {@code int} as an unsigned + * number. + * + *

Users of these utilities must be extremely careful not to mix up signed and unsigned + * {@code int} values. When possible, it is recommended that the {@link UnsignedInteger} wrapper + * class be used, at a small efficiency penalty, to enforce the distinction in the type system. + * + *

See the Guava User Guide article on unsigned + * primitive utilities. + * + * @author Louis Wasserman + * @since 11.0 + */ +@Beta +@GwtCompatible +public final class UnsignedInts { + static final long INT_MASK = 0xffffffffL; + + private UnsignedInts() {} + + static int flip(int value) { + return value ^ Integer.MIN_VALUE; + } + + /** + * Compares the two specified {@code int} values, treating them as unsigned values between {@code + * 0} and {@code 2^32 - 1} inclusive. + * + *

Java 8 users: use {@link Integer#compareUnsigned(int, int)} instead. + * + * @param a the first unsigned {@code int} to compare + * @param b the second unsigned {@code int} to compare + * @return a negative value if {@code a} is less than {@code b}; a positive value if {@code a} is + * greater than {@code b}; or zero if they are equal + */ + public static int compare(int a, int b) { + return Ints.compare(flip(a), flip(b)); + } + + /** + * Returns the value of the given {@code int} as a {@code long}, when treated as unsigned. + * + *

Java 8 users: use {@link Integer#toUnsignedLong(int)} instead. + */ + public static long toLong(int value) { + return value & INT_MASK; + } + + /** + * Returns the {@code int} value that, when treated as unsigned, is equal to {@code value}, if + * possible. + * + * @param value a value between 0 and 232-1 inclusive + * @return the {@code int} value that, when treated as unsigned, equals {@code value} + * @throws IllegalArgumentException if {@code value} is negative or greater than or equal to + * 232 + * @since 21.0 + */ + public static int checkedCast(long value) { + checkArgument((value >> Integer.SIZE) == 0, "out of range: %s", value); + return (int) value; + } + + /** + * Returns the {@code int} value that, when treated as unsigned, is nearest in value to {@code + * value}. + * + * @param value any {@code long} value + * @return {@code 2^32 - 1} if {@code value >= 2^32}, {@code 0} if {@code value <= 0}, and {@code + * value} cast to {@code int} otherwise + * @since 21.0 + */ + public static int saturatedCast(long value) { + if (value <= 0) { + return 0; + } else if (value >= (1L << 32)) { + return -1; + } else { + return (int) value; + } + } + + /** + * Returns the least value present in {@code array}, treating values as unsigned. + * + * @param array a nonempty array of unsigned {@code int} values + * @return the value present in {@code array} that is less than or equal to every other value in + * the array according to {@link #compare} + * @throws IllegalArgumentException if {@code array} is empty + */ + public static int min(int... array) { + checkArgument(array.length > 0); + int min = flip(array[0]); + for (int i = 1; i < array.length; i++) { + int next = flip(array[i]); + if (next < min) { + min = next; + } + } + return flip(min); + } + + /** + * Returns the greatest value present in {@code array}, treating values as unsigned. + * + * @param array a nonempty array of unsigned {@code int} values + * @return the value present in {@code array} that is greater than or equal to every other value + * in the array according to {@link #compare} + * @throws IllegalArgumentException if {@code array} is empty + */ + public static int max(int... array) { + checkArgument(array.length > 0); + int max = flip(array[0]); + for (int i = 1; i < array.length; i++) { + int next = flip(array[i]); + if (next > max) { + max = next; + } + } + return flip(max); + } + + /** + * Returns a string containing the supplied unsigned {@code int} values separated by {@code + * separator}. For example, {@code join("-", 1, 2, 3)} returns the string {@code "1-2-3"}. + * + * @param separator the text that should appear between consecutive values in the resulting string + * (but not at the start or end) + * @param array an array of unsigned {@code int} values, possibly empty + */ + public static String join(String separator, int... array) { + checkNotNull(separator); + if (array.length == 0) { + return ""; + } + + // For pre-sizing a builder, just get the right order of magnitude + StringBuilder builder = new StringBuilder(array.length * 5); + builder.append(toString(array[0])); + for (int i = 1; i < array.length; i++) { + builder.append(separator).append(toString(array[i])); + } + return builder.toString(); + } + + /** + * Returns a comparator that compares two arrays of unsigned {@code int} values lexicographically. That is, it + * compares, using {@link #compare(int, int)}), the first pair of values that follow any common + * prefix, or when one array is a prefix of the other, treats the shorter array as the lesser. For + * example, {@code [] < [1] < [1, 2] < [2] < [1 << 31]}. + * + *

The returned comparator is inconsistent with {@link Object#equals(Object)} (since arrays + * support only identity equality), but it is consistent with {@link Arrays#equals(int[], int[])}. + */ + public static Comparator lexicographicalComparator() { + return LexicographicalComparator.INSTANCE; + } + + enum LexicographicalComparator implements Comparator { + INSTANCE; + + @Override + public int compare(int[] left, int[] right) { + int minLength = Math.min(left.length, right.length); + for (int i = 0; i < minLength; i++) { + if (left[i] != right[i]) { + return UnsignedInts.compare(left[i], right[i]); + } + } + return left.length - right.length; + } + + @Override + public String toString() { + return "UnsignedInts.lexicographicalComparator()"; + } + } + + /** + * Sorts the array, treating its elements as unsigned 32-bit integers. + * + * @since 23.1 + */ + public static void sort(int[] array) { + checkNotNull(array); + sort(array, 0, array.length); + } + + /** + * Sorts the array between {@code fromIndex} inclusive and {@code toIndex} exclusive, treating its + * elements as unsigned 32-bit integers. + * + * @since 23.1 + */ + public static void sort(int[] array, int fromIndex, int toIndex) { + checkNotNull(array); + checkPositionIndexes(fromIndex, toIndex, array.length); + for (int i = fromIndex; i < toIndex; i++) { + array[i] = flip(array[i]); + } + Arrays.sort(array, fromIndex, toIndex); + for (int i = fromIndex; i < toIndex; i++) { + array[i] = flip(array[i]); + } + } + + /** + * Sorts the elements of {@code array} in descending order, interpreting them as unsigned 32-bit + * integers. + * + * @since 23.1 + */ + public static void sortDescending(int[] array) { + checkNotNull(array); + sortDescending(array, 0, array.length); + } + + /** + * Sorts the elements of {@code array} between {@code fromIndex} inclusive and {@code toIndex} + * exclusive in descending order, interpreting them as unsigned 32-bit integers. + * + * @since 23.1 + */ + public static void sortDescending(int[] array, int fromIndex, int toIndex) { + checkNotNull(array); + checkPositionIndexes(fromIndex, toIndex, array.length); + for (int i = fromIndex; i < toIndex; i++) { + array[i] ^= Integer.MAX_VALUE; + } + Arrays.sort(array, fromIndex, toIndex); + for (int i = fromIndex; i < toIndex; i++) { + array[i] ^= Integer.MAX_VALUE; + } + } + + /** + * Returns dividend / divisor, where the dividend and divisor are treated as unsigned 32-bit + * quantities. + * + *

Java 8 users: use {@link Integer#divideUnsigned(int, int)} instead. + * + * @param dividend the dividend (numerator) + * @param divisor the divisor (denominator) + * @throws ArithmeticException if divisor is 0 + */ + public static int divide(int dividend, int divisor) { + return (int) (toLong(dividend) / toLong(divisor)); + } + + /** + * Returns dividend % divisor, where the dividend and divisor are treated as unsigned 32-bit + * quantities. + * + *

Java 8 users: use {@link Integer#remainderUnsigned(int, int)} instead. + * + * @param dividend the dividend (numerator) + * @param divisor the divisor (denominator) + * @throws ArithmeticException if divisor is 0 + */ + public static int remainder(int dividend, int divisor) { + return (int) (toLong(dividend) % toLong(divisor)); + } + + /** + * Returns the unsigned {@code int} value represented by the given string. + * + *

Accepts a decimal, hexadecimal, or octal number given by specifying the following prefix: + * + *

    + *
  • {@code 0x}HexDigits + *
  • {@code 0X}HexDigits + *
  • {@code #}HexDigits + *
  • {@code 0}OctalDigits + *
+ * + * @throws NumberFormatException if the string does not contain a valid unsigned {@code int} value + * @since 13.0 + */ + + public static int decode(String stringValue) { + ParseRequest request = ParseRequest.fromString(stringValue); + + try { + return parseUnsignedInt(request.rawValue, request.radix); + } catch (NumberFormatException e) { + NumberFormatException decodeException = + new NumberFormatException("Error parsing value: " + stringValue); + decodeException.initCause(e); + throw decodeException; + } + } + + /** + * Returns the unsigned {@code int} value represented by the given decimal string. + * + *

Java 8 users: use {@link Integer#parseUnsignedInt(String)} instead. + * + * @throws NumberFormatException if the string does not contain a valid unsigned {@code int} value + * @throws NullPointerException if {@code s} is null (in contrast to {@link + * Integer#parseInt(String)}) + */ + + public static int parseUnsignedInt(String s) { + return parseUnsignedInt(s, 10); + } + + /** + * Returns the unsigned {@code int} value represented by a string with the given radix. + * + *

Java 8 users: use {@link Integer#parseUnsignedInt(String, int)} instead. + * + * @param string the string containing the unsigned integer representation to be parsed. + * @param radix the radix to use while parsing {@code s}; must be between {@link + * Character#MIN_RADIX} and {@link Character#MAX_RADIX}. + * @throws NumberFormatException if the string does not contain a valid unsigned {@code int}, or + * if supplied radix is invalid. + * @throws NullPointerException if {@code s} is null (in contrast to {@link + * Integer#parseInt(String)}) + */ + + public static int parseUnsignedInt(String string, int radix) { + checkNotNull(string); + long result = Long.parseLong(string, radix); + if ((result & INT_MASK) != result) { + throw new NumberFormatException( + "Input " + string + " in base " + radix + " is not in the range of an unsigned integer"); + } + return (int) result; + } + + /** + * Returns a string representation of x, where x is treated as unsigned. + * + *

Java 8 users: use {@link Integer#toUnsignedString(int)} instead. + */ + public static String toString(int x) { + return toString(x, 10); + } + + /** + * Returns a string representation of {@code x} for the given radix, where {@code x} is treated as + * unsigned. + * + *

Java 8 users: use {@link Integer#toUnsignedString(int, int)} instead. + * + * @param x the value to convert to a string. + * @param radix the radix to use while working with {@code x} + * @throws IllegalArgumentException if {@code radix} is not between {@link Character#MIN_RADIX} + * and {@link Character#MAX_RADIX}. + */ + public static String toString(int x, int radix) { + long asLong = x & INT_MASK; + return Long.toString(asLong, radix); + } +} diff --git a/src/main/java/com/google/common/primitives/UnsignedLong.java b/src/main/java/com/google/common/primitives/UnsignedLong.java new file mode 100644 index 0000000..416ae35 --- /dev/null +++ b/src/main/java/com/google/common/primitives/UnsignedLong.java @@ -0,0 +1,263 @@ +/* + * Copyright (C) 2011 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.primitives; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.annotations.GwtCompatible; + +import java.io.Serializable; +import java.math.BigInteger; + + +/** + * A wrapper class for unsigned {@code long} values, supporting arithmetic operations. + * + *

In some cases, when speed is more important than code readability, it may be faster simply to + * treat primitive {@code long} values as unsigned, using the methods from {@link UnsignedLongs}. + * + *

See the Guava User Guide article on unsigned + * primitive utilities. + * + * @author Louis Wasserman + * @author Colin Evans + * @since 11.0 + */ +@GwtCompatible(serializable = true) +public final class UnsignedLong extends Number implements Comparable, Serializable { + + private static final long UNSIGNED_MASK = 0x7fffffffffffffffL; + + public static final UnsignedLong ZERO = new UnsignedLong(0); + public static final UnsignedLong ONE = new UnsignedLong(1); + public static final UnsignedLong MAX_VALUE = new UnsignedLong(-1L); + + private final long value; + + private UnsignedLong(long value) { + this.value = value; + } + + /** + * Returns an {@code UnsignedLong} corresponding to a given bit representation. The argument is + * interpreted as an unsigned 64-bit value. Specifically, the sign bit of {@code bits} is + * interpreted as a normal bit, and all other bits are treated as usual. + * + *

If the argument is nonnegative, the returned result will be equal to {@code bits}, + * otherwise, the result will be equal to {@code 2^64 + bits}. + * + *

To represent decimal constants less than {@code 2^63}, consider {@link #valueOf(long)} + * instead. + * + * @since 14.0 + */ + public static UnsignedLong fromLongBits(long bits) { + // TODO(lowasser): consider caching small values, like Long.valueOf + return new UnsignedLong(bits); + } + + /** + * Returns an {@code UnsignedLong} representing the same value as the specified {@code long}. + * + * @throws IllegalArgumentException if {@code value} is negative + * @since 14.0 + */ + + public static UnsignedLong valueOf(long value) { + checkArgument(value >= 0, "value (%s) is outside the range for an unsigned long value", value); + return fromLongBits(value); + } + + /** + * Returns a {@code UnsignedLong} representing the same value as the specified {@code BigInteger}. + * This is the inverse operation of {@link #bigIntegerValue()}. + * + * @throws IllegalArgumentException if {@code value} is negative or {@code value >= 2^64} + */ + + public static UnsignedLong valueOf(BigInteger value) { + checkNotNull(value); + checkArgument( + value.signum() >= 0 && value.bitLength() <= Long.SIZE, + "value (%s) is outside the range for an unsigned long value", + value); + return fromLongBits(value.longValue()); + } + + /** + * Returns an {@code UnsignedLong} holding the value of the specified {@code String}, parsed as an + * unsigned {@code long} value. + * + * @throws NumberFormatException if the string does not contain a parsable unsigned {@code long} + * value + */ + + public static UnsignedLong valueOf(String string) { + return valueOf(string, 10); + } + + /** + * Returns an {@code UnsignedLong} holding the value of the specified {@code String}, parsed as an + * unsigned {@code long} value in the specified radix. + * + * @throws NumberFormatException if the string does not contain a parsable unsigned {@code long} + * value, or {@code radix} is not between {@link Character#MIN_RADIX} and {@link + * Character#MAX_RADIX} + */ + + public static UnsignedLong valueOf(String string, int radix) { + return fromLongBits(UnsignedLongs.parseUnsignedLong(string, radix)); + } + + /** + * Returns the result of adding this and {@code val}. If the result would have more than 64 bits, + * returns the low 64 bits of the result. + * + * @since 14.0 + */ + public UnsignedLong plus(UnsignedLong val) { + return fromLongBits(this.value + checkNotNull(val).value); + } + + /** + * Returns the result of subtracting this and {@code val}. If the result would have more than 64 + * bits, returns the low 64 bits of the result. + * + * @since 14.0 + */ + public UnsignedLong minus(UnsignedLong val) { + return fromLongBits(this.value - checkNotNull(val).value); + } + + /** + * Returns the result of multiplying this and {@code val}. If the result would have more than 64 + * bits, returns the low 64 bits of the result. + * + * @since 14.0 + */ + public UnsignedLong times(UnsignedLong val) { + return fromLongBits(value * checkNotNull(val).value); + } + + /** + * Returns the result of dividing this by {@code val}. + * + * @since 14.0 + */ + public UnsignedLong dividedBy(UnsignedLong val) { + return fromLongBits(UnsignedLongs.divide(value, checkNotNull(val).value)); + } + + /** + * Returns this modulo {@code val}. + * + * @since 14.0 + */ + public UnsignedLong mod(UnsignedLong val) { + return fromLongBits(UnsignedLongs.remainder(value, checkNotNull(val).value)); + } + + /** Returns the value of this {@code UnsignedLong} as an {@code int}. */ + @Override + public int intValue() { + return (int) value; + } + + /** + * Returns the value of this {@code UnsignedLong} as a {@code long}. This is an inverse operation + * to {@link #fromLongBits}. + * + *

Note that if this {@code UnsignedLong} holds a value {@code >= 2^63}, the returned value + * will be equal to {@code this - 2^64}. + */ + @Override + public long longValue() { + return value; + } + + /** + * Returns the value of this {@code UnsignedLong} as a {@code float}, analogous to a widening + * primitive conversion from {@code long} to {@code float}, and correctly rounded. + */ + @Override + public float floatValue() { + @SuppressWarnings("cast") + float fValue = (float) (value & UNSIGNED_MASK); + if (value < 0) { + fValue += 0x1.0p63f; + } + return fValue; + } + + /** + * Returns the value of this {@code UnsignedLong} as a {@code double}, analogous to a widening + * primitive conversion from {@code long} to {@code double}, and correctly rounded. + */ + @Override + public double doubleValue() { + @SuppressWarnings("cast") + double dValue = (double) (value & UNSIGNED_MASK); + if (value < 0) { + dValue += 0x1.0p63; + } + return dValue; + } + + /** Returns the value of this {@code UnsignedLong} as a {@link BigInteger}. */ + public BigInteger bigIntegerValue() { + BigInteger bigInt = BigInteger.valueOf(value & UNSIGNED_MASK); + if (value < 0) { + bigInt = bigInt.setBit(Long.SIZE - 1); + } + return bigInt; + } + + @Override + public int compareTo(UnsignedLong o) { + checkNotNull(o); + return UnsignedLongs.compare(value, o.value); + } + + @Override + public int hashCode() { + return Longs.hashCode(value); + } + + @Override + public boolean equals(Object obj) { + if (obj instanceof UnsignedLong) { + UnsignedLong other = (UnsignedLong) obj; + return value == other.value; + } + return false; + } + + /** Returns a string representation of the {@code UnsignedLong} value, in base 10. */ + @Override + public String toString() { + return UnsignedLongs.toString(value); + } + + /** + * Returns a string representation of the {@code UnsignedLong} value, in base {@code radix}. If + * {@code radix < Character.MIN_RADIX} or {@code radix > Character.MAX_RADIX}, the radix {@code + * 10} is used. + */ + public String toString(int radix) { + return UnsignedLongs.toString(value, radix); + } +} diff --git a/src/main/java/com/google/common/primitives/UnsignedLongs.java b/src/main/java/com/google/common/primitives/UnsignedLongs.java new file mode 100644 index 0000000..7fb934b --- /dev/null +++ b/src/main/java/com/google/common/primitives/UnsignedLongs.java @@ -0,0 +1,500 @@ +/* + * Copyright (C) 2011 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.primitives; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.base.Preconditions.checkPositionIndexes; + +import com.google.common.annotations.Beta; +import com.google.common.annotations.GwtCompatible; + +import java.math.BigInteger; +import java.util.Arrays; +import java.util.Comparator; + +/** + * Static utility methods pertaining to {@code long} primitives that interpret values as + * unsigned (that is, any negative value {@code x} is treated as the positive value {@code + * 2^64 + x}). The methods for which signedness is not an issue are in {@link Longs}, as well as + * signed versions of methods for which signedness is an issue. + * + *

In addition, this class provides several static methods for converting a {@code long} to a + * {@code String} and a {@code String} to a {@code long} that treat the {@code long} as an unsigned + * number. + * + *

Users of these utilities must be extremely careful not to mix up signed and unsigned + * {@code long} values. When possible, it is recommended that the {@link UnsignedLong} wrapper class + * be used, at a small efficiency penalty, to enforce the distinction in the type system. + * + *

See the Guava User Guide article on unsigned + * primitive utilities. + * + * @author Louis Wasserman + * @author Brian Milch + * @author Colin Evans + * @since 10.0 + */ +@Beta +@GwtCompatible +public final class UnsignedLongs { + private UnsignedLongs() {} + + public static final long MAX_VALUE = -1L; // Equivalent to 2^64 - 1 + + /** + * A (self-inverse) bijection which converts the ordering on unsigned longs to the ordering on + * longs, that is, {@code a <= b} as unsigned longs if and only if {@code flip(a) <= flip(b)} as + * signed longs. + */ + private static long flip(long a) { + return a ^ Long.MIN_VALUE; + } + + /** + * Compares the two specified {@code long} values, treating them as unsigned values between {@code + * 0} and {@code 2^64 - 1} inclusive. + * + *

Java 8 users: use {@link Long#compareUnsigned(long, long)} instead. + * + * @param a the first unsigned {@code long} to compare + * @param b the second unsigned {@code long} to compare + * @return a negative value if {@code a} is less than {@code b}; a positive value if {@code a} is + * greater than {@code b}; or zero if they are equal + */ + public static int compare(long a, long b) { + return Longs.compare(flip(a), flip(b)); + } + + /** + * Returns the least value present in {@code array}, treating values as unsigned. + * + * @param array a nonempty array of unsigned {@code long} values + * @return the value present in {@code array} that is less than or equal to every other value in + * the array according to {@link #compare} + * @throws IllegalArgumentException if {@code array} is empty + */ + public static long min(long... array) { + checkArgument(array.length > 0); + long min = flip(array[0]); + for (int i = 1; i < array.length; i++) { + long next = flip(array[i]); + if (next < min) { + min = next; + } + } + return flip(min); + } + + /** + * Returns the greatest value present in {@code array}, treating values as unsigned. + * + * @param array a nonempty array of unsigned {@code long} values + * @return the value present in {@code array} that is greater than or equal to every other value + * in the array according to {@link #compare} + * @throws IllegalArgumentException if {@code array} is empty + */ + public static long max(long... array) { + checkArgument(array.length > 0); + long max = flip(array[0]); + for (int i = 1; i < array.length; i++) { + long next = flip(array[i]); + if (next > max) { + max = next; + } + } + return flip(max); + } + + /** + * Returns a string containing the supplied unsigned {@code long} values separated by {@code + * separator}. For example, {@code join("-", 1, 2, 3)} returns the string {@code "1-2-3"}. + * + * @param separator the text that should appear between consecutive values in the resulting string + * (but not at the start or end) + * @param array an array of unsigned {@code long} values, possibly empty + */ + public static String join(String separator, long... array) { + checkNotNull(separator); + if (array.length == 0) { + return ""; + } + + // For pre-sizing a builder, just get the right order of magnitude + StringBuilder builder = new StringBuilder(array.length * 5); + builder.append(toString(array[0])); + for (int i = 1; i < array.length; i++) { + builder.append(separator).append(toString(array[i])); + } + return builder.toString(); + } + + /** + * Returns a comparator that compares two arrays of unsigned {@code long} values lexicographically. That is, it + * compares, using {@link #compare(long, long)}), the first pair of values that follow any common + * prefix, or when one array is a prefix of the other, treats the shorter array as the lesser. For + * example, {@code [] < [1L] < [1L, 2L] < [2L] < [1L << 63]}. + * + *

The returned comparator is inconsistent with {@link Object#equals(Object)} (since arrays + * support only identity equality), but it is consistent with {@link Arrays#equals(long[], + * long[])}. + */ + public static Comparator lexicographicalComparator() { + return LexicographicalComparator.INSTANCE; + } + + enum LexicographicalComparator implements Comparator { + INSTANCE; + + @Override + public int compare(long[] left, long[] right) { + int minLength = Math.min(left.length, right.length); + for (int i = 0; i < minLength; i++) { + if (left[i] != right[i]) { + return UnsignedLongs.compare(left[i], right[i]); + } + } + return left.length - right.length; + } + + @Override + public String toString() { + return "UnsignedLongs.lexicographicalComparator()"; + } + } + + /** + * Sorts the array, treating its elements as unsigned 64-bit integers. + * + * @since 23.1 + */ + public static void sort(long[] array) { + checkNotNull(array); + sort(array, 0, array.length); + } + + /** + * Sorts the array between {@code fromIndex} inclusive and {@code toIndex} exclusive, treating its + * elements as unsigned 64-bit integers. + * + * @since 23.1 + */ + public static void sort(long[] array, int fromIndex, int toIndex) { + checkNotNull(array); + checkPositionIndexes(fromIndex, toIndex, array.length); + for (int i = fromIndex; i < toIndex; i++) { + array[i] = flip(array[i]); + } + Arrays.sort(array, fromIndex, toIndex); + for (int i = fromIndex; i < toIndex; i++) { + array[i] = flip(array[i]); + } + } + + /** + * Sorts the elements of {@code array} in descending order, interpreting them as unsigned 64-bit + * integers. + * + * @since 23.1 + */ + public static void sortDescending(long[] array) { + checkNotNull(array); + sortDescending(array, 0, array.length); + } + + /** + * Sorts the elements of {@code array} between {@code fromIndex} inclusive and {@code toIndex} + * exclusive in descending order, interpreting them as unsigned 64-bit integers. + * + * @since 23.1 + */ + public static void sortDescending(long[] array, int fromIndex, int toIndex) { + checkNotNull(array); + checkPositionIndexes(fromIndex, toIndex, array.length); + for (int i = fromIndex; i < toIndex; i++) { + array[i] ^= Long.MAX_VALUE; + } + Arrays.sort(array, fromIndex, toIndex); + for (int i = fromIndex; i < toIndex; i++) { + array[i] ^= Long.MAX_VALUE; + } + } + + /** + * Returns dividend / divisor, where the dividend and divisor are treated as unsigned 64-bit + * quantities. + * + *

Java 8 users: use {@link Long#divideUnsigned(long, long)} instead. + * + * @param dividend the dividend (numerator) + * @param divisor the divisor (denominator) + * @throws ArithmeticException if divisor is 0 + */ + public static long divide(long dividend, long divisor) { + if (divisor < 0) { // i.e., divisor >= 2^63: + if (compare(dividend, divisor) < 0) { + return 0; // dividend < divisor + } else { + return 1; // dividend >= divisor + } + } + + // Optimization - use signed division if dividend < 2^63 + if (dividend >= 0) { + return dividend / divisor; + } + + /* + * Otherwise, approximate the quotient, check, and correct if necessary. Our approximation is + * guaranteed to be either exact or one less than the correct value. This follows from fact that + * floor(floor(x)/i) == floor(x/i) for any real x and integer i != 0. The proof is not quite + * trivial. + */ + long quotient = ((dividend >>> 1) / divisor) << 1; + long rem = dividend - quotient * divisor; + return quotient + (compare(rem, divisor) >= 0 ? 1 : 0); + } + + /** + * Returns dividend % divisor, where the dividend and divisor are treated as unsigned 64-bit + * quantities. + * + *

Java 8 users: use {@link Long#remainderUnsigned(long, long)} instead. + * + * @param dividend the dividend (numerator) + * @param divisor the divisor (denominator) + * @throws ArithmeticException if divisor is 0 + * @since 11.0 + */ + public static long remainder(long dividend, long divisor) { + if (divisor < 0) { // i.e., divisor >= 2^63: + if (compare(dividend, divisor) < 0) { + return dividend; // dividend < divisor + } else { + return dividend - divisor; // dividend >= divisor + } + } + + // Optimization - use signed modulus if dividend < 2^63 + if (dividend >= 0) { + return dividend % divisor; + } + + /* + * Otherwise, approximate the quotient, check, and correct if necessary. Our approximation is + * guaranteed to be either exact or one less than the correct value. This follows from the fact + * that floor(floor(x)/i) == floor(x/i) for any real x and integer i != 0. The proof is not + * quite trivial. + */ + long quotient = ((dividend >>> 1) / divisor) << 1; + long rem = dividend - quotient * divisor; + return rem - (compare(rem, divisor) >= 0 ? divisor : 0); + } + + /** + * Returns the unsigned {@code long} value represented by the given decimal string. + * + *

Java 8 users: use {@link Long#parseUnsignedLong(String)} instead. + * + * @throws NumberFormatException if the string does not contain a valid unsigned {@code long} + * value + * @throws NullPointerException if {@code string} is null (in contrast to {@link + * Long#parseLong(String)}) + */ + + public static long parseUnsignedLong(String string) { + return parseUnsignedLong(string, 10); + } + + /** + * Returns the unsigned {@code long} value represented by a string with the given radix. + * + *

Java 8 users: use {@link Long#parseUnsignedLong(String, int)} instead. + * + * @param string the string containing the unsigned {@code long} representation to be parsed. + * @param radix the radix to use while parsing {@code string} + * @throws NumberFormatException if the string does not contain a valid unsigned {@code long} with + * the given radix, or if {@code radix} is not between {@link Character#MIN_RADIX} and {@link + * Character#MAX_RADIX}. + * @throws NullPointerException if {@code string} is null (in contrast to {@link + * Long#parseLong(String)}) + */ + + public static long parseUnsignedLong(String string, int radix) { + checkNotNull(string); + if (string.length() == 0) { + throw new NumberFormatException("empty string"); + } + if (radix < Character.MIN_RADIX || radix > Character.MAX_RADIX) { + throw new NumberFormatException("illegal radix: " + radix); + } + + int maxSafePos = ParseOverflowDetection.maxSafeDigits[radix] - 1; + long value = 0; + for (int pos = 0; pos < string.length(); pos++) { + int digit = Character.digit(string.charAt(pos), radix); + if (digit == -1) { + throw new NumberFormatException(string); + } + if (pos > maxSafePos && ParseOverflowDetection.overflowInParse(value, digit, radix)) { + throw new NumberFormatException("Too large for unsigned long: " + string); + } + value = (value * radix) + digit; + } + + return value; + } + + /** + * Returns the unsigned {@code long} value represented by the given string. + * + *

Accepts a decimal, hexadecimal, or octal number given by specifying the following prefix: + * + *

    + *
  • {@code 0x}HexDigits + *
  • {@code 0X}HexDigits + *
  • {@code #}HexDigits + *
  • {@code 0}OctalDigits + *
+ * + * @throws NumberFormatException if the string does not contain a valid unsigned {@code long} + * value + * @since 13.0 + */ + + public static long decode(String stringValue) { + ParseRequest request = ParseRequest.fromString(stringValue); + + try { + return parseUnsignedLong(request.rawValue, request.radix); + } catch (NumberFormatException e) { + NumberFormatException decodeException = + new NumberFormatException("Error parsing value: " + stringValue); + decodeException.initCause(e); + throw decodeException; + } + } + + /* + * We move the static constants into this class so ProGuard can inline UnsignedLongs entirely + * unless the user is actually calling a parse method. + */ + private static final class ParseOverflowDetection { + private ParseOverflowDetection() {} + + // calculated as 0xffffffffffffffff / radix + static final long[] maxValueDivs = new long[Character.MAX_RADIX + 1]; + static final int[] maxValueMods = new int[Character.MAX_RADIX + 1]; + static final int[] maxSafeDigits = new int[Character.MAX_RADIX + 1]; + + static { + BigInteger overflow = new BigInteger("10000000000000000", 16); + for (int i = Character.MIN_RADIX; i <= Character.MAX_RADIX; i++) { + maxValueDivs[i] = divide(MAX_VALUE, i); + maxValueMods[i] = (int) remainder(MAX_VALUE, i); + maxSafeDigits[i] = overflow.toString(i).length() - 1; + } + } + + /** + * Returns true if (current * radix) + digit is a number too large to be represented by an + * unsigned long. This is useful for detecting overflow while parsing a string representation of + * a number. Does not verify whether supplied radix is valid, passing an invalid radix will give + * undefined results or an ArrayIndexOutOfBoundsException. + */ + static boolean overflowInParse(long current, int digit, int radix) { + if (current >= 0) { + if (current < maxValueDivs[radix]) { + return false; + } + if (current > maxValueDivs[radix]) { + return true; + } + // current == maxValueDivs[radix] + return (digit > maxValueMods[radix]); + } + + // current < 0: high bit is set + return true; + } + } + + /** + * Returns a string representation of x, where x is treated as unsigned. + * + *

Java 8 users: use {@link Long#toUnsignedString(long)} instead. + */ + public static String toString(long x) { + return toString(x, 10); + } + + /** + * Returns a string representation of {@code x} for the given radix, where {@code x} is treated as + * unsigned. + * + *

Java 8 users: use {@link Long#toUnsignedString(long, int)} instead. + * + * @param x the value to convert to a string. + * @param radix the radix to use while working with {@code x} + * @throws IllegalArgumentException if {@code radix} is not between {@link Character#MIN_RADIX} + * and {@link Character#MAX_RADIX}. + */ + public static String toString(long x, int radix) { + checkArgument( + radix >= Character.MIN_RADIX && radix <= Character.MAX_RADIX, + "radix (%s) must be between Character.MIN_RADIX and Character.MAX_RADIX", + radix); + if (x == 0) { + // Simply return "0" + return "0"; + } else if (x > 0) { + return Long.toString(x, radix); + } else { + char[] buf = new char[64]; + int i = buf.length; + if ((radix & (radix - 1)) == 0) { + // Radix is a power of two so we can avoid division. + int shift = Integer.numberOfTrailingZeros(radix); + int mask = radix - 1; + do { + buf[--i] = Character.forDigit(((int) x) & mask, radix); + x >>>= shift; + } while (x != 0); + } else { + // Separate off the last digit using unsigned division. That will leave + // a number that is nonnegative as a signed integer. + long quotient; + if ((radix & 1) == 0) { + // Fast path for the usual case where the radix is even. + quotient = (x >>> 1) / (radix >>> 1); + } else { + quotient = divide(x, radix); + } + long rem = x - quotient * radix; + buf[--i] = Character.forDigit((int) rem, radix); + x = quotient; + // Simple modulo/division approach + while (x > 0) { + buf[--i] = Character.forDigit((int) (x % radix), radix); + x /= radix; + } + } + // Generate string + return new String(buf, i, buf.length - i); + } + } +} diff --git a/src/main/java/com/google/common/reflect/AbstractInvocationHandler.java b/src/main/java/com/google/common/reflect/AbstractInvocationHandler.java new file mode 100644 index 0000000..ef8dcb8 --- /dev/null +++ b/src/main/java/com/google/common/reflect/AbstractInvocationHandler.java @@ -0,0 +1,145 @@ +/* + * Copyright (C) 2012 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.reflect; + +import com.google.common.annotations.Beta; +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; +import java.util.Arrays; + + +/** + * Abstract implementation of {@link InvocationHandler} that handles {@link Object#equals}, {@link + * Object#hashCode} and {@link Object#toString}. For example: + * + *

+ * class Unsupported extends AbstractInvocationHandler {
+ *   protected Object handleInvocation(Object proxy, Method method, Object[] args) {
+ *     throw new UnsupportedOperationException();
+ *   }
+ * }
+ *
+ * CharSequence unsupported = Reflection.newProxy(CharSequence.class, new Unsupported());
+ * 
+ * + * @author Ben Yu + * @since 12.0 + */ +@Beta +public abstract class AbstractInvocationHandler implements InvocationHandler { + + private static final Object[] NO_ARGS = {}; + + /** + * {@inheritDoc} + * + *
    + *
  • {@code proxy.hashCode()} delegates to {@link AbstractInvocationHandler#hashCode} + *
  • {@code proxy.toString()} delegates to {@link AbstractInvocationHandler#toString} + *
  • {@code proxy.equals(argument)} returns true if: + *
      + *
    • {@code proxy} and {@code argument} are of the same type + *
    • and {@link AbstractInvocationHandler#equals} returns true for the {@link + * InvocationHandler} of {@code argument} + *
    + *
  • other method calls are dispatched to {@link #handleInvocation}. + *
+ */ + @Override + public final Object invoke(Object proxy, Method method, Object [] args) + throws Throwable { + if (args == null) { + args = NO_ARGS; + } + if (args.length == 0 && method.getName().equals("hashCode")) { + return hashCode(); + } + if (args.length == 1 + && method.getName().equals("equals") + && method.getParameterTypes()[0] == Object.class) { + Object arg = args[0]; + if (arg == null) { + return false; + } + if (proxy == arg) { + return true; + } + return isProxyOfSameInterfaces(arg, proxy.getClass()) + && equals(Proxy.getInvocationHandler(arg)); + } + if (args.length == 0 && method.getName().equals("toString")) { + return toString(); + } + return handleInvocation(proxy, method, args); + } + + /** + * {@link #invoke} delegates to this method upon any method invocation on the proxy instance, + * except {@link Object#equals}, {@link Object#hashCode} and {@link Object#toString}. The result + * will be returned as the proxied method's return value. + * + *

Unlike {@link #invoke}, {@code args} will never be null. When the method has no parameter, + * an empty array is passed in. + */ + protected abstract Object handleInvocation(Object proxy, Method method, Object[] args) + throws Throwable; + + /** + * By default delegates to {@link Object#equals} so instances are only equal if they are + * identical. {@code proxy.equals(argument)} returns true if: + * + *

    + *
  • {@code proxy} and {@code argument} are of the same type + *
  • and this method returns true for the {@link InvocationHandler} of {@code argument} + *
+ * + *

Subclasses can override this method to provide custom equality. + */ + @Override + public boolean equals(Object obj) { + return super.equals(obj); + } + + /** + * By default delegates to {@link Object#hashCode}. The dynamic proxies' {@code hashCode()} will + * delegate to this method. Subclasses can override this method to provide custom equality. + */ + @Override + public int hashCode() { + return super.hashCode(); + } + + /** + * By default delegates to {@link Object#toString}. The dynamic proxies' {@code toString()} will + * delegate to this method. Subclasses can override this method to provide custom string + * representation for the proxies. + */ + @Override + public String toString() { + return super.toString(); + } + + private static boolean isProxyOfSameInterfaces(Object arg, Class proxyClass) { + return proxyClass.isInstance(arg) + // Equal proxy instances should mostly be instance of proxyClass + // Under some edge cases (such as the proxy of JDK types serialized and then deserialized) + // the proxy type may not be the same. + // We first check isProxyClass() so that the common case of comparing with non-proxy objects + // is efficient. + || (Proxy.isProxyClass(arg.getClass()) + && Arrays.equals(arg.getClass().getInterfaces(), proxyClass.getInterfaces())); + } +} diff --git a/src/main/java/com/google/common/reflect/ClassPath.java b/src/main/java/com/google/common/reflect/ClassPath.java new file mode 100644 index 0000000..0f51a91 --- /dev/null +++ b/src/main/java/com/google/common/reflect/ClassPath.java @@ -0,0 +1,591 @@ +/* + * Copyright (C) 2012 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.reflect; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.base.StandardSystemProperty.JAVA_CLASS_PATH; +import static com.google.common.base.StandardSystemProperty.PATH_SEPARATOR; +import static java.util.logging.Level.WARNING; + +import com.google.common.annotations.Beta; +import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.CharMatcher; +import com.google.common.base.Predicate; +import com.google.common.base.Splitter; +import com.google.common.collect.FluentIterable; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Maps; +import com.google.common.collect.MultimapBuilder; +import com.google.common.collect.SetMultimap; +import com.google.common.collect.Sets; +import com.google.common.io.ByteSource; +import com.google.common.io.CharSource; +import com.google.common.io.Resources; +import java.io.File; +import java.io.IOException; +import java.net.MalformedURLException; +import java.net.URISyntaxException; +import java.net.URL; +import java.net.URLClassLoader; +import java.nio.charset.Charset; +import java.util.Enumeration; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.Map.Entry; +import java.util.NoSuchElementException; +import java.util.Set; +import java.util.jar.Attributes; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; +import java.util.jar.Manifest; +import java.util.logging.Logger; + + +/** + * Scans the source of a {@link ClassLoader} and finds all loadable classes and resources. + * + *

Warning: Current limitations: + * + *

    + *
  • Looks only for files and JARs in URLs available from {@link URLClassLoader} instances or + * the {@linkplain ClassLoader#getSystemClassLoader() system class loader}. + *
  • Only understands {@code file:} URLs. + *
+ * + *

In the case of directory classloaders, symlinks are supported but cycles are not traversed. + * This guarantees discovery of each unique loadable resource. However, not all possible + * aliases for resources on cyclic paths will be listed. + * + * @author Ben Yu + * @since 14.0 + */ +@Beta +public final class ClassPath { + private static final Logger logger = Logger.getLogger(ClassPath.class.getName()); + + private static final Predicate IS_TOP_LEVEL = + new Predicate() { + @Override + public boolean apply(ClassInfo info) { + return info.className.indexOf('$') == -1; + } + }; + + /** Separator for the Class-Path manifest attribute value in jar files. */ + private static final Splitter CLASS_PATH_ATTRIBUTE_SEPARATOR = + Splitter.on(" ").omitEmptyStrings(); + + private static final String CLASS_FILE_NAME_EXTENSION = ".class"; + + private final ImmutableSet resources; + + private ClassPath(ImmutableSet resources) { + this.resources = resources; + } + + /** + * Returns a {@code ClassPath} representing all classes and resources loadable from {@code + * classloader} and its ancestor class loaders. + * + *

Warning: {@code ClassPath} can find classes and resources only from: + * + *

    + *
  • {@link URLClassLoader} instances' {@code file:} URLs + *
  • the {@linkplain ClassLoader#getSystemClassLoader() system class loader}. To search the + * system class loader even when it is not a {@link URLClassLoader} (as in Java 9), {@code + * ClassPath} searches the files from the {@code java.class.path} system property. + *
+ * + * @throws IOException if the attempt to read class path resources (jar files or directories) + * failed. + */ + public static ClassPath from(ClassLoader classloader) throws IOException { + DefaultScanner scanner = new DefaultScanner(); + scanner.scan(classloader); + return new ClassPath(scanner.getResources()); + } + + /** + * Returns all resources loadable from the current class path, including the class files of all + * loadable classes but excluding the "META-INF/MANIFEST.MF" file. + */ + public ImmutableSet getResources() { + return resources; + } + + /** + * Returns all classes loadable from the current class path. + * + * @since 16.0 + */ + public ImmutableSet getAllClasses() { + return FluentIterable.from(resources).filter(ClassInfo.class).toSet(); + } + + /** Returns all top level classes loadable from the current class path. */ + public ImmutableSet getTopLevelClasses() { + return FluentIterable.from(resources).filter(ClassInfo.class).filter(IS_TOP_LEVEL).toSet(); + } + + /** Returns all top level classes whose package name is {@code packageName}. */ + public ImmutableSet getTopLevelClasses(String packageName) { + checkNotNull(packageName); + ImmutableSet.Builder builder = ImmutableSet.builder(); + for (ClassInfo classInfo : getTopLevelClasses()) { + if (classInfo.getPackageName().equals(packageName)) { + builder.add(classInfo); + } + } + return builder.build(); + } + + /** + * Returns all top level classes whose package name is {@code packageName} or starts with {@code + * packageName} followed by a '.'. + */ + public ImmutableSet getTopLevelClassesRecursive(String packageName) { + checkNotNull(packageName); + String packagePrefix = packageName + '.'; + ImmutableSet.Builder builder = ImmutableSet.builder(); + for (ClassInfo classInfo : getTopLevelClasses()) { + if (classInfo.getName().startsWith(packagePrefix)) { + builder.add(classInfo); + } + } + return builder.build(); + } + + /** + * Represents a class path resource that can be either a class file or any other resource file + * loadable from the class path. + * + * @since 14.0 + */ + @Beta + public static class ResourceInfo { + private final String resourceName; + + final ClassLoader loader; + + static ResourceInfo of(String resourceName, ClassLoader loader) { + if (resourceName.endsWith(CLASS_FILE_NAME_EXTENSION)) { + return new ClassInfo(resourceName, loader); + } else { + return new ResourceInfo(resourceName, loader); + } + } + + ResourceInfo(String resourceName, ClassLoader loader) { + this.resourceName = checkNotNull(resourceName); + this.loader = checkNotNull(loader); + } + + /** + * Returns the url identifying the resource. + * + *

See {@link ClassLoader#getResource} + * + * @throws NoSuchElementException if the resource cannot be loaded through the class loader, + * despite physically existing in the class path. + */ + public final URL url() { + URL url = loader.getResource(resourceName); + if (url == null) { + throw new NoSuchElementException(resourceName); + } + return url; + } + + /** + * Returns a {@link ByteSource} view of the resource from which its bytes can be read. + * + * @throws NoSuchElementException if the resource cannot be loaded through the class loader, + * despite physically existing in the class path. + * @since 20.0 + */ + public final ByteSource asByteSource() { + return Resources.asByteSource(url()); + } + + /** + * Returns a {@link CharSource} view of the resource from which its bytes can be read as + * characters decoded with the given {@code charset}. + * + * @throws NoSuchElementException if the resource cannot be loaded through the class loader, + * despite physically existing in the class path. + * @since 20.0 + */ + public final CharSource asCharSource(Charset charset) { + return Resources.asCharSource(url(), charset); + } + + /** Returns the fully qualified name of the resource. Such as "com/mycomp/foo/bar.txt". */ + public final String getResourceName() { + return resourceName; + } + + @Override + public int hashCode() { + return resourceName.hashCode(); + } + + @Override + public boolean equals(Object obj) { + if (obj instanceof ResourceInfo) { + ResourceInfo that = (ResourceInfo) obj; + return resourceName.equals(that.resourceName) && loader == that.loader; + } + return false; + } + + // Do not change this arbitrarily. We rely on it for sorting ResourceInfo. + @Override + public String toString() { + return resourceName; + } + } + + /** + * Represents a class that can be loaded through {@link #load}. + * + * @since 14.0 + */ + @Beta + public static final class ClassInfo extends ResourceInfo { + private final String className; + + ClassInfo(String resourceName, ClassLoader loader) { + super(resourceName, loader); + this.className = getClassName(resourceName); + } + + /** + * Returns the package name of the class, without attempting to load the class. + * + *

Behaves identically to {@link Package#getName()} but does not require the class (or + * package) to be loaded. + */ + public String getPackageName() { + return Reflection.getPackageName(className); + } + + /** + * Returns the simple name of the underlying class as given in the source code. + * + *

Behaves identically to {@link Class#getSimpleName()} but does not require the class to be + * loaded. + */ + public String getSimpleName() { + int lastDollarSign = className.lastIndexOf('$'); + if (lastDollarSign != -1) { + String innerClassName = className.substring(lastDollarSign + 1); + // local and anonymous classes are prefixed with number (1,2,3...), anonymous classes are + // entirely numeric whereas local classes have the user supplied name as a suffix + return CharMatcher.inRange('0', '9').trimLeadingFrom(innerClassName); + } + String packageName = getPackageName(); + if (packageName.isEmpty()) { + return className; + } + + // Since this is a top level class, its simple name is always the part after package name. + return className.substring(packageName.length() + 1); + } + + /** + * Returns the fully qualified name of the class. + * + *

Behaves identically to {@link Class#getName()} but does not require the class to be + * loaded. + */ + public String getName() { + return className; + } + + /** + * Loads (but doesn't link or initialize) the class. + * + * @throws LinkageError when there were errors in loading classes that this class depends on. + * For example, {@link NoClassDefFoundError}. + */ + public Class load() { + try { + return loader.loadClass(className); + } catch (ClassNotFoundException e) { + // Shouldn't happen, since the class name is read from the class path. + throw new IllegalStateException(e); + } + } + + @Override + public String toString() { + return className; + } + } + + /** + * Abstract class that scans through the class path represented by a {@link ClassLoader} and calls + * {@link #scanDirectory} and {@link #scanJarFile} for directories and jar files on the class path + * respectively. + */ + abstract static class Scanner { + + // We only scan each file once independent of the classloader that resource might be associated + // with. + private final Set scannedUris = Sets.newHashSet(); + + public final void scan(ClassLoader classloader) throws IOException { + for (Entry entry : getClassPathEntries(classloader).entrySet()) { + scan(entry.getKey(), entry.getValue()); + } + } + + @VisibleForTesting + final void scan(File file, ClassLoader classloader) throws IOException { + if (scannedUris.add(file.getCanonicalFile())) { + scanFrom(file, classloader); + } + } + + /** Called when a directory is scanned for resource files. */ + protected abstract void scanDirectory(ClassLoader loader, File directory) throws IOException; + + /** Called when a jar file is scanned for resource entries. */ + protected abstract void scanJarFile(ClassLoader loader, JarFile file) throws IOException; + + private void scanFrom(File file, ClassLoader classloader) throws IOException { + try { + if (!file.exists()) { + return; + } + } catch (SecurityException e) { + logger.warning("Cannot access " + file + ": " + e); + // TODO(emcmanus): consider whether to log other failure cases too. + return; + } + if (file.isDirectory()) { + scanDirectory(classloader, file); + } else { + scanJar(file, classloader); + } + } + + private void scanJar(File file, ClassLoader classloader) throws IOException { + JarFile jarFile; + try { + jarFile = new JarFile(file); + } catch (IOException e) { + // Not a jar file + return; + } + try { + for (File path : getClassPathFromManifest(file, jarFile.getManifest())) { + scan(path, classloader); + } + scanJarFile(classloader, jarFile); + } finally { + try { + jarFile.close(); + } catch (IOException ignored) { + } + } + } + + /** + * Returns the class path URIs specified by the {@code Class-Path} manifest attribute, according + * to JAR + * File Specification. If {@code manifest} is null, it means the jar file has no manifest, + * and an empty set will be returned. + */ + @VisibleForTesting + static ImmutableSet getClassPathFromManifest(File jarFile, Manifest manifest) { + if (manifest == null) { + return ImmutableSet.of(); + } + ImmutableSet.Builder builder = ImmutableSet.builder(); + String classpathAttribute = + manifest.getMainAttributes().getValue(Attributes.Name.CLASS_PATH.toString()); + if (classpathAttribute != null) { + for (String path : CLASS_PATH_ATTRIBUTE_SEPARATOR.split(classpathAttribute)) { + URL url; + try { + url = getClassPathEntry(jarFile, path); + } catch (MalformedURLException e) { + // Ignore bad entry + logger.warning("Invalid Class-Path entry: " + path); + continue; + } + if (url.getProtocol().equals("file")) { + builder.add(toFile(url)); + } + } + } + return builder.build(); + } + + @VisibleForTesting + static ImmutableMap getClassPathEntries(ClassLoader classloader) { + LinkedHashMap entries = Maps.newLinkedHashMap(); + // Search parent first, since it's the order ClassLoader#loadClass() uses. + ClassLoader parent = classloader.getParent(); + if (parent != null) { + entries.putAll(getClassPathEntries(parent)); + } + for (URL url : getClassLoaderUrls(classloader)) { + if (url.getProtocol().equals("file")) { + File file = toFile(url); + if (!entries.containsKey(file)) { + entries.put(file, classloader); + } + } + } + return ImmutableMap.copyOf(entries); + } + + private static ImmutableList getClassLoaderUrls(ClassLoader classloader) { + if (classloader instanceof URLClassLoader) { + return ImmutableList.copyOf(((URLClassLoader) classloader).getURLs()); + } + if (classloader.equals(ClassLoader.getSystemClassLoader())) { + return parseJavaClassPath(); + } + return ImmutableList.of(); + } + + /** + * Returns the URLs in the class path specified by the {@code java.class.path} {@linkplain + * System#getProperty system property}. + */ + @VisibleForTesting // TODO(b/65488446): Make this a public API. + static ImmutableList parseJavaClassPath() { + ImmutableList.Builder urls = ImmutableList.builder(); + for (String entry : Splitter.on(PATH_SEPARATOR.value()).split(JAVA_CLASS_PATH.value())) { + try { + try { + urls.add(new File(entry).toURI().toURL()); + } catch (SecurityException e) { // File.toURI checks to see if the file is a directory + urls.add(new URL("file", null, new File(entry).getAbsolutePath())); + } + } catch (MalformedURLException e) { + logger.log(WARNING, "malformed classpath entry: " + entry, e); + } + } + return urls.build(); + } + + /** + * Returns the absolute uri of the Class-Path entry value as specified in JAR + * File Specification. Even though the specification only talks about relative urls, + * absolute urls are actually supported too (for example, in Maven surefire plugin). + */ + @VisibleForTesting + static URL getClassPathEntry(File jarFile, String path) throws MalformedURLException { + return new URL(jarFile.toURI().toURL(), path); + } + } + + @VisibleForTesting + static final class DefaultScanner extends Scanner { + private final SetMultimap resources = + MultimapBuilder.hashKeys().linkedHashSetValues().build(); + + ImmutableSet getResources() { + ImmutableSet.Builder builder = ImmutableSet.builder(); + for (Entry entry : resources.entries()) { + builder.add(ResourceInfo.of(entry.getValue(), entry.getKey())); + } + return builder.build(); + } + + @Override + protected void scanJarFile(ClassLoader classloader, JarFile file) { + Enumeration entries = file.entries(); + while (entries.hasMoreElements()) { + JarEntry entry = entries.nextElement(); + if (entry.isDirectory() || entry.getName().equals(JarFile.MANIFEST_NAME)) { + continue; + } + resources.get(classloader).add(entry.getName()); + } + } + + @Override + protected void scanDirectory(ClassLoader classloader, File directory) throws IOException { + Set currentPath = new HashSet<>(); + currentPath.add(directory.getCanonicalFile()); + scanDirectory(directory, classloader, "", currentPath); + } + + /** + * Recursively scan the given directory, adding resources for each file encountered. Symlinks + * which have already been traversed in the current tree path will be skipped to eliminate + * cycles; otherwise symlinks are traversed. + * + * @param directory the root of the directory to scan + * @param classloader the classloader that includes resources found in {@code directory} + * @param packagePrefix resource path prefix inside {@code classloader} for any files found + * under {@code directory} + * @param currentPath canonical files already visited in the current directory tree path, for + * cycle elimination + */ + private void scanDirectory( + File directory, ClassLoader classloader, String packagePrefix, Set currentPath) + throws IOException { + File[] files = directory.listFiles(); + if (files == null) { + logger.warning("Cannot read directory " + directory); + // IO error, just skip the directory + return; + } + for (File f : files) { + String name = f.getName(); + if (f.isDirectory()) { + File deref = f.getCanonicalFile(); + if (currentPath.add(deref)) { + scanDirectory(deref, classloader, packagePrefix + name + "/", currentPath); + currentPath.remove(deref); + } + } else { + String resourceName = packagePrefix + name; + if (!resourceName.equals(JarFile.MANIFEST_NAME)) { + resources.get(classloader).add(resourceName); + } + } + } + } + } + + @VisibleForTesting + static String getClassName(String filename) { + int classNameEnd = filename.length() - CLASS_FILE_NAME_EXTENSION.length(); + return filename.substring(0, classNameEnd).replace('/', '.'); + } + + // TODO(benyu): Try java.nio.file.Paths#get() when Guava drops JDK 6 support. + @VisibleForTesting + static File toFile(URL url) { + checkArgument(url.getProtocol().equals("file")); + try { + return new File(url.toURI()); // Accepts escaped characters like %20. + } catch (URISyntaxException e) { // URL.toURI() doesn't escape chars. + return new File(url.getPath()); // Accepts non-escaped chars like space. + } + } +} diff --git a/src/main/java/com/google/common/reflect/Element.java b/src/main/java/com/google/common/reflect/Element.java new file mode 100644 index 0000000..9f2efb4 --- /dev/null +++ b/src/main/java/com/google/common/reflect/Element.java @@ -0,0 +1,178 @@ +/* + * Copyright (C) 2012 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.reflect; + +import static com.google.common.base.Preconditions.checkNotNull; + +import java.lang.annotation.Annotation; +import java.lang.reflect.AccessibleObject; +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.Member; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; + + +/** + * Represents either a {@link Field}, a {@link Method} or a {@link Constructor}. Provides + * convenience methods such as {@link #isPublic} and {@link #isPackagePrivate}. + * + * @author Ben Yu + */ +class Element extends AccessibleObject implements Member { + + private final AccessibleObject accessibleObject; + private final Member member; + + Element(M member) { + checkNotNull(member); + this.accessibleObject = member; + this.member = member; + } + + public TypeToken getOwnerType() { + return TypeToken.of(getDeclaringClass()); + } + + @Override + public final boolean isAnnotationPresent(Class annotationClass) { + return accessibleObject.isAnnotationPresent(annotationClass); + } + + @Override + public final A getAnnotation(Class annotationClass) { + return accessibleObject.getAnnotation(annotationClass); + } + + @Override + public final Annotation[] getAnnotations() { + return accessibleObject.getAnnotations(); + } + + @Override + public final Annotation[] getDeclaredAnnotations() { + return accessibleObject.getDeclaredAnnotations(); + } + + @Override + public final void setAccessible(boolean flag) throws SecurityException { + accessibleObject.setAccessible(flag); + } + + @Override + public final boolean isAccessible() { + return accessibleObject.isAccessible(); + } + + @Override + public Class getDeclaringClass() { + return member.getDeclaringClass(); + } + + @Override + public final String getName() { + return member.getName(); + } + + @Override + public final int getModifiers() { + return member.getModifiers(); + } + + @Override + public final boolean isSynthetic() { + return member.isSynthetic(); + } + + /** Returns true if the element is public. */ + public final boolean isPublic() { + return Modifier.isPublic(getModifiers()); + } + + /** Returns true if the element is protected. */ + public final boolean isProtected() { + return Modifier.isProtected(getModifiers()); + } + + /** Returns true if the element is package-private. */ + public final boolean isPackagePrivate() { + return !isPrivate() && !isPublic() && !isProtected(); + } + + /** Returns true if the element is private. */ + public final boolean isPrivate() { + return Modifier.isPrivate(getModifiers()); + } + + /** Returns true if the element is static. */ + public final boolean isStatic() { + return Modifier.isStatic(getModifiers()); + } + + /** + * Returns {@code true} if this method is final, per {@code Modifier.isFinal(getModifiers())}. + * + *

Note that a method may still be effectively "final", or non-overridable when it has no + * {@code final} keyword. For example, it could be private, or it could be declared by a final + * class. To tell whether a method is overridable, use {@link Invokable#isOverridable}. + */ + public final boolean isFinal() { + return Modifier.isFinal(getModifiers()); + } + + /** Returns true if the method is abstract. */ + public final boolean isAbstract() { + return Modifier.isAbstract(getModifiers()); + } + + /** Returns true if the element is native. */ + public final boolean isNative() { + return Modifier.isNative(getModifiers()); + } + + /** Returns true if the method is synchronized. */ + public final boolean isSynchronized() { + return Modifier.isSynchronized(getModifiers()); + } + + /** Returns true if the field is volatile. */ + final boolean isVolatile() { + return Modifier.isVolatile(getModifiers()); + } + + /** Returns true if the field is transient. */ + final boolean isTransient() { + return Modifier.isTransient(getModifiers()); + } + + @Override + public boolean equals(Object obj) { + if (obj instanceof Element) { + Element that = (Element) obj; + return getOwnerType().equals(that.getOwnerType()) && member.equals(that.member); + } + return false; + } + + @Override + public int hashCode() { + return member.hashCode(); + } + + @Override + public String toString() { + return member.toString(); + } +} diff --git a/src/main/java/com/google/common/reflect/ImmutableTypeToInstanceMap.java b/src/main/java/com/google/common/reflect/ImmutableTypeToInstanceMap.java new file mode 100644 index 0000000..aa84e7f --- /dev/null +++ b/src/main/java/com/google/common/reflect/ImmutableTypeToInstanceMap.java @@ -0,0 +1,173 @@ +/* + * Copyright (C) 2012 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.reflect; + +import com.google.common.annotations.Beta; +import com.google.common.collect.ForwardingMap; +import com.google.common.collect.ImmutableMap; + +import java.util.Map; + +/** + * A type-to-instance map backed by an {@link ImmutableMap}. See also {@link + * MutableTypeToInstanceMap}. + * + * @author Ben Yu + * @since 13.0 + */ +@Beta +public final class ImmutableTypeToInstanceMap extends ForwardingMap, B> + implements TypeToInstanceMap { + + /** Returns an empty type to instance map. */ + public static ImmutableTypeToInstanceMap of() { + return new ImmutableTypeToInstanceMap(ImmutableMap., B>of()); + } + + /** Returns a new builder. */ + public static Builder builder() { + return new Builder(); + } + + /** + * A builder for creating immutable type-to-instance maps. Example: + * + *

{@code
+   * static final ImmutableTypeToInstanceMap> HANDLERS =
+   *     ImmutableTypeToInstanceMap.>builder()
+   *         .put(new TypeToken>() {}, new FooHandler())
+   *         .put(new TypeToken>() {}, new SubBarHandler())
+   *         .build();
+   * }
+ * + *

After invoking {@link #build()} it is still possible to add more entries and build again. + * Thus each map generated by this builder will be a superset of any map generated before it. + * + * @since 13.0 + */ + @Beta + public static final class Builder { + private final ImmutableMap.Builder, B> mapBuilder = + ImmutableMap.builder(); + + private Builder() {} + + /** + * Associates {@code key} with {@code value} in the built map. Duplicate keys are not allowed, + * and will cause {@link #build} to fail. + */ + + public Builder put(Class key, T value) { + mapBuilder.put(TypeToken.of(key), value); + return this; + } + + /** + * Associates {@code key} with {@code value} in the built map. Duplicate keys are not allowed, + * and will cause {@link #build} to fail. + */ + + public Builder put(TypeToken key, T value) { + mapBuilder.put(key.rejectTypeVariables(), value); + return this; + } + + /** + * Returns a new immutable type-to-instance map containing the entries provided to this builder. + * + * @throws IllegalArgumentException if duplicate keys were added + */ + public ImmutableTypeToInstanceMap build() { + return new ImmutableTypeToInstanceMap(mapBuilder.build()); + } + } + + private final ImmutableMap, B> delegate; + + private ImmutableTypeToInstanceMap(ImmutableMap, B> delegate) { + this.delegate = delegate; + } + + @Override + public T getInstance(TypeToken type) { + return trustedGet(type.rejectTypeVariables()); + } + + @Override + public T getInstance(Class type) { + return trustedGet(TypeToken.of(type)); + } + + /** + * Guaranteed to throw an exception and leave the map unmodified. + * + * @deprecated unsupported operation + * @throws UnsupportedOperationException always + */ + + @Deprecated + @Override + public T putInstance(TypeToken type, T value) { + throw new UnsupportedOperationException(); + } + + /** + * Guaranteed to throw an exception and leave the map unmodified. + * + * @deprecated unsupported operation + * @throws UnsupportedOperationException always + */ + + @Deprecated + @Override + public T putInstance(Class type, T value) { + throw new UnsupportedOperationException(); + } + + /** + * Guaranteed to throw an exception and leave the map unmodified. + * + * @deprecated unsupported operation + * @throws UnsupportedOperationException always + */ + + @Deprecated + @Override + public B put(TypeToken key, B value) { + throw new UnsupportedOperationException(); + } + + /** + * Guaranteed to throw an exception and leave the map unmodified. + * + * @deprecated unsupported operation + * @throws UnsupportedOperationException always + */ + @Deprecated + @Override + public void putAll(Map, ? extends B> map) { + throw new UnsupportedOperationException(); + } + + @Override + protected Map, B> delegate() { + return delegate; + } + + @SuppressWarnings("unchecked") // value could not get in if not a T + private T trustedGet(TypeToken type) { + return (T) delegate.get(type); + } +} diff --git a/src/main/java/com/google/common/reflect/Invokable.java b/src/main/java/com/google/common/reflect/Invokable.java new file mode 100644 index 0000000..40309ff --- /dev/null +++ b/src/main/java/com/google/common/reflect/Invokable.java @@ -0,0 +1,381 @@ +/* + * Copyright (C) 2012 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.reflect; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.annotations.Beta; +import com.google.common.collect.ImmutableList; + +import java.lang.annotation.Annotation; +import java.lang.reflect.AccessibleObject; +import java.lang.reflect.AnnotatedType; +import java.lang.reflect.Constructor; +import java.lang.reflect.GenericDeclaration; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Member; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.lang.reflect.Type; +import java.lang.reflect.TypeVariable; +import java.util.Arrays; + + +/** + * Wrapper around either a {@link Method} or a {@link Constructor}. Convenience API is provided to + * make common reflective operation easier to deal with, such as {@link #isPublic}, {@link + * #getParameters} etc. + * + *

In addition to convenience methods, {@link TypeToken#method} and {@link TypeToken#constructor} + * will resolve the type parameters of the method or constructor in the context of the owner type, + * which may be a subtype of the declaring class. For example: + * + *

{@code
+ * Method getMethod = List.class.getMethod("get", int.class);
+ * Invokable, ?> invokable = new TypeToken>() {}.method(getMethod);
+ * assertEquals(TypeToken.of(String.class), invokable.getReturnType()); // Not Object.class!
+ * assertEquals(new TypeToken>() {}, invokable.getOwnerType());
+ * }
+ * + * @param the type that owns this method or constructor. + * @param the return type of (or supertype thereof) the method or the declaring type of the + * constructor. + * @author Ben Yu + * @since 14.0 + */ +@Beta +public abstract class Invokable extends Element implements GenericDeclaration { + + Invokable(M member) { + super(member); + } + + /** Returns {@link Invokable} of {@code method}. */ + public static Invokable from(Method method) { + return new MethodInvokable<>(method); + } + + /** Returns {@link Invokable} of {@code constructor}. */ + public static Invokable from(Constructor constructor) { + return new ConstructorInvokable(constructor); + } + + /** + * Returns {@code true} if this is an overridable method. Constructors, private, static or final + * methods, or methods declared by final classes are not overridable. + */ + public abstract boolean isOverridable(); + + /** Returns {@code true} if this was declared to take a variable number of arguments. */ + public abstract boolean isVarArgs(); + + /** + * Invokes with {@code receiver} as 'this' and {@code args} passed to the underlying method and + * returns the return value; or calls the underlying constructor with {@code args} and returns the + * constructed instance. + * + * @throws IllegalAccessException if this {@code Constructor} object enforces Java language access + * control and the underlying method or constructor is inaccessible. + * @throws IllegalArgumentException if the number of actual and formal parameters differ; if an + * unwrapping conversion for primitive arguments fails; or if, after possible unwrapping, a + * parameter value cannot be converted to the corresponding formal parameter type by a method + * invocation conversion. + * @throws InvocationTargetException if the underlying method or constructor throws an exception. + */ + // All subclasses are owned by us and we'll make sure to get the R type right. + @SuppressWarnings("unchecked") + + public final R invoke(T receiver, Object... args) + throws InvocationTargetException, IllegalAccessException { + return (R) invokeInternal(receiver, checkNotNull(args)); + } + + /** Returns the return type of this {@code Invokable}. */ + // All subclasses are owned by us and we'll make sure to get the R type right. + @SuppressWarnings("unchecked") + public final TypeToken getReturnType() { + return (TypeToken) TypeToken.of(getGenericReturnType()); + } + + /** + * Returns all declared parameters of this {@code Invokable}. Note that if this is a constructor + * of a non-static inner class, unlike {@link Constructor#getParameterTypes}, the hidden {@code + * this} parameter of the enclosing class is excluded from the returned parameters. + */ + public final ImmutableList getParameters() { + Type[] parameterTypes = getGenericParameterTypes(); + Annotation[][] annotations = getParameterAnnotations(); + AnnotatedType[] annotatedTypes = getAnnotatedParameterTypes(); + ImmutableList.Builder builder = ImmutableList.builder(); + for (int i = 0; i < parameterTypes.length; i++) { + builder.add( + new Parameter( + this, i, TypeToken.of(parameterTypes[i]), annotations[i], annotatedTypes[i])); + } + return builder.build(); + } + + /** Returns all declared exception types of this {@code Invokable}. */ + public final ImmutableList> getExceptionTypes() { + ImmutableList.Builder> builder = ImmutableList.builder(); + for (Type type : getGenericExceptionTypes()) { + // getGenericExceptionTypes() will never return a type that's not exception + @SuppressWarnings("unchecked") + TypeToken exceptionType = + (TypeToken) TypeToken.of(type); + builder.add(exceptionType); + } + return builder.build(); + } + + /** + * Explicitly specifies the return type of this {@code Invokable}. For example: + * + *
{@code
+   * Method factoryMethod = Person.class.getMethod("create");
+   * Invokable factory = Invokable.of(getNameMethod).returning(Person.class);
+   * }
+ */ + public final Invokable returning(Class returnType) { + return returning(TypeToken.of(returnType)); + } + + /** Explicitly specifies the return type of this {@code Invokable}. */ + public final Invokable returning(TypeToken returnType) { + if (!returnType.isSupertypeOf(getReturnType())) { + throw new IllegalArgumentException( + "Invokable is known to return " + getReturnType() + ", not " + returnType); + } + @SuppressWarnings("unchecked") // guarded by previous check + Invokable specialized = (Invokable) this; + return specialized; + } + + @SuppressWarnings("unchecked") // The declaring class is T's raw class, or one of its supertypes. + @Override + public final Class getDeclaringClass() { + return (Class) super.getDeclaringClass(); + } + + /** Returns the type of {@code T}. */ + // Overridden in TypeToken#method() and TypeToken#constructor() + @SuppressWarnings("unchecked") // The declaring class is T. + @Override + public TypeToken getOwnerType() { + return (TypeToken) TypeToken.of(getDeclaringClass()); + } + + abstract Object invokeInternal(Object receiver, Object[] args) + throws InvocationTargetException, IllegalAccessException; + + abstract Type[] getGenericParameterTypes(); + + abstract AnnotatedType[] getAnnotatedParameterTypes(); + + /** This should never return a type that's not a subtype of Throwable. */ + abstract Type[] getGenericExceptionTypes(); + + abstract Annotation[][] getParameterAnnotations(); + + abstract Type getGenericReturnType(); + + public abstract AnnotatedType getAnnotatedReturnType(); + + static class MethodInvokable extends Invokable { + + final Method method; + + MethodInvokable(Method method) { + super(method); + this.method = method; + } + + @Override + final Object invokeInternal(Object receiver, Object[] args) + throws InvocationTargetException, IllegalAccessException { + return method.invoke(receiver, args); + } + + @Override + Type getGenericReturnType() { + return method.getGenericReturnType(); + } + + @Override + Type[] getGenericParameterTypes() { + return method.getGenericParameterTypes(); + } + + @Override + AnnotatedType[] getAnnotatedParameterTypes() { + return method.getAnnotatedParameterTypes(); + } + + @Override + public AnnotatedType getAnnotatedReturnType() { + return method.getAnnotatedReturnType(); + } + + @Override + Type[] getGenericExceptionTypes() { + return method.getGenericExceptionTypes(); + } + + @Override + final Annotation[][] getParameterAnnotations() { + return method.getParameterAnnotations(); + } + + @Override + public final TypeVariable[] getTypeParameters() { + return method.getTypeParameters(); + } + + @Override + public final boolean isOverridable() { + return !(isFinal() + || isPrivate() + || isStatic() + || Modifier.isFinal(getDeclaringClass().getModifiers())); + } + + @Override + public final boolean isVarArgs() { + return method.isVarArgs(); + } + } + + static class ConstructorInvokable extends Invokable { + + final Constructor constructor; + + ConstructorInvokable(Constructor constructor) { + super(constructor); + this.constructor = constructor; + } + + @Override + final Object invokeInternal(Object receiver, Object[] args) + throws InvocationTargetException, IllegalAccessException { + try { + return constructor.newInstance(args); + } catch (InstantiationException e) { + throw new RuntimeException(constructor + " failed.", e); + } + } + + /** + * If the class is parameterized, such as {@link java.util.ArrayList ArrayList}, this returns + * {@code ArrayList}. + */ + @Override + Type getGenericReturnType() { + Class declaringClass = getDeclaringClass(); + TypeVariable[] typeParams = declaringClass.getTypeParameters(); + if (typeParams.length > 0) { + return Types.newParameterizedType(declaringClass, typeParams); + } else { + return declaringClass; + } + } + + @Override + Type[] getGenericParameterTypes() { + Type[] types = constructor.getGenericParameterTypes(); + if (types.length > 0 && mayNeedHiddenThis()) { + Class[] rawParamTypes = constructor.getParameterTypes(); + if (types.length == rawParamTypes.length + && rawParamTypes[0] == getDeclaringClass().getEnclosingClass()) { + // first parameter is the hidden 'this' + return Arrays.copyOfRange(types, 1, types.length); + } + } + return types; + } + + @Override + AnnotatedType[] getAnnotatedParameterTypes() { + return constructor.getAnnotatedParameterTypes(); + } + + @Override + public AnnotatedType getAnnotatedReturnType() { + return constructor.getAnnotatedReturnType(); + } + + @Override + Type[] getGenericExceptionTypes() { + return constructor.getGenericExceptionTypes(); + } + + @Override + final Annotation[][] getParameterAnnotations() { + return constructor.getParameterAnnotations(); + } + + /** + * {@inheritDoc} + * + *

WARNING: Normally it's a smell if a class needs to be explicitly initialized, because static + * state hurts system maintainability and testability. In cases when you have no choice while + * inter-operating with a legacy framework, this method helps to keep the code less ugly. + * + * @throws ExceptionInInitializerError if an exception is thrown during initialization of a class + */ + public static void initialize(Class... classes) { + for (Class clazz : classes) { + try { + Class.forName(clazz.getName(), true, clazz.getClassLoader()); + } catch (ClassNotFoundException e) { + throw new AssertionError(e); + } + } + } + + /** + * Returns a proxy instance that implements {@code interfaceType} by dispatching method + * invocations to {@code handler}. The class loader of {@code interfaceType} will be used to + * define the proxy class. To implement multiple interfaces or specify a class loader, use {@link + * Proxy#newProxyInstance}. + * + * @throws IllegalArgumentException if {@code interfaceType} does not specify the type of a Java + * interface + */ + public static T newProxy(Class interfaceType, InvocationHandler handler) { + checkNotNull(handler); + checkArgument(interfaceType.isInterface(), "%s is not an interface", interfaceType); + Object object = + Proxy.newProxyInstance( + interfaceType.getClassLoader(), new Class[] {interfaceType}, handler); + return interfaceType.cast(object); + } + + private Reflection() {} +} diff --git a/src/main/java/com/google/common/reflect/TypeCapture.java b/src/main/java/com/google/common/reflect/TypeCapture.java new file mode 100644 index 0000000..effb382 --- /dev/null +++ b/src/main/java/com/google/common/reflect/TypeCapture.java @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2012 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.reflect; + +import static com.google.common.base.Preconditions.checkArgument; + +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; + +/** + * Captures the actual type of {@code T}. + * + * @author Ben Yu + */ +abstract class TypeCapture { + + /** Returns the captured type. */ + final Type capture() { + Type superclass = getClass().getGenericSuperclass(); + checkArgument(superclass instanceof ParameterizedType, "%s isn't parameterized", superclass); + return ((ParameterizedType) superclass).getActualTypeArguments()[0]; + } +} diff --git a/src/main/java/com/google/common/reflect/TypeParameter.java b/src/main/java/com/google/common/reflect/TypeParameter.java new file mode 100644 index 0000000..20709ba --- /dev/null +++ b/src/main/java/com/google/common/reflect/TypeParameter.java @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2011 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.reflect; + +import static com.google.common.base.Preconditions.checkArgument; + +import com.google.common.annotations.Beta; +import java.lang.reflect.Type; +import java.lang.reflect.TypeVariable; + + +/** + * Captures a free type variable that can be used in {@link TypeToken#where}. For example: + * + *

{@code
+ * static  TypeToken> listOf(Class elementType) {
+ *   return new TypeToken>() {}
+ *       .where(new TypeParameter() {}, elementType);
+ * }
+ * }
+ * + * @author Ben Yu + * @since 12.0 + */ +@Beta +public abstract class TypeParameter extends TypeCapture { + + final TypeVariable typeVariable; + + protected TypeParameter() { + Type type = capture(); + checkArgument(type instanceof TypeVariable, "%s should be a type variable.", type); + this.typeVariable = (TypeVariable) type; + } + + @Override + public final int hashCode() { + return typeVariable.hashCode(); + } + + @Override + public final boolean equals(Object o) { + if (o instanceof TypeParameter) { + TypeParameter that = (TypeParameter) o; + return typeVariable.equals(that.typeVariable); + } + return false; + } + + @Override + public String toString() { + return typeVariable.toString(); + } +} diff --git a/src/main/java/com/google/common/reflect/TypeResolver.java b/src/main/java/com/google/common/reflect/TypeResolver.java new file mode 100644 index 0000000..9c85047 --- /dev/null +++ b/src/main/java/com/google/common/reflect/TypeResolver.java @@ -0,0 +1,604 @@ +/* + * Copyright (C) 2009 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.reflect; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.base.Preconditions.checkState; +import static java.util.Arrays.asList; + +import com.google.common.annotations.Beta; +import com.google.common.base.Joiner; +import com.google.common.base.Objects; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Maps; +import java.lang.reflect.GenericArrayType; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.lang.reflect.TypeVariable; +import java.lang.reflect.WildcardType; +import java.util.Arrays; +import java.util.LinkedHashSet; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; +import java.util.concurrent.atomic.AtomicInteger; + + +/** + * An object of this class encapsulates type mappings from type variables. Mappings are established + * with {@link #where} and types are resolved using {@link #resolveType}. + * + *

Note that usually type mappings are already implied by the static type hierarchy (for example, + * the {@code E} type variable declared by class {@code List} naturally maps to {@code String} in + * the context of {@code class MyStringList implements List}. In such case, prefer to use + * {@link TypeToken#resolveType} since it's simpler and more type safe. This class should only be + * used when the type mapping isn't implied by the static type hierarchy, but provided through other + * means such as an annotation or external configuration file. + * + * @author Ben Yu + * @since 15.0 + */ +@Beta +public final class TypeResolver { + + private final TypeTable typeTable; + + public TypeResolver() { + this.typeTable = new TypeTable(); + } + + private TypeResolver(TypeTable typeTable) { + this.typeTable = typeTable; + } + + /** + * Returns a resolver that resolves types "covariantly". + * + *

For example, when resolving {@code List} in the context of {@code ArrayList}, {@code + * } is covariantly resolved to {@code } such that return type of {@code List::get} is + * {@code }. + */ + static TypeResolver covariantly(Type contextType) { + return new TypeResolver().where(TypeMappingIntrospector.getTypeMappings(contextType)); + } + + /** + * Returns a resolver that resolves types "invariantly". + * + *

For example, when resolving {@code List} in the context of {@code ArrayList}, {@code + * } cannot be invariantly resolved to {@code } because otherwise the parameter type of + * {@code List::set} will be {@code } and it'll falsely say any object can be passed into + * {@code ArrayList::set}. + * + *

Instead, {@code } will be resolved to a capture in the form of a type variable {@code + * }, effectively preventing {@code set} from accepting any type. + */ + static TypeResolver invariantly(Type contextType) { + Type invariantContext = WildcardCapturer.INSTANCE.capture(contextType); + return new TypeResolver().where(TypeMappingIntrospector.getTypeMappings(invariantContext)); + } + + /** + * Returns a new {@code TypeResolver} with type variables in {@code formal} mapping to types in + * {@code actual}. + * + *

For example, if {@code formal} is a {@code TypeVariable T}, and {@code actual} is {@code + * String.class}, then {@code new TypeResolver().where(formal, actual)} will {@linkplain + * #resolveType resolve} {@code ParameterizedType List} to {@code List}, and resolve + * {@code Map} to {@code Map} etc. Similarly, {@code formal} and + * {@code actual} can be {@code Map} and {@code Map} respectively, or they + * can be {@code E[]} and {@code String[]} respectively, or even any arbitrary combination + * thereof. + * + * @param formal The type whose type variables or itself is mapped to other type(s). It's almost + * always a bug if {@code formal} isn't a type variable and contains no type variable. Make + * sure you are passing the two parameters in the right order. + * @param actual The type that the formal type variable(s) are mapped to. It can be or contain yet + * other type variables, in which case these type variables will be further resolved if + * corresponding mappings exist in the current {@code TypeResolver} instance. + */ + public TypeResolver where(Type formal, Type actual) { + Map mappings = Maps.newHashMap(); + populateTypeMappings(mappings, checkNotNull(formal), checkNotNull(actual)); + return where(mappings); + } + + /** Returns a new {@code TypeResolver} with {@code variable} mapping to {@code type}. */ + TypeResolver where(Map mappings) { + return new TypeResolver(typeTable.where(mappings)); + } + + private static void populateTypeMappings( + final Map mappings, final Type from, final Type to) { + if (from.equals(to)) { + return; + } + new TypeVisitor() { + @Override + void visitTypeVariable(TypeVariable typeVariable) { + mappings.put(new TypeVariableKey(typeVariable), to); + } + + @Override + void visitWildcardType(WildcardType fromWildcardType) { + if (!(to instanceof WildcardType)) { + return; // okay to say is anything + } + WildcardType toWildcardType = (WildcardType) to; + Type[] fromUpperBounds = fromWildcardType.getUpperBounds(); + Type[] toUpperBounds = toWildcardType.getUpperBounds(); + Type[] fromLowerBounds = fromWildcardType.getLowerBounds(); + Type[] toLowerBounds = toWildcardType.getLowerBounds(); + checkArgument( + fromUpperBounds.length == toUpperBounds.length + && fromLowerBounds.length == toLowerBounds.length, + "Incompatible type: %s vs. %s", + fromWildcardType, + to); + for (int i = 0; i < fromUpperBounds.length; i++) { + populateTypeMappings(mappings, fromUpperBounds[i], toUpperBounds[i]); + } + for (int i = 0; i < fromLowerBounds.length; i++) { + populateTypeMappings(mappings, fromLowerBounds[i], toLowerBounds[i]); + } + } + + @Override + void visitParameterizedType(ParameterizedType fromParameterizedType) { + if (to instanceof WildcardType) { + return; // Okay to say Foo is + } + ParameterizedType toParameterizedType = expectArgument(ParameterizedType.class, to); + if (fromParameterizedType.getOwnerType() != null + && toParameterizedType.getOwnerType() != null) { + populateTypeMappings( + mappings, fromParameterizedType.getOwnerType(), toParameterizedType.getOwnerType()); + } + checkArgument( + fromParameterizedType.getRawType().equals(toParameterizedType.getRawType()), + "Inconsistent raw type: %s vs. %s", + fromParameterizedType, + to); + Type[] fromArgs = fromParameterizedType.getActualTypeArguments(); + Type[] toArgs = toParameterizedType.getActualTypeArguments(); + checkArgument( + fromArgs.length == toArgs.length, + "%s not compatible with %s", + fromParameterizedType, + toParameterizedType); + for (int i = 0; i < fromArgs.length; i++) { + populateTypeMappings(mappings, fromArgs[i], toArgs[i]); + } + } + + @Override + void visitGenericArrayType(GenericArrayType fromArrayType) { + if (to instanceof WildcardType) { + return; // Okay to say A[] is + } + Type componentType = Types.getComponentType(to); + checkArgument(componentType != null, "%s is not an array type.", to); + populateTypeMappings(mappings, fromArrayType.getGenericComponentType(), componentType); + } + + @Override + void visitClass(Class fromClass) { + if (to instanceof WildcardType) { + return; // Okay to say Foo is + } + // Can't map from a raw class to anything other than itself or a wildcard. + // You can't say "assuming String is Integer". + // And we don't support "assuming String is T"; user has to say "assuming T is String". + throw new IllegalArgumentException("No type mapping from " + fromClass + " to " + to); + } + }.visit(from); + } + + /** + * Resolves all type variables in {@code type} and all downstream types and returns a + * corresponding type with type variables resolved. + */ + public Type resolveType(Type type) { + checkNotNull(type); + if (type instanceof TypeVariable) { + return typeTable.resolve((TypeVariable) type); + } else if (type instanceof ParameterizedType) { + return resolveParameterizedType((ParameterizedType) type); + } else if (type instanceof GenericArrayType) { + return resolveGenericArrayType((GenericArrayType) type); + } else if (type instanceof WildcardType) { + return resolveWildcardType((WildcardType) type); + } else { + // if Class, no resolution needed, we are done. + return type; + } + } + + Type[] resolveTypesInPlace(Type[] types) { + for (int i = 0; i < types.length; i++) { + types[i] = resolveType(types[i]); + } + return types; + } + + private Type[] resolveTypes(Type[] types) { + Type[] result = new Type[types.length]; + for (int i = 0; i < types.length; i++) { + result[i] = resolveType(types[i]); + } + return result; + } + + private WildcardType resolveWildcardType(WildcardType type) { + Type[] lowerBounds = type.getLowerBounds(); + Type[] upperBounds = type.getUpperBounds(); + return new Types.WildcardTypeImpl(resolveTypes(lowerBounds), resolveTypes(upperBounds)); + } + + private Type resolveGenericArrayType(GenericArrayType type) { + Type componentType = type.getGenericComponentType(); + Type resolvedComponentType = resolveType(componentType); + return Types.newArrayType(resolvedComponentType); + } + + private ParameterizedType resolveParameterizedType(ParameterizedType type) { + Type owner = type.getOwnerType(); + Type resolvedOwner = (owner == null) ? null : resolveType(owner); + Type resolvedRawType = resolveType(type.getRawType()); + + Type[] args = type.getActualTypeArguments(); + Type[] resolvedArgs = resolveTypes(args); + return Types.newParameterizedTypeWithOwner( + resolvedOwner, (Class) resolvedRawType, resolvedArgs); + } + + private static T expectArgument(Class type, Object arg) { + try { + return type.cast(arg); + } catch (ClassCastException e) { + throw new IllegalArgumentException(arg + " is not a " + type.getSimpleName()); + } + } + + /** A TypeTable maintains mapping from {@link TypeVariable} to types. */ + private static class TypeTable { + private final ImmutableMap map; + + TypeTable() { + this.map = ImmutableMap.of(); + } + + private TypeTable(ImmutableMap map) { + this.map = map; + } + + /** Returns a new {@code TypeResolver} with {@code variable} mapping to {@code type}. */ + final TypeTable where(Map mappings) { + ImmutableMap.Builder builder = ImmutableMap.builder(); + builder.putAll(map); + for (Entry mapping : mappings.entrySet()) { + TypeVariableKey variable = mapping.getKey(); + Type type = mapping.getValue(); + checkArgument(!variable.equalsType(type), "Type variable %s bound to itself", variable); + builder.put(variable, type); + } + return new TypeTable(builder.build()); + } + + final Type resolve(final TypeVariable var) { + final TypeTable unguarded = this; + TypeTable guarded = + new TypeTable() { + @Override + public Type resolveInternal(TypeVariable intermediateVar, TypeTable forDependent) { + if (intermediateVar.getGenericDeclaration().equals(var.getGenericDeclaration())) { + return intermediateVar; + } + return unguarded.resolveInternal(intermediateVar, forDependent); + } + }; + return resolveInternal(var, guarded); + } + + /** + * Resolves {@code var} using the encapsulated type mapping. If it maps to yet another + * non-reified type or has bounds, {@code forDependants} is used to do further resolution, which + * doesn't try to resolve any type variable on generic declarations that are already being + * resolved. + * + *

Should only be called and overridden by {@link #resolve(TypeVariable)}. + */ + Type resolveInternal(TypeVariable var, TypeTable forDependants) { + Type type = map.get(new TypeVariableKey(var)); + if (type == null) { + Type[] bounds = var.getBounds(); + if (bounds.length == 0) { + return var; + } + Type[] resolvedBounds = new TypeResolver(forDependants).resolveTypes(bounds); + /* + * We'd like to simply create our own TypeVariable with the newly resolved bounds. There's + * just one problem: Starting with JDK 7u51, the JDK TypeVariable's equals() method doesn't + * recognize instances of our TypeVariable implementation. This is a problem because users + * compare TypeVariables from the JDK against TypeVariables returned by TypeResolver. To + * work with all JDK versions, TypeResolver must return the appropriate TypeVariable + * implementation in each of the three possible cases: + * + * 1. Prior to JDK 7u51, the JDK TypeVariable implementation interoperates with ours. + * Therefore, we can always create our own TypeVariable. + * + * 2. Starting with JDK 7u51, the JDK TypeVariable implementations does not interoperate + * with ours. Therefore, we have to be careful about whether we create our own TypeVariable: + * + * 2a. If the resolved types are identical to the original types, then we can return the + * original, identical JDK TypeVariable. By doing so, we sidestep the problem entirely. + * + * 2b. If the resolved types are different from the original types, things are trickier. The + * only way to get a TypeVariable instance for the resolved types is to create our own. The + * created TypeVariable will not interoperate with any JDK TypeVariable. But this is OK: We + * don't _want_ our new TypeVariable to be equal to the JDK TypeVariable because it has + * _different bounds_ than the JDK TypeVariable. And it wouldn't make sense for our new + * TypeVariable to be equal to any _other_ JDK TypeVariable, either, because any other JDK + * TypeVariable must have a different declaration or name. The only TypeVariable that our + * new TypeVariable _will_ be equal to is an equivalent TypeVariable that was also created + * by us. And that equality is guaranteed to hold because it doesn't involve the JDK + * TypeVariable implementation at all. + */ + if (Types.NativeTypeVariableEquals.NATIVE_TYPE_VARIABLE_ONLY + && Arrays.equals(bounds, resolvedBounds)) { + return var; + } + return Types.newArtificialTypeVariable( + var.getGenericDeclaration(), var.getName(), resolvedBounds); + } + // in case the type is yet another type variable. + return new TypeResolver(forDependants).resolveType(type); + } + } + + private static final class TypeMappingIntrospector extends TypeVisitor { + + private final Map mappings = Maps.newHashMap(); + + /** + * Returns type mappings using type parameters and type arguments found in the generic + * superclass and the super interfaces of {@code contextClass}. + */ + static ImmutableMap getTypeMappings(Type contextType) { + checkNotNull(contextType); + TypeMappingIntrospector introspector = new TypeMappingIntrospector(); + introspector.visit(contextType); + return ImmutableMap.copyOf(introspector.mappings); + } + + @Override + void visitClass(Class clazz) { + visit(clazz.getGenericSuperclass()); + visit(clazz.getGenericInterfaces()); + } + + @Override + void visitParameterizedType(ParameterizedType parameterizedType) { + Class rawClass = (Class) parameterizedType.getRawType(); + TypeVariable[] vars = rawClass.getTypeParameters(); + Type[] typeArgs = parameterizedType.getActualTypeArguments(); + checkState(vars.length == typeArgs.length); + for (int i = 0; i < vars.length; i++) { + map(new TypeVariableKey(vars[i]), typeArgs[i]); + } + visit(rawClass); + visit(parameterizedType.getOwnerType()); + } + + @Override + void visitTypeVariable(TypeVariable t) { + visit(t.getBounds()); + } + + @Override + void visitWildcardType(WildcardType t) { + visit(t.getUpperBounds()); + } + + private void map(final TypeVariableKey var, final Type arg) { + if (mappings.containsKey(var)) { + // Mapping already established + // This is possible when following both superClass -> enclosingClass + // and enclosingclass -> superClass paths. + // Since we follow the path of superclass first, enclosing second, + // superclass mapping should take precedence. + return; + } + // First, check whether var -> arg forms a cycle + for (Type t = arg; t != null; t = mappings.get(TypeVariableKey.forLookup(t))) { + if (var.equalsType(t)) { + // cycle detected, remove the entire cycle from the mapping so that + // each type variable resolves deterministically to itself. + // Otherwise, a F -> T cycle will end up resolving both F and T + // nondeterministically to either F or T. + for (Type x = arg; x != null; x = mappings.remove(TypeVariableKey.forLookup(x))) {} + return; + } + } + mappings.put(var, arg); + } + } + + // This is needed when resolving types against a context with wildcards + // For example: + // class Holder { + // void set(T data) {...} + // } + // Holder> should *not* resolve the set() method to set(List data). + // Instead, it should create a capture of the wildcard so that set() rejects any List. + private static class WildcardCapturer { + + static final WildcardCapturer INSTANCE = new WildcardCapturer(); + + private final AtomicInteger id; + + private WildcardCapturer() { + this(new AtomicInteger()); + } + + private WildcardCapturer(AtomicInteger id) { + this.id = id; + } + + final Type capture(Type type) { + checkNotNull(type); + if (type instanceof Class) { + return type; + } + if (type instanceof TypeVariable) { + return type; + } + if (type instanceof GenericArrayType) { + GenericArrayType arrayType = (GenericArrayType) type; + return Types.newArrayType( + notForTypeVariable().capture(arrayType.getGenericComponentType())); + } + if (type instanceof ParameterizedType) { + ParameterizedType parameterizedType = (ParameterizedType) type; + Class rawType = (Class) parameterizedType.getRawType(); + TypeVariable[] typeVars = rawType.getTypeParameters(); + Type[] typeArgs = parameterizedType.getActualTypeArguments(); + for (int i = 0; i < typeArgs.length; i++) { + typeArgs[i] = forTypeVariable(typeVars[i]).capture(typeArgs[i]); + } + return Types.newParameterizedTypeWithOwner( + notForTypeVariable().captureNullable(parameterizedType.getOwnerType()), + rawType, + typeArgs); + } + if (type instanceof WildcardType) { + WildcardType wildcardType = (WildcardType) type; + Type[] lowerBounds = wildcardType.getLowerBounds(); + if (lowerBounds.length == 0) { // ? extends something changes to capture-of + return captureAsTypeVariable(wildcardType.getUpperBounds()); + } else { + // TODO(benyu): handle ? super T somehow. + return type; + } + } + throw new AssertionError("must have been one of the known types"); + } + + TypeVariable captureAsTypeVariable(Type[] upperBounds) { + String name = + "capture#" + id.incrementAndGet() + "-of ? extends " + Joiner.on('&').join(upperBounds); + return Types.newArtificialTypeVariable(WildcardCapturer.class, name, upperBounds); + } + + private WildcardCapturer forTypeVariable(final TypeVariable typeParam) { + return new WildcardCapturer(id) { + @Override + TypeVariable captureAsTypeVariable(Type[] upperBounds) { + Set combined = new LinkedHashSet<>(asList(upperBounds)); + // Since this is an artifically generated type variable, we don't bother checking + // subtyping between declared type bound and actual type bound. So it's possible that we + // may generate something like . + // Checking subtype between declared and actual type bounds + // adds recursive isSubtypeOf() call and feels complicated. + // There is no contract one way or another as long as isSubtypeOf() works as expected. + combined.addAll(asList(typeParam.getBounds())); + if (combined.size() > 1) { // Object is implicit and only useful if it's the only bound. + combined.remove(Object.class); + } + return super.captureAsTypeVariable(combined.toArray(new Type[0])); + } + }; + } + + private WildcardCapturer notForTypeVariable() { + return new WildcardCapturer(id); + } + + private Type captureNullable(Type type) { + if (type == null) { + return null; + } + return capture(type); + } + } + + /** + * Wraps around {@code TypeVariable} to ensure that any two type variables are equal as long as + * they are declared by the same {@link java.lang.reflect.GenericDeclaration} and have the same + * name, even if their bounds differ. + * + *

While resolving a type variable from a {@code var -> type} map, we don't care whether the + * type variable's bound has been partially resolved. As long as the type variable "identity" + * matches. + * + *

On the other hand, if for example we are resolving {@code List} to {@code + * List}, we need to compare that {@code } is unequal to {@code } in order to decide to use the transformed type instead of the original type. + */ + static final class TypeVariableKey { + private final TypeVariable var; + + TypeVariableKey(TypeVariable var) { + this.var = checkNotNull(var); + } + + @Override + public int hashCode() { + return Objects.hashCode(var.getGenericDeclaration(), var.getName()); + } + + @Override + public boolean equals(Object obj) { + if (obj instanceof TypeVariableKey) { + TypeVariableKey that = (TypeVariableKey) obj; + return equalsTypeVariable(that.var); + } else { + return false; + } + } + + @Override + public String toString() { + return var.toString(); + } + + /** Wraps {@code t} in a {@code TypeVariableKey} if it's a type variable. */ + static TypeVariableKey forLookup(Type t) { + if (t instanceof TypeVariable) { + return new TypeVariableKey((TypeVariable) t); + } else { + return null; + } + } + + /** + * Returns true if {@code type} is a {@code TypeVariable} with the same name and declared by the + * same {@code GenericDeclaration}. + */ + boolean equalsType(Type type) { + if (type instanceof TypeVariable) { + return equalsTypeVariable((TypeVariable) type); + } else { + return false; + } + } + + private boolean equalsTypeVariable(TypeVariable that) { + return var.getGenericDeclaration().equals(that.getGenericDeclaration()) + && var.getName().equals(that.getName()); + } + } +} diff --git a/src/main/java/com/google/common/reflect/TypeToInstanceMap.java b/src/main/java/com/google/common/reflect/TypeToInstanceMap.java new file mode 100644 index 0000000..e4f0576 --- /dev/null +++ b/src/main/java/com/google/common/reflect/TypeToInstanceMap.java @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2012 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.reflect; + +import com.google.common.annotations.Beta; + +import java.util.Map; + + +/** + * A map, each entry of which maps a {@link TypeToken} to an instance of that type. In addition to + * implementing {@code Map}, the additional type-safe operations {@link #putInstance} and {@link + * #getInstance} are available. + * + *

Generally, implementations don't support {@link #put} and {@link #putAll} because there is no + * way to check an object at runtime to be an instance of a {@link TypeToken}. Instead, caller + * should use the type safe {@link #putInstance}. + * + *

Also, if caller suppresses unchecked warnings and passes in an {@code Iterable} for + * type {@code Iterable}, the map won't be able to detect and throw type error. + * + *

Like any other {@code Map}, this map may contain entries for primitive types, + * and a primitive type and its corresponding wrapper type may map to different values. + * + * @param the common supertype that all entries must share; often this is simply {@link Object} + * @author Ben Yu + * @since 13.0 + */ +@Beta +public interface TypeToInstanceMap extends Map, B> { + + /** + * Returns the value the specified class is mapped to, or {@code null} if no entry for this class + * is present. This will only return a value that was bound to this specific class, not a value + * that may have been bound to a subtype. + * + *

{@code getInstance(Foo.class)} is equivalent to {@code + * getInstance(TypeToken.of(Foo.class))}. + */ + T getInstance(Class type); + + /** + * Returns the value the specified type is mapped to, or {@code null} if no entry for this type is + * present. This will only return a value that was bound to this specific type, not a value that + * may have been bound to a subtype. + */ + T getInstance(TypeToken type); + + /** + * Maps the specified class to the specified value. Does not associate this value with any + * of the class's supertypes. + * + *

{@code putInstance(Foo.class, foo)} is equivalent to {@code + * putInstance(TypeToken.of(Foo.class), foo)}. + * + * @return the value previously associated with this class (possibly {@code null}), or {@code + * null} if there was no previous entry. + */ + + T putInstance(Class type, T value); + + /** + * Maps the specified type to the specified value. Does not associate this value with any + * of the type's supertypes. + * + * @return the value previously associated with this type (possibly {@code null}), or {@code null} + * if there was no previous entry. + */ + + T putInstance(TypeToken type, T value); +} diff --git a/src/main/java/com/google/common/reflect/TypeToken.java b/src/main/java/com/google/common/reflect/TypeToken.java new file mode 100644 index 0000000..4c668f5 --- /dev/null +++ b/src/main/java/com/google/common/reflect/TypeToken.java @@ -0,0 +1,1442 @@ +/* + * Copyright (C) 2006 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.reflect; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.base.Preconditions.checkState; + +import com.google.common.annotations.Beta; +import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.Joiner; +import com.google.common.base.Predicate; +import com.google.common.collect.FluentIterable; +import com.google.common.collect.ForwardingSet; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Maps; +import com.google.common.collect.Ordering; +import com.google.common.primitives.Primitives; + +import java.io.Serializable; +import java.lang.reflect.Constructor; +import java.lang.reflect.GenericArrayType; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.lang.reflect.TypeVariable; +import java.lang.reflect.WildcardType; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Comparator; +import java.util.List; +import java.util.Map; +import java.util.Set; + + + +/** + * A {@link Type} with generics. + * + *

Operations that are otherwise only available in {@link Class} are implemented to support + * {@code Type}, for example {@link #isSubtypeOf}, {@link #isArray} and {@link #getComponentType}. + * It also provides additional utilities such as {@link #getTypes}, {@link #resolveType}, etc. + * + *

There are three ways to get a {@code TypeToken} instance: + * + *

+ * + *

{@code TypeToken} is serializable when no type variable is contained in the type. + * + *

Note to Guice users: {@code} TypeToken is similar to Guice's {@code TypeLiteral} class except + * that it is serializable and offers numerous additional utility methods. + * + * @author Bob Lee + * @author Sven Mawson + * @author Ben Yu + * @since 12.0 + */ +@Beta +@SuppressWarnings("serial") // SimpleTypeToken is the serialized form. +public abstract class TypeToken extends TypeCapture implements Serializable { + + private final Type runtimeType; + + /** Resolver for resolving parameter and field types with {@link #runtimeType} as context. */ + private transient TypeResolver invariantTypeResolver; + + /** Resolver for resolving covariant types with {@link #runtimeType} as context. */ + private transient TypeResolver covariantTypeResolver; + + /** + * Constructs a new type token of {@code T}. + * + *

Clients create an empty anonymous subclass. Doing so embeds the type parameter in the + * anonymous class's type hierarchy so we can reconstitute it at runtime despite erasure. + * + *

For example: + * + *

{@code
+   * TypeToken> t = new TypeToken>() {};
+   * }
+ */ + protected TypeToken() { + this.runtimeType = capture(); + checkState( + !(runtimeType instanceof TypeVariable), + "Cannot construct a TypeToken for a type variable.\n" + + "You probably meant to call new TypeToken<%s>(getClass()) " + + "that can resolve the type variable for you.\n" + + "If you do need to create a TypeToken of a type variable, " + + "please use TypeToken.of() instead.", + runtimeType); + } + + /** + * Constructs a new type token of {@code T} while resolving free type variables in the context of + * {@code declaringClass}. + * + *

Clients create an empty anonymous subclass. Doing so embeds the type parameter in the + * anonymous class's type hierarchy so we can reconstitute it at runtime despite erasure. + * + *

For example: + * + *

{@code
+   * abstract class IKnowMyType {
+   *   TypeToken getMyType() {
+   *     return new TypeToken(getClass()) {};
+   *   }
+   * }
+   *
+   * new IKnowMyType() {}.getMyType() => String
+   * }
+ */ + protected TypeToken(Class declaringClass) { + Type captured = super.capture(); + if (captured instanceof Class) { + this.runtimeType = captured; + } else { + this.runtimeType = TypeResolver.covariantly(declaringClass).resolveType(captured); + } + } + + private TypeToken(Type type) { + this.runtimeType = checkNotNull(type); + } + + /** Returns an instance of type token that wraps {@code type}. */ + public static TypeToken of(Class type) { + return new SimpleTypeToken(type); + } + + /** Returns an instance of type token that wraps {@code type}. */ + public static TypeToken of(Type type) { + return new SimpleTypeToken<>(type); + } + + /** + * Returns the raw type of {@code T}. Formally speaking, if {@code T} is returned by {@link + * java.lang.reflect.Method#getGenericReturnType}, the raw type is what's returned by {@link + * java.lang.reflect.Method#getReturnType} of the same method object. Specifically: + * + *
    + *
  • If {@code T} is a {@code Class} itself, {@code T} itself is returned. + *
  • If {@code T} is a {@link ParameterizedType}, the raw type of the parameterized type is + * returned. + *
  • If {@code T} is a {@link GenericArrayType}, the returned type is the corresponding array + * class. For example: {@code List[] => List[]}. + *
  • If {@code T} is a type variable or a wildcard type, the raw type of the first upper bound + * is returned. For example: {@code => Foo}. + *
+ */ + public final Class getRawType() { + // For wildcard or type variable, the first bound determines the runtime type. + Class rawType = getRawTypes().iterator().next(); + @SuppressWarnings("unchecked") // raw type is |T| + Class result = (Class) rawType; + return result; + } + + /** Returns the represented type. */ + public final Type getType() { + return runtimeType; + } + + /** + * Returns a new {@code TypeToken} where type variables represented by {@code typeParam} are + * substituted by {@code typeArg}. For example, it can be used to construct {@code Map} for + * any {@code K} and {@code V} type: + * + *
{@code
+   * static  TypeToken> mapOf(
+   *     TypeToken keyType, TypeToken valueType) {
+   *   return new TypeToken>() {}
+   *       .where(new TypeParameter() {}, keyType)
+   *       .where(new TypeParameter() {}, valueType);
+   * }
+   * }
+ * + * @param The parameter type + * @param typeParam the parameter type variable + * @param typeArg the actual type to substitute + */ + public final TypeToken where(TypeParameter typeParam, TypeToken typeArg) { + TypeResolver resolver = + new TypeResolver() + .where( + ImmutableMap.of( + new TypeResolver.TypeVariableKey(typeParam.typeVariable), typeArg.runtimeType)); + // If there's any type error, we'd report now rather than later. + return new SimpleTypeToken(resolver.resolveType(runtimeType)); + } + + /** + * Returns a new {@code TypeToken} where type variables represented by {@code typeParam} are + * substituted by {@code typeArg}. For example, it can be used to construct {@code Map} for + * any {@code K} and {@code V} type: + * + *
{@code
+   * static  TypeToken> mapOf(
+   *     Class keyType, Class valueType) {
+   *   return new TypeToken>() {}
+   *       .where(new TypeParameter() {}, keyType)
+   *       .where(new TypeParameter() {}, valueType);
+   * }
+   * }
+ * + * @param The parameter type + * @param typeParam the parameter type variable + * @param typeArg the actual type to substitute + */ + public final TypeToken where(TypeParameter typeParam, Class typeArg) { + return where(typeParam, of(typeArg)); + } + + /** + * Resolves the given {@code type} against the type context represented by this type. For example: + * + *
{@code
+   * new TypeToken>() {}.resolveType(
+   *     List.class.getMethod("get", int.class).getGenericReturnType())
+   * => String.class
+   * }
+ */ + public final TypeToken resolveType(Type type) { + checkNotNull(type); + // Being conservative here because the user could use resolveType() to resolve a type in an + // invariant context. + return of(getInvariantTypeResolver().resolveType(type)); + } + + private TypeToken resolveSupertype(Type type) { + TypeToken supertype = of(getCovariantTypeResolver().resolveType(type)); + // super types' type mapping is a subset of type mapping of this type. + supertype.covariantTypeResolver = covariantTypeResolver; + supertype.invariantTypeResolver = invariantTypeResolver; + return supertype; + } + + /** + * Returns the generic superclass of this type or {@code null} if the type represents {@link + * Object} or an interface. This method is similar but different from {@link + * Class#getGenericSuperclass}. For example, {@code new TypeToken() + * {}.getGenericSuperclass()} will return {@code new TypeToken>() {}}; while + * {@code StringArrayList.class.getGenericSuperclass()} will return {@code ArrayList}, where + * {@code E} is the type variable declared by class {@code ArrayList}. + * + *

If this type is a type variable or wildcard, its first upper bound is examined and returned + * if the bound is a class or extends from a class. This means that the returned type could be a + * type variable too. + */ + final TypeToken getGenericSuperclass() { + if (runtimeType instanceof TypeVariable) { + // First bound is always the super class, if one exists. + return boundAsSuperclass(((TypeVariable) runtimeType).getBounds()[0]); + } + if (runtimeType instanceof WildcardType) { + // wildcard has one and only one upper bound. + return boundAsSuperclass(((WildcardType) runtimeType).getUpperBounds()[0]); + } + Type superclass = getRawType().getGenericSuperclass(); + if (superclass == null) { + return null; + } + @SuppressWarnings("unchecked") // super class of T + TypeToken superToken = (TypeToken) resolveSupertype(superclass); + return superToken; + } + + private TypeToken boundAsSuperclass(Type bound) { + TypeToken token = of(bound); + if (token.getRawType().isInterface()) { + return null; + } + @SuppressWarnings("unchecked") // only upper bound of T is passed in. + TypeToken superclass = (TypeToken) token; + return superclass; + } + + /** + * Returns the generic interfaces that this type directly {@code implements}. This method is + * similar but different from {@link Class#getGenericInterfaces()}. For example, {@code new + * TypeToken>() {}.getGenericInterfaces()} will return a list that contains {@code + * new TypeToken>() {}}; while {@code List.class.getGenericInterfaces()} will + * return an array that contains {@code Iterable}, where the {@code T} is the type variable + * declared by interface {@code Iterable}. + * + *

If this type is a type variable or wildcard, its upper bounds are examined and those that + * are either an interface or upper-bounded only by interfaces are returned. This means that the + * returned types could include type variables too. + */ + final ImmutableList> getGenericInterfaces() { + if (runtimeType instanceof TypeVariable) { + return boundsAsInterfaces(((TypeVariable) runtimeType).getBounds()); + } + if (runtimeType instanceof WildcardType) { + return boundsAsInterfaces(((WildcardType) runtimeType).getUpperBounds()); + } + ImmutableList.Builder> builder = ImmutableList.builder(); + for (Type interfaceType : getRawType().getGenericInterfaces()) { + @SuppressWarnings("unchecked") // interface of T + TypeToken resolvedInterface = + (TypeToken) resolveSupertype(interfaceType); + builder.add(resolvedInterface); + } + return builder.build(); + } + + private ImmutableList> boundsAsInterfaces(Type[] bounds) { + ImmutableList.Builder> builder = ImmutableList.builder(); + for (Type bound : bounds) { + @SuppressWarnings("unchecked") // upper bound of T + TypeToken boundType = (TypeToken) of(bound); + if (boundType.getRawType().isInterface()) { + builder.add(boundType); + } + } + return builder.build(); + } + + /** + * Returns the set of interfaces and classes that this type is or is a subtype of. The returned + * types are parameterized with proper type arguments. + * + *

Subtypes are always listed before supertypes. But the reverse is not true. A type isn't + * necessarily a subtype of all the types following. Order between types without subtype + * relationship is arbitrary and not guaranteed. + * + *

Specifically, returns true if any of the following conditions is met: + * + *

    + *
  1. 'this' and {@code formalType} are equal. + *
  2. 'this' and {@code formalType} have equal canonical form. + *
  3. {@code formalType} is {@code } and 'this' is a subtype of {@code Foo}. + *
  4. {@code formalType} is {@code } and 'this' is a supertype of {@code Foo}. + *
+ * + * Note that condition 2 isn't technically accurate under the context of a recursively bounded + * type variables. For example, {@code Enum>} canonicalizes to {@code Enum} + * where {@code E} is the type variable declared on the {@code Enum} class declaration. It's + * technically not true that {@code Foo>>} is a subtype of {@code + * Foo>} according to JLS. See testRecursiveWildcardSubtypeBug() for a real example. + * + *

It appears that properly handling recursive type bounds in the presence of implicit type + * bounds is not easy. For now we punt, hoping that this defect should rarely cause issues in real + * code. + * + * @param formalType is {@code Foo} a supertype of {@code Foo}? + * @param declaration The type variable in the context of a parameterized type. Used to infer type + * bound when {@code formalType} is a wildcard with implicit upper bound. + */ + private boolean is(Type formalType, TypeVariable declaration) { + if (runtimeType.equals(formalType)) { + return true; + } + if (formalType instanceof WildcardType) { + WildcardType your = canonicalizeWildcardType(declaration, (WildcardType) formalType); + // if "formalType" is , "this" can be: + // Foo, SubFoo, , , or + // . + // if "formalType" is , "this" can be: + // Foo, SuperFoo, or . + return every(your.getUpperBounds()).isSupertypeOf(runtimeType) + && every(your.getLowerBounds()).isSubtypeOf(runtimeType); + } + return canonicalizeWildcardsInType(runtimeType).equals(canonicalizeWildcardsInType(formalType)); + } + + /** + * In reflection, {@code Foo.getUpperBounds()[0]} is always {@code Object.class}, even when Foo + * is defined as {@code Foo}. Thus directly calling {@code .is(String.class)} + * will return false. To mitigate, we canonicalize wildcards by enforcing the following + * invariants: + * + *

    + *
  1. {@code canonicalize(t)} always produces the equal result for equivalent types. For + * example both {@code Enum} and {@code Enum>} canonicalize to {@code + * Enum}. + *
  2. {@code canonicalize(t)} produces a "literal" supertype of t. For example: {@code Enum>} canonicalizes to {@code Enum}, which is a supertype (if we disregard + * the upper bound is implicitly an Enum too). + *
  3. If {@code canonicalize(A) == canonicalize(B)}, then {@code Foo.isSubtypeOf(Foo)} + * and vice versa. i.e. {@code A.is(B)} and {@code B.is(A)}. + *
  4. {@code canonicalize(canonicalize(A)) == canonicalize(A)}. + *
+ */ + private static Type canonicalizeTypeArg(TypeVariable declaration, Type typeArg) { + return typeArg instanceof WildcardType + ? canonicalizeWildcardType(declaration, ((WildcardType) typeArg)) + : canonicalizeWildcardsInType(typeArg); + } + + private static Type canonicalizeWildcardsInType(Type type) { + if (type instanceof ParameterizedType) { + return canonicalizeWildcardsInParameterizedType((ParameterizedType) type); + } + if (type instanceof GenericArrayType) { + return Types.newArrayType( + canonicalizeWildcardsInType(((GenericArrayType) type).getGenericComponentType())); + } + return type; + } + + // WARNING: the returned type may have empty upper bounds, which may violate common expectations + // by user code or even some of our own code. It's fine for the purpose of checking subtypes. + // Just don't ever let the user access it. + private static WildcardType canonicalizeWildcardType( + TypeVariable declaration, WildcardType type) { + Type[] declared = declaration.getBounds(); + List upperBounds = new ArrayList<>(); + for (Type bound : type.getUpperBounds()) { + if (!any(declared).isSubtypeOf(bound)) { + upperBounds.add(canonicalizeWildcardsInType(bound)); + } + } + return new Types.WildcardTypeImpl(type.getLowerBounds(), upperBounds.toArray(new Type[0])); + } + + private static ParameterizedType canonicalizeWildcardsInParameterizedType( + ParameterizedType type) { + Class rawType = (Class) type.getRawType(); + TypeVariable[] typeVars = rawType.getTypeParameters(); + Type[] typeArgs = type.getActualTypeArguments(); + for (int i = 0; i < typeArgs.length; i++) { + typeArgs[i] = canonicalizeTypeArg(typeVars[i], typeArgs[i]); + } + return Types.newParameterizedTypeWithOwner(type.getOwnerType(), rawType, typeArgs); + } + + private static Bounds every(Type[] bounds) { + // Every bound must match. On any false, result is false. + return new Bounds(bounds, false); + } + + private static Bounds any(Type[] bounds) { + // Any bound matches. On any true, result is true. + return new Bounds(bounds, true); + } + + private static class Bounds { + private final Type[] bounds; + private final boolean target; + + Bounds(Type[] bounds, boolean target) { + this.bounds = bounds; + this.target = target; + } + + boolean isSubtypeOf(Type supertype) { + for (Type bound : bounds) { + if (of(bound).isSubtypeOf(supertype) == target) { + return target; + } + } + return !target; + } + + boolean isSupertypeOf(Type subtype) { + TypeToken type = of(subtype); + for (Type bound : bounds) { + if (type.isSubtypeOf(bound) == target) { + return target; + } + } + return !target; + } + } + + private ImmutableSet> getRawTypes() { + final ImmutableSet.Builder> builder = ImmutableSet.builder(); + new TypeVisitor() { + @Override + void visitTypeVariable(TypeVariable t) { + visit(t.getBounds()); + } + + @Override + void visitWildcardType(WildcardType t) { + visit(t.getUpperBounds()); + } + + @Override + void visitParameterizedType(ParameterizedType t) { + builder.add((Class) t.getRawType()); + } + + @Override + void visitClass(Class t) { + builder.add(t); + } + + @Override + void visitGenericArrayType(GenericArrayType t) { + builder.add(Types.getArrayClass(of(t.getGenericComponentType()).getRawType())); + } + }.visit(runtimeType); + // Cast from ImmutableSet> to ImmutableSet> + @SuppressWarnings({"unchecked", "rawtypes"}) + ImmutableSet> result = (ImmutableSet) builder.build(); + return result; + } + + private boolean isOwnedBySubtypeOf(Type supertype) { + for (TypeToken type : getTypes()) { + Type ownerType = type.getOwnerTypeIfPresent(); + if (ownerType != null && of(ownerType).isSubtypeOf(supertype)) { + return true; + } + } + return false; + } + + /** + * Returns the owner type of a {@link ParameterizedType} or enclosing class of a {@link Class}, or + * null otherwise. + */ + private Type getOwnerTypeIfPresent() { + if (runtimeType instanceof ParameterizedType) { + return ((ParameterizedType) runtimeType).getOwnerType(); + } else if (runtimeType instanceof Class) { + return ((Class) runtimeType).getEnclosingClass(); + } else { + return null; + } + } + + /** + * Returns the type token representing the generic type declaration of {@code cls}. For example: + * {@code TypeToken.getGenericType(Iterable.class)} returns {@code Iterable}. + * + *

If {@code cls} isn't parameterized and isn't a generic array, the type token of the class is + * returned. + */ + @VisibleForTesting + static TypeToken toGenericType(Class cls) { + if (cls.isArray()) { + Type arrayOfGenericType = + Types.newArrayType( + // If we are passed with int[].class, don't turn it to GenericArrayType + toGenericType(cls.getComponentType()).runtimeType); + @SuppressWarnings("unchecked") // array is covariant + TypeToken result = (TypeToken) of(arrayOfGenericType); + return result; + } + TypeVariable>[] typeParams = cls.getTypeParameters(); + Type ownerType = + cls.isMemberClass() && !Modifier.isStatic(cls.getModifiers()) + ? toGenericType(cls.getEnclosingClass()).runtimeType + : null; + + if ((typeParams.length > 0) || ((ownerType != null) && ownerType != cls.getEnclosingClass())) { + @SuppressWarnings("unchecked") // Like, it's Iterable for Iterable.class + TypeToken type = + (TypeToken) + of(Types.newParameterizedTypeWithOwner(ownerType, cls, typeParams)); + return type; + } else { + return of(cls); + } + } + + private TypeResolver getCovariantTypeResolver() { + TypeResolver resolver = covariantTypeResolver; + if (resolver == null) { + resolver = (covariantTypeResolver = TypeResolver.covariantly(runtimeType)); + } + return resolver; + } + + private TypeResolver getInvariantTypeResolver() { + TypeResolver resolver = invariantTypeResolver; + if (resolver == null) { + resolver = (invariantTypeResolver = TypeResolver.invariantly(runtimeType)); + } + return resolver; + } + + private TypeToken getSupertypeFromUpperBounds( + Class supertype, Type[] upperBounds) { + for (Type upperBound : upperBounds) { + @SuppressWarnings("unchecked") // T's upperbound is . + TypeToken bound = (TypeToken) of(upperBound); + if (bound.isSubtypeOf(supertype)) { + @SuppressWarnings({"rawtypes", "unchecked"}) // guarded by the isSubtypeOf check. + TypeToken result = bound.getSupertype((Class) supertype); + return result; + } + } + throw new IllegalArgumentException(supertype + " isn't a super type of " + this); + } + + private TypeToken getSubtypeFromLowerBounds(Class subclass, Type[] lowerBounds) { + if (lowerBounds.length > 0) { + @SuppressWarnings("unchecked") // T's lower bound is + TypeToken bound = (TypeToken) of(lowerBounds[0]); + // Java supports only one lowerbound anyway. + return bound.getSubtype(subclass); + } + throw new IllegalArgumentException(subclass + " isn't a subclass of " + this); + } + + private TypeToken getArraySupertype(Class supertype) { + // with component type, we have lost generic type information + // Use raw type so that compiler allows us to call getSupertype() + @SuppressWarnings("rawtypes") + TypeToken componentType = + checkNotNull(getComponentType(), "%s isn't a super type of %s", supertype, this); + // array is covariant. component type is super type, so is the array type. + @SuppressWarnings("unchecked") // going from raw type back to generics + TypeToken componentSupertype = componentType.getSupertype(supertype.getComponentType()); + @SuppressWarnings("unchecked") // component type is super type, so is array type. + TypeToken result = + (TypeToken) + // If we are passed with int[].class, don't turn it to GenericArrayType + of(newArrayClassOrGenericArrayType(componentSupertype.runtimeType)); + return result; + } + + private TypeToken getArraySubtype(Class subclass) { + // array is covariant. component type is subtype, so is the array type. + TypeToken componentSubtype = getComponentType().getSubtype(subclass.getComponentType()); + @SuppressWarnings("unchecked") // component type is subtype, so is array type. + TypeToken result = + (TypeToken) + // If we are passed with int[].class, don't turn it to GenericArrayType + of(newArrayClassOrGenericArrayType(componentSubtype.runtimeType)); + return result; + } + + private Type resolveTypeArgsForSubclass(Class subclass) { + // If both runtimeType and subclass are not parameterized, return subclass + // If runtimeType is not parameterized but subclass is, process subclass as a parameterized type + // If runtimeType is a raw type (i.e. is a parameterized type specified as a Class), we + // return subclass as a raw type + if (runtimeType instanceof Class + && ((subclass.getTypeParameters().length == 0) + || (getRawType().getTypeParameters().length != 0))) { + // no resolution needed + return subclass; + } + // class Base {} + // class Sub extends Base {} + // Base.subtype(Sub.class): + + // Sub.getSupertype(Base.class) => Base + // => X=String, Y=Integer + // => Sub=Sub + TypeToken genericSubtype = toGenericType(subclass); + @SuppressWarnings({"rawtypes", "unchecked"}) // subclass isn't + Type supertypeWithArgsFromSubtype = + genericSubtype.getSupertype((Class) getRawType()).runtimeType; + return new TypeResolver() + .where(supertypeWithArgsFromSubtype, runtimeType) + .resolveType(genericSubtype.runtimeType); + } + + /** + * Creates an array class if {@code componentType} is a class, or else, a {@link + * GenericArrayType}. This is what Java7 does for generic array type parameters. + */ + private static Type newArrayClassOrGenericArrayType(Type componentType) { + return Types.JavaVersion.JAVA7.newArrayType(componentType); + } + + private static final class SimpleTypeToken extends TypeToken { + + SimpleTypeToken(Type type) { + super(type); + } + + private static final long serialVersionUID = 0; + } + + /** + * Collects parent types from a sub type. + * + * @param The type "kind". Either a TypeToken, or Class. + */ + private abstract static class TypeCollector { + + static final TypeCollector> FOR_GENERIC_TYPE = + new TypeCollector>() { + @Override + Class getRawType(TypeToken type) { + return type.getRawType(); + } + + @Override + Iterable> getInterfaces(TypeToken type) { + return type.getGenericInterfaces(); + } + + @Override + + TypeToken getSuperclass(TypeToken type) { + return type.getGenericSuperclass(); + } + }; + + static final TypeCollector> FOR_RAW_TYPE = + new TypeCollector>() { + @Override + Class getRawType(Class type) { + return type; + } + + @Override + Iterable> getInterfaces(Class type) { + return Arrays.asList(type.getInterfaces()); + } + + @Override + + Class getSuperclass(Class type) { + return type.getSuperclass(); + } + }; + + /** For just classes, we don't have to traverse interfaces. */ + final TypeCollector classesOnly() { + return new ForwardingTypeCollector(this) { + @Override + Iterable getInterfaces(K type) { + return ImmutableSet.of(); + } + + @Override + ImmutableList collectTypes(Iterable types) { + ImmutableList.Builder builder = ImmutableList.builder(); + for (K type : types) { + if (!getRawType(type).isInterface()) { + builder.add(type); + } + } + return super.collectTypes(builder.build()); + } + }; + } + + final ImmutableList collectTypes(K type) { + return collectTypes(ImmutableList.of(type)); + } + + ImmutableList collectTypes(Iterable types) { + // type -> order number. 1 for Object, 2 for anything directly below, so on so forth. + Map map = Maps.newHashMap(); + for (K type : types) { + collectTypes(type, map); + } + return sortKeysByValue(map, Ordering.natural().reverse()); + } + + /** Collects all types to map, and returns the total depth from T up to Object. */ + + private int collectTypes(K type, Map map) { + Integer existing = map.get(type); + if (existing != null) { + // short circuit: if set contains type it already contains its supertypes + return existing; + } + // Interfaces should be listed before Object. + int aboveMe = getRawType(type).isInterface() ? 1 : 0; + for (K interfaceType : getInterfaces(type)) { + aboveMe = Math.max(aboveMe, collectTypes(interfaceType, map)); + } + K superclass = getSuperclass(type); + if (superclass != null) { + aboveMe = Math.max(aboveMe, collectTypes(superclass, map)); + } + /* + * TODO(benyu): should we include Object for interface? Also, CharSequence[] and Object[] for + * String[]? + * + */ + map.put(type, aboveMe + 1); + return aboveMe + 1; + } + + private static ImmutableList sortKeysByValue( + final Map map, final Comparator valueComparator) { + Ordering keyOrdering = + new Ordering() { + @Override + public int compare(K left, K right) { + return valueComparator.compare(map.get(left), map.get(right)); + } + }; + return keyOrdering.immutableSortedCopy(map.keySet()); + } + + abstract Class getRawType(K type); + + abstract Iterable getInterfaces(K type); + + abstract K getSuperclass(K type); + + private static class ForwardingTypeCollector extends TypeCollector { + + private final TypeCollector delegate; + + ForwardingTypeCollector(TypeCollector delegate) { + this.delegate = delegate; + } + + @Override + Class getRawType(K type) { + return delegate.getRawType(type); + } + + @Override + Iterable getInterfaces(K type) { + return delegate.getInterfaces(type); + } + + @Override + K getSuperclass(K type) { + return delegate.getSuperclass(type); + } + } + } + + // This happens to be the hash of the class as of now. So setting it makes a backward compatible + // change. Going forward, if any incompatible change is added, we can change the UID back to 1. + private static final long serialVersionUID = 3637540370352322684L; +} diff --git a/src/main/java/com/google/common/reflect/TypeVisitor.java b/src/main/java/com/google/common/reflect/TypeVisitor.java new file mode 100644 index 0000000..3e8436d --- /dev/null +++ b/src/main/java/com/google/common/reflect/TypeVisitor.java @@ -0,0 +1,103 @@ +/* + * Copyright (C) 2013 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.reflect; + +import com.google.common.collect.Sets; +import java.lang.reflect.GenericArrayType; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.lang.reflect.TypeVariable; +import java.lang.reflect.WildcardType; +import java.util.Set; + +/** + * Based on what a {@link Type} is, dispatch it to the corresponding {@code visit*} method. By + * default, no recursion is done for type arguments or type bounds. But subclasses can opt to do + * recursion by calling {@link #visit} for any {@code Type} while visitation is in progress. For + * example, this can be used to reject wildcards or type variables contained in a type as in: + * + *

{@code
+ * new TypeVisitor() {
+ *   protected void visitParameterizedType(ParameterizedType t) {
+ *     visit(t.getOwnerType());
+ *     visit(t.getActualTypeArguments());
+ *   }
+ *   protected void visitGenericArrayType(GenericArrayType t) {
+ *     visit(t.getGenericComponentType());
+ *   }
+ *   protected void visitTypeVariable(TypeVariable t) {
+ *     throw new IllegalArgumentException("Cannot contain type variable.");
+ *   }
+ *   protected void visitWildcardType(WildcardType t) {
+ *     throw new IllegalArgumentException("Cannot contain wildcard type.");
+ *   }
+ * }.visit(type);
+ * }
+ * + *

One {@code Type} is visited at most once. The second time the same type is visited, it's + * ignored by {@link #visit}. This avoids infinite recursion caused by recursive type bounds. + * + *

This class is not thread safe. + * + * @author Ben Yu + */ +abstract class TypeVisitor { + + private final Set visited = Sets.newHashSet(); + + /** + * Visits the given types. Null types are ignored. This allows subclasses to call {@code + * visit(parameterizedType.getOwnerType())} safely without having to check nulls. + */ + public final void visit(Type... types) { + for (Type type : types) { + if (type == null || !visited.add(type)) { + // null owner type, or already visited; + continue; + } + boolean succeeded = false; + try { + if (type instanceof TypeVariable) { + visitTypeVariable((TypeVariable) type); + } else if (type instanceof WildcardType) { + visitWildcardType((WildcardType) type); + } else if (type instanceof ParameterizedType) { + visitParameterizedType((ParameterizedType) type); + } else if (type instanceof Class) { + visitClass((Class) type); + } else if (type instanceof GenericArrayType) { + visitGenericArrayType((GenericArrayType) type); + } else { + throw new AssertionError("Unknown type: " + type); + } + succeeded = true; + } finally { + if (!succeeded) { // When the visitation failed, we don't want to ignore the second. + visited.remove(type); + } + } + } + } + + void visitClass(Class t) {} + + void visitGenericArrayType(GenericArrayType t) {} + + void visitParameterizedType(ParameterizedType t) {} + + void visitTypeVariable(TypeVariable t) {} + + void visitWildcardType(WildcardType t) {} +} diff --git a/src/main/java/com/google/common/reflect/Types.java b/src/main/java/com/google/common/reflect/Types.java new file mode 100644 index 0000000..3e1b21f --- /dev/null +++ b/src/main/java/com/google/common/reflect/Types.java @@ -0,0 +1,681 @@ +/* + * Copyright (C) 2011 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.reflect; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.collect.Iterables.transform; + +import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.Function; +import com.google.common.base.Joiner; +import com.google.common.base.Objects; +import com.google.common.base.Predicates; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Iterables; +import java.io.Serializable; +import java.lang.reflect.AnnotatedElement; +import java.lang.reflect.Array; +import java.lang.reflect.GenericArrayType; +import java.lang.reflect.GenericDeclaration; +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Proxy; +import java.lang.reflect.Type; +import java.lang.reflect.TypeVariable; +import java.lang.reflect.WildcardType; +import java.security.AccessControlException; +import java.util.Arrays; +import java.util.Collection; +import java.util.Map.Entry; +import java.util.concurrent.atomic.AtomicReference; + + +/** + * Utilities for working with {@link Type}. + * + * @author Ben Yu + */ +final class Types { + + /** Class#toString without the "class " and "interface " prefixes */ + private static final Function TYPE_NAME = + new Function() { + @Override + public String apply(Type from) { + return JavaVersion.CURRENT.typeName(from); + } + }; + + private static final Joiner COMMA_JOINER = Joiner.on(", ").useForNull("null"); + + /** Returns the array type of {@code componentType}. */ + static Type newArrayType(Type componentType) { + if (componentType instanceof WildcardType) { + WildcardType wildcard = (WildcardType) componentType; + Type[] lowerBounds = wildcard.getLowerBounds(); + checkArgument(lowerBounds.length <= 1, "Wildcard cannot have more than one lower bounds."); + if (lowerBounds.length == 1) { + return supertypeOf(newArrayType(lowerBounds[0])); + } else { + Type[] upperBounds = wildcard.getUpperBounds(); + checkArgument(upperBounds.length == 1, "Wildcard should have only one upper bound."); + return subtypeOf(newArrayType(upperBounds[0])); + } + } + return JavaVersion.CURRENT.newArrayType(componentType); + } + + /** + * Returns a type where {@code rawType} is parameterized by {@code arguments} and is owned by + * {@code ownerType}. + */ + static ParameterizedType newParameterizedTypeWithOwner( + Type ownerType, Class rawType, Type... arguments) { + if (ownerType == null) { + return newParameterizedType(rawType, arguments); + } + // ParameterizedTypeImpl constructor already checks, but we want to throw NPE before IAE + checkNotNull(arguments); + checkArgument(rawType.getEnclosingClass() != null, "Owner type for unenclosed %s", rawType); + return new ParameterizedTypeImpl(ownerType, rawType, arguments); + } + + /** Returns a type where {@code rawType} is parameterized by {@code arguments}. */ + static ParameterizedType newParameterizedType(Class rawType, Type... arguments) { + return new ParameterizedTypeImpl( + ClassOwnership.JVM_BEHAVIOR.getOwnerType(rawType), rawType, arguments); + } + + /** Decides what owner type to use for constructing {@link ParameterizedType} from a raw class. */ + private enum ClassOwnership { + OWNED_BY_ENCLOSING_CLASS { + @Override + + Class getOwnerType(Class rawType) { + return rawType.getEnclosingClass(); + } + }, + LOCAL_CLASS_HAS_NO_OWNER { + @Override + + Class getOwnerType(Class rawType) { + if (rawType.isLocalClass()) { + return null; + } else { + return rawType.getEnclosingClass(); + } + } + }; + + abstract Class getOwnerType(Class rawType); + + static final ClassOwnership JVM_BEHAVIOR = detectJvmBehavior(); + + private static ClassOwnership detectJvmBehavior() { + class LocalClass {} + Class subclass = new LocalClass() {}.getClass(); + ParameterizedType parameterizedType = (ParameterizedType) subclass.getGenericSuperclass(); + for (ClassOwnership behavior : ClassOwnership.values()) { + if (behavior.getOwnerType(LocalClass.class) == parameterizedType.getOwnerType()) { + return behavior; + } + } + throw new AssertionError(); + } + } + + /** + * Returns a new {@link TypeVariable} that belongs to {@code declaration} with {@code name} and + * {@code bounds}. + */ + static TypeVariable newArtificialTypeVariable( + D declaration, String name, Type... bounds) { + return newTypeVariableImpl( + declaration, name, (bounds.length == 0) ? new Type[] {Object.class} : bounds); + } + + /** Returns a new {@link WildcardType} with {@code upperBound}. */ + @VisibleForTesting + static WildcardType subtypeOf(Type upperBound) { + return new WildcardTypeImpl(new Type[0], new Type[] {upperBound}); + } + + /** Returns a new {@link WildcardType} with {@code lowerBound}. */ + @VisibleForTesting + static WildcardType supertypeOf(Type lowerBound) { + return new WildcardTypeImpl(new Type[] {lowerBound}, new Type[] {Object.class}); + } + + /** + * Returns human readable string representation of {@code type}. + * + *

The format is subject to change. + */ + static String toString(Type type) { + return (type instanceof Class) ? ((Class) type).getName() : type.toString(); + } + + static Type getComponentType(Type type) { + checkNotNull(type); + final AtomicReference result = new AtomicReference<>(); + new TypeVisitor() { + @Override + void visitTypeVariable(TypeVariable t) { + result.set(subtypeOfComponentType(t.getBounds())); + } + + @Override + void visitWildcardType(WildcardType t) { + result.set(subtypeOfComponentType(t.getUpperBounds())); + } + + @Override + void visitGenericArrayType(GenericArrayType t) { + result.set(t.getGenericComponentType()); + } + + @Override + void visitClass(Class t) { + result.set(t.getComponentType()); + } + }.visit(type); + return result.get(); + } + + /** + * Returns {@code ? extends X} if any of {@code bounds} is a subtype of {@code X[]}; or null + * otherwise. + */ + private static Type subtypeOfComponentType(Type[] bounds) { + for (Type bound : bounds) { + Type componentType = getComponentType(bound); + if (componentType != null) { + // Only the first bound can be a class or array. + // Bounds after the first can only be interfaces. + if (componentType instanceof Class) { + Class componentClass = (Class) componentType; + if (componentClass.isPrimitive()) { + return componentClass; + } + } + return subtypeOf(componentType); + } + } + return null; + } + + private static final class GenericArrayTypeImpl implements GenericArrayType, Serializable { + + private final Type componentType; + + GenericArrayTypeImpl(Type componentType) { + this.componentType = JavaVersion.CURRENT.usedInGenericType(componentType); + } + + @Override + public Type getGenericComponentType() { + return componentType; + } + + @Override + public String toString() { + return Types.toString(componentType) + "[]"; + } + + @Override + public int hashCode() { + return componentType.hashCode(); + } + + @Override + public boolean equals(Object obj) { + if (obj instanceof GenericArrayType) { + GenericArrayType that = (GenericArrayType) obj; + return Objects.equal(getGenericComponentType(), that.getGenericComponentType()); + } + return false; + } + + private static final long serialVersionUID = 0; + } + + private static final class ParameterizedTypeImpl implements ParameterizedType, Serializable { + + private final Type ownerType; + private final ImmutableList argumentsList; + private final Class rawType; + + ParameterizedTypeImpl(Type ownerType, Class rawType, Type[] typeArguments) { + checkNotNull(rawType); + checkArgument(typeArguments.length == rawType.getTypeParameters().length); + disallowPrimitiveType(typeArguments, "type parameter"); + this.ownerType = ownerType; + this.rawType = rawType; + this.argumentsList = JavaVersion.CURRENT.usedInGenericType(typeArguments); + } + + @Override + public Type[] getActualTypeArguments() { + return toArray(argumentsList); + } + + @Override + public Type getRawType() { + return rawType; + } + + @Override + public Type getOwnerType() { + return ownerType; + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + if (ownerType != null && JavaVersion.CURRENT.jdkTypeDuplicatesOwnerName()) { + builder.append(JavaVersion.CURRENT.typeName(ownerType)).append('.'); + } + return builder + .append(rawType.getName()) + .append('<') + .append(COMMA_JOINER.join(transform(argumentsList, TYPE_NAME))) + .append('>') + .toString(); + } + + @Override + public int hashCode() { + return (ownerType == null ? 0 : ownerType.hashCode()) + ^ argumentsList.hashCode() + ^ rawType.hashCode(); + } + + @Override + public boolean equals(Object other) { + if (!(other instanceof ParameterizedType)) { + return false; + } + ParameterizedType that = (ParameterizedType) other; + return getRawType().equals(that.getRawType()) + && Objects.equal(getOwnerType(), that.getOwnerType()) + && Arrays.equals(getActualTypeArguments(), that.getActualTypeArguments()); + } + + private static final long serialVersionUID = 0; + } + + private static TypeVariable newTypeVariableImpl( + D genericDeclaration, String name, Type[] bounds) { + TypeVariableImpl typeVariableImpl = + new TypeVariableImpl(genericDeclaration, name, bounds); + @SuppressWarnings("unchecked") + TypeVariable typeVariable = + Reflection.newProxy( + TypeVariable.class, new TypeVariableInvocationHandler(typeVariableImpl)); + return typeVariable; + } + + /** + * Invocation handler to work around a compatibility problem between Java 7 and Java 8. + * + *

Java 8 introduced a new method {@code getAnnotatedBounds()} in the {@link TypeVariable} + * interface, whose return type {@code AnnotatedType[]} is also new in Java 8. That means that we + * cannot implement that interface in source code in a way that will compile on both Java 7 and + * Java 8. If we include the {@code getAnnotatedBounds()} method then its return type means it + * won't compile on Java 7, while if we don't include the method then the compiler will complain + * that an abstract method is unimplemented. So instead we use a dynamic proxy to get an + * implementation. If the method being called on the {@code TypeVariable} instance has the same + * name as one of the public methods of {@link TypeVariableImpl}, the proxy calls the same method + * on its instance of {@code TypeVariableImpl}. Otherwise it throws {@link + * UnsupportedOperationException}; this should only apply to {@code getAnnotatedBounds()}. This + * does mean that users on Java 8 who obtain an instance of {@code TypeVariable} from {@link + * TypeResolver#resolveType} will not be able to call {@code getAnnotatedBounds()} on it, but that + * should hopefully be rare. + * + *

By default this method does nothing. + */ + protected void startUp() throws Exception {} + + /** + * Run the service. This method is invoked on the execution thread. Implementations must respond + * to stop requests. You could poll for lifecycle changes in a work loop: + * + *

+   *   public void run() {
+   *     while ({@link #isRunning()}) {
+   *       // perform a unit of work
+   *     }
+   *   }
+   * 
+ * + *

...or you could respond to stop requests by implementing {@link #triggerShutdown()}, which + * should cause {@link #run()} to return. + */ + protected abstract void run() throws Exception; + + /** + * Stop the service. This method is invoked on the execution thread. + * + *

By default this method does nothing. + */ + // TODO: consider supporting a TearDownTestCase-like API + protected void shutDown() throws Exception {} + + /** + * Invoked to request the service to stop. + * + *

By default this method does nothing. + */ + protected void triggerShutdown() {} + + /** + * Returns the {@link Executor} that will be used to run this service. Subclasses may override + * this method to use a custom {@link Executor}, which may configure its worker thread with a + * specific name, thread group or priority. The returned executor's {@link + * Executor#execute(Runnable) execute()} method is called when this service is started, and should + * return promptly. + * + *

The default implementation returns a new {@link Executor} that sets the name of its threads + * to the string returned by {@link #serviceName} + */ + protected Executor executor() { + return new Executor() { + @Override + public void execute(Runnable command) { + MoreExecutors.newThread(serviceName(), command).start(); + } + }; + } + + @Override + public String toString() { + return serviceName() + " [" + state() + "]"; + } + + @Override + public final boolean isRunning() { + return delegate.isRunning(); + } + + @Override + public final State state() { + return delegate.state(); + } + + /** @since 13.0 */ + @Override + public final void addListener(Listener listener, Executor executor) { + delegate.addListener(listener, executor); + } + + /** @since 14.0 */ + @Override + public final Throwable failureCause() { + return delegate.failureCause(); + } + + /** @since 15.0 */ + + @Override + public final Service startAsync() { + delegate.startAsync(); + return this; + } + + /** @since 15.0 */ + + @Override + public final Service stopAsync() { + delegate.stopAsync(); + return this; + } + + /** @since 15.0 */ + @Override + public final void awaitRunning() { + delegate.awaitRunning(); + } + + /** @since 15.0 */ + @Override + public final void awaitRunning(long timeout, TimeUnit unit) throws TimeoutException { + delegate.awaitRunning(timeout, unit); + } + + /** @since 15.0 */ + @Override + public final void awaitTerminated() { + delegate.awaitTerminated(); + } + + /** @since 15.0 */ + @Override + public final void awaitTerminated(long timeout, TimeUnit unit) throws TimeoutException { + delegate.awaitTerminated(timeout, unit); + } + + /** + * Returns the name of this service. {@link AbstractExecutionThreadService} may include the name + * in debugging output. + * + *

Subclasses may override this method. + * + * @since 14.0 (present in 10.0 as getServiceName) + */ + protected String serviceName() { + return getClass().getSimpleName(); + } +} diff --git a/src/main/java/com/google/common/util/concurrent/AbstractFuture.java b/src/main/java/com/google/common/util/concurrent/AbstractFuture.java new file mode 100644 index 0000000..0c1537c --- /dev/null +++ b/src/main/java/com/google/common/util/concurrent/AbstractFuture.java @@ -0,0 +1,1242 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.util.concurrent; + +import com.google.common.annotations.Beta; +import com.google.common.annotations.GwtCompatible; +import com.google.common.util.concurrent.internal.InternalFutureFailureAccess; +import com.google.common.util.concurrent.internal.InternalFutures; + +import java.util.Locale; +import java.util.concurrent.CancellationException; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Executor; +import java.util.concurrent.Future; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; +import java.util.concurrent.locks.LockSupport; +import java.util.logging.Level; +import java.util.logging.Logger; + +import static com.google.common.base.Preconditions.checkNotNull; +import static java.util.concurrent.atomic.AtomicReferenceFieldUpdater.newUpdater; + +/** + * An abstract implementation of {@link ListenableFuture}, intended for advanced users only. More + * common ways to create a {@code ListenableFuture} include instantiating a {@link SettableFuture}, + * submitting a task to a {@link ListeningExecutorService}, and deriving a {@code Future} from an + * existing one, typically using methods like {@link Futures#transform(ListenableFuture, + * com.google.common.base.Function, Executor) Futures.transform} and {@link + * Futures#catching(ListenableFuture, Class, com.google.common.base.Function, + * Executor) Futures.catching}. + * + *

This class implements all methods in {@code ListenableFuture}. Subclasses should provide a way + * to set the result of the computation through the protected methods {@link #set(Object)}, {@link + * #setFuture(ListenableFuture)} and {@link #setException(Throwable)}. Subclasses may also override + * {@link #afterDone()}, which will be invoked automatically when the future completes. Subclasses + * should rarely override other methods. + * + * @author Sven Mawson + * @author Luke Sandberg + * @since 1.0 + */ +@SuppressWarnings("ShortCircuitBoolean") // we use non-short circuiting comparisons intentionally +@GwtCompatible(emulated = true) +public abstract class AbstractFuture extends InternalFutureFailureAccess + implements ListenableFuture { + // NOTE: Whenever both tests are cheap and functional, it's faster to use &, | instead of &&, || + + private static final boolean GENERATE_CANCELLATION_CAUSES = + Boolean.parseBoolean( + System.getProperty("guava.concurrent.generate_cancellation_cause", "false")); + + /** + * Tag interface marking trusted subclasses. This enables some optimizations. The implementation + * of this interface must also be an AbstractFuture and must not override or expose for overriding + * any of the public methods of ListenableFuture. + */ + interface Trusted extends ListenableFuture {} + + /** + * A less abstract subclass of AbstractFuture. This can be used to optimize setFuture by ensuring + * that {@link #get} calls exactly the implementation of {@link AbstractFuture#get}. + */ + abstract static class TrustedFuture extends AbstractFuture implements Trusted { + @Override + public final V get() throws InterruptedException, ExecutionException { + return super.get(); + } + + @Override + public final V get(long timeout, TimeUnit unit) + throws InterruptedException, ExecutionException, TimeoutException { + return super.get(timeout, unit); + } + + @Override + public final boolean isDone() { + return super.isDone(); + } + + @Override + public final boolean isCancelled() { + return super.isCancelled(); + } + + @Override + public final void addListener(Runnable listener, Executor executor) { + super.addListener(listener, executor); + } + + @Override + public final boolean cancel(boolean mayInterruptIfRunning) { + return super.cancel(mayInterruptIfRunning); + } + } + + // Logger to log exceptions caught when running listeners. + private static final Logger log = Logger.getLogger(AbstractFuture.class.getName()); + + // A heuristic for timed gets. If the remaining timeout is less than this, spin instead of + // blocking. This value is what AbstractQueuedSynchronizer uses. + private static final long SPIN_THRESHOLD_NANOS = 1000L; + + private static final AtomicHelper ATOMIC_HELPER; + + static { + AtomicHelper helper; + Throwable thrownAtomicReferenceFieldUpdaterFailure = null; + + try { + helper = + new SafeAtomicHelper( + newUpdater(Waiter.class, Thread.class, "thread"), + newUpdater(Waiter.class, Waiter.class, "next"), + newUpdater(AbstractFuture.class, Waiter.class, "waiters"), + newUpdater(AbstractFuture.class, Listener.class, "listeners"), + newUpdater(AbstractFuture.class, Object.class, "value")); + } catch (Throwable atomicReferenceFieldUpdaterFailure) { + // Some Android 5.0.x Samsung devices have bugs in JDK reflection APIs that cause + // getDeclaredField to throw a NoSuchFieldException when the field is definitely there. + // For these users fallback to a suboptimal implementation, based on synchronized. This will + // be a definite performance hit to those users. + thrownAtomicReferenceFieldUpdaterFailure = atomicReferenceFieldUpdaterFailure; + helper = new SynchronizedHelper(); + } + + ATOMIC_HELPER = helper; + + // Prevent rare disastrous classloading in first call to LockSupport.park. + // See: https://bugs.openjdk.java.net/browse/JDK-8074773 + @SuppressWarnings("unused") + Class ensureLoaded = LockSupport.class; + + // Log after all static init is finished; if an installed logger uses any Futures methods, it + // shouldn't break in cases where reflection is missing/broken. + if (thrownAtomicReferenceFieldUpdaterFailure != null) { + log.log( + Level.SEVERE, "SafeAtomicHelper is broken!", thrownAtomicReferenceFieldUpdaterFailure); + } + } + + /** Waiter links form a Treiber stack, in the {@link #waiters} field. */ + private static final class Waiter { + static final Waiter TOMBSTONE = new Waiter(false /* ignored param */); + + volatile Thread thread; + volatile Waiter next; + + /** + * Constructor for the TOMBSTONE, avoids use of ATOMIC_HELPER in case this class is loaded + * before the ATOMIC_HELPER. Apparently this is possible on some android platforms. + */ + Waiter(boolean unused) {} + + Waiter() { + // avoid volatile write, write is made visible by subsequent CAS on waiters field + ATOMIC_HELPER.putThread(this, Thread.currentThread()); + } + + // non-volatile write to the next field. Should be made visible by subsequent CAS on waiters + // field. + void setNext(Waiter next) { + ATOMIC_HELPER.putNext(this, next); + } + + void unpark() { + // This is racy with removeWaiter. The consequence of the race is that we may spuriously call + // unpark even though the thread has already removed itself from the list. But even if we did + // use a CAS, that race would still exist (it would just be ever so slightly smaller). + Thread w = thread; + if (w != null) { + thread = null; + LockSupport.unpark(w); + } + } + } + + /** + * Marks the given node as 'deleted' (null waiter) and then scans the list to unlink all deleted + * nodes. This is an O(n) operation in the common case (and O(n^2) in the worst), but we are saved + * by two things. + * + *

    + *
  • This is only called when a waiting thread times out or is interrupted. Both of which + * should be rare. + *
  • The waiters list should be very short. + *
+ */ + private void removeWaiter(Waiter node) { + node.thread = null; // mark as 'deleted' + restart: + while (true) { + Waiter pred = null; + Waiter curr = waiters; + if (curr == Waiter.TOMBSTONE) { + return; // give up if someone is calling complete + } + Waiter succ; + while (curr != null) { + succ = curr.next; + if (curr.thread != null) { // we aren't unlinking this node, update pred. + pred = curr; + } else if (pred != null) { // We are unlinking this node and it has a predecessor. + pred.next = succ; + if (pred.thread == null) { // We raced with another node that unlinked pred. Restart. + continue restart; + } + } else if (!ATOMIC_HELPER.casWaiters(this, curr, succ)) { // We are unlinking head + continue restart; // We raced with an add or complete + } + curr = succ; + } + break; + } + } + + /** Listeners also form a stack through the {@link #listeners} field. */ + private static final class Listener { + static final Listener TOMBSTONE = new Listener(null, null); + final Runnable task; + final Executor executor; + + // writes to next are made visible by subsequent CAS's on the listeners field + Listener next; + + Listener(Runnable task, Executor executor) { + this.task = task; + this.executor = executor; + } + } + + /** A special value to represent {@code null}. */ + private static final Object NULL = new Object(); + + /** A special value to represent failure, when {@link #setException} is called successfully. */ + private static final class Failure { + static final Failure FALLBACK_INSTANCE = + new Failure( + new Throwable("Failure occurred while trying to finish a future.") { + @Override + public synchronized Throwable fillInStackTrace() { + return this; // no stack trace + } + }); + final Throwable exception; + + Failure(Throwable exception) { + this.exception = checkNotNull(exception); + } + } + + /** A special value to represent cancellation and the 'wasInterrupted' bit. */ + private static final class Cancellation { + // constants to use when GENERATE_CANCELLATION_CAUSES = false + static final Cancellation CAUSELESS_INTERRUPTED; + static final Cancellation CAUSELESS_CANCELLED; + + static { + if (GENERATE_CANCELLATION_CAUSES) { + CAUSELESS_CANCELLED = null; + CAUSELESS_INTERRUPTED = null; + } else { + CAUSELESS_CANCELLED = new Cancellation(false, null); + CAUSELESS_INTERRUPTED = new Cancellation(true, null); + } + } + + final boolean wasInterrupted; + final Throwable cause; + + Cancellation(boolean wasInterrupted, Throwable cause) { + this.wasInterrupted = wasInterrupted; + this.cause = cause; + } + } + + /** A special value that encodes the 'setFuture' state. */ + private static final class SetFuture implements Runnable { + final AbstractFuture owner; + final ListenableFuture future; + + SetFuture(AbstractFuture owner, ListenableFuture future) { + this.owner = owner; + this.future = future; + } + + @Override + public void run() { + if (owner.value != this) { + // nothing to do, we must have been cancelled, don't bother inspecting the future. + return; + } + Object valueToSet = getFutureValue(future); + if (ATOMIC_HELPER.casValue(owner, this, valueToSet)) { + complete(owner); + } + } + } + + // TODO(lukes): investigate using the @Contended annotation on these fields when jdk8 is + // available. + /** + * This field encodes the current state of the future. + * + *

The valid values are: + * + *

    + *
  • {@code null} initial state, nothing has happened. + *
  • {@link Cancellation} terminal state, {@code cancel} was called. + *
  • {@link Failure} terminal state, {@code setException} was called. + *
  • {@link SetFuture} intermediate state, {@code setFuture} was called. + *
  • {@link #NULL} terminal state, {@code set(null)} was called. + *
  • Any other non-null value, terminal state, {@code set} was called with a non-null + * argument. + *
+ */ + private volatile Object value; + + /** All listeners. */ + private volatile Listener listeners; + + /** All waiting threads. */ + private volatile Waiter waiters; + + /** Constructor for use by subclasses. */ + protected AbstractFuture() {} + + // Gets and Timed Gets + // + // * Be responsive to interruption + // * Don't create Waiter nodes if you aren't going to park, this helps reduce contention on the + // waiters field. + // * Future completion is defined by when #value becomes non-null/non SetFuture + // * Future completion can be observed if the waiters field contains a TOMBSTONE + + // Timed Get + // There are a few design constraints to consider + // * We want to be responsive to small timeouts, unpark() has non trivial latency overheads (I + // have observed 12 micros on 64 bit linux systems to wake up a parked thread). So if the + // timeout is small we shouldn't park(). This needs to be traded off with the cpu overhead of + // spinning, so we use SPIN_THRESHOLD_NANOS which is what AbstractQueuedSynchronizer uses for + // similar purposes. + // * We want to behave reasonably for timeouts of 0 + // * We are more responsive to completion than timeouts. This is because parkNanos depends on + // system scheduling and as such we could either miss our deadline, or unpark() could be delayed + // so that it looks like we timed out even though we didn't. For comparison FutureTask respects + // completion preferably and AQS is non-deterministic (depends on where in the queue the waiter + // is). If we wanted to be strict about it, we could store the unpark() time in the Waiter node + // and we could use that to make a decision about whether or not we timed out prior to being + // unparked. + + /** + * {@inheritDoc} + * + *

The default {@link AbstractFuture} implementation throws {@code InterruptedException} if the + * current thread is interrupted during the call, even if the value is already available. + * + * @throws CancellationException {@inheritDoc} + */ + @Override + public V get(long timeout, TimeUnit unit) + throws InterruptedException, TimeoutException, ExecutionException { + // NOTE: if timeout < 0, remainingNanos will be < 0 and we will fall into the while(true) loop + // at the bottom and throw a timeoutexception. + final long timeoutNanos = unit.toNanos(timeout); // we rely on the implicit null check on unit. + long remainingNanos = timeoutNanos; + if (Thread.interrupted()) { + throw new InterruptedException(); + } + Object localValue = value; + if (localValue != null & !(localValue instanceof SetFuture)) { + return getDoneValue(localValue); + } + // we delay calling nanoTime until we know we will need to either park or spin + final long endNanos = remainingNanos > 0 ? System.nanoTime() + remainingNanos : 0; + long_wait_loop: + if (remainingNanos >= SPIN_THRESHOLD_NANOS) { + Waiter oldHead = waiters; + if (oldHead != Waiter.TOMBSTONE) { + Waiter node = new Waiter(); + do { + node.setNext(oldHead); + if (ATOMIC_HELPER.casWaiters(this, oldHead, node)) { + while (true) { + LockSupport.parkNanos(this, remainingNanos); + // Check interruption first, if we woke up due to interruption we need to honor that. + if (Thread.interrupted()) { + removeWaiter(node); + throw new InterruptedException(); + } + + // Otherwise re-read and check doneness. If we loop then it must have been a spurious + // wakeup + localValue = value; + if (localValue != null & !(localValue instanceof SetFuture)) { + return getDoneValue(localValue); + } + + // timed out? + remainingNanos = endNanos - System.nanoTime(); + if (remainingNanos < SPIN_THRESHOLD_NANOS) { + // Remove the waiter, one way or another we are done parking this thread. + removeWaiter(node); + break long_wait_loop; // jump down to the busy wait loop + } + } + } + oldHead = waiters; // re-read and loop. + } while (oldHead != Waiter.TOMBSTONE); + } + // re-read value, if we get here then we must have observed a TOMBSTONE while trying to add a + // waiter. + return getDoneValue(value); + } + // If we get here then we have remainingNanos < SPIN_THRESHOLD_NANOS and there is no node on the + // waiters list + while (remainingNanos > 0) { + localValue = value; + if (localValue != null & !(localValue instanceof SetFuture)) { + return getDoneValue(localValue); + } + if (Thread.interrupted()) { + throw new InterruptedException(); + } + remainingNanos = endNanos - System.nanoTime(); + } + + String futureToString = toString(); + final String unitString = unit.toString().toLowerCase(Locale.ROOT); + String message = "Waited " + timeout + " " + unit.toString().toLowerCase(Locale.ROOT); + // Only report scheduling delay if larger than our spin threshold - otherwise it's just noise + if (remainingNanos + SPIN_THRESHOLD_NANOS < 0) { + // We over-waited for our timeout. + message += " (plus "; + long overWaitNanos = -remainingNanos; + long overWaitUnits = unit.convert(overWaitNanos, TimeUnit.NANOSECONDS); + long overWaitLeftoverNanos = overWaitNanos - unit.toNanos(overWaitUnits); + boolean shouldShowExtraNanos = + overWaitUnits == 0 || overWaitLeftoverNanos > SPIN_THRESHOLD_NANOS; + if (overWaitUnits > 0) { + message += overWaitUnits + " " + unitString; + if (shouldShowExtraNanos) { + message += ","; + } + message += " "; + } + if (shouldShowExtraNanos) { + message += overWaitLeftoverNanos + " nanoseconds "; + } + + message += "delay)"; + } + // It's confusing to see a completed future in a timeout message; if isDone() returns false, + // then we know it must have given a pending toString value earlier. If not, then the future + // completed after the timeout expired, and the message might be success. + if (isDone()) { + throw new TimeoutException(message + " but future completed as timeout expired"); + } + throw new TimeoutException(message + " for " + futureToString); + } + + /** + * {@inheritDoc} + * + *

The default {@link AbstractFuture} implementation throws {@code InterruptedException} if the + * current thread is interrupted during the call, even if the value is already available. + * + * @throws CancellationException {@inheritDoc} + */ + @Override + public V get() throws InterruptedException, ExecutionException { + if (Thread.interrupted()) { + throw new InterruptedException(); + } + Object localValue = value; + if (localValue != null & !(localValue instanceof SetFuture)) { + return getDoneValue(localValue); + } + Waiter oldHead = waiters; + if (oldHead != Waiter.TOMBSTONE) { + Waiter node = new Waiter(); + do { + node.setNext(oldHead); + if (ATOMIC_HELPER.casWaiters(this, oldHead, node)) { + // we are on the stack, now wait for completion. + while (true) { + LockSupport.park(this); + // Check interruption first, if we woke up due to interruption we need to honor that. + if (Thread.interrupted()) { + removeWaiter(node); + throw new InterruptedException(); + } + // Otherwise re-read and check doneness. If we loop then it must have been a spurious + // wakeup + localValue = value; + if (localValue != null & !(localValue instanceof SetFuture)) { + return getDoneValue(localValue); + } + } + } + oldHead = waiters; // re-read and loop. + } while (oldHead != Waiter.TOMBSTONE); + } + // re-read value, if we get here then we must have observed a TOMBSTONE while trying to add a + // waiter. + return getDoneValue(value); + } + + /** Unboxes {@code obj}. Assumes that obj is not {@code null} or a {@link SetFuture}. */ + private V getDoneValue(Object obj) throws ExecutionException { + // While this seems like it might be too branch-y, simple benchmarking proves it to be + // unmeasurable (comparing done AbstractFutures with immediateFuture) + if (obj instanceof Cancellation) { + throw cancellationExceptionWithCause("Task was cancelled.", ((Cancellation) obj).cause); + } else if (obj instanceof Failure) { + throw new ExecutionException(((Failure) obj).exception); + } else if (obj == NULL) { + return null; + } else { + @SuppressWarnings("unchecked") // this is the only other option + V asV = (V) obj; + return asV; + } + } + + @Override + public boolean isDone() { + final Object localValue = value; + return localValue != null & !(localValue instanceof SetFuture); + } + + @Override + public boolean isCancelled() { + final Object localValue = value; + return localValue instanceof Cancellation; + } + + /** + * {@inheritDoc} + * + *

If a cancellation attempt succeeds on a {@code Future} that had previously been {@linkplain + * #setFuture set asynchronously}, then the cancellation will also be propagated to the delegate + * {@code Future} that was supplied in the {@code setFuture} call. + * + *

Rather than override this method to perform additional cancellation work or cleanup, + * subclasses should override {@link #afterDone}, consulting {@link #isCancelled} and {@link + * #wasInterrupted} as necessary. This ensures that the work is done even if the future is + * cancelled without a call to {@code cancel}, such as by calling {@code + * setFuture(cancelledFuture)}. + */ + @Override + public boolean cancel(boolean mayInterruptIfRunning) { + Object localValue = value; + boolean rValue = false; + if (localValue == null | localValue instanceof SetFuture) { + // Try to delay allocating the exception. At this point we may still lose the CAS, but it is + // certainly less likely. + Object valueToSet = + GENERATE_CANCELLATION_CAUSES + ? new Cancellation( + mayInterruptIfRunning, new CancellationException("Future.cancel() was called.")) + : (mayInterruptIfRunning + ? Cancellation.CAUSELESS_INTERRUPTED + : Cancellation.CAUSELESS_CANCELLED); + AbstractFuture abstractFuture = this; + while (true) { + if (ATOMIC_HELPER.casValue(abstractFuture, localValue, valueToSet)) { + rValue = true; + // We call interuptTask before calling complete(), which is consistent with + // FutureTask + if (mayInterruptIfRunning) { + abstractFuture.interruptTask(); + } + complete(abstractFuture); + if (localValue instanceof SetFuture) { + // propagate cancellation to the future set in setfuture, this is racy, and we don't + // care if we are successful or not. + ListenableFuture futureToPropagateTo = ((SetFuture) localValue).future; + if (futureToPropagateTo instanceof Trusted) { + // If the future is a TrustedFuture then we specifically avoid calling cancel() + // this has 2 benefits + // 1. for long chains of futures strung together with setFuture we consume less stack + // 2. we avoid allocating Cancellation objects at every level of the cancellation + // chain + // We can only do this for TrustedFuture, because TrustedFuture.cancel is final and + // does nothing but delegate to this method. + AbstractFuture trusted = (AbstractFuture) futureToPropagateTo; + localValue = trusted.value; + if (localValue == null | localValue instanceof SetFuture) { + abstractFuture = trusted; + continue; // loop back up and try to complete the new future + } + } else { + // not a TrustedFuture, call cancel directly. + futureToPropagateTo.cancel(mayInterruptIfRunning); + } + } + break; + } + // obj changed, reread + localValue = abstractFuture.value; + if (!(localValue instanceof SetFuture)) { + // obj cannot be null at this point, because value can only change from null to non-null. + // So if value changed (and it did since we lost the CAS), then it cannot be null and + // since it isn't a SetFuture, then the future must be done and we should exit the loop + break; + } + } + } + return rValue; + } + + /** + * Subclasses can override this method to implement interruption of the future's computation. The + * method is invoked automatically by a successful call to {@link #cancel(boolean) cancel(true)}. + * + *

The default implementation does nothing. + * + *

This method is likely to be deprecated. Prefer to override {@link #afterDone}, checking + * {@link #wasInterrupted} to decide whether to interrupt your task. + * + * @since 10.0 + */ + protected void interruptTask() {} + + /** + * Returns true if this future was cancelled with {@code mayInterruptIfRunning} set to {@code + * true}. + * + * @since 14.0 + */ + protected final boolean wasInterrupted() { + final Object localValue = value; + return (localValue instanceof Cancellation) && ((Cancellation) localValue).wasInterrupted; + } + + /** + * {@inheritDoc} + * + * @since 10.0 + */ + @Override + public void addListener(Runnable listener, Executor executor) { + checkNotNull(listener, "Runnable was null."); + checkNotNull(executor, "Executor was null."); + // Checking isDone and listeners != TOMBSTONE may seem redundant, but our contract for + // addListener says that listeners execute 'immediate' if the future isDone(). However, our + // protocol for completing a future is to assign the value field (which sets isDone to true) and + // then to release waiters, followed by executing afterDone(), followed by releasing listeners. + // That means that it is possible to observe that the future isDone and that your listeners + // don't execute 'immediately'. By checking isDone here we avoid that. + // A corollary to all that is that we don't need to check isDone inside the loop because if we + // get into the loop we know that we weren't done when we entered and therefore we aren't under + // an obligation to execute 'immediately'. + if (!isDone()) { + Listener oldHead = listeners; + if (oldHead != Listener.TOMBSTONE) { + Listener newNode = new Listener(listener, executor); + do { + newNode.next = oldHead; + if (ATOMIC_HELPER.casListeners(this, oldHead, newNode)) { + return; + } + oldHead = listeners; // re-read + } while (oldHead != Listener.TOMBSTONE); + } + } + // If we get here then the Listener TOMBSTONE was set, which means the future is done, call + // the listener. + executeListener(listener, executor); + } + + /** + * Sets the result of this {@code Future} unless this {@code Future} has already been cancelled or + * set (including {@linkplain #setFuture set asynchronously}). When a call to this method returns, + * the {@code Future} is guaranteed to be {@linkplain #isDone done} only if the call was + * accepted (in which case it returns {@code true}). If it returns {@code false}, the {@code + * Future} may have previously been set asynchronously, in which case its result may not be known + * yet. That result, though not yet known, cannot be overridden by a call to a {@code set*} + * method, only by a call to {@link #cancel}. + * + * @param value the value to be used as the result + * @return true if the attempt was accepted, completing the {@code Future} + */ + protected boolean set(V value) { + Object valueToSet = value == null ? NULL : value; + if (ATOMIC_HELPER.casValue(this, null, valueToSet)) { + complete(this); + return true; + } + return false; + } + + /** + * Sets the failed result of this {@code Future} unless this {@code Future} has already been + * cancelled or set (including {@linkplain #setFuture set asynchronously}). When a call to this + * method returns, the {@code Future} is guaranteed to be {@linkplain #isDone done} only if + * the call was accepted (in which case it returns {@code true}). If it returns {@code false}, the + * {@code Future} may have previously been set asynchronously, in which case its result may not be + * known yet. That result, though not yet known, cannot be overridden by a call to a {@code set*} + * method, only by a call to {@link #cancel}. + * + * @param throwable the exception to be used as the failed result + * @return true if the attempt was accepted, completing the {@code Future} + */ + protected boolean setException(Throwable throwable) { + Object valueToSet = new Failure(checkNotNull(throwable)); + if (ATOMIC_HELPER.casValue(this, null, valueToSet)) { + complete(this); + return true; + } + return false; + } + + /** + * Sets the result of this {@code Future} to match the supplied input {@code Future} once the + * supplied {@code Future} is done, unless this {@code Future} has already been cancelled or set + * (including "set asynchronously," defined below). + * + *

If the supplied future is {@linkplain #isDone done} when this method is called and the call + * is accepted, then this future is guaranteed to have been completed with the supplied future by + * the time this method returns. If the supplied future is not done and the call is accepted, then + * the future will be set asynchronously. Note that such a result, though not yet known, + * cannot be overridden by a call to a {@code set*} method, only by a call to {@link #cancel}. + * + *

If the call {@code setFuture(delegate)} is accepted and this {@code Future} is later + * cancelled, cancellation will be propagated to {@code delegate}. Additionally, any call to + * {@code setFuture} after any cancellation will propagate cancellation to the supplied {@code + * Future}. + * + *

Note that, even if the supplied future is cancelled and it causes this future to complete, + * it will never trigger interruption behavior. In particular, it will not cause this future to + * invoke the {@link #interruptTask} method, and the {@link #wasInterrupted} method will not + * return {@code true}. + * + * @param future the future to delegate to + * @return true if the attempt was accepted, indicating that the {@code Future} was not previously + * cancelled or set. + * @since 19.0 + */ + @Beta + protected boolean setFuture(ListenableFuture future) { + checkNotNull(future); + Object localValue = value; + if (localValue == null) { + if (future.isDone()) { + Object value = getFutureValue(future); + if (ATOMIC_HELPER.casValue(this, null, value)) { + complete(this); + return true; + } + return false; + } + SetFuture valueToSet = new SetFuture(this, future); + if (ATOMIC_HELPER.casValue(this, null, valueToSet)) { + // the listener is responsible for calling completeWithFuture, directExecutor is appropriate + // since all we are doing is unpacking a completed future which should be fast. + try { + future.addListener(valueToSet, DirectExecutor.INSTANCE); + } catch (Throwable t) { + // addListener has thrown an exception! SetFuture.run can't throw any exceptions so this + // must have been caused by addListener itself. The most likely explanation is a + // misconfigured mock. Try to switch to Failure. + Failure failure; + try { + failure = new Failure(t); + } catch (Throwable oomMostLikely) { + failure = Failure.FALLBACK_INSTANCE; + } + // Note: The only way this CAS could fail is if cancel() has raced with us. That is ok. + boolean unused = ATOMIC_HELPER.casValue(this, valueToSet, failure); + } + return true; + } + localValue = value; // we lost the cas, fall through and maybe cancel + } + // The future has already been set to something. If it is cancellation we should cancel the + // incoming future. + if (localValue instanceof Cancellation) { + // we don't care if it fails, this is best-effort. + future.cancel(((Cancellation) localValue).wasInterrupted); + } + return false; + } + + /** + * Returns a value that satisfies the contract of the {@link #value} field based on the state of + * given future. + * + *

This is approximately the inverse of {@link #getDoneValue(Object)} + */ + private static Object getFutureValue(ListenableFuture future) { + if (future instanceof Trusted) { + // Break encapsulation for TrustedFuture instances since we know that subclasses cannot + // override .get() (since it is final) and therefore this is equivalent to calling .get() + // and unpacking the exceptions like we do below (just much faster because it is a single + // field read instead of a read, several branches and possibly creating exceptions). + Object v = ((AbstractFuture) future).value; + if (v instanceof Cancellation) { + // If the other future was interrupted, clear the interrupted bit while preserving the cause + // this will make it consistent with how non-trustedfutures work which cannot propagate the + // wasInterrupted bit + Cancellation c = (Cancellation) v; + if (c.wasInterrupted) { + v = + c.cause != null + ? new Cancellation(/* wasInterrupted= */ false, c.cause) + : Cancellation.CAUSELESS_CANCELLED; + } + } + return v; + } + if (future instanceof InternalFutureFailureAccess) { + Throwable throwable = + InternalFutures.tryInternalFastPathGetFailure((InternalFutureFailureAccess) future); + if (throwable != null) { + return new Failure(throwable); + } + } + boolean wasCancelled = future.isCancelled(); + // Don't allocate a CancellationException if it's not necessary + if (!GENERATE_CANCELLATION_CAUSES & wasCancelled) { + return Cancellation.CAUSELESS_CANCELLED; + } + // Otherwise calculate the value by calling .get() + try { + Object v = getUninterruptibly(future); + if (wasCancelled) { + return new Cancellation( + false, + new IllegalArgumentException( + "get() did not throw CancellationException, despite reporting " + + "isCancelled() == true: " + + future)); + } + return v == null ? NULL : v; + } catch (ExecutionException exception) { + if (wasCancelled) { + return new Cancellation( + false, + new IllegalArgumentException( + "get() did not throw CancellationException, despite reporting " + + "isCancelled() == true: " + + future, + exception)); + } + return new Failure(exception.getCause()); + } catch (CancellationException cancellation) { + if (!wasCancelled) { + return new Failure( + new IllegalArgumentException( + "get() threw CancellationException, despite reporting isCancelled() == false: " + + future, + cancellation)); + } + return new Cancellation(false, cancellation); + } catch (Throwable t) { + return new Failure(t); + } + } + + /** + * An inlined private copy of {@link Uninterruptibles#getUninterruptibly} used to break an + * internal dependency on other /util/concurrent classes. + */ + private static V getUninterruptibly(Future future) throws ExecutionException { + boolean interrupted = false; + try { + while (true) { + try { + return future.get(); + } catch (InterruptedException e) { + interrupted = true; + } + } + } finally { + if (interrupted) { + Thread.currentThread().interrupt(); + } + } + } + + /** Unblocks all threads and runs all listeners. */ + private static void complete(AbstractFuture future) { + Listener next = null; + outer: + while (true) { + future.releaseWaiters(); + // We call this before the listeners in order to avoid needing to manage a separate stack data + // structure for them. Also, some implementations rely on this running prior to listeners + // so that the cleanup work is visible to listeners. + // afterDone() should be generally fast and only used for cleanup work... but in theory can + // also be recursive and create StackOverflowErrors + future.afterDone(); + // push the current set of listeners onto next + next = future.clearListeners(next); + future = null; + while (next != null) { + Listener curr = next; + next = next.next; + Runnable task = curr.task; + if (task instanceof SetFuture) { + SetFuture setFuture = (SetFuture) task; + // We unwind setFuture specifically to avoid StackOverflowErrors in the case of long + // chains of SetFutures + // Handling this special case is important because there is no way to pass an executor to + // setFuture, so a user couldn't break the chain by doing this themselves. It is also + // potentially common if someone writes a recursive Futures.transformAsync transformer. + future = setFuture.owner; + if (future.value == setFuture) { + Object valueToSet = getFutureValue(setFuture.future); + if (ATOMIC_HELPER.casValue(future, setFuture, valueToSet)) { + continue outer; + } + } + // other wise the future we were trying to set is already done. + } else { + executeListener(task, curr.executor); + } + } + break; + } + } + + /** + * Callback method that is called exactly once after the future is completed. + * + *

If {@link #interruptTask} is also run during completion, {@link #afterDone} runs after it. + * + *

The default implementation of this method in {@code AbstractFuture} does nothing. This is + * intended for very lightweight cleanup work, for example, timing statistics or clearing fields. + * If your task does anything heavier consider, just using a listener with an executor. + * + * @since 20.0 + */ + @Beta + protected void afterDone() {} + + // TODO(b/114236866): Inherit doc from InternalFutureFailureAccess. Also, -link to its URL. + /** + * Usually returns {@code null} but, if this {@code Future} has failed, may optionally + * return the cause of the failure. "Failure" means specifically "completed with an exception"; it + * does not include "was cancelled." To be explicit: If this method returns a non-null value, + * then: + * + *

    + *
  • {@code isDone()} must return {@code true} + *
  • {@code isCancelled()} must return {@code false} + *
  • {@code get()} must not block, and it must throw an {@code ExecutionException} with the + * return value of this method as its cause + *
+ * + *

This method is {@code protected} so that classes like {@code + * com.google.common.util.concurrent.SettableFuture} do not expose it to their users as an + * instance method. In the unlikely event that you need to call this method, call {@link + * InternalFutures#tryInternalFastPathGetFailure(InternalFutureFailureAccess)}. + * + * @since 27.0 + */ + @Override + protected final Throwable tryInternalFastPathGetFailure() { + if (this instanceof Trusted) { + Object obj = value; + if (obj instanceof Failure) { + return ((Failure) obj).exception; + } + } + return null; + } + + /** + * If this future has been cancelled (and possibly interrupted), cancels (and possibly interrupts) + * the given future (if available). + */ + final void maybePropagateCancellationTo(Future related) { + if (related != null & isCancelled()) { + related.cancel(wasInterrupted()); + } + } + + /** Releases all threads in the {@link #waiters} list, and clears the list. */ + private void releaseWaiters() { + Waiter head; + do { + head = waiters; + } while (!ATOMIC_HELPER.casWaiters(this, head, Waiter.TOMBSTONE)); + for (Waiter currentWaiter = head; currentWaiter != null; currentWaiter = currentWaiter.next) { + currentWaiter.unpark(); + } + } + + /** + * Clears the {@link #listeners} list and prepends its contents to {@code onto}, least recently + * added first. + */ + private Listener clearListeners(Listener onto) { + // We need to + // 1. atomically swap the listeners with TOMBSTONE, this is because addListener uses that to + // to synchronize with us + // 2. reverse the linked list, because despite our rather clear contract, people depend on us + // executing listeners in the order they were added + // 3. push all the items onto 'onto' and return the new head of the stack + Listener head; + do { + head = listeners; + } while (!ATOMIC_HELPER.casListeners(this, head, Listener.TOMBSTONE)); + Listener reversedList = onto; + while (head != null) { + Listener tmp = head; + head = head.next; + tmp.next = reversedList; + reversedList = tmp; + } + return reversedList; + } + + // TODO(user): move parts into a default method on ListenableFuture? + @Override + public String toString() { + StringBuilder builder = new StringBuilder().append(super.toString()).append("[status="); + if (isCancelled()) { + builder.append("CANCELLED"); + } else if (isDone()) { + addDoneString(builder); + } else { + String pendingDescription; + try { + pendingDescription = pendingToString(); + } catch (RuntimeException e) { + // Don't call getMessage or toString() on the exception, in case the exception thrown by the + // subclass is implemented with bugs similar to the subclass. + pendingDescription = "Exception thrown from implementation: " + e.getClass(); + } + // The future may complete during or before the call to getPendingToString, so we use null + // as a signal that we should try checking if the future is done again. + if (pendingDescription != null && !pendingDescription.isEmpty()) { + builder.append("PENDING, info=[").append(pendingDescription).append("]"); + } else if (isDone()) { + addDoneString(builder); + } else { + builder.append("PENDING"); + } + } + return builder.append("]").toString(); + } + + /** + * Provide a human-readable explanation of why this future has not yet completed. + * + * @return null if an explanation cannot be provided because the future is done. + * @since 23.0 + */ + protected String pendingToString() { + Object localValue = value; + if (localValue instanceof SetFuture) { + return "setFuture=[" + userObjectToString(((SetFuture) localValue).future) + "]"; + } else if (this instanceof ScheduledFuture) { + return "remaining delay=[" + + ((ScheduledFuture) this).getDelay(TimeUnit.MILLISECONDS) + + " ms]"; + } + return null; + } + + private void addDoneString(StringBuilder builder) { + try { + V value = getUninterruptibly(this); + builder.append("SUCCESS, result=[").append(userObjectToString(value)).append("]"); + } catch (ExecutionException e) { + builder.append("FAILURE, cause=[").append(e.getCause()).append("]"); + } catch (CancellationException e) { + builder.append("CANCELLED"); // shouldn't be reachable + } catch (RuntimeException e) { + builder.append("UNKNOWN, cause=[").append(e.getClass()).append(" thrown from get()]"); + } + } + + /** Helper for printing user supplied objects into our toString method. */ + private String userObjectToString(Object o) { + // This is some basic recursion detection for when people create cycles via set/setFuture + // This is however only partial protection though since it only detects self loops. We could + // detect arbitrary cycles using a thread local or possibly by catching StackOverflowExceptions + // but this should be a good enough solution (it is also what jdk collections do in these cases) + if (o == this) { + return "this future"; + } + return String.valueOf(o); + } + + /** + * Submits the given runnable to the given {@link Executor} catching and logging all {@linkplain + * RuntimeException runtime exceptions} thrown by the executor. + */ + private static void executeListener(Runnable runnable, Executor executor) { + try { + executor.execute(runnable); + } catch (RuntimeException e) { + // Log it and keep going -- bad runnable and/or executor. Don't punish the other runnables if + // we're given a bad one. We only catch RuntimeException because we want Errors to propagate + // up. + log.log( + Level.SEVERE, + "RuntimeException while executing runnable " + runnable + " with executor " + executor, + e); + } + } + + private abstract static class AtomicHelper { + /** Non volatile write of the thread to the {@link Waiter#thread} field. */ + abstract void putThread(Waiter waiter, Thread newValue); + + /** Non volatile write of the waiter to the {@link Waiter#next} field. */ + abstract void putNext(Waiter waiter, Waiter newValue); + + /** Performs a CAS operation on the {@link #waiters} field. */ + abstract boolean casWaiters(AbstractFuture future, Waiter expect, Waiter update); + + /** Performs a CAS operation on the {@link #listeners} field. */ + abstract boolean casListeners(AbstractFuture future, Listener expect, Listener update); + + /** Performs a CAS operation on the {@link #value} field. */ + abstract boolean casValue(AbstractFuture future, Object expect, Object update); + } + + /** {@link AtomicHelper} based on {@link AtomicReferenceFieldUpdater}. */ + private static final class SafeAtomicHelper extends AtomicHelper { + final AtomicReferenceFieldUpdater waiterThreadUpdater; + final AtomicReferenceFieldUpdater waiterNextUpdater; + final AtomicReferenceFieldUpdater waitersUpdater; + final AtomicReferenceFieldUpdater listenersUpdater; + final AtomicReferenceFieldUpdater valueUpdater; + + SafeAtomicHelper( + AtomicReferenceFieldUpdater waiterThreadUpdater, + AtomicReferenceFieldUpdater waiterNextUpdater, + AtomicReferenceFieldUpdater waitersUpdater, + AtomicReferenceFieldUpdater listenersUpdater, + AtomicReferenceFieldUpdater valueUpdater) { + this.waiterThreadUpdater = waiterThreadUpdater; + this.waiterNextUpdater = waiterNextUpdater; + this.waitersUpdater = waitersUpdater; + this.listenersUpdater = listenersUpdater; + this.valueUpdater = valueUpdater; + } + + @Override + void putThread(Waiter waiter, Thread newValue) { + waiterThreadUpdater.lazySet(waiter, newValue); + } + + @Override + void putNext(Waiter waiter, Waiter newValue) { + waiterNextUpdater.lazySet(waiter, newValue); + } + + @Override + boolean casWaiters(AbstractFuture future, Waiter expect, Waiter update) { + return waitersUpdater.compareAndSet(future, expect, update); + } + + @Override + boolean casListeners(AbstractFuture future, Listener expect, Listener update) { + return listenersUpdater.compareAndSet(future, expect, update); + } + + @Override + boolean casValue(AbstractFuture future, Object expect, Object update) { + return valueUpdater.compareAndSet(future, expect, update); + } + } + + /** + * {@link AtomicHelper} based on {@code synchronized} and volatile writes. + * + *

This is an implementation of last resort for when certain basic VM features are broken (like + * AtomicReferenceFieldUpdater). + */ + private static final class SynchronizedHelper extends AtomicHelper { + @Override + void putThread(Waiter waiter, Thread newValue) { + waiter.thread = newValue; + } + + @Override + void putNext(Waiter waiter, Waiter newValue) { + waiter.next = newValue; + } + + @Override + boolean casWaiters(AbstractFuture future, Waiter expect, Waiter update) { + synchronized (future) { + if (future.waiters == expect) { + future.waiters = update; + return true; + } + return false; + } + } + + @Override + boolean casListeners(AbstractFuture future, Listener expect, Listener update) { + synchronized (future) { + if (future.listeners == expect) { + future.listeners = update; + return true; + } + return false; + } + } + + @Override + boolean casValue(AbstractFuture future, Object expect, Object update) { + synchronized (future) { + if (future.value == expect) { + future.value = update; + return true; + } + return false; + } + } + } + + private static CancellationException cancellationExceptionWithCause(String message, Throwable cause) { + CancellationException exception = new CancellationException(message); + exception.initCause(cause); + return exception; + } +} diff --git a/src/main/java/com/google/common/util/concurrent/AbstractIdleService.java b/src/main/java/com/google/common/util/concurrent/AbstractIdleService.java new file mode 100644 index 0000000..788d4fc --- /dev/null +++ b/src/main/java/com/google/common/util/concurrent/AbstractIdleService.java @@ -0,0 +1,195 @@ +/* + * Copyright (C) 2009 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.util.concurrent; + +import com.google.common.annotations.Beta; +import com.google.common.annotations.GwtIncompatible; +import com.google.common.base.Supplier; + + +import java.util.concurrent.Executor; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +/** + * Base class for services that do not need a thread while "running" but may need one during startup + * and shutdown. Subclasses can implement {@link #startUp} and {@link #shutDown} methods, each which + * run in a executor which by default uses a separate thread for each method. + * + * @author Chris Nokleberg + * @since 1.0 + */ +@Beta +@GwtIncompatible +public abstract class AbstractIdleService implements Service { + + /* Thread names will look like {@code "MyService STARTING"}. */ + private final Supplier threadNameSupplier = new ThreadNameSupplier(); + + + private final class ThreadNameSupplier implements Supplier { + @Override + public String get() { + return serviceName() + " " + state(); + } + } + + /* use AbstractService for state management */ + private final Service delegate = new DelegateService(); + + + private final class DelegateService extends AbstractService { + @Override + protected final void doStart() { + MoreExecutors.renamingDecorator(executor(), threadNameSupplier) + .execute( + new Runnable() { + @Override + public void run() { + try { + startUp(); + notifyStarted(); + } catch (Throwable t) { + notifyFailed(t); + } + } + }); + } + + @Override + protected final void doStop() { + MoreExecutors.renamingDecorator(executor(), threadNameSupplier) + .execute( + new Runnable() { + @Override + public void run() { + try { + shutDown(); + notifyStopped(); + } catch (Throwable t) { + notifyFailed(t); + } + } + }); + } + + @Override + public String toString() { + return AbstractIdleService.this.toString(); + } + } + + /** Constructor for use by subclasses. */ + protected AbstractIdleService() {} + + /** Start the service. */ + protected abstract void startUp() throws Exception; + + /** Stop the service. */ + protected abstract void shutDown() throws Exception; + + /** + * Returns the {@link Executor} that will be used to run this service. Subclasses may override + * this method to use a custom {@link Executor}, which may configure its worker thread with a + * specific name, thread group or priority. The returned executor's {@link + * Executor#execute(Runnable) execute()} method is called when this service is started and + * stopped, and should return promptly. + */ + protected Executor executor() { + return new Executor() { + @Override + public void execute(Runnable command) { + MoreExecutors.newThread(threadNameSupplier.get(), command).start(); + } + }; + } + + @Override + public String toString() { + return serviceName() + " [" + state() + "]"; + } + + @Override + public final boolean isRunning() { + return delegate.isRunning(); + } + + @Override + public final State state() { + return delegate.state(); + } + + /** @since 13.0 */ + @Override + public final void addListener(Listener listener, Executor executor) { + delegate.addListener(listener, executor); + } + + /** @since 14.0 */ + @Override + public final Throwable failureCause() { + return delegate.failureCause(); + } + + /** @since 15.0 */ + + @Override + public final Service startAsync() { + delegate.startAsync(); + return this; + } + + /** @since 15.0 */ + + @Override + public final Service stopAsync() { + delegate.stopAsync(); + return this; + } + + /** @since 15.0 */ + @Override + public final void awaitRunning() { + delegate.awaitRunning(); + } + + /** @since 15.0 */ + @Override + public final void awaitRunning(long timeout, TimeUnit unit) throws TimeoutException { + delegate.awaitRunning(timeout, unit); + } + + /** @since 15.0 */ + @Override + public final void awaitTerminated() { + delegate.awaitTerminated(); + } + + /** @since 15.0 */ + @Override + public final void awaitTerminated(long timeout, TimeUnit unit) throws TimeoutException { + delegate.awaitTerminated(timeout, unit); + } + + /** + * Returns the name of this service. {@link AbstractIdleService} may include the name in debugging + * output. + * + * @since 14.0 + */ + protected String serviceName() { + return getClass().getSimpleName(); + } +} diff --git a/src/main/java/com/google/common/util/concurrent/AbstractListeningExecutorService.java b/src/main/java/com/google/common/util/concurrent/AbstractListeningExecutorService.java new file mode 100644 index 0000000..f857a40 --- /dev/null +++ b/src/main/java/com/google/common/util/concurrent/AbstractListeningExecutorService.java @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2011 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.util.concurrent; + +import com.google.common.annotations.Beta; +import com.google.common.annotations.GwtIncompatible; + +import java.util.concurrent.AbstractExecutorService; +import java.util.concurrent.Callable; +import java.util.concurrent.RunnableFuture; + + +/** + * Abstract {@link ListeningExecutorService} implementation that creates {@link ListenableFuture} + * instances for each {@link Runnable} and {@link Callable} submitted to it. These tasks are run + * with the abstract {@link #execute execute(Runnable)} method. + * + *

In addition to {@link #execute}, subclasses must implement all methods related to shutdown and + * termination. + * + * @author Chris Povirk + * @since 14.0 + */ +@Beta + +@GwtIncompatible +public abstract class AbstractListeningExecutorService extends AbstractExecutorService + implements ListeningExecutorService { + + /** @since 19.0 (present with return type {@code ListenableFutureTask} since 14.0) */ + @Override + protected final RunnableFuture newTaskFor(Runnable runnable, T value) { + return TrustedListenableFutureTask.create(runnable, value); + } + + /** @since 19.0 (present with return type {@code ListenableFutureTask} since 14.0) */ + @Override + protected final RunnableFuture newTaskFor(Callable callable) { + return TrustedListenableFutureTask.create(callable); + } + + @Override + public ListenableFuture submit(Runnable task) { + return (ListenableFuture) super.submit(task); + } + + @Override + public ListenableFuture submit(Runnable task, T result) { + return (ListenableFuture) super.submit(task, result); + } + + @Override + public ListenableFuture submit(Callable task) { + return (ListenableFuture) super.submit(task); + } +} diff --git a/src/main/java/com/google/common/util/concurrent/AbstractScheduledService.java b/src/main/java/com/google/common/util/concurrent/AbstractScheduledService.java new file mode 100644 index 0000000..ce95b85 --- /dev/null +++ b/src/main/java/com/google/common/util/concurrent/AbstractScheduledService.java @@ -0,0 +1,627 @@ +/* + * Copyright (C) 2011 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.util.concurrent; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.util.concurrent.Internal.toNanosSaturated; +import static com.google.common.util.concurrent.MoreExecutors.directExecutor; + +import com.google.common.annotations.Beta; +import com.google.common.annotations.GwtIncompatible; +import com.google.common.base.Supplier; + + + +import java.time.Duration; +import java.util.concurrent.Callable; +import java.util.concurrent.Executor; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.concurrent.locks.ReentrantLock; +import java.util.logging.Level; +import java.util.logging.Logger; + + + +/** + * Base class for services that can implement {@link #startUp} and {@link #shutDown} but while in + * the "running" state need to perform a periodic task. Subclasses can implement {@link #startUp}, + * {@link #shutDown} and also a {@link #runOneIteration} method that will be executed periodically. + * + *

This class uses the {@link ScheduledExecutorService} returned from {@link #executor} to run + * the {@link #startUp} and {@link #shutDown} methods and also uses that service to schedule the + * {@link #runOneIteration} that will be executed periodically as specified by its {@link + * Scheduler}. When this service is asked to stop via {@link #stopAsync} it will cancel the periodic + * task (but not interrupt it) and wait for it to stop before running the {@link #shutDown} method. + * + *

Subclasses are guaranteed that the life cycle methods ({@link #runOneIteration}, {@link + * #startUp} and {@link #shutDown}) will never run concurrently. Notably, if any execution of {@link + * #runOneIteration} takes longer than its schedule defines, then subsequent executions may start + * late. Also, all life cycle methods are executed with a lock held, so subclasses can safely modify + * shared state without additional synchronization necessary for visibility to later executions of + * the life cycle methods. + * + *

Usage Example

+ * + *

Here is a sketch of a service which crawls a website and uses the scheduling capabilities to + * rate limit itself. + * + *

{@code
+ * class CrawlingService extends AbstractScheduledService {
+ *   private Set visited;
+ *   private Queue toCrawl;
+ *   protected void startUp() throws Exception {
+ *     toCrawl = readStartingUris();
+ *   }
+ *
+ *   protected void runOneIteration() throws Exception {
+ *     Uri uri = toCrawl.remove();
+ *     Collection newUris = crawl(uri);
+ *     visited.add(uri);
+ *     for (Uri newUri : newUris) {
+ *       if (!visited.contains(newUri)) { toCrawl.add(newUri); }
+ *     }
+ *   }
+ *
+ *   protected void shutDown() throws Exception {
+ *     saveUris(toCrawl);
+ *   }
+ *
+ *   protected Scheduler scheduler() {
+ *     return Scheduler.newFixedRateSchedule(0, 1, TimeUnit.SECONDS);
+ *   }
+ * }
+ * }
+ * + *

This class uses the life cycle methods to read in a list of starting URIs and save the set of + * outstanding URIs when shutting down. Also, it takes advantage of the scheduling functionality to + * rate limit the number of queries we perform. + * + * @author Luke Sandberg + * @since 11.0 + */ +@Beta +@GwtIncompatible +public abstract class AbstractScheduledService implements Service { + private static final Logger logger = Logger.getLogger(AbstractScheduledService.class.getName()); + + /** + * A scheduler defines the policy for how the {@link AbstractScheduledService} should run its + * task. + * + *

Consider using the {@link #newFixedDelaySchedule} and {@link #newFixedRateSchedule} factory + * methods, these provide {@link Scheduler} instances for the common use case of running the + * service with a fixed schedule. If more flexibility is needed then consider subclassing {@link + * CustomScheduler}. + * + * @author Luke Sandberg + * @since 11.0 + */ + public abstract static class Scheduler { + /** + * Returns a {@link Scheduler} that schedules the task using the {@link + * ScheduledExecutorService#scheduleWithFixedDelay} method. + * + * @param initialDelay the time to delay first execution + * @param delay the delay between the termination of one execution and the commencement of the + * next + * @since 28.0 + */ + public static Scheduler newFixedDelaySchedule(Duration initialDelay, Duration delay) { + return newFixedDelaySchedule( + toNanosSaturated(initialDelay), toNanosSaturated(delay), TimeUnit.NANOSECONDS); + } + + /** + * Returns a {@link Scheduler} that schedules the task using the {@link + * ScheduledExecutorService#scheduleWithFixedDelay} method. + * + * @param initialDelay the time to delay first execution + * @param delay the delay between the termination of one execution and the commencement of the + * next + * @param unit the time unit of the initialDelay and delay parameters + */ + @SuppressWarnings("GoodTime") // should accept a java.time.Duration + public static Scheduler newFixedDelaySchedule( + final long initialDelay, final long delay, final TimeUnit unit) { + checkNotNull(unit); + checkArgument(delay > 0, "delay must be > 0, found %s", delay); + return new Scheduler() { + @Override + public Future schedule( + AbstractService service, ScheduledExecutorService executor, Runnable task) { + return executor.scheduleWithFixedDelay(task, initialDelay, delay, unit); + } + }; + } + + /** + * Returns a {@link Scheduler} that schedules the task using the {@link + * ScheduledExecutorService#scheduleAtFixedRate} method. + * + * @param initialDelay the time to delay first execution + * @param period the period between successive executions of the task + * @since 28.0 + */ + public static Scheduler newFixedRateSchedule(Duration initialDelay, Duration period) { + return newFixedRateSchedule( + toNanosSaturated(initialDelay), toNanosSaturated(period), TimeUnit.NANOSECONDS); + } + + /** + * Returns a {@link Scheduler} that schedules the task using the {@link + * ScheduledExecutorService#scheduleAtFixedRate} method. + * + * @param initialDelay the time to delay first execution + * @param period the period between successive executions of the task + * @param unit the time unit of the initialDelay and period parameters + */ + @SuppressWarnings("GoodTime") // should accept a java.time.Duration + public static Scheduler newFixedRateSchedule( + final long initialDelay, final long period, final TimeUnit unit) { + checkNotNull(unit); + checkArgument(period > 0, "period must be > 0, found %s", period); + return new Scheduler() { + @Override + public Future schedule( + AbstractService service, ScheduledExecutorService executor, Runnable task) { + return executor.scheduleAtFixedRate(task, initialDelay, period, unit); + } + }; + } + + /** Schedules the task to run on the provided executor on behalf of the service. */ + abstract Future schedule( + AbstractService service, ScheduledExecutorService executor, Runnable runnable); + + private Scheduler() {} + } + + /* use AbstractService for state management */ + private final AbstractService delegate = new ServiceDelegate(); + + + private final class ServiceDelegate extends AbstractService { + + // A handle to the running task so that we can stop it when a shutdown has been requested. + // These two fields are volatile because their values will be accessed from multiple threads. + private volatile Future runningTask; + private volatile ScheduledExecutorService executorService; + + // This lock protects the task so we can ensure that none of the template methods (startUp, + // shutDown or runOneIteration) run concurrently with one another. + // TODO(lukes): why don't we use ListenableFuture to sequence things? Then we could drop the + // lock. + private final ReentrantLock lock = new ReentrantLock(); + + + class Task implements Runnable { + @Override + public void run() { + lock.lock(); + try { + if (runningTask.isCancelled()) { + // task may have been cancelled while blocked on the lock. + return; + } + AbstractScheduledService.this.runOneIteration(); + } catch (Throwable t) { + try { + shutDown(); + } catch (Exception ignored) { + logger.log( + Level.WARNING, + "Error while attempting to shut down the service after failure.", + ignored); + } + notifyFailed(t); + runningTask.cancel(false); // prevent future invocations. + } finally { + lock.unlock(); + } + } + } + + private final Runnable task = new Task(); + + @Override + protected final void doStart() { + executorService = + MoreExecutors.renamingDecorator( + executor(), + new Supplier() { + @Override + public String get() { + return serviceName() + " " + state(); + } + }); + executorService.execute( + new Runnable() { + @Override + public void run() { + lock.lock(); + try { + startUp(); + runningTask = scheduler().schedule(delegate, executorService, task); + notifyStarted(); + } catch (Throwable t) { + notifyFailed(t); + if (runningTask != null) { + // prevent the task from running if possible + runningTask.cancel(false); + } + } finally { + lock.unlock(); + } + } + }); + } + + @Override + protected final void doStop() { + runningTask.cancel(false); + executorService.execute( + new Runnable() { + @Override + public void run() { + try { + lock.lock(); + try { + if (state() != State.STOPPING) { + // This means that the state has changed since we were scheduled. This implies + // that an execution of runOneIteration has thrown an exception and we have + // transitioned to a failed state, also this means that shutDown has already + // been called, so we do not want to call it again. + return; + } + shutDown(); + } finally { + lock.unlock(); + } + notifyStopped(); + } catch (Throwable t) { + notifyFailed(t); + } + } + }); + } + + @Override + public String toString() { + return AbstractScheduledService.this.toString(); + } + } + + /** Constructor for use by subclasses. */ + protected AbstractScheduledService() {} + + /** + * Run one iteration of the scheduled task. If any invocation of this method throws an exception, + * the service will transition to the {@link Service.State#FAILED} state and this method will no + * longer be called. + */ + protected abstract void runOneIteration() throws Exception; + + /** + * Start the service. + * + *

By default this method does nothing. + */ + protected void startUp() throws Exception {} + + /** + * Stop the service. This is guaranteed not to run concurrently with {@link #runOneIteration}. + * + *

By default this method does nothing. + */ + protected void shutDown() throws Exception {} + + /** + * Returns the {@link Scheduler} object used to configure this service. This method will only be + * called once. + */ + // TODO(cpovirk): @ForOverride + protected abstract Scheduler scheduler(); + + /** + * Returns the {@link ScheduledExecutorService} that will be used to execute the {@link #startUp}, + * {@link #runOneIteration} and {@link #shutDown} methods. If this method is overridden the + * executor will not be {@linkplain ScheduledExecutorService#shutdown shutdown} when this service + * {@linkplain Service.State#TERMINATED terminates} or {@linkplain Service.State#TERMINATED + * fails}. Subclasses may override this method to supply a custom {@link ScheduledExecutorService} + * instance. This method is guaranteed to only be called once. + * + *

By default this returns a new {@link ScheduledExecutorService} with a single thread thread + * pool that sets the name of the thread to the {@linkplain #serviceName() service name}. Also, + * the pool will be {@linkplain ScheduledExecutorService#shutdown() shut down} when the service + * {@linkplain Service.State#TERMINATED terminates} or {@linkplain Service.State#TERMINATED + * fails}. + */ + protected ScheduledExecutorService executor() { + + class ThreadFactoryImpl implements ThreadFactory { + @Override + public Thread newThread(Runnable runnable) { + return MoreExecutors.newThread(serviceName(), runnable); + } + } + final ScheduledExecutorService executor = + Executors.newSingleThreadScheduledExecutor(new ThreadFactoryImpl()); + // Add a listener to shutdown the executor after the service is stopped. This ensures that the + // JVM shutdown will not be prevented from exiting after this service has stopped or failed. + // Technically this listener is added after start() was called so it is a little gross, but it + // is called within doStart() so we know that the service cannot terminate or fail concurrently + // with adding this listener so it is impossible to miss an event that we are interested in. + addListener( + new Listener() { + @Override + public void terminated(State from) { + executor.shutdown(); + } + + @Override + public void failed(State from, Throwable failure) { + executor.shutdown(); + } + }, + directExecutor()); + return executor; + } + + /** + * Returns the name of this service. {@link AbstractScheduledService} may include the name in + * debugging output. + * + * @since 14.0 + */ + protected String serviceName() { + return getClass().getSimpleName(); + } + + @Override + public String toString() { + return serviceName() + " [" + state() + "]"; + } + + @Override + public final boolean isRunning() { + return delegate.isRunning(); + } + + @Override + public final State state() { + return delegate.state(); + } + + /** @since 13.0 */ + @Override + public final void addListener(Listener listener, Executor executor) { + delegate.addListener(listener, executor); + } + + /** @since 14.0 */ + @Override + public final Throwable failureCause() { + return delegate.failureCause(); + } + + /** @since 15.0 */ + + @Override + public final Service startAsync() { + delegate.startAsync(); + return this; + } + + /** @since 15.0 */ + + @Override + public final Service stopAsync() { + delegate.stopAsync(); + return this; + } + + /** @since 15.0 */ + @Override + public final void awaitRunning() { + delegate.awaitRunning(); + } + + /** @since 15.0 */ + @Override + public final void awaitRunning(long timeout, TimeUnit unit) throws TimeoutException { + delegate.awaitRunning(timeout, unit); + } + + /** @since 15.0 */ + @Override + public final void awaitTerminated() { + delegate.awaitTerminated(); + } + + /** @since 15.0 */ + @Override + public final void awaitTerminated(long timeout, TimeUnit unit) throws TimeoutException { + delegate.awaitTerminated(timeout, unit); + } + + /** + * A {@link Scheduler} that provides a convenient way for the {@link AbstractScheduledService} to + * use a dynamically changing schedule. After every execution of the task, assuming it hasn't been + * cancelled, the {@link #getNextSchedule} method will be called. + * + * @author Luke Sandberg + * @since 11.0 + */ + @Beta + public abstract static class CustomScheduler extends Scheduler { + + /** A callable class that can reschedule itself using a {@link CustomScheduler}. */ + private class ReschedulableCallable extends ForwardingFuture implements Callable { + + /** The underlying task. */ + private final Runnable wrappedRunnable; + + /** The executor on which this Callable will be scheduled. */ + private final ScheduledExecutorService executor; + + /** + * The service that is managing this callable. This is used so that failure can be reported + * properly. + */ + private final AbstractService service; + + /** + * This lock is used to ensure safe and correct cancellation, it ensures that a new task is + * not scheduled while a cancel is ongoing. Also it protects the currentFuture variable to + * ensure that it is assigned atomically with being scheduled. + */ + private final ReentrantLock lock = new ReentrantLock(); + + /** The future that represents the next execution of this task. */ + + private Future currentFuture; + + ReschedulableCallable( + AbstractService service, ScheduledExecutorService executor, Runnable runnable) { + this.wrappedRunnable = runnable; + this.executor = executor; + this.service = service; + } + + @Override + public Void call() throws Exception { + wrappedRunnable.run(); + reschedule(); + return null; + } + + /** Atomically reschedules this task and assigns the new future to {@link #currentFuture}. */ + public void reschedule() { + // invoke the callback outside the lock, prevents some shenanigans. + Schedule schedule; + try { + schedule = CustomScheduler.this.getNextSchedule(); + } catch (Throwable t) { + service.notifyFailed(t); + return; + } + // We reschedule ourselves with a lock held for two reasons. 1. we want to make sure that + // cancel calls cancel on the correct future. 2. we want to make sure that the assignment + // to currentFuture doesn't race with itself so that currentFuture is assigned in the + // correct order. + Throwable scheduleFailure = null; + lock.lock(); + try { + if (currentFuture == null || !currentFuture.isCancelled()) { + currentFuture = executor.schedule(this, schedule.delay, schedule.unit); + } + } catch (Throwable e) { + // If an exception is thrown by the subclass then we need to make sure that the service + // notices and transitions to the FAILED state. We do it by calling notifyFailed directly + // because the service does not monitor the state of the future so if the exception is not + // caught and forwarded to the service the task would stop executing but the service would + // have no idea. + // TODO(lukes): consider building everything in terms of ListenableScheduledFuture then + // the AbstractService could monitor the future directly. Rescheduling is still hard... + // but it would help with some of these lock ordering issues. + scheduleFailure = e; + } finally { + lock.unlock(); + } + // Call notifyFailed outside the lock to avoid lock ordering issues. + if (scheduleFailure != null) { + service.notifyFailed(scheduleFailure); + } + } + + // N.B. Only protect cancel and isCancelled because those are the only methods that are + // invoked by the AbstractScheduledService. + @Override + public boolean cancel(boolean mayInterruptIfRunning) { + // Ensure that a task cannot be rescheduled while a cancel is ongoing. + lock.lock(); + try { + return currentFuture.cancel(mayInterruptIfRunning); + } finally { + lock.unlock(); + } + } + + @Override + public boolean isCancelled() { + lock.lock(); + try { + return currentFuture.isCancelled(); + } finally { + lock.unlock(); + } + } + + @Override + protected Future delegate() { + throw new UnsupportedOperationException( + "Only cancel and isCancelled is supported by this future"); + } + } + + @Override + final Future schedule( + AbstractService service, ScheduledExecutorService executor, Runnable runnable) { + ReschedulableCallable task = new ReschedulableCallable(service, executor, runnable); + task.reschedule(); + return task; + } + + /** + * A value object that represents an absolute delay until a task should be invoked. + * + * @author Luke Sandberg + * @since 11.0 + */ + @Beta + protected static final class Schedule { + + private final long delay; + private final TimeUnit unit; + + /** + * @param delay the time from now to delay execution + * @param unit the time unit of the delay parameter + */ + public Schedule(long delay, TimeUnit unit) { + this.delay = delay; + this.unit = checkNotNull(unit); + } + } + + /** + * Calculates the time at which to next invoke the task. + * + *

This is guaranteed to be called immediately after the task has completed an iteration and + * on the same thread as the previous execution of {@link + * AbstractScheduledService#runOneIteration}. + * + * @return a schedule that defines the delay before the next execution. + */ + // TODO(cpovirk): @ForOverride + protected abstract Schedule getNextSchedule() throws Exception; + } +} diff --git a/src/main/java/com/google/common/util/concurrent/AbstractService.java b/src/main/java/com/google/common/util/concurrent/AbstractService.java new file mode 100644 index 0000000..1653e53 --- /dev/null +++ b/src/main/java/com/google/common/util/concurrent/AbstractService.java @@ -0,0 +1,615 @@ +/* + * Copyright (C) 2009 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.util.concurrent; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.base.Preconditions.checkState; +import static com.google.common.util.concurrent.Service.State.FAILED; +import static com.google.common.util.concurrent.Service.State.NEW; +import static com.google.common.util.concurrent.Service.State.RUNNING; +import static com.google.common.util.concurrent.Service.State.STARTING; +import static com.google.common.util.concurrent.Service.State.STOPPING; +import static com.google.common.util.concurrent.Service.State.TERMINATED; + +import com.google.common.annotations.Beta; +import com.google.common.annotations.GwtIncompatible; +import com.google.common.util.concurrent.Monitor.Guard; +import com.google.common.util.concurrent.Service.State; // javadoc needs this + + + + +import java.util.concurrent.Executor; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + + +/** + * Base class for implementing services that can handle {@link #doStart} and {@link #doStop} + * requests, responding to them with {@link #notifyStarted()} and {@link #notifyStopped()} + * callbacks. Its subclasses must manage threads manually; consider {@link + * AbstractExecutionThreadService} if you need only a single execution thread. + * + * @author Jesse Wilson + * @author Luke Sandberg + * @since 1.0 + */ +@Beta +@GwtIncompatible +public abstract class AbstractService implements Service { + private static final ListenerCallQueue.Event STARTING_EVENT = + new ListenerCallQueue.Event() { + @Override + public void call(Listener listener) { + listener.starting(); + } + + @Override + public String toString() { + return "starting()"; + } + }; + private static final ListenerCallQueue.Event RUNNING_EVENT = + new ListenerCallQueue.Event() { + @Override + public void call(Listener listener) { + listener.running(); + } + + @Override + public String toString() { + return "running()"; + } + }; + private static final ListenerCallQueue.Event STOPPING_FROM_STARTING_EVENT = + stoppingEvent(STARTING); + private static final ListenerCallQueue.Event STOPPING_FROM_RUNNING_EVENT = + stoppingEvent(RUNNING); + + private static final ListenerCallQueue.Event TERMINATED_FROM_NEW_EVENT = + terminatedEvent(NEW); + private static final ListenerCallQueue.Event TERMINATED_FROM_STARTING_EVENT = + terminatedEvent(STARTING); + private static final ListenerCallQueue.Event TERMINATED_FROM_RUNNING_EVENT = + terminatedEvent(RUNNING); + private static final ListenerCallQueue.Event TERMINATED_FROM_STOPPING_EVENT = + terminatedEvent(STOPPING); + + private static ListenerCallQueue.Event terminatedEvent(final State from) { + return new ListenerCallQueue.Event() { + @Override + public void call(Listener listener) { + listener.terminated(from); + } + + @Override + public String toString() { + return "terminated({from = " + from + "})"; + } + }; + } + + private static ListenerCallQueue.Event stoppingEvent(final State from) { + return new ListenerCallQueue.Event() { + @Override + public void call(Listener listener) { + listener.stopping(from); + } + + @Override + public String toString() { + return "stopping({from = " + from + "})"; + } + }; + } + + private final Monitor monitor = new Monitor(); + + private final Guard isStartable = new IsStartableGuard(); + + + private final class IsStartableGuard extends Guard { + IsStartableGuard() { + super(AbstractService.this.monitor); + } + + @Override + public boolean isSatisfied() { + return state() == NEW; + } + } + + private final Guard isStoppable = new IsStoppableGuard(); + + + private final class IsStoppableGuard extends Guard { + IsStoppableGuard() { + super(AbstractService.this.monitor); + } + + @Override + public boolean isSatisfied() { + return state().compareTo(RUNNING) <= 0; + } + } + + private final Guard hasReachedRunning = new HasReachedRunningGuard(); + + + private final class HasReachedRunningGuard extends Guard { + HasReachedRunningGuard() { + super(AbstractService.this.monitor); + } + + @Override + public boolean isSatisfied() { + return state().compareTo(RUNNING) >= 0; + } + } + + private final Guard isStopped = new IsStoppedGuard(); + + + private final class IsStoppedGuard extends Guard { + IsStoppedGuard() { + super(AbstractService.this.monitor); + } + + @Override + public boolean isSatisfied() { + return state().isTerminal(); + } + } + + /** The listeners to notify during a state transition. */ + private final ListenerCallQueue listeners = new ListenerCallQueue<>(); + + /** + * The current state of the service. This should be written with the lock held but can be read + * without it because it is an immutable object in a volatile field. This is desirable so that + * methods like {@link #state}, {@link #failureCause} and notably {@link #toString} can be run + * without grabbing the lock. + * + *

To update this field correctly the lock must be held to guarantee that the state is + * consistent. + */ + private volatile StateSnapshot snapshot = new StateSnapshot(NEW); + + /** Constructor for use by subclasses. */ + protected AbstractService() {} + + /** + * This method is called by {@link #startAsync} to initiate service startup. The invocation of + * this method should cause a call to {@link #notifyStarted()}, either during this method's run, + * or after it has returned. If startup fails, the invocation should cause a call to {@link + * #notifyFailed(Throwable)} instead. + * + *

This method should return promptly; prefer to do work on a different thread where it is + * convenient. It is invoked exactly once on service startup, even when {@link #startAsync} is + * called multiple times. + */ + protected abstract void doStart(); + + /** + * This method should be used to initiate service shutdown. The invocation of this method should + * cause a call to {@link #notifyStopped()}, either during this method's run, or after it has + * returned. If shutdown fails, the invocation should cause a call to {@link + * #notifyFailed(Throwable)} instead. + * + *

This method should return promptly; prefer to do work on a different thread where it is + * convenient. It is invoked exactly once on service shutdown, even when {@link #stopAsync} is + * called multiple times. + * + *

If {@link #stopAsync} is called on a {@link State#STARTING} service, this method is not + * invoked immediately. Instead, it will be deferred until after the service is {@link + * State#RUNNING}. Services that need to cancel startup work can override {#link #doCancelStart}. + */ + protected abstract void doStop(); + + /** + * This method is called by {@link #stopAsync} when the service is still starting (i.e. {@link + * #startAsync} has been called but {@link #notifyStarted} has not). Subclasses can override the + * method to cancel pending work and then call {@link #notifyStopped} to stop the service. + * + *

This method should return promptly; prefer to do work on a different thread where it is + * convenient. It is invoked exactly once on service shutdown, even when {@link #stopAsync} is + * called multiple times. + * + *

When this method is called {@link #state()} will return {@link State#STOPPING}, which is the + * external state observable by the caller of {@link #stopAsync}. + * + * @since 27.0 + */ + protected void doCancelStart() {} + + + @Override + public final Service startAsync() { + if (monitor.enterIf(isStartable)) { + try { + snapshot = new StateSnapshot(STARTING); + enqueueStartingEvent(); + doStart(); + } catch (Throwable startupFailure) { + notifyFailed(startupFailure); + } finally { + monitor.leave(); + dispatchListenerEvents(); + } + } else { + throw new IllegalStateException("Service " + this + " has already been started"); + } + return this; + } + + + @Override + public final Service stopAsync() { + if (monitor.enterIf(isStoppable)) { + try { + State previous = state(); + switch (previous) { + case NEW: + snapshot = new StateSnapshot(TERMINATED); + enqueueTerminatedEvent(NEW); + break; + case STARTING: + snapshot = new StateSnapshot(STARTING, true, null); + enqueueStoppingEvent(STARTING); + doCancelStart(); + break; + case RUNNING: + snapshot = new StateSnapshot(STOPPING); + enqueueStoppingEvent(RUNNING); + doStop(); + break; + case STOPPING: + case TERMINATED: + case FAILED: + // These cases are impossible due to the if statement above. + throw new AssertionError("isStoppable is incorrectly implemented, saw: " + previous); + } + } catch (Throwable shutdownFailure) { + notifyFailed(shutdownFailure); + } finally { + monitor.leave(); + dispatchListenerEvents(); + } + } + return this; + } + + @Override + public final void awaitRunning() { + monitor.enterWhenUninterruptibly(hasReachedRunning); + try { + checkCurrentState(RUNNING); + } finally { + monitor.leave(); + } + } + + @Override + public final void awaitRunning(long timeout, TimeUnit unit) throws TimeoutException { + if (monitor.enterWhenUninterruptibly(hasReachedRunning, timeout, unit)) { + try { + checkCurrentState(RUNNING); + } finally { + monitor.leave(); + } + } else { + // It is possible due to races the we are currently in the expected state even though we + // timed out. e.g. if we weren't event able to grab the lock within the timeout we would never + // even check the guard. I don't think we care too much about this use case but it could lead + // to a confusing error message. + throw new TimeoutException("Timed out waiting for " + this + " to reach the RUNNING state."); + } + } + + @Override + public final void awaitTerminated() { + monitor.enterWhenUninterruptibly(isStopped); + try { + checkCurrentState(TERMINATED); + } finally { + monitor.leave(); + } + } + + @Override + public final void awaitTerminated(long timeout, TimeUnit unit) throws TimeoutException { + if (monitor.enterWhenUninterruptibly(isStopped, timeout, unit)) { + try { + checkCurrentState(TERMINATED); + } finally { + monitor.leave(); + } + } else { + // It is possible due to races the we are currently in the expected state even though we + // timed out. e.g. if we weren't event able to grab the lock within the timeout we would never + // even check the guard. I don't think we care too much about this use case but it could lead + // to a confusing error message. + throw new TimeoutException( + "Timed out waiting for " + + this + + " to reach a terminal state. " + + "Current state: " + + state()); + } + } + + /** Checks that the current state is equal to the expected state. */ + private void checkCurrentState(State expected) { + State actual = state(); + if (actual != expected) { + if (actual == FAILED) { + // Handle this specially so that we can include the failureCause, if there is one. + throw new IllegalStateException( + "Expected the service " + this + " to be " + expected + ", but the service has FAILED", + failureCause()); + } + throw new IllegalStateException( + "Expected the service " + this + " to be " + expected + ", but was " + actual); + } + } + + /** + * Implementing classes should invoke this method once their service has started. It will cause + * the service to transition from {@link State#STARTING} to {@link State#RUNNING}. + * + * @throws IllegalStateException if the service is not {@link State#STARTING}. + */ + protected final void notifyStarted() { + monitor.enter(); + try { + // We have to examine the internal state of the snapshot here to properly handle the stop + // while starting case. + if (snapshot.state != STARTING) { + IllegalStateException failure = + new IllegalStateException( + "Cannot notifyStarted() when the service is " + snapshot.state); + notifyFailed(failure); + throw failure; + } + + if (snapshot.shutdownWhenStartupFinishes) { + snapshot = new StateSnapshot(STOPPING); + // We don't call listeners here because we already did that when we set the + // shutdownWhenStartupFinishes flag. + doStop(); + } else { + snapshot = new StateSnapshot(RUNNING); + enqueueRunningEvent(); + } + } finally { + monitor.leave(); + dispatchListenerEvents(); + } + } + + /** + * Implementing classes should invoke this method once their service has stopped. It will cause + * the service to transition from {@link State#STARTING} or {@link State#STOPPING} to {@link + * State#TERMINATED}. + * + * @throws IllegalStateException if the service is not one of {@link State#STOPPING}, {@link + * State#STARTING}, or {@link State#RUNNING}. + */ + protected final void notifyStopped() { + monitor.enter(); + try { + State previous = state(); + switch (previous) { + case NEW: + case TERMINATED: + case FAILED: + throw new IllegalStateException("Cannot notifyStopped() when the service is " + previous); + case RUNNING: + case STARTING: + case STOPPING: + snapshot = new StateSnapshot(TERMINATED); + enqueueTerminatedEvent(previous); + break; + } + } finally { + monitor.leave(); + dispatchListenerEvents(); + } + } + + /** + * Invoke this method to transition the service to the {@link State#FAILED}. The service will + * not be stopped if it is running. Invoke this method when a service has failed critically + * or otherwise cannot be started nor stopped. + */ + protected final void notifyFailed(Throwable cause) { + checkNotNull(cause); + + monitor.enter(); + try { + State previous = state(); + switch (previous) { + case NEW: + case TERMINATED: + throw new IllegalStateException("Failed while in state:" + previous, cause); + case RUNNING: + case STARTING: + case STOPPING: + snapshot = new StateSnapshot(FAILED, false, cause); + enqueueFailedEvent(previous, cause); + break; + case FAILED: + // Do nothing + break; + } + } finally { + monitor.leave(); + dispatchListenerEvents(); + } + } + + @Override + public final boolean isRunning() { + return state() == RUNNING; + } + + @Override + public final State state() { + return snapshot.externalState(); + } + + /** @since 14.0 */ + @Override + public final Throwable failureCause() { + return snapshot.failureCause(); + } + + /** @since 13.0 */ + @Override + public final void addListener(Listener listener, Executor executor) { + listeners.addListener(listener, executor); + } + + @Override + public String toString() { + return getClass().getSimpleName() + " [" + state() + "]"; + } + + /** + * Attempts to execute all the listeners in {@link #listeners} while not holding the {@link + * #monitor}. + */ + private void dispatchListenerEvents() { + if (!monitor.isOccupiedByCurrentThread()) { + listeners.dispatch(); + } + } + + private void enqueueStartingEvent() { + listeners.enqueue(STARTING_EVENT); + } + + private void enqueueRunningEvent() { + listeners.enqueue(RUNNING_EVENT); + } + + private void enqueueStoppingEvent(final State from) { + if (from == State.STARTING) { + listeners.enqueue(STOPPING_FROM_STARTING_EVENT); + } else if (from == State.RUNNING) { + listeners.enqueue(STOPPING_FROM_RUNNING_EVENT); + } else { + throw new AssertionError(); + } + } + + private void enqueueTerminatedEvent(final State from) { + switch (from) { + case NEW: + listeners.enqueue(TERMINATED_FROM_NEW_EVENT); + break; + case STARTING: + listeners.enqueue(TERMINATED_FROM_STARTING_EVENT); + break; + case RUNNING: + listeners.enqueue(TERMINATED_FROM_RUNNING_EVENT); + break; + case STOPPING: + listeners.enqueue(TERMINATED_FROM_STOPPING_EVENT); + break; + case TERMINATED: + case FAILED: + throw new AssertionError(); + } + } + + private void enqueueFailedEvent(final State from, final Throwable cause) { + // can't memoize this one due to the exception + listeners.enqueue( + new ListenerCallQueue.Event() { + @Override + public void call(Listener listener) { + listener.failed(from, cause); + } + + @Override + public String toString() { + return "failed({from = " + from + ", cause = " + cause + "})"; + } + }); + } + + /** + * An immutable snapshot of the current state of the service. This class represents a consistent + * snapshot of the state and therefore it can be used to answer simple queries without needing to + * grab a lock. + */ + // except that Throwable is mutable (initCause(), setStackTrace(), mutable subclasses). + private static final class StateSnapshot { + /** + * The internal state, which equals external state unless shutdownWhenStartupFinishes is true. + */ + final State state; + + /** If true, the user requested a shutdown while the service was still starting up. */ + final boolean shutdownWhenStartupFinishes; + + /** + * The exception that caused this service to fail. This will be {@code null} unless the service + * has failed. + */ + final Throwable failure; + + StateSnapshot(State internalState) { + this(internalState, false, null); + } + + StateSnapshot( + State internalState, boolean shutdownWhenStartupFinishes, Throwable failure) { + checkArgument( + !shutdownWhenStartupFinishes || internalState == STARTING, + "shutdownWhenStartupFinishes can only be set if state is STARTING. Got %s instead.", + internalState); + checkArgument( + !(failure != null ^ internalState == FAILED), + "A failure cause should be set if and only if the state is failed. Got %s and %s " + + "instead.", + internalState, + failure); + this.state = internalState; + this.shutdownWhenStartupFinishes = shutdownWhenStartupFinishes; + this.failure = failure; + } + + /** @see Service#state() */ + State externalState() { + if (shutdownWhenStartupFinishes && state == STARTING) { + return STOPPING; + } else { + return state; + } + } + + /** @see Service#failureCause() */ + Throwable failureCause() { + checkState( + state == FAILED, + "failureCause() is only valid if the service has failed, service is %s", + state); + return failure; + } + } +} diff --git a/src/main/java/com/google/common/util/concurrent/AbstractTransformFuture.java b/src/main/java/com/google/common/util/concurrent/AbstractTransformFuture.java new file mode 100644 index 0000000..0e5c84d --- /dev/null +++ b/src/main/java/com/google/common/util/concurrent/AbstractTransformFuture.java @@ -0,0 +1,248 @@ +/* + * Copyright (C) 2006 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.util.concurrent; + +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.util.concurrent.Futures.getDone; +import static com.google.common.util.concurrent.MoreExecutors.rejectionPropagatingExecutor; + +import com.google.common.annotations.GwtCompatible; +import com.google.common.base.Function; + +import java.util.concurrent.CancellationException; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Executor; + + +/** Implementations of {@code Futures.transform*}. */ +@GwtCompatible +abstract class AbstractTransformFuture extends FluentFuture.TrustedFuture + implements Runnable { + static ListenableFuture create( + ListenableFuture input, + AsyncFunction function, + Executor executor) { + checkNotNull(executor); + AsyncTransformFuture output = new AsyncTransformFuture<>(input, function); + input.addListener(output, rejectionPropagatingExecutor(executor, output)); + return output; + } + + static ListenableFuture create( + ListenableFuture input, Function function, Executor executor) { + checkNotNull(function); + TransformFuture output = new TransformFuture<>(input, function); + input.addListener(output, rejectionPropagatingExecutor(executor, output)); + return output; + } + + /* + * In certain circumstances, this field might theoretically not be visible to an afterDone() call + * triggered by cancel(). For details, see the comments on the fields of TimeoutFuture. + */ + ListenableFuture inputFuture; + F function; + + AbstractTransformFuture(ListenableFuture inputFuture, F function) { + this.inputFuture = checkNotNull(inputFuture); + this.function = checkNotNull(function); + } + + @Override + public final void run() { + ListenableFuture localInputFuture = inputFuture; + F localFunction = function; + if (isCancelled() | localInputFuture == null | localFunction == null) { + return; + } + inputFuture = null; + + if (localInputFuture.isCancelled()) { + @SuppressWarnings("unchecked") + boolean unused = + setFuture((ListenableFuture) localInputFuture); // Respects cancellation cause setting + return; + } + + /* + * Any of the setException() calls below can fail if the output Future is cancelled between now + * and then. This means that we're silently swallowing an exception -- maybe even an Error. But + * this is no worse than what FutureTask does in that situation. Additionally, because the + * Future was cancelled, its listeners have been run, so its consumers will not hang. + * + * Contrast this to the situation we have if setResult() throws, a situation described below. + */ + I sourceResult; + try { + sourceResult = getDone(localInputFuture); + } catch (CancellationException e) { + // TODO(user): verify future behavior - unify logic with getFutureValue in AbstractFuture. This + // code should be unreachable with correctly implemented Futures. + // Cancel this future and return. + // At this point, inputFuture is cancelled and outputFuture doesn't exist, so the value of + // mayInterruptIfRunning is irrelevant. + cancel(false); + return; + } catch (ExecutionException e) { + // Set the cause of the exception as this future's exception. + setException(e.getCause()); + return; + } catch (RuntimeException e) { + // Bug in inputFuture.get(). Propagate to the output Future so that its consumers don't hang. + setException(e); + return; + } catch (Error e) { + /* + * StackOverflowError, OutOfMemoryError (e.g., from allocating ExecutionException), or + * something. Try to treat it like a RuntimeException. If we overflow the stack again, the + * resulting Error will propagate upward up to the root call to set(). + */ + setException(e); + return; + } + + T transformResult; + try { + transformResult = doTransform(localFunction, sourceResult); + } catch (Throwable t) { + // This exception is irrelevant in this thread, but useful for the client. + setException(t); + return; + } finally { + function = null; + } + + /* + * If set()/setValue() throws an Error, we let it propagate. Why? The most likely Error is a + * StackOverflowError (from deep transform(..., directExecutor()) nesting), and calling + * setException(stackOverflowError) would fail: + * + * - If the stack overflowed before set()/setValue() could even store the result in the output + * Future, then a call setException() would likely also overflow. + * + * - If the stack overflowed after set()/setValue() stored its result, then a call to + * setException() will be a no-op because the Future is already done. + * + * Both scenarios are bad: The output Future might never complete, or, if it does complete, it + * might not run some of its listeners. The likely result is that the app will hang. (And of + * course stack overflows are bad news in general. For example, we may have overflowed in the + * middle of defining a class. If so, that class will never be loadable in this process.) The + * best we can do (since logging may overflow the stack) is to let the error propagate. Because + * it is an Error, it won't be caught and logged by AbstractFuture.executeListener. Instead, it + * can propagate through many layers of AbstractTransformFuture up to the root call to set(). + * + * https://github.com/google/guava/issues/2254 + * + * Other kinds of Errors are possible: + * + * - OutOfMemoryError from allocations in setFuture(): The calculus here is similar to + * StackOverflowError: We can't reliably call setException(error). + * + * - Any kind of Error from a listener. Even if we could distinguish that case (by exposing some + * extra state from AbstractFuture), our options are limited: A call to setException() would be + * a no-op. We could log, but if that's what we really want, we should modify + * AbstractFuture.executeListener to do so, since that method would have the ability to continue + * to execute other listeners. + * + * What about RuntimeException? If there is a bug in set()/setValue() that produces one, it will + * propagate, too, but only as far as AbstractFuture.executeListener, which will catch and log + * it. + */ + setResult(transformResult); + } + + /** Template method for subtypes to actually run the transform. */ + abstract T doTransform(F function, I result) throws Exception; + + /** Template method for subtypes to actually set the result. */ + abstract void setResult(T result); + + @Override + protected final void afterDone() { + maybePropagateCancellationTo(inputFuture); + this.inputFuture = null; + this.function = null; + } + + @Override + protected String pendingToString() { + ListenableFuture localInputFuture = inputFuture; + F localFunction = function; + String superString = super.pendingToString(); + String resultString = ""; + if (localInputFuture != null) { + resultString = "inputFuture=[" + localInputFuture + "], "; + } + if (localFunction != null) { + return resultString + "function=[" + localFunction + "]"; + } else if (superString != null) { + return resultString + superString; + } + return null; + } + + /** + * An {@link AbstractTransformFuture} that delegates to an {@link AsyncFunction} and {@link + * #setFuture(ListenableFuture)}. + */ + private static final class AsyncTransformFuture + extends AbstractTransformFuture< + I, O, AsyncFunction, ListenableFuture> { + AsyncTransformFuture( + ListenableFuture inputFuture, AsyncFunction function) { + super(inputFuture, function); + } + + @Override + ListenableFuture doTransform( + AsyncFunction function, I input) throws Exception { + ListenableFuture outputFuture = function.apply(input); + checkNotNull( + outputFuture, + "AsyncFunction.apply returned null instead of a Future. " + + "Did you mean to return immediateFuture(null)? %s", + function); + return outputFuture; + } + + @Override + void setResult(ListenableFuture result) { + setFuture(result); + } + } + + /** + * An {@link AbstractTransformFuture} that delegates to a {@link Function} and {@link + * #set(Object)}. + */ + private static final class TransformFuture + extends AbstractTransformFuture, O> { + TransformFuture( + ListenableFuture inputFuture, Function function) { + super(inputFuture, function); + } + + @Override + + O doTransform(Function function, I input) { + return function.apply(input); + } + + @Override + void setResult(O result) { + set(result); + } + } +} diff --git a/src/main/java/com/google/common/util/concurrent/AggregateFuture.java b/src/main/java/com/google/common/util/concurrent/AggregateFuture.java new file mode 100644 index 0000000..594164b --- /dev/null +++ b/src/main/java/com/google/common/util/concurrent/AggregateFuture.java @@ -0,0 +1,342 @@ +/* + * Copyright (C) 2006 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.util.concurrent; + +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.base.Preconditions.checkState; +import static com.google.common.util.concurrent.AggregateFuture.ReleaseResourcesReason.ALL_INPUT_FUTURES_PROCESSED; +import static com.google.common.util.concurrent.AggregateFuture.ReleaseResourcesReason.OUTPUT_FUTURE_DONE; +import static com.google.common.util.concurrent.Futures.getDone; +import static com.google.common.util.concurrent.MoreExecutors.directExecutor; +import static java.util.logging.Level.SEVERE; + +import com.google.common.annotations.GwtCompatible; +import com.google.common.collect.ImmutableCollection; + +import java.util.Set; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; +import java.util.logging.Logger; + + +/** + * A future whose value is derived from a collection of input futures. + * + * @param the type of the individual inputs + * @param the type of the output (i.e. this) future + */ +@GwtCompatible +abstract class AggregateFuture extends AggregateFutureState { + private static final Logger logger = Logger.getLogger(AggregateFuture.class.getName()); + + /** + * The input futures. After {@link #init}, this field is read only by {@link #afterDone()} (to + * propagate cancellation) and {@link #toString()}. To access the futures' values, {@code + * AggregateFuture} attaches listeners that hold references to one or more inputs. And in the case + * of {@link CombinedFuture}, the user-supplied callback usually has its own references to inputs. + */ + /* + * In certain circumstances, this field might theoretically not be visible to an afterDone() call + * triggered by cancel(). For details, see the comments on the fields of TimeoutFuture. + */ + private ImmutableCollection> futures; + + private final boolean allMustSucceed; + private final boolean collectsValues; + + AggregateFuture( + ImmutableCollection> futures, + boolean allMustSucceed, + boolean collectsValues) { + super(futures.size()); + this.futures = checkNotNull(futures); + this.allMustSucceed = allMustSucceed; + this.collectsValues = collectsValues; + } + + @Override + protected final void afterDone() { + super.afterDone(); + + ImmutableCollection> localFutures = futures; + releaseResources(OUTPUT_FUTURE_DONE); // nulls out `futures` + + if (isCancelled() & localFutures != null) { + boolean wasInterrupted = wasInterrupted(); + for (Future future : localFutures) { + future.cancel(wasInterrupted); + } + } + /* + * We don't call clearSeenExceptions() until processCompleted(). Prior to that, it may be needed + * again if some outstanding input fails. + */ + } + + @Override + protected final String pendingToString() { + ImmutableCollection> localFutures = futures; + if (localFutures != null) { + return "futures=" + localFutures; + } + return super.pendingToString(); + } + + /** + * Must be called at the end of each subclass's constructor. This method performs the "real" + * initialization; we can't put this in the constructor because, in the case where futures are + * already complete, we would not initialize the subclass before calling {@link + * #collectValueFromNonCancelledFuture}. As this is called after the subclass is constructed, + * we're guaranteed to have properly initialized the subclass. + */ + final void init() { + // Corner case: List is empty. + if (futures.isEmpty()) { + handleAllCompleted(); + return; + } + + // NOTE: If we ever want to use a custom executor here, have a look at CombinedFuture as we'll + // need to handle RejectedExecutionException + + if (allMustSucceed) { + // We need fail fast, so we have to keep track of which future failed so we can propagate + // the exception immediately + + // Register a listener on each Future in the list to update the state of this future. + // Note that if all the futures on the list are done prior to completing this loop, the last + // call to addListener() will callback to setOneValue(), transitively call our cleanup + // listener, and set this.futures to null. + // This is not actually a problem, since the foreach only needs this.futures to be non-null + // at the beginning of the loop. + int i = 0; + for (final ListenableFuture future : futures) { + final int index = i++; + future.addListener( + new Runnable() { + @Override + public void run() { + try { + if (future.isCancelled()) { + // Clear futures prior to cancelling children. This sets our own state but lets + // the input futures keep running, as some of them may be used elsewhere. + futures = null; + cancel(false); + } else { + collectValueFromNonCancelledFuture(index, future); + } + } finally { + /* + * "null" means: There is no need to access `futures` again during + * `processCompleted` because we're reading each value during a call to + * handleOneInputDone. + */ + decrementCountAndMaybeComplete(null); + } + } + }, + directExecutor()); + } + } else { + /* + * We'll call the user callback or collect the values only when all inputs complete, + * regardless of whether some failed. This lets us avoid calling expensive methods like + * Future.get() when we don't need to (specifically, for whenAllComplete().call*()), and it + * lets all futures share the same listener. + * + * We store `localFutures` inside the listener because `this.futures` might be nulled out by + * the time the listener runs for the final future -- at which point we need to check all + * inputs for exceptions *if* we're collecting values. If we're not, then the listener doesn't + * need access to the futures again, so we can just pass `null`. + * + * TODO(b/112550045): Allocating a single, cheaper listener is (I think) only an optimization. + * If we make some other optimizations, this one will no longer be necessary. The optimization + * could actually hurt in some cases, as it forces us to keep all inputs in memory until the + * final input completes. + */ + final ImmutableCollection> localFutures = + collectsValues ? futures : null; + Runnable listener = + new Runnable() { + @Override + public void run() { + decrementCountAndMaybeComplete(localFutures); + } + }; + for (ListenableFuture future : futures) { + future.addListener(listener, directExecutor()); + } + } + } + + /** + * Fails this future with the given Throwable if {@link #allMustSucceed} is true. Also, logs the + * throwable if it is an {@link Error} or if {@link #allMustSucceed} is {@code true}, the + * throwable did not cause this future to fail, and it is the first time we've seen that + * particular Throwable. + */ + private void handleException(Throwable throwable) { + checkNotNull(throwable); + + if (allMustSucceed) { + // As soon as the first one fails, make that failure the result of the output future. + // The results of all other inputs are then ignored (except for logging any failures). + boolean completedWithFailure = setException(throwable); + if (!completedWithFailure) { + // Go up the causal chain to see if we've already seen this cause; if we have, even if + // it's wrapped by a different exception, don't log it. + boolean firstTimeSeeingThisException = addCausalChain(getOrInitSeenExceptions(), throwable); + if (firstTimeSeeingThisException) { + log(throwable); + return; + } + } + } + + /* + * TODO(cpovirk): Should whenAllComplete().call*() log errors, too? Currently, it doesn't call + * handleException() at all. + */ + if (throwable instanceof Error) { + /* + * TODO(cpovirk): Do we really want to log this if we called setException(throwable) and it + * returned true? This was intentional (CL 46470009), but it seems odd compared to how we + * normally handle Error. + * + * Similarly, do we really want to log the same Error more than once? + */ + log(throwable); + } + } + + private static void log(Throwable throwable) { + String message = + (throwable instanceof Error) + ? "Input Future failed with Error" + : "Got more than one input Future failure. Logging failures after the first"; + logger.log(SEVERE, message, throwable); + } + + @Override + final void addInitialException(Set seen) { + checkNotNull(seen); + if (!isCancelled()) { + // TODO(cpovirk): Think about whether we could/should use Verify to check this. + boolean unused = addCausalChain(seen, tryInternalFastPathGetFailure()); + } + } + + /** + * Collects the result (success or failure) of one input future. The input must not have been + * cancelled. For details on when this is called, see {@link #collectOneValue}. + */ + private void collectValueFromNonCancelledFuture(int index, Future future) { + try { + // We get the result, even if collectOneValue is a no-op, so that we can fail fast. + collectOneValue(index, getDone(future)); + } catch (ExecutionException e) { + handleException(e.getCause()); + } catch (Throwable t) { + handleException(t); + } + } + + private void decrementCountAndMaybeComplete( + + ImmutableCollection> + futuresIfNeedToCollectAtCompletion) { + int newRemaining = decrementRemainingAndGet(); + checkState(newRemaining >= 0, "Less than 0 remaining futures"); + if (newRemaining == 0) { + processCompleted(futuresIfNeedToCollectAtCompletion); + } + } + + private void processCompleted( + + ImmutableCollection> + futuresIfNeedToCollectAtCompletion) { + if (futuresIfNeedToCollectAtCompletion != null) { + int i = 0; + for (Future future : futuresIfNeedToCollectAtCompletion) { + if (!future.isCancelled()) { + collectValueFromNonCancelledFuture(i, future); + } + i++; + } + } + clearSeenExceptions(); + handleAllCompleted(); + /* + * Null out fields, including some used in handleAllCompleted() above (like + * `CollectionFuture.values`). This might be a no-op: If this future completed during + * handleAllCompleted(), they will already have been nulled out. But in the case of + * whenAll*().call*(), this future may be pending until the callback runs -- or even longer in + * the case of callAsync(), which waits for the callback's returned future to complete. + */ + releaseResources(ALL_INPUT_FUTURES_PROCESSED); + } + + /** + * Clears fields that are no longer needed after this future has completed -- or at least all its + * inputs have completed (more precisely, after {@link #handleAllCompleted()} has been called). + * Often called multiple times (that is, both when the inputs complete and when the output + * completes). + * + *

This is similar to our proposed {@code afterCommit} method but not quite the same. See the + * description of CL 265462958. + */ + // TODO(user): Write more tests for memory retention. + void releaseResources(ReleaseResourcesReason reason) { + checkNotNull(reason); + /* + * All elements of `futures` are completed, or this future has already completed and read + * `futures` into a local variable (in preparation for propagating cancellation to them). In + * either case, no one needs to read `futures` for cancellation purposes later. (And + * cancellation purposes are the main reason to access `futures`, as discussed in its docs.) + */ + this.futures = null; + } + + enum ReleaseResourcesReason { + OUTPUT_FUTURE_DONE, + ALL_INPUT_FUTURES_PROCESSED, + } + + /** + * If {@code allMustSucceed} is true, called as each future completes; otherwise, if {@code + * collectsValues} is true, called for each future when all futures complete. + */ + abstract void collectOneValue(int index, InputT returnValue); + + abstract void handleAllCompleted(); + + /** Adds the chain to the seen set, and returns whether all the chain was new to us. */ + private static boolean addCausalChain(Set seen, Throwable t) { + for (; t != null; t = t.getCause()) { + boolean firstTimeSeen = seen.add(t); + if (!firstTimeSeen) { + /* + * We've seen this, so we've seen its causes, too. No need to re-add them. (There's one case + * where this isn't true, but we ignore it: If we record an exception, then someone calls + * initCause() on it, and then we examine it again, we'll conclude that we've seen the whole + * chain before when it fact we haven't. But this should be rare.) + */ + return false; + } + } + return true; + } +} diff --git a/src/main/java/com/google/common/util/concurrent/AggregateFutureState.java b/src/main/java/com/google/common/util/concurrent/AggregateFutureState.java new file mode 100644 index 0000000..900c620 --- /dev/null +++ b/src/main/java/com/google/common/util/concurrent/AggregateFutureState.java @@ -0,0 +1,194 @@ +/* + * Copyright (C) 2015 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.util.concurrent; + +import static com.google.common.collect.Sets.newConcurrentHashSet; +import static java.util.concurrent.atomic.AtomicIntegerFieldUpdater.newUpdater; +import static java.util.concurrent.atomic.AtomicReferenceFieldUpdater.newUpdater; + +import com.google.common.annotations.GwtCompatible; + +import java.util.Set; +import java.util.concurrent.atomic.AtomicIntegerFieldUpdater; +import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * A helper which does some thread-safe operations for aggregate futures, which must be implemented + * differently in GWT. Namely: + * + *

    + *
  • Lazily initializes a set of seen exceptions + *
  • Decrements a counter atomically + *
+ */ +@GwtCompatible(emulated = true) +abstract class AggregateFutureState extends AbstractFuture.TrustedFuture { + // Lazily initialized the first time we see an exception; not released until all the input futures + // have completed and we have processed them all. + private volatile Set seenExceptions = null; + + private volatile int remaining; + + private static final AtomicHelper ATOMIC_HELPER; + + private static final Logger log = Logger.getLogger(AggregateFutureState.class.getName()); + + static { + AtomicHelper helper; + Throwable thrownReflectionFailure = null; + try { + helper = + new SafeAtomicHelper( + newUpdater(AggregateFutureState.class, (Class) Set.class, "seenExceptions"), + newUpdater(AggregateFutureState.class, "remaining")); + } catch (Throwable reflectionFailure) { + // Some Android 5.0.x Samsung devices have bugs in JDK reflection APIs that cause + // getDeclaredField to throw a NoSuchFieldException when the field is definitely there. + // For these users fallback to a suboptimal implementation, based on synchronized. This will + // be a definite performance hit to those users. + thrownReflectionFailure = reflectionFailure; + helper = new SynchronizedAtomicHelper(); + } + ATOMIC_HELPER = helper; + // Log after all static init is finished; if an installed logger uses any Futures methods, it + // shouldn't break in cases where reflection is missing/broken. + if (thrownReflectionFailure != null) { + log.log(Level.SEVERE, "SafeAtomicHelper is broken!", thrownReflectionFailure); + } + } + + AggregateFutureState(int remainingFutures) { + this.remaining = remainingFutures; + } + + final Set getOrInitSeenExceptions() { + /* + * The initialization of seenExceptions has to be more complicated than we'd like. The simple + * approach would be for each caller CAS it from null to a Set populated with its exception. But + * there's another race: If the first thread fails with an exception and a second thread + * immediately fails with the same exception: + * + * Thread1: calls setException(), which returns true, context switch before it can CAS + * seenExceptions to its exception + * + * Thread2: calls setException(), which returns false, CASes seenExceptions to its exception, + * and wrongly believes that its exception is new (leading it to logging it when it shouldn't) + * + * Our solution is for threads to CAS seenExceptions from null to a Set populated with _the + * initial exception_, no matter which thread does the work. This ensures that seenExceptions + * always contains not just the current thread's exception but also the initial thread's. + */ + Set seenExceptionsLocal = seenExceptions; + if (seenExceptionsLocal == null) { + // TODO(cpovirk): Should we use a simpler (presumably cheaper) data structure? + /* + * Using weak references here could let us release exceptions earlier, but: + * + * 1. On Android, querying a WeakReference blocks if the GC is doing an otherwise-concurrent + * pass. + * + * 2. We would probably choose to compare exceptions using == instead of equals() (for + * consistency with how weak references are cleared). That's a behavior change -- arguably the + * removal of a feature. + * + * Fortunately, exceptions rarely contain references to expensive resources. + */ + + // + seenExceptionsLocal = newConcurrentHashSet(); + /* + * Other handleException() callers may see this as soon as we publish it. We need to populate + * it with the initial failure before we do, or else they may think that the initial failure + * has never been seen before. + */ + addInitialException(seenExceptionsLocal); + + ATOMIC_HELPER.compareAndSetSeenExceptions(this, null, seenExceptionsLocal); + /* + * If another handleException() caller created the set, we need to use that copy in case yet + * other callers have added to it. + * + * This read is guaranteed to get us the right value because we only set this once (here). + */ + seenExceptionsLocal = seenExceptions; + } + return seenExceptionsLocal; + } + + /** Populates {@code seen} with the exception that was passed to {@code setException}. */ + abstract void addInitialException(Set seen); + + final int decrementRemainingAndGet() { + return ATOMIC_HELPER.decrementAndGetRemainingCount(this); + } + + final void clearSeenExceptions() { + seenExceptions = null; + } + + private abstract static class AtomicHelper { + /** Atomic compare-and-set of the {@link AggregateFutureState#seenExceptions} field. */ + abstract void compareAndSetSeenExceptions( + AggregateFutureState state, Set expect, Set update); + + /** Atomic decrement-and-get of the {@link AggregateFutureState#remaining} field. */ + abstract int decrementAndGetRemainingCount(AggregateFutureState state); + } + + private static final class SafeAtomicHelper extends AtomicHelper { + final AtomicReferenceFieldUpdater> seenExceptionsUpdater; + + final AtomicIntegerFieldUpdater remainingCountUpdater; + + SafeAtomicHelper( + AtomicReferenceFieldUpdater seenExceptionsUpdater, + AtomicIntegerFieldUpdater remainingCountUpdater) { + this.seenExceptionsUpdater = seenExceptionsUpdater; + this.remainingCountUpdater = remainingCountUpdater; + } + + @Override + void compareAndSetSeenExceptions( + AggregateFutureState state, Set expect, Set update) { + seenExceptionsUpdater.compareAndSet(state, expect, update); + } + + @Override + int decrementAndGetRemainingCount(AggregateFutureState state) { + return remainingCountUpdater.decrementAndGet(state); + } + } + + private static final class SynchronizedAtomicHelper extends AtomicHelper { + @Override + void compareAndSetSeenExceptions( + AggregateFutureState state, Set expect, Set update) { + synchronized (state) { + if (state.seenExceptions == expect) { + state.seenExceptions = update; + } + } + } + + @Override + int decrementAndGetRemainingCount(AggregateFutureState state) { + synchronized (state) { + return --state.remaining; + } + } + } +} diff --git a/src/main/java/com/google/common/util/concurrent/AsyncCallable.java b/src/main/java/com/google/common/util/concurrent/AsyncCallable.java new file mode 100644 index 0000000..f39a882 --- /dev/null +++ b/src/main/java/com/google/common/util/concurrent/AsyncCallable.java @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2015 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.util.concurrent; + +import com.google.common.annotations.Beta; +import com.google.common.annotations.GwtCompatible; +import java.util.concurrent.Future; + +/** + * Computes a value, possibly asynchronously. For an example usage and more information, see {@link + * Futures.FutureCombiner#callAsync(AsyncCallable, java.util.concurrent.Executor)}. + * + *

Much like {@link java.util.concurrent.Callable}, but returning a {@link ListenableFuture} + * result. + * + * @since 20.0 + */ +@Beta +@FunctionalInterface +@GwtCompatible +public interface AsyncCallable { + /** + * Computes a result {@code Future}. The output {@code Future} need not be {@linkplain + * Future#isDone done}, making {@code AsyncCallable} suitable for asynchronous derivations. + * + *

Throwing an exception from this method is equivalent to returning a failing {@link + * ListenableFuture}. + */ + ListenableFuture call() throws Exception; +} diff --git a/src/main/java/com/google/common/util/concurrent/AsyncFunction.java b/src/main/java/com/google/common/util/concurrent/AsyncFunction.java new file mode 100644 index 0000000..078af2b --- /dev/null +++ b/src/main/java/com/google/common/util/concurrent/AsyncFunction.java @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2011 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.util.concurrent; + +import com.google.common.annotations.GwtCompatible; +import java.util.concurrent.Future; + + +/** + * Transforms a value, possibly asynchronously. For an example usage and more information, see + * {@link Futures#transformAsync(ListenableFuture, AsyncFunction, Executor)}. + * + * @author Chris Povirk + * @since 11.0 + */ +@GwtCompatible +@FunctionalInterface +public interface AsyncFunction { + /** + * Returns an output {@code Future} to use in place of the given {@code input}. The output {@code + * Future} need not be {@linkplain Future#isDone done}, making {@code AsyncFunction} suitable for + * asynchronous derivations. + * + *

Throwing an exception from this method is equivalent to returning a failing {@code Future}. + */ + ListenableFuture apply(I input) throws Exception; +} diff --git a/src/main/java/com/google/common/util/concurrent/AtomicDouble.java b/src/main/java/com/google/common/util/concurrent/AtomicDouble.java new file mode 100644 index 0000000..f20a38e --- /dev/null +++ b/src/main/java/com/google/common/util/concurrent/AtomicDouble.java @@ -0,0 +1,247 @@ +/* + * 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/ + */ + +/* + * Source: + * http://gee.cs.oswego.edu/cgi-bin/viewcvs.cgi/jsr166/src/jsr166e/extra/AtomicDouble.java?revision=1.13 + * (Modified to adapt to guava coding conventions and + * to use AtomicLongFieldUpdater instead of sun.misc.Unsafe) + */ + +package com.google.common.util.concurrent; + +import static java.lang.Double.doubleToRawLongBits; +import static java.lang.Double.longBitsToDouble; + +import com.google.common.annotations.GwtIncompatible; + + +import java.util.concurrent.atomic.AtomicLongFieldUpdater; + +/** + * A {@code double} value that may be updated atomically. See the {@link + * java.util.concurrent.atomic} package specification for description of the properties of atomic + * variables. An {@code AtomicDouble} is used in applications such as atomic accumulation, and + * cannot be used as a replacement for a {@link Double}. However, this class does extend {@code + * Number} to allow uniform access by tools and utilities that deal with numerically-based classes. + * + *

{@code
+ * static boolean bitEquals(double x, double y) {
+ *   long xBits = Double.doubleToRawLongBits(x);
+ *   long yBits = Double.doubleToRawLongBits(y);
+ *   return xBits == yBits;
+ * }
+ * }
+ * + *

It is possible to write a more scalable updater, at the cost of giving up strict atomicity. + * See for example + * DoubleAdder. + * + * @author Doug Lea + * @author Martin Buchholz + * @since 11.0 + */ +@GwtIncompatible +public class AtomicDouble extends Number implements java.io.Serializable { + private static final long serialVersionUID = 0L; + + private transient volatile long value; + + private static final AtomicLongFieldUpdater updater = + AtomicLongFieldUpdater.newUpdater(AtomicDouble.class, "value"); + + /** + * Creates a new {@code AtomicDouble} with the given initial value. + * + * @param initialValue the initial value + */ + public AtomicDouble(double initialValue) { + value = doubleToRawLongBits(initialValue); + } + + /** Creates a new {@code AtomicDouble} with initial value {@code 0.0}. */ + public AtomicDouble() { + // assert doubleToRawLongBits(0.0) == 0L; + } + + /** + * Gets the current value. + * + * @return the current value + */ + public final double get() { + return longBitsToDouble(value); + } + + /** + * Sets to the given value. + * + * @param newValue the new value + */ + public final void set(double newValue) { + long next = doubleToRawLongBits(newValue); + value = next; + } + + /** + * Eventually sets to the given value. + * + * @param newValue the new value + */ + public final void lazySet(double newValue) { + long next = doubleToRawLongBits(newValue); + updater.lazySet(this, next); + } + + /** + * Atomically sets to the given value and returns the old value. + * + * @param newValue the new value + * @return the previous value + */ + public final double getAndSet(double newValue) { + long next = doubleToRawLongBits(newValue); + return longBitsToDouble(updater.getAndSet(this, next)); + } + + /** + * Atomically sets the value to the given updated value if the current value is bitwise equal to the expected value. + * + * @param expect the expected value + * @param update the new value + * @return {@code true} if successful. False return indicates that the actual value was not + * bitwise equal to the expected value. + */ + public final boolean compareAndSet(double expect, double update) { + return updater.compareAndSet(this, doubleToRawLongBits(expect), doubleToRawLongBits(update)); + } + + /** + * Atomically sets the value to the given updated value if the current value is bitwise equal to the expected value. + * + *

May + * fail spuriously and does not provide ordering guarantees, so is only rarely an appropriate + * alternative to {@code compareAndSet}. + * + * @param expect the expected value + * @param update the new value + * @return {@code true} if successful + */ + public final boolean weakCompareAndSet(double expect, double update) { + return updater.weakCompareAndSet( + this, doubleToRawLongBits(expect), doubleToRawLongBits(update)); + } + + /** + * Atomically adds the given value to the current value. + * + * @param delta the value to add + * @return the previous value + */ + + public final double getAndAdd(double delta) { + while (true) { + long current = value; + double currentVal = longBitsToDouble(current); + double nextVal = currentVal + delta; + long next = doubleToRawLongBits(nextVal); + if (updater.compareAndSet(this, current, next)) { + return currentVal; + } + } + } + + /** + * Atomically adds the given value to the current value. + * + * @param delta the value to add + * @return the updated value + */ + + public final double addAndGet(double delta) { + while (true) { + long current = value; + double currentVal = longBitsToDouble(current); + double nextVal = currentVal + delta; + long next = doubleToRawLongBits(nextVal); + if (updater.compareAndSet(this, current, next)) { + return nextVal; + } + } + } + + /** + * Returns the String representation of the current value. + * + * @return the String representation of the current value + */ + @Override + public String toString() { + return Double.toString(get()); + } + + /** + * Returns the value of this {@code AtomicDouble} as an {@code int} after a narrowing primitive + * conversion. + */ + @Override + public int intValue() { + return (int) get(); + } + + /** + * Returns the value of this {@code AtomicDouble} as a {@code long} after a narrowing primitive + * conversion. + */ + @Override + public long longValue() { + return (long) get(); + } + + /** + * Returns the value of this {@code AtomicDouble} as a {@code float} after a narrowing primitive + * conversion. + */ + @Override + public float floatValue() { + return (float) get(); + } + + /** Returns the value of this {@code AtomicDouble} as a {@code double}. */ + @Override + public double doubleValue() { + return get(); + } + + /** + * Saves the state to a stream (that is, serializes it). + * + * @serialData The current value is emitted (a {@code double}). + */ + private void writeObject(java.io.ObjectOutputStream s) throws java.io.IOException { + s.defaultWriteObject(); + + s.writeDouble(get()); + } + + /** Reconstitutes the instance from a stream (that is, deserializes it). */ + private void readObject(java.io.ObjectInputStream s) + throws java.io.IOException, ClassNotFoundException { + s.defaultReadObject(); + + set(s.readDouble()); + } +} diff --git a/src/main/java/com/google/common/util/concurrent/AtomicDoubleArray.java b/src/main/java/com/google/common/util/concurrent/AtomicDoubleArray.java new file mode 100644 index 0000000..e45bf92 --- /dev/null +++ b/src/main/java/com/google/common/util/concurrent/AtomicDoubleArray.java @@ -0,0 +1,259 @@ +/* + * 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/ + */ + +/* + * Source: + * http://gee.cs.oswego.edu/cgi-bin/viewcvs.cgi/jsr166/src/jsr166e/extra/AtomicDoubleArray.java?revision=1.5 + * (Modified to adapt to guava coding conventions and + * to use AtomicLongArray instead of sun.misc.Unsafe) + */ + +package com.google.common.util.concurrent; + +import static java.lang.Double.doubleToRawLongBits; +import static java.lang.Double.longBitsToDouble; + +import com.google.common.annotations.GwtIncompatible; +import com.google.common.primitives.ImmutableLongArray; + +import java.util.concurrent.atomic.AtomicLongArray; + +/** + * A {@code double} array in which elements may be updated atomically. See the {@link + * java.util.concurrent.atomic} package specification for description of the properties of atomic + * variables. + * + *

This class compares primitive {@code double} values in methods such as + * {@link #compareAndSet} by comparing their bitwise representation using {@link + * Double#doubleToRawLongBits}, which differs from both the primitive double {@code ==} operator and + * from {@link Double#equals}, as if implemented by: + * + *

{@code
+ * static boolean bitEquals(double x, double y) {
+ *   long xBits = Double.doubleToRawLongBits(x);
+ *   long yBits = Double.doubleToRawLongBits(y);
+ *   return xBits == yBits;
+ * }
+ * }
+ * + * @author Doug Lea + * @author Martin Buchholz + * @since 11.0 + */ +@GwtIncompatible +public class AtomicDoubleArray implements java.io.Serializable { + private static final long serialVersionUID = 0L; + + // Making this non-final is the lesser evil according to Effective + // Java 2nd Edition Item 76: Write readObject methods defensively. + private transient AtomicLongArray longs; + + /** + * Creates a new {@code AtomicDoubleArray} of the given length, with all elements initially zero. + * + * @param length the length of the array + */ + public AtomicDoubleArray(int length) { + this.longs = new AtomicLongArray(length); + } + + /** + * Creates a new {@code AtomicDoubleArray} with the same length as, and all elements copied from, + * the given array. + * + * @param array the array to copy elements from + * @throws NullPointerException if array is null + */ + public AtomicDoubleArray(double[] array) { + final int len = array.length; + long[] longArray = new long[len]; + for (int i = 0; i < len; i++) { + longArray[i] = doubleToRawLongBits(array[i]); + } + this.longs = new AtomicLongArray(longArray); + } + + /** + * Returns the length of the array. + * + * @return the length of the array + */ + public final int length() { + return longs.length(); + } + + /** + * Gets the current value at position {@code i}. + * + * @param i the index + * @return the current value + */ + public final double get(int i) { + return longBitsToDouble(longs.get(i)); + } + + /** + * Sets the element at position {@code i} to the given value. + * + * @param i the index + * @param newValue the new value + */ + public final void set(int i, double newValue) { + long next = doubleToRawLongBits(newValue); + longs.set(i, next); + } + + /** + * Eventually sets the element at position {@code i} to the given value. + * + * @param i the index + * @param newValue the new value + */ + public final void lazySet(int i, double newValue) { + long next = doubleToRawLongBits(newValue); + longs.lazySet(i, next); + } + + /** + * Atomically sets the element at position {@code i} to the given value and returns the old value. + * + * @param i the index + * @param newValue the new value + * @return the previous value + */ + public final double getAndSet(int i, double newValue) { + long next = doubleToRawLongBits(newValue); + return longBitsToDouble(longs.getAndSet(i, next)); + } + + /** + * Atomically sets the element at position {@code i} to the given updated value if the current + * value is bitwise equal to the expected value. + * + * @param i the index + * @param expect the expected value + * @param update the new value + * @return true if successful. False return indicates that the actual value was not equal to the + * expected value. + */ + public final boolean compareAndSet(int i, double expect, double update) { + return longs.compareAndSet(i, doubleToRawLongBits(expect), doubleToRawLongBits(update)); + } + + /** + * Atomically sets the element at position {@code i} to the given updated value if the current + * value is bitwise equal to the expected value. + * + *

May + * fail spuriously and does not provide ordering guarantees, so is only rarely an appropriate + * alternative to {@code compareAndSet}. + * + * @param i the index + * @param expect the expected value + * @param update the new value + * @return true if successful + */ + public final boolean weakCompareAndSet(int i, double expect, double update) { + return longs.weakCompareAndSet(i, doubleToRawLongBits(expect), doubleToRawLongBits(update)); + } + + /** + * Atomically adds the given value to the element at index {@code i}. + * + * @param i the index + * @param delta the value to add + * @return the previous value + */ + + public final double getAndAdd(int i, double delta) { + while (true) { + long current = longs.get(i); + double currentVal = longBitsToDouble(current); + double nextVal = currentVal + delta; + long next = doubleToRawLongBits(nextVal); + if (longs.compareAndSet(i, current, next)) { + return currentVal; + } + } + } + + /** + * Atomically adds the given value to the element at index {@code i}. + * + * @param i the index + * @param delta the value to add + * @return the updated value + */ + + public double addAndGet(int i, double delta) { + while (true) { + long current = longs.get(i); + double currentVal = longBitsToDouble(current); + double nextVal = currentVal + delta; + long next = doubleToRawLongBits(nextVal); + if (longs.compareAndSet(i, current, next)) { + return nextVal; + } + } + } + + /** + * Returns the String representation of the current values of array. + * + * @return the String representation of the current values of array + */ + @Override + public String toString() { + int iMax = length() - 1; + if (iMax == -1) { + return "[]"; + } + + // Double.toString(Math.PI).length() == 17 + StringBuilder b = new StringBuilder((17 + 2) * (iMax + 1)); + b.append('['); + for (int i = 0; ; i++) { + b.append(longBitsToDouble(longs.get(i))); + if (i == iMax) { + return b.append(']').toString(); + } + b.append(',').append(' '); + } + } + + /** + * Saves the state to a stream (that is, serializes it). + * + * @serialData The length of the array is emitted (int), followed by all of its elements (each a + * {@code double}) in the proper order. + */ + private void writeObject(java.io.ObjectOutputStream s) throws java.io.IOException { + s.defaultWriteObject(); + + // Write out array length + int length = length(); + s.writeInt(length); + + // Write out all elements in the proper order. + for (int i = 0; i < length; i++) { + s.writeDouble(get(i)); + } + } + + /** Reconstitutes the instance from a stream (that is, deserializes it). */ + private void readObject(java.io.ObjectInputStream s) + throws java.io.IOException, ClassNotFoundException { + s.defaultReadObject(); + + int length = s.readInt(); + ImmutableLongArray.Builder builder = ImmutableLongArray.builder(); + for (int i = 0; i < length; i++) { + builder.add(doubleToRawLongBits(s.readDouble())); + } + this.longs = new AtomicLongArray(builder.build().toArray()); + } +} diff --git a/src/main/java/com/google/common/util/concurrent/AtomicLongMap.java b/src/main/java/com/google/common/util/concurrent/AtomicLongMap.java new file mode 100644 index 0000000..9f08428 --- /dev/null +++ b/src/main/java/com/google/common/util/concurrent/AtomicLongMap.java @@ -0,0 +1,345 @@ +/* + * Copyright (C) 2011 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.util.concurrent; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.annotations.Beta; +import com.google.common.annotations.GwtCompatible; + +import java.io.Serializable; +import java.util.Collections; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicLong; +import java.util.function.LongBinaryOperator; +import java.util.function.LongUnaryOperator; + + +/** + * A map containing {@code long} values that can be atomically updated. While writes to a + * traditional {@code Map} rely on {@code put(K, V)}, the typical mechanism for writing to this map + * is {@code addAndGet(K, long)}, which adds a {@code long} to the value currently associated with + * {@code K}. If a key has not yet been associated with a value, its implicit value is zero. + * + *

Most methods in this class treat absent values and zero values identically, as individually + * documented. Exceptions to this are {@link #containsKey}, {@link #size}, {@link #isEmpty}, {@link + * #asMap}, and {@link #toString}. + * + *

Instances of this class may be used by multiple threads concurrently. All operations are + * atomic unless otherwise noted. + * + *

Note: If your values are always positive and less than 2^31, you may wish to use a + * {@link com.google.common.collect.Multiset} such as {@link + * com.google.common.collect.ConcurrentHashMultiset} instead. + * + *

Warning: Unlike {@code Multiset}, entries whose values are zero are not automatically + * removed from the map. Instead they must be removed manually with {@link #removeAllZeros}. + * + * @author Charles Fry + * @since 11.0 + */ +@GwtCompatible +public final class AtomicLongMap implements Serializable { + private final ConcurrentHashMap map; + + private AtomicLongMap(ConcurrentHashMap map) { + this.map = checkNotNull(map); + } + + /** Creates an {@code AtomicLongMap}. */ + public static AtomicLongMap create() { + return new AtomicLongMap(new ConcurrentHashMap<>()); + } + + /** Creates an {@code AtomicLongMap} with the same mappings as the specified {@code Map}. */ + public static AtomicLongMap create(Map m) { + AtomicLongMap result = create(); + result.putAll(m); + return result; + } + + /** + * Returns the value associated with {@code key}, or zero if there is no value associated with + * {@code key}. + */ + public long get(K key) { + return map.getOrDefault(key, 0L); + } + + /** + * Increments by one the value currently associated with {@code key}, and returns the new value. + */ + + public long incrementAndGet(K key) { + return addAndGet(key, 1); + } + + /** + * Decrements by one the value currently associated with {@code key}, and returns the new value. + */ + + public long decrementAndGet(K key) { + return addAndGet(key, -1); + } + + /** + * Adds {@code delta} to the value currently associated with {@code key}, and returns the new + * value. + */ + + public long addAndGet(K key, long delta) { + return accumulateAndGet(key, delta, Long::sum); + } + + /** + * Increments by one the value currently associated with {@code key}, and returns the old value. + */ + + public long getAndIncrement(K key) { + return getAndAdd(key, 1); + } + + /** + * Decrements by one the value currently associated with {@code key}, and returns the old value. + */ + + public long getAndDecrement(K key) { + return getAndAdd(key, -1); + } + + /** + * Adds {@code delta} to the value currently associated with {@code key}, and returns the old + * value. + */ + + public long getAndAdd(K key, long delta) { + return getAndAccumulate(key, delta, Long::sum); + } + + /** + * Updates the value currently associated with {@code key} with the specified function, and + * returns the new value. If there is not currently a value associated with {@code key}, the + * function is applied to {@code 0L}. + * + * @since 21.0 + */ + + public long updateAndGet(K key, LongUnaryOperator updaterFunction) { + checkNotNull(updaterFunction); + return map.compute( + key, (k, value) -> updaterFunction.applyAsLong((value == null) ? 0L : value.longValue())); + } + + /** + * Updates the value currently associated with {@code key} with the specified function, and + * returns the old value. If there is not currently a value associated with {@code key}, the + * function is applied to {@code 0L}. + * + * @since 21.0 + */ + + public long getAndUpdate(K key, LongUnaryOperator updaterFunction) { + checkNotNull(updaterFunction); + AtomicLong holder = new AtomicLong(); + map.compute( + key, + (k, value) -> { + long oldValue = (value == null) ? 0L : value.longValue(); + holder.set(oldValue); + return updaterFunction.applyAsLong(oldValue); + }); + return holder.get(); + } + + /** + * Updates the value currently associated with {@code key} by combining it with {@code x} via the + * specified accumulator function, returning the new value. The previous value associated with + * {@code key} (or zero, if there is none) is passed as the first argument to {@code + * accumulatorFunction}, and {@code x} is passed as the second argument. + * + * @since 21.0 + */ + + public long accumulateAndGet(K key, long x, LongBinaryOperator accumulatorFunction) { + checkNotNull(accumulatorFunction); + return updateAndGet(key, oldValue -> accumulatorFunction.applyAsLong(oldValue, x)); + } + + /** + * Updates the value currently associated with {@code key} by combining it with {@code x} via the + * specified accumulator function, returning the old value. The previous value associated with + * {@code key} (or zero, if there is none) is passed as the first argument to {@code + * accumulatorFunction}, and {@code x} is passed as the second argument. + * + * @since 21.0 + */ + + public long getAndAccumulate(K key, long x, LongBinaryOperator accumulatorFunction) { + checkNotNull(accumulatorFunction); + return getAndUpdate(key, oldValue -> accumulatorFunction.applyAsLong(oldValue, x)); + } + + /** + * Associates {@code newValue} with {@code key} in this map, and returns the value previously + * associated with {@code key}, or zero if there was no such value. + */ + + public long put(K key, long newValue) { + return getAndUpdate(key, x -> newValue); + } + + /** + * Copies all of the mappings from the specified map to this map. The effect of this call is + * equivalent to that of calling {@code put(k, v)} on this map once for each mapping from key + * {@code k} to value {@code v} in the specified map. The behavior of this operation is undefined + * if the specified map is modified while the operation is in progress. + */ + public void putAll(Map m) { + m.forEach(this::put); + } + + /** + * Removes and returns the value associated with {@code key}. If {@code key} is not in the map, + * this method has no effect and returns zero. + */ + + public long remove(K key) { + Long result = map.remove(key); + return (result == null) ? 0L : result.longValue(); + } + + /** + * If {@code (key, value)} is currently in the map, this method removes it and returns true; + * otherwise, this method returns false. + */ + boolean remove(K key, long value) { + return map.remove(key, value); + } + + /** + * Atomically remove {@code key} from the map iff its associated value is 0. + * + * @since 20.0 + */ + @Beta + + public boolean removeIfZero(K key) { + return remove(key, 0); + } + + /** + * Removes all mappings from this map whose values are zero. + * + *

This method is not atomic: the map may be visible in intermediate states, where some of the + * zero values have been removed and others have not. + */ + public void removeAllZeros() { + map.values().removeIf(x -> x == 0); + } + + /** + * Returns the sum of all values in this map. + * + *

This method is not atomic: the sum may or may not include other concurrent operations. + */ + public long sum() { + return map.values().stream().mapToLong(Long::longValue).sum(); + } + + private transient Map asMap; + + /** Returns a live, read-only view of the map backing this {@code AtomicLongMap}. */ + public Map asMap() { + Map result = asMap; + return (result == null) ? asMap = createAsMap() : result; + } + + private Map createAsMap() { + return Collections.unmodifiableMap(map); + } + + /** Returns true if this map contains a mapping for the specified key. */ + public boolean containsKey(Object key) { + return map.containsKey(key); + } + + /** + * Returns the number of key-value mappings in this map. If the map contains more than {@code + * Integer.MAX_VALUE} elements, returns {@code Integer.MAX_VALUE}. + */ + public int size() { + return map.size(); + } + + /** Returns {@code true} if this map contains no key-value mappings. */ + public boolean isEmpty() { + return map.isEmpty(); + } + + /** + * Removes all of the mappings from this map. The map will be empty after this call returns. + * + *

This method is not atomic: the map may not be empty after returning if there were concurrent + * writes. + */ + public void clear() { + map.clear(); + } + + @Override + public String toString() { + return map.toString(); + } + + /** + * If {@code key} is not already associated with a value or if {@code key} is associated with + * zero, associate it with {@code newValue}. Returns the previous value associated with {@code + * key}, or zero if there was no mapping for {@code key}. + */ + long putIfAbsent(K key, long newValue) { + AtomicBoolean noValue = new AtomicBoolean(false); + Long result = + map.compute( + key, + (k, oldValue) -> { + if (oldValue == null || oldValue == 0) { + noValue.set(true); + return newValue; + } else { + return oldValue; + } + }); + return noValue.get() ? 0L : result.longValue(); + } + + /** + * If {@code (key, expectedOldValue)} is currently in the map, this method replaces {@code + * expectedOldValue} with {@code newValue} and returns true; otherwise, this method returns false. + * + *

If {@code expectedOldValue} is zero, this method will succeed if {@code (key, zero)} is + * currently in the map, or if {@code key} is not in the map at all. + */ + boolean replace(K key, long expectedOldValue, long newValue) { + if (expectedOldValue == 0L) { + return putIfAbsent(key, newValue) == 0L; + } else { + return map.replace(key, expectedOldValue, newValue); + } + } +} diff --git a/src/main/java/com/google/common/util/concurrent/Atomics.java b/src/main/java/com/google/common/util/concurrent/Atomics.java new file mode 100644 index 0000000..69e3a21 --- /dev/null +++ b/src/main/java/com/google/common/util/concurrent/Atomics.java @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2010 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.util.concurrent; + +import com.google.common.annotations.GwtIncompatible; +import java.util.concurrent.atomic.AtomicReference; +import java.util.concurrent.atomic.AtomicReferenceArray; + + +/** + * Static utility methods pertaining to classes in the {@code java.util.concurrent.atomic} package. + * + * @author Kurt Alfred Kluever + * @since 10.0 + */ +@GwtIncompatible +public final class Atomics { + private Atomics() {} + + /** + * Creates an {@code AtomicReference} instance with no initial value. + * + * @return a new {@code AtomicReference} with no initial value + */ + public static AtomicReference newReference() { + return new AtomicReference(); + } + + /** + * Creates an {@code AtomicReference} instance with the given initial value. + * + * @param initialValue the initial value + * @return a new {@code AtomicReference} with the given initial value + */ + public static AtomicReference newReference(V initialValue) { + return new AtomicReference(initialValue); + } + + /** + * Creates an {@code AtomicReferenceArray} instance of given length. + * + * @param length the length of the array + * @return a new {@code AtomicReferenceArray} with the given length + */ + public static AtomicReferenceArray newReferenceArray(int length) { + return new AtomicReferenceArray(length); + } + + /** + * Creates an {@code AtomicReferenceArray} instance with the same length as, and all elements + * copied from, the given array. + * + * @param array the array to copy elements from + * @return a new {@code AtomicReferenceArray} copied from the given array + */ + public static AtomicReferenceArray newReferenceArray(E[] array) { + return new AtomicReferenceArray(array); + } +} diff --git a/src/main/java/com/google/common/util/concurrent/Callables.java b/src/main/java/com/google/common/util/concurrent/Callables.java new file mode 100644 index 0000000..ab7b49a --- /dev/null +++ b/src/main/java/com/google/common/util/concurrent/Callables.java @@ -0,0 +1,143 @@ +/* + * Copyright (C) 2009 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.util.concurrent; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.annotations.Beta; +import com.google.common.annotations.GwtCompatible; +import com.google.common.annotations.GwtIncompatible; +import com.google.common.base.Supplier; +import java.util.concurrent.Callable; + + +/** + * Static utility methods pertaining to the {@link Callable} interface. + * + * @author Isaac Shum + * @since 1.0 + */ +@GwtCompatible(emulated = true) +public final class Callables { + private Callables() {} + + /** Creates a {@code Callable} which immediately returns a preset value each time it is called. */ + public static Callable returning(final T value) { + return new Callable() { + @Override + public T call() { + return value; + } + }; + } + + /** + * Creates an {@link AsyncCallable} from a {@link Callable}. + * + *

The {@link AsyncCallable} returns the {@link ListenableFuture} resulting from {@link + * ListeningExecutorService#submit(Callable)}. + * + * @since 20.0 + */ + @Beta + @GwtIncompatible + public static AsyncCallable asAsyncCallable( + final Callable callable, final ListeningExecutorService listeningExecutorService) { + checkNotNull(callable); + checkNotNull(listeningExecutorService); + return new AsyncCallable() { + @Override + public ListenableFuture call() throws Exception { + return listeningExecutorService.submit(callable); + } + }; + } + + /** + * Wraps the given callable such that for the duration of {@link Callable#call} the thread that is + * running will have the given name. + * + * + * @param callable The callable to wrap + * @param nameSupplier The supplier of thread names, {@link Supplier#get get} will be called once + * for each invocation of the wrapped callable. + */ + @GwtIncompatible // threads + static Callable threadRenaming( + final Callable callable, final Supplier nameSupplier) { + checkNotNull(nameSupplier); + checkNotNull(callable); + return new Callable() { + @Override + public T call() throws Exception { + Thread currentThread = Thread.currentThread(); + String oldName = currentThread.getName(); + boolean restoreName = trySetName(nameSupplier.get(), currentThread); + try { + return callable.call(); + } finally { + if (restoreName) { + boolean unused = trySetName(oldName, currentThread); + } + } + } + }; + } + + /** + * Wraps the given runnable such that for the duration of {@link Runnable#run} the thread that is + * running with have the given name. + * + * + * @param task The Runnable to wrap + * @param nameSupplier The supplier of thread names, {@link Supplier#get get} will be called once + * for each invocation of the wrapped callable. + */ + @GwtIncompatible // threads + static Runnable threadRenaming(final Runnable task, final Supplier nameSupplier) { + checkNotNull(nameSupplier); + checkNotNull(task); + return new Runnable() { + @Override + public void run() { + Thread currentThread = Thread.currentThread(); + String oldName = currentThread.getName(); + boolean restoreName = trySetName(nameSupplier.get(), currentThread); + try { + task.run(); + } finally { + if (restoreName) { + boolean unused = trySetName(oldName, currentThread); + } + } + } + }; + } + + /** Tries to set name of the given {@link Thread}, returns true if successful. */ + @GwtIncompatible // threads + private static boolean trySetName(final String threadName, Thread currentThread) { + /* + * setName should usually succeed, but the security manager can prohibit it. Is there a way to + * see if we have the modifyThread permission without catching an exception? + */ + try { + currentThread.setName(threadName); + return true; + } catch (SecurityException e) { + return false; + } + } +} diff --git a/src/main/java/com/google/common/util/concurrent/CollectionFuture.java b/src/main/java/com/google/common/util/concurrent/CollectionFuture.java new file mode 100644 index 0000000..3131f09 --- /dev/null +++ b/src/main/java/com/google/common/util/concurrent/CollectionFuture.java @@ -0,0 +1,91 @@ +/* + * Copyright (C) 2006 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.util.concurrent; + +import static com.google.common.collect.Lists.newArrayListWithCapacity; +import static java.util.Collections.unmodifiableList; + +import com.google.common.annotations.GwtCompatible; +import com.google.common.base.Optional; +import com.google.common.collect.ImmutableCollection; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Lists; +import java.util.List; + + +/** Aggregate future that collects (stores) results of each future. */ +@GwtCompatible(emulated = true) +abstract class CollectionFuture extends AggregateFuture { + private List> values; + + CollectionFuture( + ImmutableCollection> futures, + boolean allMustSucceed) { + super(futures, allMustSucceed, true); + + this.values = + futures.isEmpty() + ? ImmutableList.>of() + : Lists.>newArrayListWithCapacity(futures.size()); + + // Populate the results list with null initially. + for (int i = 0; i < futures.size(); ++i) { + values.add(null); + } + } + + @Override + final void collectOneValue(int index, V returnValue) { + List> localValues = values; + if (localValues != null) { + localValues.set(index, Optional.fromNullable(returnValue)); + } + } + + @Override + final void handleAllCompleted() { + List> localValues = values; + if (localValues != null) { + set(combine(localValues)); + } + } + + @Override + void releaseResources(ReleaseResourcesReason reason) { + super.releaseResources(reason); + this.values = null; + } + + abstract C combine(List> values); + + /** Used for {@link Futures#allAsList} and {@link Futures#successfulAsList}. */ + static final class ListFuture extends CollectionFuture> { + ListFuture( + ImmutableCollection> futures, + boolean allMustSucceed) { + super(futures, allMustSucceed); + init(); + } + + @Override + public List combine(List> values) { + List result = newArrayListWithCapacity(values.size()); + for (Optional element : values) { + result.add(element != null ? element.orNull() : null); + } + return unmodifiableList(result); + } + } +} diff --git a/src/main/java/com/google/common/util/concurrent/CombinedFuture.java b/src/main/java/com/google/common/util/concurrent/CombinedFuture.java new file mode 100644 index 0000000..b46c5ad --- /dev/null +++ b/src/main/java/com/google/common/util/concurrent/CombinedFuture.java @@ -0,0 +1,201 @@ +/* + * Copyright (C) 2015 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.util.concurrent; + +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.util.concurrent.AggregateFuture.ReleaseResourcesReason.OUTPUT_FUTURE_DONE; + +import com.google.common.annotations.GwtCompatible; +import com.google.common.collect.ImmutableCollection; + +import java.util.concurrent.Callable; +import java.util.concurrent.CancellationException; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Executor; +import java.util.concurrent.RejectedExecutionException; + + +/** Aggregate future that computes its value by calling a callable. */ +@GwtCompatible +final class CombinedFuture extends AggregateFuture { + private CombinedFutureInterruptibleTask task; + + CombinedFuture( + ImmutableCollection> futures, + boolean allMustSucceed, + Executor listenerExecutor, + AsyncCallable callable) { + super(futures, allMustSucceed, false); + this.task = new AsyncCallableInterruptibleTask(callable, listenerExecutor); + init(); + } + + CombinedFuture( + ImmutableCollection> futures, + boolean allMustSucceed, + Executor listenerExecutor, + Callable callable) { + super(futures, allMustSucceed, false); + this.task = new CallableInterruptibleTask(callable, listenerExecutor); + init(); + } + + @Override + void collectOneValue(int index, Object returnValue) {} + + @Override + void handleAllCompleted() { + CombinedFutureInterruptibleTask localTask = task; + if (localTask != null) { + localTask.execute(); + } + } + + @Override + void releaseResources(ReleaseResourcesReason reason) { + super.releaseResources(reason); + /* + * If the output future is done, then it won't need to interrupt the task later, so it can clear + * its reference to it. + * + * If the output future is *not* done, then the task field will be cleared after the task runs + * or after the output future is done, whichever comes first. + */ + if (reason == OUTPUT_FUTURE_DONE) { + this.task = null; + } + } + + @Override + protected void interruptTask() { + CombinedFutureInterruptibleTask localTask = task; + if (localTask != null) { + localTask.interruptTask(); + } + } + + + private abstract class CombinedFutureInterruptibleTask extends InterruptibleTask { + private final Executor listenerExecutor; + boolean thrownByExecute = true; + + CombinedFutureInterruptibleTask(Executor listenerExecutor) { + this.listenerExecutor = checkNotNull(listenerExecutor); + } + + @Override + final boolean isDone() { + return CombinedFuture.this.isDone(); + } + + final void execute() { + try { + listenerExecutor.execute(this); + } catch (RejectedExecutionException e) { + if (thrownByExecute) { + CombinedFuture.this.setException(e); + } + } + } + + @Override + final void afterRanInterruptibly(T result, Throwable error) { + /* + * The future no longer needs to interrupt this task, so it no longer needs a reference to it. + * + * TODO(cpovirk): It might be nice for our InterruptibleTask subclasses to null out their + * `callable` fields automatically. That would make it less important for us to null out the + * reference to `task` here (though it's still nice to do so in case our reference to the + * executor keeps it alive). Ideally, nulling out `callable` would be the responsibility of + * InterruptibleTask itself so that its other subclasses also benefit. (Handling `callable` in + * InterruptibleTask itself might also eliminate some of the existing boilerplate for, e.g., + * pendingToString().) + */ + CombinedFuture.this.task = null; + + if (error != null) { + if (error instanceof ExecutionException) { + CombinedFuture.this.setException(error.getCause()); + } else if (error instanceof CancellationException) { + cancel(false); + } else { + CombinedFuture.this.setException(error); + } + } else { + setValue(result); + } + } + + abstract void setValue(T value); + } + + + private final class AsyncCallableInterruptibleTask + extends CombinedFutureInterruptibleTask> { + private final AsyncCallable callable; + + AsyncCallableInterruptibleTask(AsyncCallable callable, Executor listenerExecutor) { + super(listenerExecutor); + this.callable = checkNotNull(callable); + } + + @Override + ListenableFuture runInterruptibly() throws Exception { + thrownByExecute = false; + ListenableFuture result = callable.call(); + return checkNotNull( + result, + "AsyncCallable.call returned null instead of a Future. " + + "Did you mean to return immediateFuture(null)? %s", + callable); + } + + @Override + void setValue(ListenableFuture value) { + CombinedFuture.this.setFuture(value); + } + + @Override + String toPendingString() { + return callable.toString(); + } + } + + + private final class CallableInterruptibleTask extends CombinedFutureInterruptibleTask { + private final Callable callable; + + CallableInterruptibleTask(Callable callable, Executor listenerExecutor) { + super(listenerExecutor); + this.callable = checkNotNull(callable); + } + + @Override + V runInterruptibly() throws Exception { + thrownByExecute = false; + return callable.call(); + } + + @Override + void setValue(V value) { + CombinedFuture.this.set(value); + } + + @Override + String toPendingString() { + return callable.toString(); + } + } +} diff --git a/src/main/java/com/google/common/util/concurrent/CycleDetectingLockFactory.java b/src/main/java/com/google/common/util/concurrent/CycleDetectingLockFactory.java new file mode 100644 index 0000000..41f3a5a --- /dev/null +++ b/src/main/java/com/google/common/util/concurrent/CycleDetectingLockFactory.java @@ -0,0 +1,969 @@ +/* + * Copyright (C) 2011 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.util.concurrent; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.annotations.Beta; +import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.MoreObjects; +import com.google.common.base.Preconditions; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Lists; +import com.google.common.collect.MapMaker; +import com.google.common.collect.Maps; +import com.google.common.collect.Sets; + + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.EnumMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.locks.ReentrantLock; +import java.util.concurrent.locks.ReentrantReadWriteLock; +import java.util.logging.Level; +import java.util.logging.Logger; + + +/** + * The {@code CycleDetectingLockFactory} creates {@link ReentrantLock} instances and {@link + * ReentrantReadWriteLock} instances that detect potential deadlock by checking for cycles in lock + * acquisition order. + * + *

Potential deadlocks detected when calling the {@code lock()}, {@code lockInterruptibly()}, or + * {@code tryLock()} methods will result in the execution of the {@link Policy} specified when + * creating the factory. The currently available policies are: + * + *

    + *
  • DISABLED + *
  • WARN + *
  • THROW + *
+ * + *

The locks created by a factory instance will detect lock acquisition cycles with locks created + * by other {@code CycleDetectingLockFactory} instances (except those with {@code Policy.DISABLED}). + * A lock's behavior when a cycle is detected, however, is defined by the {@code Policy} of the + * factory that created it. This allows detection of cycles across components while delegating + * control over lock behavior to individual components. + * + *

Applications are encouraged to use a {@code CycleDetectingLockFactory} to create any locks for + * which external/unmanaged code is executed while the lock is held. (See caveats under + * Performance). + * + *

Cycle Detection + * + *

Deadlocks can arise when locks are acquired in an order that forms a cycle. In a simple + * example involving two locks and two threads, deadlock occurs when one thread acquires Lock A, and + * then Lock B, while another thread acquires Lock B, and then Lock A: + * + *

+ * Thread1: acquire(LockA) --X acquire(LockB)
+ * Thread2: acquire(LockB) --X acquire(LockA)
+ * 
+ * + *

Neither thread will progress because each is waiting for the other. In more complex + * applications, cycles can arise from interactions among more than 2 locks: + * + *

+ * Thread1: acquire(LockA) --X acquire(LockB)
+ * Thread2: acquire(LockB) --X acquire(LockC)
+ * ...
+ * ThreadN: acquire(LockN) --X acquire(LockA)
+ * 
+ * + *

The implementation detects cycles by constructing a directed graph in which each lock + * represents a node and each edge represents an acquisition ordering between two locks. + * + *

    + *
  • Each lock adds (and removes) itself to/from a ThreadLocal Set of acquired locks when the + * Thread acquires its first hold (and releases its last remaining hold). + *
  • Before the lock is acquired, the lock is checked against the current set of acquired + * locks---to each of the acquired locks, an edge from the soon-to-be-acquired lock is either + * verified or created. + *
  • If a new edge needs to be created, the outgoing edges of the acquired locks are traversed + * to check for a cycle that reaches the lock to be acquired. If no cycle is detected, a new + * "safe" edge is created. + *
  • If a cycle is detected, an "unsafe" (cyclic) edge is created to represent a potential + * deadlock situation, and the appropriate Policy is executed. + *
+ * + *

Note that detection of potential deadlock does not necessarily indicate that deadlock will + * happen, as it is possible that higher level application logic prevents the cyclic lock + * acquisition from occurring. One example of a false positive is: + * + *

+ * LockA -> LockB -> LockC
+ * LockA -> LockC -> LockB
+ * 
+ * + *

ReadWriteLocks + * + *

While {@code ReadWriteLock} instances have different properties and can form cycles without + * potential deadlock, this class treats {@code ReadWriteLock} instances as equivalent to + * traditional exclusive locks. Although this increases the false positives that the locks detect + * (i.e. cycles that will not actually result in deadlock), it simplifies the algorithm and + * implementation considerably. The assumption is that a user of this factory wishes to eliminate + * any cyclic acquisition ordering. + * + *

Explicit Lock Acquisition Ordering + * + *

The {@link CycleDetectingLockFactory.WithExplicitOrdering} class can be used to enforce an + * application-specific ordering in addition to performing general cycle detection. + * + *

Garbage Collection + * + *

In order to allow proper garbage collection of unused locks, the edges of the lock graph are + * weak references. + * + *

Performance + * + *

The extra bookkeeping done by cycle detecting locks comes at some cost to performance. + * Benchmarks (as of December 2011) show that: + * + *

    + *
  • for an unnested {@code lock()} and {@code unlock()}, a cycle detecting lock takes 38ns as + * opposed to the 24ns taken by a plain lock. + *
  • for nested locking, the cost increases with the depth of the nesting: + *
      + *
    • 2 levels: average of 64ns per lock()/unlock() + *
    • 3 levels: average of 77ns per lock()/unlock() + *
    • 4 levels: average of 99ns per lock()/unlock() + *
    • 5 levels: average of 103ns per lock()/unlock() + *
    • 10 levels: average of 184ns per lock()/unlock() + *
    • 20 levels: average of 393ns per lock()/unlock() + *
    + *
+ * + *

As such, the CycleDetectingLockFactory may not be suitable for performance-critical + * applications which involve tightly-looped or deeply-nested locking algorithms. + * + * @author Darick Tong + * @since 13.0 + */ +@Beta + // TODO(cpovirk): Consider being more strict. +@GwtIncompatible +public class CycleDetectingLockFactory { + + /** + * Encapsulates the action to be taken when a potential deadlock is encountered. Clients can use + * one of the predefined {@link Policies} or specify a custom implementation. Implementations must + * be thread-safe. + * + * @since 13.0 + */ + @Beta + public interface Policy { + + /** + * Called when a potential deadlock is encountered. Implementations can throw the given {@code + * exception} and/or execute other desired logic. + * + *

Note that the method will be called even upon an invocation of {@code tryLock()}. Although + * {@code tryLock()} technically recovers from deadlock by eventually timing out, this behavior + * is chosen based on the assumption that it is the application's wish to prohibit any cyclical + * lock acquisitions. + */ + void handlePotentialDeadlock(PotentialDeadlockException exception); + } + + /** + * Pre-defined {@link Policy} implementations. + * + * @since 13.0 + */ + @Beta + public enum Policies implements Policy { + /** + * When potential deadlock is detected, this policy results in the throwing of the {@code + * PotentialDeadlockException} indicating the potential deadlock, which includes stack traces + * illustrating the cycle in lock acquisition order. + */ + THROW { + @Override + public void handlePotentialDeadlock(PotentialDeadlockException e) { + throw e; + } + }, + + /** + * When potential deadlock is detected, this policy results in the logging of a {@link + * Level#SEVERE} message indicating the potential deadlock, which includes stack traces + * illustrating the cycle in lock acquisition order. + */ + WARN { + @Override + public void handlePotentialDeadlock(PotentialDeadlockException e) { + logger.log(Level.SEVERE, "Detected potential deadlock", e); + } + }, + + /** + * Disables cycle detection. This option causes the factory to return unmodified lock + * implementations provided by the JDK, and is provided to allow applications to easily + * parameterize when cycle detection is enabled. + * + *

Note that locks created by a factory with this policy will not participate the + * cycle detection performed by locks created by other factories. + */ + DISABLED { + @Override + public void handlePotentialDeadlock(PotentialDeadlockException e) {} + }; + } + + /** Creates a new factory with the specified policy. */ + public static CycleDetectingLockFactory newInstance(Policy policy) { + return new CycleDetectingLockFactory(policy); + } + + /** Equivalent to {@code newReentrantLock(lockName, false)}. */ + public ReentrantLock newReentrantLock(String lockName) { + return newReentrantLock(lockName, false); + } + + /** + * Creates a {@link ReentrantLock} with the given fairness policy. The {@code lockName} is used in + * the warning or exception output to help identify the locks involved in the detected deadlock. + */ + public ReentrantLock newReentrantLock(String lockName, boolean fair) { + return policy == Policies.DISABLED + ? new ReentrantLock(fair) + : new CycleDetectingReentrantLock(new LockGraphNode(lockName), fair); + } + + /** Equivalent to {@code newReentrantReadWriteLock(lockName, false)}. */ + public ReentrantReadWriteLock newReentrantReadWriteLock(String lockName) { + return newReentrantReadWriteLock(lockName, false); + } + + /** + * Creates a {@link ReentrantReadWriteLock} with the given fairness policy. The {@code lockName} + * is used in the warning or exception output to help identify the locks involved in the detected + * deadlock. + */ + public ReentrantReadWriteLock newReentrantReadWriteLock(String lockName, boolean fair) { + return policy == Policies.DISABLED + ? new ReentrantReadWriteLock(fair) + : new CycleDetectingReentrantReadWriteLock(new LockGraphNode(lockName), fair); + } + + // A static mapping from an Enum type to its set of LockGraphNodes. + private static final ConcurrentMap, Map> + lockGraphNodesPerType = new MapMaker().weakKeys().makeMap(); + + /** Creates a {@code CycleDetectingLockFactory.WithExplicitOrdering}. */ + public static > WithExplicitOrdering newInstanceWithExplicitOrdering( + Class enumClass, Policy policy) { + // createNodes maps each enumClass to a Map with the corresponding enum key + // type. + checkNotNull(enumClass); + checkNotNull(policy); + @SuppressWarnings("unchecked") + Map lockGraphNodes = (Map) getOrCreateNodes(enumClass); + return new WithExplicitOrdering(policy, lockGraphNodes); + } + + private static Map getOrCreateNodes(Class clazz) { + Map existing = lockGraphNodesPerType.get(clazz); + if (existing != null) { + return existing; + } + Map created = createNodes(clazz); + existing = lockGraphNodesPerType.putIfAbsent(clazz, created); + return MoreObjects.firstNonNull(existing, created); + } + + /** + * For a given Enum type, creates an immutable map from each of the Enum's values to a + * corresponding LockGraphNode, with the {@code allowedPriorLocks} and {@code + * disallowedPriorLocks} prepopulated with nodes according to the natural ordering of the + * associated Enum values. + */ + @VisibleForTesting + static > Map createNodes(Class clazz) { + EnumMap map = Maps.newEnumMap(clazz); + E[] keys = clazz.getEnumConstants(); + final int numKeys = keys.length; + ArrayList nodes = Lists.newArrayListWithCapacity(numKeys); + // Create a LockGraphNode for each enum value. + for (E key : keys) { + LockGraphNode node = new LockGraphNode(getLockName(key)); + nodes.add(node); + map.put(key, node); + } + // Pre-populate all allowedPriorLocks with nodes of smaller ordinal. + for (int i = 1; i < numKeys; i++) { + nodes.get(i).checkAcquiredLocks(Policies.THROW, nodes.subList(0, i)); + } + // Pre-populate all disallowedPriorLocks with nodes of larger ordinal. + for (int i = 0; i < numKeys - 1; i++) { + nodes.get(i).checkAcquiredLocks(Policies.DISABLED, nodes.subList(i + 1, numKeys)); + } + return Collections.unmodifiableMap(map); + } + + /** + * For the given Enum value {@code rank}, returns the value's {@code "EnumClass.name"}, which is + * used in exception and warning output. + */ + private static String getLockName(Enum rank) { + return rank.getDeclaringClass().getSimpleName() + "." + rank.name(); + } + + /** + * A {@code CycleDetectingLockFactory.WithExplicitOrdering} provides the additional enforcement of + * an application-specified ordering of lock acquisitions. The application defines the allowed + * ordering with an {@code Enum} whose values each correspond to a lock type. The order in which + * the values are declared dictates the allowed order of lock acquisition. In other words, locks + * corresponding to smaller values of {@link Enum#ordinal()} should only be acquired before locks + * with larger ordinals. Example: + * + *

{@code
+   * enum MyLockOrder {
+   *   FIRST, SECOND, THIRD;
+   * }
+   *
+   * CycleDetectingLockFactory.WithExplicitOrdering factory =
+   *   CycleDetectingLockFactory.newInstanceWithExplicitOrdering(Policies.THROW);
+   *
+   * Lock lock1 = factory.newReentrantLock(MyLockOrder.FIRST);
+   * Lock lock2 = factory.newReentrantLock(MyLockOrder.SECOND);
+   * Lock lock3 = factory.newReentrantLock(MyLockOrder.THIRD);
+   *
+   * lock1.lock();
+   * lock3.lock();
+   * lock2.lock();  // will throw an IllegalStateException
+   * }
+ * + *

As with all locks created by instances of {@code CycleDetectingLockFactory} explicitly + * ordered locks participate in general cycle detection with all other cycle detecting locks, and + * a lock's behavior when detecting a cyclic lock acquisition is defined by the {@code Policy} of + * the factory that created it. + * + *

Note, however, that although multiple locks can be created for a given Enum value, whether + * it be through separate factory instances or through multiple calls to the same factory, + * attempting to acquire multiple locks with the same Enum value (within the same thread) will + * result in an IllegalStateException regardless of the factory's policy. For example: + * + *

{@code
+   * CycleDetectingLockFactory.WithExplicitOrdering factory1 =
+   *   CycleDetectingLockFactory.newInstanceWithExplicitOrdering(...);
+   * CycleDetectingLockFactory.WithExplicitOrdering factory2 =
+   *   CycleDetectingLockFactory.newInstanceWithExplicitOrdering(...);
+   *
+   * Lock lockA = factory1.newReentrantLock(MyLockOrder.FIRST);
+   * Lock lockB = factory1.newReentrantLock(MyLockOrder.FIRST);
+   * Lock lockC = factory2.newReentrantLock(MyLockOrder.FIRST);
+   *
+   * lockA.lock();
+   *
+   * lockB.lock();  // will throw an IllegalStateException
+   * lockC.lock();  // will throw an IllegalStateException
+   *
+   * lockA.lock();  // reentrant acquisition is okay
+   * }
+ * + *

It is the responsibility of the application to ensure that multiple lock instances with the + * same rank are never acquired in the same thread. + * + * @param The Enum type representing the explicit lock ordering. + * @since 13.0 + */ + @Beta + public static final class WithExplicitOrdering> + extends CycleDetectingLockFactory { + + private final Map lockGraphNodes; + + @VisibleForTesting + WithExplicitOrdering(Policy policy, Map lockGraphNodes) { + super(policy); + this.lockGraphNodes = lockGraphNodes; + } + + /** Equivalent to {@code newReentrantLock(rank, false)}. */ + public ReentrantLock newReentrantLock(E rank) { + return newReentrantLock(rank, false); + } + + /** + * Creates a {@link ReentrantLock} with the given fairness policy and rank. The values returned + * by {@link Enum#getDeclaringClass()} and {@link Enum#name()} are used to describe the lock in + * warning or exception output. + * + * @throws IllegalStateException If the factory has already created a {@code Lock} with the + * specified rank. + */ + public ReentrantLock newReentrantLock(E rank, boolean fair) { + return policy == Policies.DISABLED + ? new ReentrantLock(fair) + : new CycleDetectingReentrantLock(lockGraphNodes.get(rank), fair); + } + + /** Equivalent to {@code newReentrantReadWriteLock(rank, false)}. */ + public ReentrantReadWriteLock newReentrantReadWriteLock(E rank) { + return newReentrantReadWriteLock(rank, false); + } + + /** + * Creates a {@link ReentrantReadWriteLock} with the given fairness policy and rank. The values + * returned by {@link Enum#getDeclaringClass()} and {@link Enum#name()} are used to describe the + * lock in warning or exception output. + * + * @throws IllegalStateException If the factory has already created a {@code Lock} with the + * specified rank. + */ + public ReentrantReadWriteLock newReentrantReadWriteLock(E rank, boolean fair) { + return policy == Policies.DISABLED + ? new ReentrantReadWriteLock(fair) + : new CycleDetectingReentrantReadWriteLock(lockGraphNodes.get(rank), fair); + } + } + + //////// Implementation ///////// + + private static final Logger logger = Logger.getLogger(CycleDetectingLockFactory.class.getName()); + + final Policy policy; + + private CycleDetectingLockFactory(Policy policy) { + this.policy = checkNotNull(policy); + } + + /** + * Tracks the currently acquired locks for each Thread, kept up to date by calls to {@link + * #aboutToAcquire(CycleDetectingLock)} and {@link #lockStateChanged(CycleDetectingLock)}. + */ + // This is logically a Set, but an ArrayList is used to minimize the amount + // of allocation done on lock()/unlock(). + private static final ThreadLocal> acquiredLocks = + new ThreadLocal>() { + @Override + protected ArrayList initialValue() { + return Lists.newArrayListWithCapacity(3); + } + }; + + /** + * A Throwable used to record a stack trace that illustrates an example of a specific lock + * acquisition ordering. The top of the stack trace is truncated such that it starts with the + * acquisition of the lock in question, e.g. + * + *

+   * com...ExampleStackTrace: LockB -> LockC
+   *   at com...CycleDetectingReentrantLock.lock(CycleDetectingLockFactory.java:443)
+   *   at ...
+   *   at ...
+   *   at com...MyClass.someMethodThatAcquiresLockB(MyClass.java:123)
+   * 
+ */ + private static class ExampleStackTrace extends IllegalStateException { + + static final StackTraceElement[] EMPTY_STACK_TRACE = new StackTraceElement[0]; + + static final ImmutableSet EXCLUDED_CLASS_NAMES = + ImmutableSet.of( + CycleDetectingLockFactory.class.getName(), + ExampleStackTrace.class.getName(), + LockGraphNode.class.getName()); + + ExampleStackTrace(LockGraphNode node1, LockGraphNode node2) { + super(node1.getLockName() + " -> " + node2.getLockName()); + StackTraceElement[] origStackTrace = getStackTrace(); + for (int i = 0, n = origStackTrace.length; i < n; i++) { + if (WithExplicitOrdering.class.getName().equals(origStackTrace[i].getClassName())) { + // For pre-populated disallowedPriorLocks edges, omit the stack trace. + setStackTrace(EMPTY_STACK_TRACE); + break; + } + if (!EXCLUDED_CLASS_NAMES.contains(origStackTrace[i].getClassName())) { + setStackTrace(Arrays.copyOfRange(origStackTrace, i, n)); + break; + } + } + } + } + + /** + * Represents a detected cycle in lock acquisition ordering. The exception includes a causal chain + * of {@code ExampleStackTrace} instances to illustrate the cycle, e.g. + * + *
+   * com....PotentialDeadlockException: Potential Deadlock from LockC -> ReadWriteA
+   *   at ...
+   *   at ...
+   * Caused by: com...ExampleStackTrace: LockB -> LockC
+   *   at ...
+   *   at ...
+   * Caused by: com...ExampleStackTrace: ReadWriteA -> LockB
+   *   at ...
+   *   at ...
+   * 
+ * + *

Instances are logged for the {@code Policies.WARN}, and thrown for {@code Policies.THROW}. + * + * @since 13.0 + */ + @Beta + public static final class PotentialDeadlockException extends ExampleStackTrace { + + private final ExampleStackTrace conflictingStackTrace; + + private PotentialDeadlockException( + LockGraphNode node1, LockGraphNode node2, ExampleStackTrace conflictingStackTrace) { + super(node1, node2); + this.conflictingStackTrace = conflictingStackTrace; + initCause(conflictingStackTrace); + } + + public ExampleStackTrace getConflictingStackTrace() { + return conflictingStackTrace; + } + + /** + * Appends the chain of messages from the {@code conflictingStackTrace} to the original {@code + * message}. + */ + @Override + public String getMessage() { + StringBuilder message = new StringBuilder(super.getMessage()); + for (Throwable t = conflictingStackTrace; t != null; t = t.getCause()) { + message.append(", ").append(t.getMessage()); + } + return message.toString(); + } + } + + /** + * Internal Lock implementations implement the {@code CycleDetectingLock} interface, allowing the + * detection logic to treat all locks in the same manner. + */ + private interface CycleDetectingLock { + + /** @return the {@link LockGraphNode} associated with this lock. */ + LockGraphNode getLockGraphNode(); + + /** @return {@code true} if the current thread has acquired this lock. */ + boolean isAcquiredByCurrentThread(); + } + + /** + * A {@code LockGraphNode} associated with each lock instance keeps track of the directed edges in + * the lock acquisition graph. + */ + private static class LockGraphNode { + + /** + * The map tracking the locks that are known to be acquired before this lock, each associated + * with an example stack trace. Locks are weakly keyed to allow proper garbage collection when + * they are no longer referenced. + */ + final Map allowedPriorLocks = + new MapMaker().weakKeys().makeMap(); + + /** + * The map tracking lock nodes that can cause a lock acquisition cycle if acquired before this + * node. + */ + final Map disallowedPriorLocks = + new MapMaker().weakKeys().makeMap(); + + final String lockName; + + LockGraphNode(String lockName) { + this.lockName = Preconditions.checkNotNull(lockName); + } + + String getLockName() { + return lockName; + } + + void checkAcquiredLocks(Policy policy, List acquiredLocks) { + for (int i = 0, size = acquiredLocks.size(); i < size; i++) { + checkAcquiredLock(policy, acquiredLocks.get(i)); + } + } + + /** + * Checks the acquisition-ordering between {@code this}, which is about to be acquired, and the + * specified {@code acquiredLock}. + * + *

When this method returns, the {@code acquiredLock} should be in either the {@code + * preAcquireLocks} map, for the case in which it is safe to acquire {@code this} after the + * {@code acquiredLock}, or in the {@code disallowedPriorLocks} map, in which case it is not + * safe. + */ + void checkAcquiredLock(Policy policy, LockGraphNode acquiredLock) { + // checkAcquiredLock() should never be invoked by a lock that has already + // been acquired. For unordered locks, aboutToAcquire() ensures this by + // checking isAcquiredByCurrentThread(). For ordered locks, however, this + // can happen because multiple locks may share the same LockGraphNode. In + // this situation, throw an IllegalStateException as defined by contract + // described in the documentation of WithExplicitOrdering. + Preconditions.checkState( + this != acquiredLock, + "Attempted to acquire multiple locks with the same rank %s", + acquiredLock.getLockName()); + + if (allowedPriorLocks.containsKey(acquiredLock)) { + // The acquisition ordering from "acquiredLock" to "this" has already + // been verified as safe. In a properly written application, this is + // the common case. + return; + } + PotentialDeadlockException previousDeadlockException = disallowedPriorLocks.get(acquiredLock); + if (previousDeadlockException != null) { + // Previously determined to be an unsafe lock acquisition. + // Create a new PotentialDeadlockException with the same causal chain + // (the example cycle) as that of the cached exception. + PotentialDeadlockException exception = + new PotentialDeadlockException( + acquiredLock, this, previousDeadlockException.getConflictingStackTrace()); + policy.handlePotentialDeadlock(exception); + return; + } + // Otherwise, it's the first time seeing this lock relationship. Look for + // a path from the acquiredLock to this. + Set seen = Sets.newIdentityHashSet(); + ExampleStackTrace path = acquiredLock.findPathTo(this, seen); + + if (path == null) { + // this can be safely acquired after the acquiredLock. + // + // Note that there is a race condition here which can result in missing + // a cyclic edge: it's possible for two threads to simultaneous find + // "safe" edges which together form a cycle. Preventing this race + // condition efficiently without _introducing_ deadlock is probably + // tricky. For now, just accept the race condition---missing a warning + // now and then is still better than having no deadlock detection. + allowedPriorLocks.put(acquiredLock, new ExampleStackTrace(acquiredLock, this)); + } else { + // Unsafe acquisition order detected. Create and cache a + // PotentialDeadlockException. + PotentialDeadlockException exception = + new PotentialDeadlockException(acquiredLock, this, path); + disallowedPriorLocks.put(acquiredLock, exception); + policy.handlePotentialDeadlock(exception); + } + } + + /** + * Performs a depth-first traversal of the graph edges defined by each node's {@code + * allowedPriorLocks} to find a path between {@code this} and the specified {@code lock}. + * + * @return If a path was found, a chained {@link ExampleStackTrace} illustrating the path to the + * {@code lock}, or {@code null} if no path was found. + */ + private ExampleStackTrace findPathTo(LockGraphNode node, Set seen) { + if (!seen.add(this)) { + return null; // Already traversed this node. + } + ExampleStackTrace found = allowedPriorLocks.get(node); + if (found != null) { + return found; // Found a path ending at the node! + } + // Recurse the edges. + for (Entry entry : allowedPriorLocks.entrySet()) { + LockGraphNode preAcquiredLock = entry.getKey(); + found = preAcquiredLock.findPathTo(node, seen); + if (found != null) { + // One of this node's allowedPriorLocks found a path. Prepend an + // ExampleStackTrace(preAcquiredLock, this) to the returned chain of + // ExampleStackTraces. + ExampleStackTrace path = new ExampleStackTrace(preAcquiredLock, this); + path.setStackTrace(entry.getValue().getStackTrace()); + path.initCause(found); + return path; + } + } + return null; + } + } + + /** + * CycleDetectingLock implementations must call this method before attempting to acquire the lock. + */ + private void aboutToAcquire(CycleDetectingLock lock) { + if (!lock.isAcquiredByCurrentThread()) { + ArrayList acquiredLockList = acquiredLocks.get(); + LockGraphNode node = lock.getLockGraphNode(); + node.checkAcquiredLocks(policy, acquiredLockList); + acquiredLockList.add(node); + } + } + + /** + * CycleDetectingLock implementations must call this method in a {@code finally} clause after any + * attempt to change the lock state, including both lock and unlock attempts. Failure to do so can + * result in corrupting the acquireLocks set. + */ + private static void lockStateChanged(CycleDetectingLock lock) { + if (!lock.isAcquiredByCurrentThread()) { + ArrayList acquiredLockList = acquiredLocks.get(); + LockGraphNode node = lock.getLockGraphNode(); + // Iterate in reverse because locks are usually locked/unlocked in a + // LIFO order. + for (int i = acquiredLockList.size() - 1; i >= 0; i--) { + if (acquiredLockList.get(i) == node) { + acquiredLockList.remove(i); + break; + } + } + } + } + + final class CycleDetectingReentrantLock extends ReentrantLock implements CycleDetectingLock { + + private final LockGraphNode lockGraphNode; + + private CycleDetectingReentrantLock(LockGraphNode lockGraphNode, boolean fair) { + super(fair); + this.lockGraphNode = Preconditions.checkNotNull(lockGraphNode); + } + + ///// CycleDetectingLock methods. ///// + + @Override + public LockGraphNode getLockGraphNode() { + return lockGraphNode; + } + + @Override + public boolean isAcquiredByCurrentThread() { + return isHeldByCurrentThread(); + } + + ///// Overridden ReentrantLock methods. ///// + + @Override + public void lock() { + aboutToAcquire(this); + try { + super.lock(); + } finally { + lockStateChanged(this); + } + } + + @Override + public void lockInterruptibly() throws InterruptedException { + aboutToAcquire(this); + try { + super.lockInterruptibly(); + } finally { + lockStateChanged(this); + } + } + + @Override + public boolean tryLock() { + aboutToAcquire(this); + try { + return super.tryLock(); + } finally { + lockStateChanged(this); + } + } + + @Override + public boolean tryLock(long timeout, TimeUnit unit) throws InterruptedException { + aboutToAcquire(this); + try { + return super.tryLock(timeout, unit); + } finally { + lockStateChanged(this); + } + } + + @Override + public void unlock() { + try { + super.unlock(); + } finally { + lockStateChanged(this); + } + } + } + + final class CycleDetectingReentrantReadWriteLock extends ReentrantReadWriteLock + implements CycleDetectingLock { + + // These ReadLock/WriteLock implementations shadow those in the + // ReentrantReadWriteLock superclass. They are simply wrappers around the + // internal Sync object, so this is safe since the shadowed locks are never + // exposed or used. + private final CycleDetectingReentrantReadLock readLock; + private final CycleDetectingReentrantWriteLock writeLock; + + private final LockGraphNode lockGraphNode; + + private CycleDetectingReentrantReadWriteLock(LockGraphNode lockGraphNode, boolean fair) { + super(fair); + this.readLock = new CycleDetectingReentrantReadLock(this); + this.writeLock = new CycleDetectingReentrantWriteLock(this); + this.lockGraphNode = Preconditions.checkNotNull(lockGraphNode); + } + + ///// Overridden ReentrantReadWriteLock methods. ///// + + @Override + public ReadLock readLock() { + return readLock; + } + + @Override + public WriteLock writeLock() { + return writeLock; + } + + ///// CycleDetectingLock methods. ///// + + @Override + public LockGraphNode getLockGraphNode() { + return lockGraphNode; + } + + @Override + public boolean isAcquiredByCurrentThread() { + return isWriteLockedByCurrentThread() || getReadHoldCount() > 0; + } + } + + private class CycleDetectingReentrantReadLock extends ReentrantReadWriteLock.ReadLock { + + final CycleDetectingReentrantReadWriteLock readWriteLock; + + CycleDetectingReentrantReadLock(CycleDetectingReentrantReadWriteLock readWriteLock) { + super(readWriteLock); + this.readWriteLock = readWriteLock; + } + + @Override + public void lock() { + aboutToAcquire(readWriteLock); + try { + super.lock(); + } finally { + lockStateChanged(readWriteLock); + } + } + + @Override + public void lockInterruptibly() throws InterruptedException { + aboutToAcquire(readWriteLock); + try { + super.lockInterruptibly(); + } finally { + lockStateChanged(readWriteLock); + } + } + + @Override + public boolean tryLock() { + aboutToAcquire(readWriteLock); + try { + return super.tryLock(); + } finally { + lockStateChanged(readWriteLock); + } + } + + @Override + public boolean tryLock(long timeout, TimeUnit unit) throws InterruptedException { + aboutToAcquire(readWriteLock); + try { + return super.tryLock(timeout, unit); + } finally { + lockStateChanged(readWriteLock); + } + } + + @Override + public void unlock() { + try { + super.unlock(); + } finally { + lockStateChanged(readWriteLock); + } + } + } + + private class CycleDetectingReentrantWriteLock extends ReentrantReadWriteLock.WriteLock { + + final CycleDetectingReentrantReadWriteLock readWriteLock; + + CycleDetectingReentrantWriteLock(CycleDetectingReentrantReadWriteLock readWriteLock) { + super(readWriteLock); + this.readWriteLock = readWriteLock; + } + + @Override + public void lock() { + aboutToAcquire(readWriteLock); + try { + super.lock(); + } finally { + lockStateChanged(readWriteLock); + } + } + + @Override + public void lockInterruptibly() throws InterruptedException { + aboutToAcquire(readWriteLock); + try { + super.lockInterruptibly(); + } finally { + lockStateChanged(readWriteLock); + } + } + + @Override + public boolean tryLock() { + aboutToAcquire(readWriteLock); + try { + return super.tryLock(); + } finally { + lockStateChanged(readWriteLock); + } + } + + @Override + public boolean tryLock(long timeout, TimeUnit unit) throws InterruptedException { + aboutToAcquire(readWriteLock); + try { + return super.tryLock(timeout, unit); + } finally { + lockStateChanged(readWriteLock); + } + } + + @Override + public void unlock() { + try { + super.unlock(); + } finally { + lockStateChanged(readWriteLock); + } + } + } +} diff --git a/src/main/java/com/google/common/util/concurrent/DirectExecutor.java b/src/main/java/com/google/common/util/concurrent/DirectExecutor.java new file mode 100644 index 0000000..3022971 --- /dev/null +++ b/src/main/java/com/google/common/util/concurrent/DirectExecutor.java @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.util.concurrent; + +import com.google.common.annotations.GwtCompatible; +import java.util.concurrent.Executor; + +/** + * An {@link Executor} that runs each task in the thread that invokes {@link Executor#execute + * execute}. + */ +@GwtCompatible +enum DirectExecutor implements Executor { + INSTANCE; + + @Override + public void execute(Runnable command) { + command.run(); + } + + @Override + public String toString() { + return "MoreExecutors.directExecutor()"; + } +} diff --git a/src/main/java/com/google/common/util/concurrent/ExecutionError.java b/src/main/java/com/google/common/util/concurrent/ExecutionError.java new file mode 100644 index 0000000..a4f3d26 --- /dev/null +++ b/src/main/java/com/google/common/util/concurrent/ExecutionError.java @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2011 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.util.concurrent; + +import com.google.common.annotations.GwtCompatible; + + +/** + * {@link Error} variant of {@link java.util.concurrent.ExecutionException}. As with {@code + * ExecutionException}, the error's {@linkplain #getCause() cause} comes from a failed task, + * possibly run in another thread. That cause should itself be an {@code Error}; if not, use {@code + * ExecutionException} or {@link UncheckedExecutionException}. This allows the client code to + * continue to distinguish between exceptions and errors, even when they come from other threads. + * + * @author Chris Povirk + * @since 10.0 + */ +@GwtCompatible +public class ExecutionError extends Error { + /** Creates a new instance with {@code null} as its detail message. */ + protected ExecutionError() {} + + /** Creates a new instance with the given detail message. */ + protected ExecutionError(String message) { + super(message); + } + + /** Creates a new instance with the given detail message and cause. */ + public ExecutionError(String message, Error cause) { + super(message, cause); + } + + /** Creates a new instance with the given cause. */ + public ExecutionError(Error cause) { + super(cause); + } + + private static final long serialVersionUID = 0; +} diff --git a/src/main/java/com/google/common/util/concurrent/ExecutionList.java b/src/main/java/com/google/common/util/concurrent/ExecutionList.java new file mode 100644 index 0000000..f8e90ef --- /dev/null +++ b/src/main/java/com/google/common/util/concurrent/ExecutionList.java @@ -0,0 +1,164 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.util.concurrent; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.annotations.GwtIncompatible; + +import java.util.concurrent.Executor; +import java.util.logging.Level; +import java.util.logging.Logger; + + +/** + * A support class for {@code ListenableFuture} implementations to manage their listeners. An + * instance contains a list of listeners, each with an associated {@code Executor}, and guarantees + * that every {@code Runnable} that is {@linkplain #add added} will be executed after {@link + * #execute()} is called. Any {@code Runnable} added after the call to {@code execute} is still + * guaranteed to execute. There is no guarantee, however, that listeners will be executed in the + * order that they are added. + * + *

Exceptions thrown by a listener will be propagated up to the executor. Any exception thrown + * during {@code Executor.execute} (e.g., a {@code RejectedExecutionException} or an exception + * thrown by {@linkplain MoreExecutors#directExecutor direct execution}) will be caught and logged. + * + * @author Nishant Thakkar + * @author Sven Mawson + * @since 1.0 + */ +@GwtIncompatible +public final class ExecutionList { + /** Logger to log exceptions caught when running runnables. */ + private static final Logger log = Logger.getLogger(ExecutionList.class.getName()); + + /** + * The runnable, executor pairs to execute. This acts as a stack threaded through the {@link + * RunnableExecutorPair#next} field. + */ + + private RunnableExecutorPair runnables; + + + private boolean executed; + + /** Creates a new, empty {@link ExecutionList}. */ + public ExecutionList() {} + + /** + * Adds the {@code Runnable} and accompanying {@code Executor} to the list of listeners to + * execute. If execution has already begun, the listener is executed immediately. + * + *

When selecting an executor, note that {@code directExecutor} is dangerous in some cases. See + * the discussion in the {@link ListenableFuture#addListener ListenableFuture.addListener} + * documentation. + */ + public void add(Runnable runnable, Executor executor) { + // Fail fast on a null. We throw NPE here because the contract of Executor states that it throws + // NPE on null listener, so we propagate that contract up into the add method as well. + checkNotNull(runnable, "Runnable was null."); + checkNotNull(executor, "Executor was null."); + + // Lock while we check state. We must maintain the lock while adding the new pair so that + // another thread can't run the list out from under us. We only add to the list if we have not + // yet started execution. + synchronized (this) { + if (!executed) { + runnables = new RunnableExecutorPair(runnable, executor, runnables); + return; + } + } + // Execute the runnable immediately. Because of scheduling this may end up getting called before + // some of the previously added runnables, but we're OK with that. If we want to change the + // contract to guarantee ordering among runnables we'd have to modify the logic here to allow + // it. + executeListener(runnable, executor); + } + + /** + * Runs this execution list, executing all existing pairs in the order they were added. However, + * note that listeners added after this point may be executed before those previously added, and + * note that the execution order of all listeners is ultimately chosen by the implementations of + * the supplied executors. + * + *

This method is idempotent. Calling it several times in parallel is semantically equivalent + * to calling it exactly once. + * + * @since 10.0 (present in 1.0 as {@code run}) + */ + public void execute() { + // Lock while we update our state so the add method above will finish adding any listeners + // before we start to run them. + RunnableExecutorPair list; + synchronized (this) { + if (executed) { + return; + } + executed = true; + list = runnables; + runnables = null; // allow GC to free listeners even if this stays around for a while. + } + // If we succeeded then list holds all the runnables we to execute. The pairs in the stack are + // in the opposite order from how they were added so we need to reverse the list to fulfill our + // contract. + // This is somewhat annoying, but turns out to be very fast in practice. Alternatively, we could + // drop the contract on the method that enforces this queue like behavior since depending on it + // is likely to be a bug anyway. + + // N.B. All writes to the list and the next pointers must have happened before the above + // synchronized block, so we can iterate the list without the lock held here. + RunnableExecutorPair reversedList = null; + while (list != null) { + RunnableExecutorPair tmp = list; + list = list.next; + tmp.next = reversedList; + reversedList = tmp; + } + while (reversedList != null) { + executeListener(reversedList.runnable, reversedList.executor); + reversedList = reversedList.next; + } + } + + /** + * Submits the given runnable to the given {@link Executor} catching and logging all {@linkplain + * RuntimeException runtime exceptions} thrown by the executor. + */ + private static void executeListener(Runnable runnable, Executor executor) { + try { + executor.execute(runnable); + } catch (RuntimeException e) { + // Log it and keep going -- bad runnable and/or executor. Don't punish the other runnables if + // we're given a bad one. We only catch RuntimeException because we want Errors to propagate + // up. + log.log( + Level.SEVERE, + "RuntimeException while executing runnable " + runnable + " with executor " + executor, + e); + } + } + + private static final class RunnableExecutorPair { + final Runnable runnable; + final Executor executor; + RunnableExecutorPair next; + + RunnableExecutorPair(Runnable runnable, Executor executor, RunnableExecutorPair next) { + this.runnable = runnable; + this.executor = executor; + this.next = next; + } + } +} diff --git a/src/main/java/com/google/common/util/concurrent/ExecutionSequencer.java b/src/main/java/com/google/common/util/concurrent/ExecutionSequencer.java new file mode 100644 index 0000000..88929b6 --- /dev/null +++ b/src/main/java/com/google/common/util/concurrent/ExecutionSequencer.java @@ -0,0 +1,167 @@ +/* + * Copyright (C) 2018 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.util.concurrent; + +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.util.concurrent.ExecutionSequencer.RunningState.CANCELLED; +import static com.google.common.util.concurrent.ExecutionSequencer.RunningState.NOT_RUN; +import static com.google.common.util.concurrent.ExecutionSequencer.RunningState.STARTED; +import static com.google.common.util.concurrent.Futures.immediateCancelledFuture; +import static com.google.common.util.concurrent.Futures.immediateFuture; +import static com.google.common.util.concurrent.MoreExecutors.directExecutor; + +import com.google.common.annotations.Beta; +import java.util.concurrent.Callable; +import java.util.concurrent.Executor; +import java.util.concurrent.atomic.AtomicReference; + +/** + * Serializes execution of a set of operations. This class guarantees that a submitted callable will + * not be called before previously submitted callables (and any {@code Future}s returned from them) + * have completed. + * + *

This class implements a superset of the behavior of {@link + * MoreExecutors#newSequentialExecutor}. If your tasks all run on the same underlying executor and + * don't need to wait for {@code Future}s returned from {@code AsyncCallable}s, use it instead. + * + * @since 26.0 + */ +@Beta +public final class ExecutionSequencer { + + private ExecutionSequencer() {} + + /** Creates a new instance. */ + public static ExecutionSequencer create() { + return new ExecutionSequencer(); + } + + enum RunningState { + NOT_RUN, + CANCELLED, + STARTED, + } + + /** This reference acts as a pointer tracking the head of a linked list of ListenableFutures. */ + private final AtomicReference> ref = + new AtomicReference<>(immediateFuture(null)); + + /** + * Enqueues a task to run when the previous task (if any) completes. + * + *

Cancellation does not propagate from the output future to a callable that has begun to + * execute, but if the output future is cancelled before {@link Callable#call()} is invoked, + * {@link Callable#call()} will not be invoked. + */ + public ListenableFuture submit(final Callable callable, Executor executor) { + checkNotNull(callable); + return submitAsync( + new AsyncCallable() { + @Override + public ListenableFuture call() throws Exception { + return immediateFuture(callable.call()); + } + + @Override + public String toString() { + return callable.toString(); + } + }, + executor); + } + + /** + * Enqueues a task to run when the previous task (if any) completes. + * + *

Cancellation does not propagate from the output future to the future returned from {@code + * callable} or a callable that has begun to execute, but if the output future is cancelled before + * {@link AsyncCallable#call()} is invoked, {@link AsyncCallable#call()} will not be invoked. + */ + public ListenableFuture submitAsync( + final AsyncCallable callable, final Executor executor) { + checkNotNull(callable); + final AtomicReference runningState = new AtomicReference<>(NOT_RUN); + final AsyncCallable task = + new AsyncCallable() { + @Override + public ListenableFuture call() throws Exception { + if (!runningState.compareAndSet(NOT_RUN, STARTED)) { + return immediateCancelledFuture(); + } + return callable.call(); + } + + @Override + public String toString() { + return callable.toString(); + } + }; + /* + * Four futures are at play here: + * taskFuture is the future tracking the result of the callable. + * newFuture is a future that completes after this and all prior tasks are done. + * oldFuture is the previous task's newFuture. + * outputFuture is the future we return to the caller, a nonCancellationPropagating taskFuture. + * + * newFuture is guaranteed to only complete once all tasks previously submitted to this instance + * have completed - namely after oldFuture is done, and taskFuture has either completed or been + * cancelled before the callable started execution. + */ + final SettableFuture newFuture = SettableFuture.create(); + + final ListenableFuture oldFuture = ref.getAndSet(newFuture); + + // Invoke our task once the previous future completes. + final ListenableFuture taskFuture = + Futures.submitAsync( + task, + new Executor() { + @Override + public void execute(Runnable runnable) { + oldFuture.addListener(runnable, executor); + } + }); + + final ListenableFuture outputFuture = Futures.nonCancellationPropagating(taskFuture); + + // newFuture's lifetime is determined by taskFuture, which can't complete before oldFuture + // unless taskFuture is cancelled, in which case it falls back to oldFuture. This ensures that + // if the future we return is cancelled, we don't begin execution of the next task until after + // oldFuture completes. + Runnable listener = + new Runnable() { + @Override + public void run() { + if (taskFuture.isDone() + // If this CAS succeeds, we know that the provided callable will never be invoked, + // so when oldFuture completes it is safe to allow the next submitted task to + // proceed. + || (outputFuture.isCancelled() && runningState.compareAndSet(NOT_RUN, CANCELLED))) { + // Since the value of oldFuture can only ever be immediateFuture(null) or setFuture of + // a future that eventually came from immediateFuture(null), this doesn't leak + // throwables or completion values. + newFuture.setFuture(oldFuture); + } + } + }; + // Adding the listener to both futures guarantees that newFuture will aways be set. Adding to + // taskFuture guarantees completion if the callable is invoked, and adding to outputFuture + // propagates cancellation if the callable has not yet been invoked. + outputFuture.addListener(listener, directExecutor()); + taskFuture.addListener(listener, directExecutor()); + + return outputFuture; + } +} diff --git a/src/main/java/com/google/common/util/concurrent/FakeTimeLimiter.java b/src/main/java/com/google/common/util/concurrent/FakeTimeLimiter.java new file mode 100644 index 0000000..762897a --- /dev/null +++ b/src/main/java/com/google/common/util/concurrent/FakeTimeLimiter.java @@ -0,0 +1,97 @@ +/* + * Copyright (C) 2006 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.util.concurrent; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.annotations.Beta; +import com.google.common.annotations.GwtIncompatible; + +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; + +/** + * A TimeLimiter implementation which actually does not attempt to limit time at all. This may be + * desirable to use in some unit tests. More importantly, attempting to debug a call which is + * time-limited would be extremely annoying, so this gives you a time-limiter you can easily swap in + * for your real time-limiter while you're debugging. + * + * @author Kevin Bourrillion + * @author Jens Nyman + * @since 1.0 + */ +@Beta + +@GwtIncompatible +public final class FakeTimeLimiter implements TimeLimiter { + @Override + public T newProxy( + T target, Class interfaceType, long timeoutDuration, TimeUnit timeoutUnit) { + checkNotNull(target); + checkNotNull(interfaceType); + checkNotNull(timeoutUnit); + return target; // ha ha + } + + @Override + public T callWithTimeout(Callable callable, long timeoutDuration, TimeUnit timeoutUnit) + throws ExecutionException { + checkNotNull(callable); + checkNotNull(timeoutUnit); + try { + return callable.call(); + } catch (RuntimeException e) { + throw new UncheckedExecutionException(e); + } catch (Exception e) { + throw new ExecutionException(e); + } catch (Error e) { + throw new ExecutionError(e); + } catch (Throwable e) { + // It's a non-Error, non-Exception Throwable. Such classes are usually intended to extend + // Exception, so we'll treat it like an Exception. + throw new ExecutionException(e); + } + } + + @Override + public T callUninterruptiblyWithTimeout( + Callable callable, long timeoutDuration, TimeUnit timeoutUnit) throws ExecutionException { + return callWithTimeout(callable, timeoutDuration, timeoutUnit); + } + + @Override + public void runWithTimeout(Runnable runnable, long timeoutDuration, TimeUnit timeoutUnit) { + checkNotNull(runnable); + checkNotNull(timeoutUnit); + try { + runnable.run(); + } catch (RuntimeException e) { + throw new UncheckedExecutionException(e); + } catch (Error e) { + throw new ExecutionError(e); + } catch (Throwable e) { + // It's a non-Error, non-Exception Throwable. Such classes are usually intended to extend + // Exception, so we'll treat it like a RuntimeException. + throw new UncheckedExecutionException(e); + } + } + + @Override + public void runUninterruptiblyWithTimeout( + Runnable runnable, long timeoutDuration, TimeUnit timeoutUnit) { + runWithTimeout(runnable, timeoutDuration, timeoutUnit); + } +} diff --git a/src/main/java/com/google/common/util/concurrent/FluentFuture.java b/src/main/java/com/google/common/util/concurrent/FluentFuture.java new file mode 100644 index 0000000..615f7e0 --- /dev/null +++ b/src/main/java/com/google/common/util/concurrent/FluentFuture.java @@ -0,0 +1,404 @@ +/* + * Copyright (C) 2006 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.util.concurrent; + +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.util.concurrent.Internal.toNanosSaturated; + +import com.google.common.annotations.Beta; +import com.google.common.annotations.GwtCompatible; +import com.google.common.annotations.GwtIncompatible; +import com.google.common.base.Function; + +import java.time.Duration; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Executor; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +/** + * A {@link ListenableFuture} that supports fluent chains of operations. For example: + * + *
{@code
+ * ListenableFuture adminIsLoggedIn =
+ *     FluentFuture.from(usersDatabase.getAdminUser())
+ *         .transform(User::getId, directExecutor())
+ *         .transform(ActivityService::isLoggedIn, threadPool)
+ *         .catching(RpcException.class, e -> false, directExecutor());
+ * }
+ * + *

Alternatives

+ * + *

Frameworks

+ * + *

When chaining together a graph of asynchronous operations, you will often find it easier to + * use a framework. Frameworks automate the process, often adding features like monitoring, + * debugging, and cancellation. Examples of frameworks include: + * + *

+ * + *

{@link java.util.concurrent.CompletableFuture} / {@link java.util.concurrent.CompletionStage} + *

+ * + *

Users of {@code CompletableFuture} will likely want to continue using {@code + * CompletableFuture}. {@code FluentFuture} is targeted at people who use {@code ListenableFuture}, + * who can't use Java 8, or who want an API more focused than {@code CompletableFuture}. (If you + * need to adapt between {@code CompletableFuture} and {@code ListenableFuture}, consider Future Converter.) + * + *

Extension

+ * + * If you want a class like {@code FluentFuture} but with extra methods, we recommend declaring your + * own subclass of {@link ListenableFuture}, complete with a method like {@link #from} to adapt an + * existing {@code ListenableFuture}, implemented atop a {@link ForwardingListenableFuture} that + * forwards to that future and adds the desired methods. + * + * @since 23.0 + */ +@Beta +@GwtCompatible(emulated = true) +public abstract class FluentFuture extends GwtFluentFutureCatchingSpecialization { + + /** + * A less abstract subclass of AbstractFuture. This can be used to optimize setFuture by ensuring + * that {@link #get} calls exactly the implementation of {@link AbstractFuture#get}. + */ + abstract static class TrustedFuture extends FluentFuture + implements AbstractFuture.Trusted { + + @Override + public final V get() throws InterruptedException, ExecutionException { + return super.get(); + } + + + @Override + public final V get(long timeout, TimeUnit unit) + throws InterruptedException, ExecutionException, TimeoutException { + return super.get(timeout, unit); + } + + @Override + public final boolean isDone() { + return super.isDone(); + } + + @Override + public final boolean isCancelled() { + return super.isCancelled(); + } + + @Override + public final void addListener(Runnable listener, Executor executor) { + super.addListener(listener, executor); + } + + + @Override + public final boolean cancel(boolean mayInterruptIfRunning) { + return super.cancel(mayInterruptIfRunning); + } + } + + FluentFuture() {} + + /** + * Converts the given {@code ListenableFuture} to an equivalent {@code FluentFuture}. + * + *

If the given {@code ListenableFuture} is already a {@code FluentFuture}, it is returned + * directly. If not, it is wrapped in a {@code FluentFuture} that delegates all calls to the + * original {@code ListenableFuture}. + */ + public static FluentFuture from(ListenableFuture future) { + return future instanceof FluentFuture + ? (FluentFuture) future + : new ForwardingFluentFuture(future); + } + + /** + * Simply returns its argument. + * + * @deprecated no need to use this + * @since 28.0 + */ + @Deprecated + public static FluentFuture from(FluentFuture future) { + return checkNotNull(future); + } + + /** + * Returns a {@code Future} whose result is taken from this {@code Future} or, if this {@code + * Future} fails with the given {@code exceptionType}, from the result provided by the {@code + * fallback}. {@link Function#apply} is not invoked until the primary input has failed, so if the + * primary input succeeds, it is never invoked. If, during the invocation of {@code fallback}, an + * exception is thrown, this exception is used as the result of the output {@code Future}. + * + *

Usage example: + * + *

{@code
+   * // Falling back to a zero counter in case an exception happens when processing the RPC to fetch
+   * // counters.
+   * ListenableFuture faultTolerantFuture =
+   *     fetchCounters().catching(FetchException.class, x -> 0, directExecutor());
+   * }
+ * + *

When selecting an executor, note that {@code directExecutor} is dangerous in some cases. See + * the discussion in the {@link #addListener} documentation. All its warnings about heavyweight + * listeners are also applicable to heavyweight functions passed to this method. + * + *

This method is similar to {@link java.util.concurrent.CompletableFuture#exceptionally}. It + * can also serve some of the use cases of {@link java.util.concurrent.CompletableFuture#handle} + * and {@link java.util.concurrent.CompletableFuture#handleAsync} when used along with {@link + * #transform}. + * + * @param exceptionType the exception type that triggers use of {@code fallback}. The exception + * type is matched against the input's exception. "The input's exception" means the cause of + * the {@link ExecutionException} thrown by {@code input.get()} or, if {@code get()} throws a + * different kind of exception, that exception itself. To avoid hiding bugs and other + * unrecoverable errors, callers should prefer more specific types, avoiding {@code + * Throwable.class} in particular. + * @param fallback the {@link Function} to be called if the input fails with the expected + * exception type. The function's argument is the input's exception. "The input's exception" + * means the cause of the {@link ExecutionException} thrown by {@code this.get()} or, if + * {@code get()} throws a different kind of exception, that exception itself. + * @param executor the executor that runs {@code fallback} if the input fails + */ + @Partially.GwtIncompatible("AVAILABLE but requires exceptionType to be Throwable.class") + public final FluentFuture catching( + Class exceptionType, Function fallback, Executor executor) { + return (FluentFuture) Futures.catching(this, exceptionType, fallback, executor); + } + + /** + * Returns a {@code Future} whose result is taken from this {@code Future} or, if this {@code + * Future} fails with the given {@code exceptionType}, from the result provided by the {@code + * fallback}. {@link AsyncFunction#apply} is not invoked until the primary input has failed, so if + * the primary input succeeds, it is never invoked. If, during the invocation of {@code fallback}, + * an exception is thrown, this exception is used as the result of the output {@code Future}. + * + *

Usage examples: + * + *

{@code
+   * // Falling back to a zero counter in case an exception happens when processing the RPC to fetch
+   * // counters.
+   * ListenableFuture faultTolerantFuture =
+   *     fetchCounters().catchingAsync(
+   *         FetchException.class, x -> immediateFuture(0), directExecutor());
+   * }
+ * + *

The fallback can also choose to propagate the original exception when desired: + * + *

{@code
+   * // Falling back to a zero counter only in case the exception was a
+   * // TimeoutException.
+   * ListenableFuture faultTolerantFuture =
+   *     fetchCounters().catchingAsync(
+   *         FetchException.class,
+   *         e -> {
+   *           if (omitDataOnFetchFailure) {
+   *             return immediateFuture(0);
+   *           }
+   *           throw e;
+   *         },
+   *         directExecutor());
+   * }
+ * + *

When selecting an executor, note that {@code directExecutor} is dangerous in some cases. See + * the discussion in the {@link #addListener} documentation. All its warnings about heavyweight + * listeners are also applicable to heavyweight functions passed to this method. (Specifically, + * {@code directExecutor} functions should avoid heavyweight operations inside {@code + * AsyncFunction.apply}. Any heavyweight operations should occur in other threads responsible for + * completing the returned {@code Future}.) + * + *

This method is similar to {@link java.util.concurrent.CompletableFuture#exceptionally}. It + * can also serve some of the use cases of {@link java.util.concurrent.CompletableFuture#handle} + * and {@link java.util.concurrent.CompletableFuture#handleAsync} when used along with {@link + * #transform}. + * + * @param exceptionType the exception type that triggers use of {@code fallback}. The exception + * type is matched against the input's exception. "The input's exception" means the cause of + * the {@link ExecutionException} thrown by {@code this.get()} or, if {@code get()} throws a + * different kind of exception, that exception itself. To avoid hiding bugs and other + * unrecoverable errors, callers should prefer more specific types, avoiding {@code + * Throwable.class} in particular. + * @param fallback the {@link AsyncFunction} to be called if the input fails with the expected + * exception type. The function's argument is the input's exception. "The input's exception" + * means the cause of the {@link ExecutionException} thrown by {@code input.get()} or, if + * {@code get()} throws a different kind of exception, that exception itself. + * @param executor the executor that runs {@code fallback} if the input fails + */ + @Partially.GwtIncompatible("AVAILABLE but requires exceptionType to be Throwable.class") + public final FluentFuture catchingAsync( + Class exceptionType, AsyncFunction fallback, Executor executor) { + return (FluentFuture) Futures.catchingAsync(this, exceptionType, fallback, executor); + } + + /** + * Returns a future that delegates to this future but will finish early (via a {@link + * TimeoutException} wrapped in an {@link ExecutionException}) if the specified timeout expires. + * If the timeout expires, not only will the output future finish, but also the input future + * ({@code this}) will be cancelled and interrupted. + * + * @param timeout when to time out the future + * @param scheduledExecutor The executor service to enforce the timeout. + * @since 28.0 + */ + @GwtIncompatible // ScheduledExecutorService + public final FluentFuture withTimeout( + Duration timeout, ScheduledExecutorService scheduledExecutor) { + return withTimeout(toNanosSaturated(timeout), TimeUnit.NANOSECONDS, scheduledExecutor); + } + + /** + * Returns a future that delegates to this future but will finish early (via a {@link + * TimeoutException} wrapped in an {@link ExecutionException}) if the specified timeout expires. + * If the timeout expires, not only will the output future finish, but also the input future + * ({@code this}) will be cancelled and interrupted. + * + * @param timeout when to time out the future + * @param unit the time unit of the time parameter + * @param scheduledExecutor The executor service to enforce the timeout. + */ + @GwtIncompatible // ScheduledExecutorService + @SuppressWarnings("GoodTime") // should accept a java.time.Duration + public final FluentFuture withTimeout( + long timeout, TimeUnit unit, ScheduledExecutorService scheduledExecutor) { + return (FluentFuture) Futures.withTimeout(this, timeout, unit, scheduledExecutor); + } + + /** + * Returns a new {@code Future} whose result is asynchronously derived from the result of this + * {@code Future}. If the input {@code Future} fails, the returned {@code Future} fails with the + * same exception (and the function is not invoked). + * + *

More precisely, the returned {@code Future} takes its result from a {@code Future} produced + * by applying the given {@code AsyncFunction} to the result of the original {@code Future}. + * Example usage: + * + *

{@code
+   * FluentFuture rowKeyFuture = FluentFuture.from(indexService.lookUp(query));
+   * ListenableFuture queryFuture =
+   *     rowKeyFuture.transformAsync(dataService::readFuture, executor);
+   * }
+ * + *

When selecting an executor, note that {@code directExecutor} is dangerous in some cases. See + * the discussion in the {@link #addListener} documentation. All its warnings about heavyweight + * listeners are also applicable to heavyweight functions passed to this method. (Specifically, + * {@code directExecutor} functions should avoid heavyweight operations inside {@code + * AsyncFunction.apply}. Any heavyweight operations should occur in other threads responsible for + * completing the returned {@code Future}.) + * + *

The returned {@code Future} attempts to keep its cancellation state in sync with that of the + * input future and that of the future returned by the chain function. That is, if the returned + * {@code Future} is cancelled, it will attempt to cancel the other two, and if either of the + * other two is cancelled, the returned {@code Future} will receive a callback in which it will + * attempt to cancel itself. + * + *

This method is similar to {@link java.util.concurrent.CompletableFuture#thenCompose} and + * {@link java.util.concurrent.CompletableFuture#thenComposeAsync}. It can also serve some of the + * use cases of {@link java.util.concurrent.CompletableFuture#handle} and {@link + * java.util.concurrent.CompletableFuture#handleAsync} when used along with {@link #catching}. + * + * @param function A function to transform the result of this future to the result of the output + * future + * @param executor Executor to run the function in. + * @return A future that holds result of the function (if the input succeeded) or the original + * input's failure (if not) + */ + public final FluentFuture transformAsync( + AsyncFunction function, Executor executor) { + return (FluentFuture) Futures.transformAsync(this, function, executor); + } + + /** + * Returns a new {@code Future} whose result is derived from the result of this {@code Future}. If + * this input {@code Future} fails, the returned {@code Future} fails with the same exception (and + * the function is not invoked). Example usage: + * + *

{@code
+   * ListenableFuture> rowsFuture =
+   *     queryFuture.transform(QueryResult::getRows, executor);
+   * }
+ * + *

When selecting an executor, note that {@code directExecutor} is dangerous in some cases. See + * the discussion in the {@link #addListener} documentation. All its warnings about heavyweight + * listeners are also applicable to heavyweight functions passed to this method. + * + *

The returned {@code Future} attempts to keep its cancellation state in sync with that of the + * input future. That is, if the returned {@code Future} is cancelled, it will attempt to cancel + * the input, and if the input is cancelled, the returned {@code Future} will receive a callback + * in which it will attempt to cancel itself. + * + *

An example use of this method is to convert a serializable object returned from an RPC into + * a POJO. + * + *

This method is similar to {@link java.util.concurrent.CompletableFuture#thenApply} and + * {@link java.util.concurrent.CompletableFuture#thenApplyAsync}. It can also serve some of the + * use cases of {@link java.util.concurrent.CompletableFuture#handle} and {@link + * java.util.concurrent.CompletableFuture#handleAsync} when used along with {@link #catching}. + * + * @param function A Function to transform the results of this future to the results of the + * returned future. + * @param executor Executor to run the function in. + * @return A future that holds result of the transformation. + */ + public final FluentFuture transform(Function function, Executor executor) { + return (FluentFuture) Futures.transform(this, function, executor); + } + + /** + * Registers separate success and failure callbacks to be run when this {@code Future}'s + * computation is {@linkplain java.util.concurrent.Future#isDone() complete} or, if the + * computation is already complete, immediately. + * + *

The callback is run on {@code executor}. There is no guaranteed ordering of execution of + * callbacks, but any callback added through this method is guaranteed to be called once the + * computation is complete. + * + *

Example: + * + *

{@code
+   * future.addCallback(
+   *     new FutureCallback() {
+   *       public void onSuccess(QueryResult result) {
+   *         storeInCache(result);
+   *       }
+   *       public void onFailure(Throwable t) {
+   *         reportError(t);
+   *       }
+   *     }, executor);
+   * }
+ * + *

When selecting an executor, note that {@code directExecutor} is dangerous in some cases. See + * the discussion in the {@link #addListener} documentation. All its warnings about heavyweight + * listeners are also applicable to heavyweight callbacks passed to this method. + * + *

For a more general interface to attach a completion listener, see {@link #addListener}. + * + *

This method is similar to {@link java.util.concurrent.CompletableFuture#whenComplete} and + * {@link java.util.concurrent.CompletableFuture#whenCompleteAsync}. It also serves the use case + * of {@link java.util.concurrent.CompletableFuture#thenAccept} and {@link + * java.util.concurrent.CompletableFuture#thenAcceptAsync}. + * + * @param callback The callback to invoke when this {@code Future} is completed. + * @param executor The executor to run {@code callback} when the future completes. + */ + public final void addCallback(FutureCallback callback, Executor executor) { + Futures.addCallback(this, callback, executor); + } +} diff --git a/src/main/java/com/google/common/util/concurrent/ForwardingBlockingDeque.java b/src/main/java/com/google/common/util/concurrent/ForwardingBlockingDeque.java new file mode 100644 index 0000000..2485966 --- /dev/null +++ b/src/main/java/com/google/common/util/concurrent/ForwardingBlockingDeque.java @@ -0,0 +1,130 @@ +/* + * Copyright (C) 2012 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.util.concurrent; + +import com.google.common.annotations.GwtIncompatible; +import com.google.common.collect.ForwardingDeque; +import java.util.Collection; +import java.util.concurrent.BlockingDeque; +import java.util.concurrent.TimeUnit; + +/** + * A {@link BlockingDeque} which forwards all its method calls to another {@code BlockingDeque}. + * Subclasses should override one or more methods to modify the behavior of the backing deque as + * desired per the decorator pattern. + * + *

Warning: The methods of {@code ForwardingBlockingDeque} forward indiscriminately + * to the methods of the delegate. For example, overriding {@link #add} alone will not change + * the behaviour of {@link #offer} which can lead to unexpected behaviour. In this case, you should + * override {@code offer} as well, either providing your own implementation, or delegating to the + * provided {@code standardOffer} method. + * + *

{@code default} method warning: This class does not forward calls to {@code + * default} methods. Instead, it inherits their default implementations. When those implementations + * invoke methods, they invoke methods on the {@code ForwardingBlockingDeque}. + * + *

The {@code standard} methods are not guaranteed to be thread-safe, even when all of the + * methods that they depend on are thread-safe. + * + * @author Emily Soldal + * @since 21.0 (since 14.0 as {@link com.google.common.collect.ForwardingBlockingDeque}) + */ +@GwtIncompatible +public abstract class ForwardingBlockingDeque extends ForwardingDeque + implements BlockingDeque { + + /** Constructor for use by subclasses. */ + protected ForwardingBlockingDeque() {} + + @Override + protected abstract BlockingDeque delegate(); + + @Override + public int remainingCapacity() { + return delegate().remainingCapacity(); + } + + @Override + public void putFirst(E e) throws InterruptedException { + delegate().putFirst(e); + } + + @Override + public void putLast(E e) throws InterruptedException { + delegate().putLast(e); + } + + @Override + public boolean offerFirst(E e, long timeout, TimeUnit unit) throws InterruptedException { + return delegate().offerFirst(e, timeout, unit); + } + + @Override + public boolean offerLast(E e, long timeout, TimeUnit unit) throws InterruptedException { + return delegate().offerLast(e, timeout, unit); + } + + @Override + public E takeFirst() throws InterruptedException { + return delegate().takeFirst(); + } + + @Override + public E takeLast() throws InterruptedException { + return delegate().takeLast(); + } + + @Override + public E pollFirst(long timeout, TimeUnit unit) throws InterruptedException { + return delegate().pollFirst(timeout, unit); + } + + @Override + public E pollLast(long timeout, TimeUnit unit) throws InterruptedException { + return delegate().pollLast(timeout, unit); + } + + @Override + public void put(E e) throws InterruptedException { + delegate().put(e); + } + + @Override + public boolean offer(E e, long timeout, TimeUnit unit) throws InterruptedException { + return delegate().offer(e, timeout, unit); + } + + @Override + public E take() throws InterruptedException { + return delegate().take(); + } + + @Override + public E poll(long timeout, TimeUnit unit) throws InterruptedException { + return delegate().poll(timeout, unit); + } + + @Override + public int drainTo(Collection c) { + return delegate().drainTo(c); + } + + @Override + public int drainTo(Collection c, int maxElements) { + return delegate().drainTo(c, maxElements); + } +} diff --git a/src/main/java/com/google/common/util/concurrent/ForwardingBlockingQueue.java b/src/main/java/com/google/common/util/concurrent/ForwardingBlockingQueue.java new file mode 100644 index 0000000..2f61b9e --- /dev/null +++ b/src/main/java/com/google/common/util/concurrent/ForwardingBlockingQueue.java @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2010 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.util.concurrent; + +import com.google.common.annotations.GwtIncompatible; +import com.google.common.collect.ForwardingQueue; + +import java.util.Collection; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.TimeUnit; + +/** + * A {@link BlockingQueue} which forwards all its method calls to another {@link BlockingQueue}. + * Subclasses should override one or more methods to modify the behavior of the backing collection + * as desired per the decorator + * pattern. + * + *

{@code default} method warning: This class does not forward calls to {@code + * default} methods. Instead, it inherits their default implementations. When those implementations + * invoke methods, they invoke methods on the {@code ForwardingBlockingQueue}. + * + * @author Raimundo Mirisola + * @param the type of elements held in this collection + * @since 4.0 + */ + // TODO(cpovirk): Consider being more strict. +@GwtIncompatible +public abstract class ForwardingBlockingQueue extends ForwardingQueue + implements BlockingQueue { + + /** Constructor for use by subclasses. */ + protected ForwardingBlockingQueue() {} + + @Override + protected abstract BlockingQueue delegate(); + + @Override + public int drainTo(Collection c, int maxElements) { + return delegate().drainTo(c, maxElements); + } + + @Override + public int drainTo(Collection c) { + return delegate().drainTo(c); + } + + @Override + public boolean offer(E e, long timeout, TimeUnit unit) throws InterruptedException { + return delegate().offer(e, timeout, unit); + } + + @Override + public E poll(long timeout, TimeUnit unit) throws InterruptedException { + return delegate().poll(timeout, unit); + } + + @Override + public void put(E e) throws InterruptedException { + delegate().put(e); + } + + @Override + public int remainingCapacity() { + return delegate().remainingCapacity(); + } + + @Override + public E take() throws InterruptedException { + return delegate().take(); + } +} diff --git a/src/main/java/com/google/common/util/concurrent/ForwardingCondition.java b/src/main/java/com/google/common/util/concurrent/ForwardingCondition.java new file mode 100644 index 0000000..62c4d4c --- /dev/null +++ b/src/main/java/com/google/common/util/concurrent/ForwardingCondition.java @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2017 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.util.concurrent; + +import java.util.Date; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.locks.Condition; + +/** Forwarding wrapper around a {@code Condition}. */ +abstract class ForwardingCondition implements Condition { + abstract Condition delegate(); + + @Override + public void await() throws InterruptedException { + delegate().await(); + } + + @Override + public boolean await(long time, TimeUnit unit) throws InterruptedException { + return delegate().await(time, unit); + } + + @Override + public void awaitUninterruptibly() { + delegate().awaitUninterruptibly(); + } + + @Override + public long awaitNanos(long nanosTimeout) throws InterruptedException { + return delegate().awaitNanos(nanosTimeout); + } + + @Override + public boolean awaitUntil(Date deadline) throws InterruptedException { + return delegate().awaitUntil(deadline); + } + + @Override + public void signal() { + delegate().signal(); + } + + @Override + public void signalAll() { + delegate().signalAll(); + } +} diff --git a/src/main/java/com/google/common/util/concurrent/ForwardingExecutorService.java b/src/main/java/com/google/common/util/concurrent/ForwardingExecutorService.java new file mode 100644 index 0000000..8d505a0 --- /dev/null +++ b/src/main/java/com/google/common/util/concurrent/ForwardingExecutorService.java @@ -0,0 +1,116 @@ +/* + * Copyright (C) 2011 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.util.concurrent; + +import com.google.common.annotations.GwtIncompatible; +import com.google.common.collect.ForwardingObject; + +import java.util.Collection; +import java.util.List; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +/** + * An executor service which forwards all its method calls to another executor service. Subclasses + * should override one or more methods to modify the behavior of the backing executor service as + * desired per the decorator pattern. + * + * @author Kurt Alfred Kluever + * @since 10.0 + */ + // TODO(cpovirk): Consider being more strict. +@GwtIncompatible +public abstract class ForwardingExecutorService extends ForwardingObject + implements ExecutorService { + /** Constructor for use by subclasses. */ + protected ForwardingExecutorService() {} + + @Override + protected abstract ExecutorService delegate(); + + @Override + public boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException { + return delegate().awaitTermination(timeout, unit); + } + + @Override + public List> invokeAll(Collection> tasks) + throws InterruptedException { + return delegate().invokeAll(tasks); + } + + @Override + public List> invokeAll( + Collection> tasks, long timeout, TimeUnit unit) + throws InterruptedException { + return delegate().invokeAll(tasks, timeout, unit); + } + + @Override + public T invokeAny(Collection> tasks) + throws InterruptedException, ExecutionException { + return delegate().invokeAny(tasks); + } + + @Override + public T invokeAny(Collection> tasks, long timeout, TimeUnit unit) + throws InterruptedException, ExecutionException, TimeoutException { + return delegate().invokeAny(tasks, timeout, unit); + } + + @Override + public boolean isShutdown() { + return delegate().isShutdown(); + } + + @Override + public boolean isTerminated() { + return delegate().isTerminated(); + } + + @Override + public void shutdown() { + delegate().shutdown(); + } + + @Override + public List shutdownNow() { + return delegate().shutdownNow(); + } + + @Override + public void execute(Runnable command) { + delegate().execute(command); + } + + @Override + public Future submit(Callable task) { + return delegate().submit(task); + } + + @Override + public Future submit(Runnable task) { + return delegate().submit(task); + } + + @Override + public Future submit(Runnable task, T result) { + return delegate().submit(task, result); + } +} diff --git a/src/main/java/com/google/common/util/concurrent/ForwardingFluentFuture.java b/src/main/java/com/google/common/util/concurrent/ForwardingFluentFuture.java new file mode 100644 index 0000000..984fd68 --- /dev/null +++ b/src/main/java/com/google/common/util/concurrent/ForwardingFluentFuture.java @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2009 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.util.concurrent; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.annotations.GwtCompatible; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Executor; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +/** + * {@link FluentFuture} that forwards all calls to a delegate. + * + *

Extension

+ * + * If you want a class like {@code FluentFuture} but with extra methods, we recommend declaring your + * own subclass of {@link ListenableFuture}, complete with a method like {@link #from} to adapt an + * existing {@code ListenableFuture}, implemented atop a {@link ForwardingListenableFuture} that + * forwards to that future and adds the desired methods. + */ +@GwtCompatible +final class ForwardingFluentFuture extends FluentFuture { + private final ListenableFuture delegate; + + ForwardingFluentFuture(ListenableFuture delegate) { + this.delegate = checkNotNull(delegate); + } + + @Override + public void addListener(Runnable listener, Executor executor) { + delegate.addListener(listener, executor); + } + + @Override + public boolean cancel(boolean mayInterruptIfRunning) { + return delegate.cancel(mayInterruptIfRunning); + } + + @Override + public boolean isCancelled() { + return delegate.isCancelled(); + } + + @Override + public boolean isDone() { + return delegate.isDone(); + } + + @Override + public V get() throws InterruptedException, ExecutionException { + return delegate.get(); + } + + @Override + public V get(long timeout, TimeUnit unit) + throws InterruptedException, ExecutionException, TimeoutException { + return delegate.get(timeout, unit); + } + + @Override + public String toString() { + return delegate.toString(); + } +} diff --git a/src/main/java/com/google/common/util/concurrent/ForwardingFuture.java b/src/main/java/com/google/common/util/concurrent/ForwardingFuture.java new file mode 100644 index 0000000..3678591 --- /dev/null +++ b/src/main/java/com/google/common/util/concurrent/ForwardingFuture.java @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2009 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.util.concurrent; + +import com.google.common.annotations.GwtCompatible; +import com.google.common.base.Preconditions; +import com.google.common.collect.ForwardingObject; + +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +/** + * A {@link Future} which forwards all its method calls to another future. Subclasses should + * override one or more methods to modify the behavior of the backing future as desired per the decorator pattern. + * + *

Most subclasses can just use {@link SimpleForwardingFuture}. + * + * @author Sven Mawson + * @since 1.0 + */ + // TODO(cpovirk): Consider being more strict. +@GwtCompatible +public abstract class ForwardingFuture extends ForwardingObject implements Future { + /** Constructor for use by subclasses. */ + protected ForwardingFuture() {} + + @Override + protected abstract Future delegate(); + + @Override + public boolean cancel(boolean mayInterruptIfRunning) { + return delegate().cancel(mayInterruptIfRunning); + } + + @Override + public boolean isCancelled() { + return delegate().isCancelled(); + } + + @Override + public boolean isDone() { + return delegate().isDone(); + } + + @Override + public V get() throws InterruptedException, ExecutionException { + return delegate().get(); + } + + @Override + public V get(long timeout, TimeUnit unit) + throws InterruptedException, ExecutionException, TimeoutException { + return delegate().get(timeout, unit); + } + + // TODO(cpovirk): Use standard Javadoc form for SimpleForwarding* class and constructor + /** + * A simplified version of {@link ForwardingFuture} where subclasses can pass in an already + * constructed {@link Future} as the delegate. + * + * @since 9.0 + */ + public abstract static class SimpleForwardingFuture extends ForwardingFuture { + private final Future delegate; + + protected SimpleForwardingFuture(Future delegate) { + this.delegate = Preconditions.checkNotNull(delegate); + } + + @Override + protected final Future delegate() { + return delegate; + } + } +} diff --git a/src/main/java/com/google/common/util/concurrent/ForwardingListenableFuture.java b/src/main/java/com/google/common/util/concurrent/ForwardingListenableFuture.java new file mode 100644 index 0000000..5b67d5c --- /dev/null +++ b/src/main/java/com/google/common/util/concurrent/ForwardingListenableFuture.java @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2009 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.util.concurrent; + +import com.google.common.annotations.GwtCompatible; +import com.google.common.base.Preconditions; + +import java.util.concurrent.Executor; + +/** + * A {@link ListenableFuture} which forwards all its method calls to another future. Subclasses + * should override one or more methods to modify the behavior of the backing future as desired per + * the decorator pattern. + * + *

Most subclasses can just use {@link SimpleForwardingListenableFuture}. + * + * @author Shardul Deo + * @since 4.0 + */ + // TODO(cpovirk): Consider being more strict. +@GwtCompatible +public abstract class ForwardingListenableFuture extends ForwardingFuture + implements ListenableFuture { + + /** Constructor for use by subclasses. */ + protected ForwardingListenableFuture() {} + + @Override + protected abstract ListenableFuture delegate(); + + @Override + public void addListener(Runnable listener, Executor exec) { + delegate().addListener(listener, exec); + } + + // TODO(cpovirk): Use standard Javadoc form for SimpleForwarding* class and constructor + /** + * A simplified version of {@link ForwardingListenableFuture} where subclasses can pass in an + * already constructed {@link ListenableFuture} as the delegate. + * + * @since 9.0 + */ + public abstract static class SimpleForwardingListenableFuture + extends ForwardingListenableFuture { + private final ListenableFuture delegate; + + protected SimpleForwardingListenableFuture(ListenableFuture delegate) { + this.delegate = Preconditions.checkNotNull(delegate); + } + + @Override + protected final ListenableFuture delegate() { + return delegate; + } + } +} diff --git a/src/main/java/com/google/common/util/concurrent/ForwardingListeningExecutorService.java b/src/main/java/com/google/common/util/concurrent/ForwardingListeningExecutorService.java new file mode 100644 index 0000000..2ef198e --- /dev/null +++ b/src/main/java/com/google/common/util/concurrent/ForwardingListeningExecutorService.java @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2011 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.util.concurrent; + +import com.google.common.annotations.GwtIncompatible; + +import java.util.concurrent.Callable; + +/** + * A listening executor service which forwards all its method calls to another listening executor + * service. Subclasses should override one or more methods to modify the behavior of the backing + * executor service as desired per the decorator pattern. + * + * @author Isaac Shum + * @since 10.0 + */ + // TODO(cpovirk): Consider being more strict. +@GwtIncompatible +public abstract class ForwardingListeningExecutorService extends ForwardingExecutorService + implements ListeningExecutorService { + /** Constructor for use by subclasses. */ + protected ForwardingListeningExecutorService() {} + + @Override + protected abstract ListeningExecutorService delegate(); + + @Override + public ListenableFuture submit(Callable task) { + return delegate().submit(task); + } + + @Override + public ListenableFuture submit(Runnable task) { + return delegate().submit(task); + } + + @Override + public ListenableFuture submit(Runnable task, T result) { + return delegate().submit(task, result); + } +} diff --git a/src/main/java/com/google/common/util/concurrent/ForwardingLock.java b/src/main/java/com/google/common/util/concurrent/ForwardingLock.java new file mode 100644 index 0000000..8c50787 --- /dev/null +++ b/src/main/java/com/google/common/util/concurrent/ForwardingLock.java @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2017 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.util.concurrent; + +import java.util.concurrent.TimeUnit; +import java.util.concurrent.locks.Condition; +import java.util.concurrent.locks.Lock; + +/** Forwarding wrapper around a {@code Lock}. */ +abstract class ForwardingLock implements Lock { + abstract Lock delegate(); + + @Override + public void lock() { + delegate().lock(); + } + + @Override + public void lockInterruptibly() throws InterruptedException { + delegate().lockInterruptibly(); + } + + @Override + public boolean tryLock() { + return delegate().tryLock(); + } + + @Override + public boolean tryLock(long time, TimeUnit unit) throws InterruptedException { + return delegate().tryLock(time, unit); + } + + @Override + public void unlock() { + delegate().unlock(); + } + + @Override + public Condition newCondition() { + return delegate().newCondition(); + } +} diff --git a/src/main/java/com/google/common/util/concurrent/FutureCallback.java b/src/main/java/com/google/common/util/concurrent/FutureCallback.java new file mode 100644 index 0000000..3dc3dda --- /dev/null +++ b/src/main/java/com/google/common/util/concurrent/FutureCallback.java @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2011 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.util.concurrent; + +import com.google.common.annotations.GwtCompatible; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; + + +/** + * A callback for accepting the results of a {@link java.util.concurrent.Future} computation + * asynchronously. + * + *

To attach to a {@link ListenableFuture} use {@link Futures#addCallback}. + * + * @author Anthony Zana + * @since 10.0 + */ +@GwtCompatible +public interface FutureCallback { + /** Invoked with the result of the {@code Future} computation when it is successful. */ + void onSuccess(V result); + + /** + * Invoked when a {@code Future} computation fails or is canceled. + * + *

If the future's {@link Future#get() get} method throws an {@link ExecutionException}, then + * the cause is passed to this method. Any other thrown object is passed unaltered. + */ + void onFailure(Throwable t); +} diff --git a/src/main/java/com/google/common/util/concurrent/Futures.java b/src/main/java/com/google/common/util/concurrent/Futures.java new file mode 100644 index 0000000..e57044b --- /dev/null +++ b/src/main/java/com/google/common/util/concurrent/Futures.java @@ -0,0 +1,1335 @@ +/* + * Copyright (C) 2006 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.util.concurrent; + +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.base.Preconditions.checkState; +import static com.google.common.util.concurrent.Internal.toNanosSaturated; +import static com.google.common.util.concurrent.MoreExecutors.directExecutor; +import static com.google.common.util.concurrent.Uninterruptibles.getUninterruptibly; + +import com.google.common.annotations.Beta; +import com.google.common.annotations.GwtCompatible; +import com.google.common.annotations.GwtIncompatible; +import com.google.common.base.Function; +import com.google.common.base.MoreObjects; +import com.google.common.base.Preconditions; +import com.google.common.collect.ImmutableList; +import com.google.common.util.concurrent.CollectionFuture.ListFuture; +import com.google.common.util.concurrent.ImmediateFuture.ImmediateCancelledFuture; +import com.google.common.util.concurrent.ImmediateFuture.ImmediateFailedFuture; + +import java.time.Duration; +import java.util.Collection; +import java.util.List; +import java.util.concurrent.Callable; +import java.util.concurrent.CancellationException; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Executor; +import java.util.concurrent.Future; +import java.util.concurrent.RejectedExecutionException; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.concurrent.atomic.AtomicInteger; + + +/** + * Static utility methods pertaining to the {@link Future} interface. + * + *

Many of these methods use the {@link ListenableFuture} API; consult the Guava User Guide + * article on {@code + * ListenableFuture}. + * + *

The main purpose of {@code ListenableFuture} is to help you chain together a graph of + * asynchronous operations. You can chain them together manually with calls to methods like {@link + * Futures#transform(ListenableFuture, Function, Executor) Futures.transform}, but you will often + * find it easier to use a framework. Frameworks automate the process, often adding features like + * monitoring, debugging, and cancellation. Examples of frameworks include: + * + *

+ * + *

If you do chain your operations manually, you may want to use {@link FluentFuture}. + * + * @author Kevin Bourrillion + * @author Nishant Thakkar + * @author Sven Mawson + * @since 1.0 + */ +@GwtCompatible(emulated = true) +public final class Futures extends GwtFuturesCatchingSpecialization { + + // A note on memory visibility. + // Many of the utilities in this class (transform, withFallback, withTimeout, asList, combine) + // have two requirements that significantly complicate their design. + // 1. Cancellation should propagate from the returned future to the input future(s). + // 2. The returned futures shouldn't unnecessarily 'pin' their inputs after completion. + // + // A consequence of these requirements is that the delegate futures cannot be stored in + // final fields. + // + // For simplicity the rest of this description will discuss Futures.catching since it is the + // simplest instance, though very similar descriptions apply to many other classes in this file. + // + // In the constructor of AbstractCatchingFuture, the delegate future is assigned to a field + // 'inputFuture'. That field is non-final and non-volatile. There are 2 places where the + // 'inputFuture' field is read and where we will have to consider visibility of the write + // operation in the constructor. + // + // 1. In the listener that performs the callback. In this case it is fine since inputFuture is + // assigned prior to calling addListener, and addListener happens-before any invocation of the + // listener. Notably, this means that 'volatile' is unnecessary to make 'inputFuture' visible + // to the listener. + // + // 2. In done() where we may propagate cancellation to the input. In this case it is _not_ fine. + // There is currently nothing that enforces that the write to inputFuture in the constructor is + // visible to done(). This is because there is no happens before edge between the write and a + // (hypothetical) unsafe read by our caller. Note: adding 'volatile' does not fix this issue, + // it would just add an edge such that if done() observed non-null, then it would also + // definitely observe all earlier writes, but we still have no guarantee that done() would see + // the inital write (just stronger guarantees if it does). + // + // See: http://cs.oswego.edu/pipermail/concurrency-interest/2015-January/013800.html + // For a (long) discussion about this specific issue and the general futility of life. + // + // For the time being we are OK with the problem discussed above since it requires a caller to + // introduce a very specific kind of data-race. And given the other operations performed by these + // methods that involve volatile read/write operations, in practice there is no issue. Also, the + // way in such a visibility issue would surface is most likely as a failure of cancel() to + // propagate to the input. Cancellation propagation is fundamentally racy so this is fine. + // + // Future versions of the JMM may revise safe construction semantics in such a way that we can + // safely publish these objects and we won't need this whole discussion. + // TODO(user,lukes): consider adding volatile to all these fields since in current known JVMs + // that should resolve the issue. This comes at the cost of adding more write barriers to the + // implementations. + + private Futures() {} + + /** + * Creates a {@code ListenableFuture} which has its value set immediately upon construction. The + * getters just return the value. This {@code Future} can't be canceled or timed out and its + * {@code isDone()} method always returns {@code true}. + */ + public static ListenableFuture immediateFuture(V value) { + if (value == null) { + // This cast is safe because null is assignable to V for all V (i.e. it is bivariant) + @SuppressWarnings("unchecked") + ListenableFuture typedNull = (ListenableFuture) ImmediateFuture.NULL; + return typedNull; + } + return new ImmediateFuture<>(value); + } + + /** + * Returns a {@code ListenableFuture} which has an exception set immediately upon construction. + * + *

The returned {@code Future} can't be cancelled, and its {@code isDone()} method always + * returns {@code true}. Calling {@code get()} will immediately throw the provided {@code + * Throwable} wrapped in an {@code ExecutionException}. + */ + public static ListenableFuture immediateFailedFuture(Throwable throwable) { + checkNotNull(throwable); + return new ImmediateFailedFuture(throwable); + } + + /** + * Creates a {@code ListenableFuture} which is cancelled immediately upon construction, so that + * {@code isCancelled()} always returns {@code true}. + * + * @since 14.0 + */ + public static ListenableFuture immediateCancelledFuture() { + return new ImmediateCancelledFuture(); + } + + /** + * Executes {@code callable} on the specified {@code executor}, returning a {@code Future}. + * + * @throws RejectedExecutionException if the task cannot be scheduled for execution + * @since NEXT + */ + @Beta + public static ListenableFuture submit(Callable callable, Executor executor) { + TrustedListenableFutureTask task = TrustedListenableFutureTask.create(callable); + executor.execute(task); + return task; + } + + /** + * Executes {@code runnable} on the specified {@code executor}, returning a {@code Future} that + * will complete after execution. + * + * @throws RejectedExecutionException if the task cannot be scheduled for execution + * @since NEXT + */ + @Beta + public static ListenableFuture submit(Runnable runnable, Executor executor) { + TrustedListenableFutureTask task = TrustedListenableFutureTask.create(runnable, null); + executor.execute(task); + return task; + } + + /** + * Executes {@code callable} on the specified {@code executor}, returning a {@code Future}. + * + * @throws RejectedExecutionException if the task cannot be scheduled for execution + * @since 23.0 + */ + @Beta + public static ListenableFuture submitAsync(AsyncCallable callable, Executor executor) { + TrustedListenableFutureTask task = TrustedListenableFutureTask.create(callable); + executor.execute(task); + return task; + } + + /** + * Schedules {@code callable} on the specified {@code executor}, returning a {@code Future}. + * + * @throws RejectedExecutionException if the task cannot be scheduled for execution + * @since 28.0 + */ + @Beta + @GwtIncompatible // java.util.concurrent.ScheduledExecutorService + public static ListenableFuture scheduleAsync( + AsyncCallable callable, Duration delay, ScheduledExecutorService executorService) { + return scheduleAsync(callable, toNanosSaturated(delay), TimeUnit.NANOSECONDS, executorService); + } + + /** + * Schedules {@code callable} on the specified {@code executor}, returning a {@code Future}. + * + * @throws RejectedExecutionException if the task cannot be scheduled for execution + * @since 23.0 + */ + @Beta + @GwtIncompatible // java.util.concurrent.ScheduledExecutorService + @SuppressWarnings("GoodTime") // should accept a java.time.Duration + public static ListenableFuture scheduleAsync( + AsyncCallable callable, + long delay, + TimeUnit timeUnit, + ScheduledExecutorService executorService) { + TrustedListenableFutureTask task = TrustedListenableFutureTask.create(callable); + final Future scheduled = executorService.schedule(task, delay, timeUnit); + task.addListener( + new Runnable() { + @Override + public void run() { + // Don't want to interrupt twice + scheduled.cancel(false); + } + }, + directExecutor()); + return task; + } + + /** + * Returns a {@code Future} whose result is taken from the given primary {@code input} or, if the + * primary input fails with the given {@code exceptionType}, from the result provided by the + * {@code fallback}. {@link Function#apply} is not invoked until the primary input has failed, so + * if the primary input succeeds, it is never invoked. If, during the invocation of {@code + * fallback}, an exception is thrown, this exception is used as the result of the output {@code + * Future}. + * + *

Usage example: + * + *

{@code
+   * ListenableFuture fetchCounterFuture = ...;
+   *
+   * // Falling back to a zero counter in case an exception happens when
+   * // processing the RPC to fetch counters.
+   * ListenableFuture faultTolerantFuture = Futures.catching(
+   *     fetchCounterFuture, FetchException.class, x -> 0, directExecutor());
+   * }
+ * + *

When selecting an executor, note that {@code directExecutor} is dangerous in some cases. See + * the discussion in the {@link ListenableFuture#addListener ListenableFuture.addListener} + * documentation. All its warnings about heavyweight listeners are also applicable to heavyweight + * functions passed to this method. + * + * @param input the primary input {@code Future} + * @param exceptionType the exception type that triggers use of {@code fallback}. The exception + * type is matched against the input's exception. "The input's exception" means the cause of + * the {@link ExecutionException} thrown by {@code input.get()} or, if {@code get()} throws a + * different kind of exception, that exception itself. To avoid hiding bugs and other + * unrecoverable errors, callers should prefer more specific types, avoiding {@code + * Throwable.class} in particular. + * @param fallback the {@link Function} to be called if {@code input} fails with the expected + * exception type. The function's argument is the input's exception. "The input's exception" + * means the cause of the {@link ExecutionException} thrown by {@code input.get()} or, if + * {@code get()} throws a different kind of exception, that exception itself. + * @param executor the executor that runs {@code fallback} if {@code input} fails + * @since 19.0 + */ + @Beta + @Partially.GwtIncompatible("AVAILABLE but requires exceptionType to be Throwable.class") + public static ListenableFuture catching( + ListenableFuture input, + Class exceptionType, + Function fallback, + Executor executor) { + return AbstractCatchingFuture.create(input, exceptionType, fallback, executor); + } + + /** + * Returns a {@code Future} whose result is taken from the given primary {@code input} or, if the + * primary input fails with the given {@code exceptionType}, from the result provided by the + * {@code fallback}. {@link AsyncFunction#apply} is not invoked until the primary input has + * failed, so if the primary input succeeds, it is never invoked. If, during the invocation of + * {@code fallback}, an exception is thrown, this exception is used as the result of the output + * {@code Future}. + * + *

Usage examples: + * + *

{@code
+   * ListenableFuture fetchCounterFuture = ...;
+   *
+   * // Falling back to a zero counter in case an exception happens when
+   * // processing the RPC to fetch counters.
+   * ListenableFuture faultTolerantFuture = Futures.catchingAsync(
+   *     fetchCounterFuture, FetchException.class, x -> immediateFuture(0), directExecutor());
+   * }
+ * + *

The fallback can also choose to propagate the original exception when desired: + * + *

{@code
+   * ListenableFuture fetchCounterFuture = ...;
+   *
+   * // Falling back to a zero counter only in case the exception was a
+   * // TimeoutException.
+   * ListenableFuture faultTolerantFuture = Futures.catchingAsync(
+   *     fetchCounterFuture,
+   *     FetchException.class,
+   *     e -> {
+   *       if (omitDataOnFetchFailure) {
+   *         return immediateFuture(0);
+   *       }
+   *       throw e;
+   *     },
+   *     directExecutor());
+   * }
+ * + *

When selecting an executor, note that {@code directExecutor} is dangerous in some cases. See + * the discussion in the {@link ListenableFuture#addListener ListenableFuture.addListener} + * documentation. All its warnings about heavyweight listeners are also applicable to heavyweight + * functions passed to this method. (Specifically, {@code directExecutor} functions should avoid + * heavyweight operations inside {@code AsyncFunction.apply}. Any heavyweight operations should + * occur in other threads responsible for completing the returned {@code Future}.) + * + * @param input the primary input {@code Future} + * @param exceptionType the exception type that triggers use of {@code fallback}. The exception + * type is matched against the input's exception. "The input's exception" means the cause of + * the {@link ExecutionException} thrown by {@code input.get()} or, if {@code get()} throws a + * different kind of exception, that exception itself. To avoid hiding bugs and other + * unrecoverable errors, callers should prefer more specific types, avoiding {@code + * Throwable.class} in particular. + * @param fallback the {@link AsyncFunction} to be called if {@code input} fails with the expected + * exception type. The function's argument is the input's exception. "The input's exception" + * means the cause of the {@link ExecutionException} thrown by {@code input.get()} or, if + * {@code get()} throws a different kind of exception, that exception itself. + * @param executor the executor that runs {@code fallback} if {@code input} fails + * @since 19.0 (similar functionality in 14.0 as {@code withFallback}) + */ + @Beta + @Partially.GwtIncompatible("AVAILABLE but requires exceptionType to be Throwable.class") + public static ListenableFuture catchingAsync( + ListenableFuture input, + Class exceptionType, + AsyncFunction fallback, + Executor executor) { + return AbstractCatchingFuture.create(input, exceptionType, fallback, executor); + } + + /** + * Returns a future that delegates to another but will finish early (via a {@link + * TimeoutException} wrapped in an {@link ExecutionException}) if the specified duration expires. + * + *

The delegate future is interrupted and cancelled if it times out. + * + * @param delegate The future to delegate to. + * @param time when to timeout the future + * @param scheduledExecutor The executor service to enforce the timeout. + * @since 28.0 + */ + @Beta + @GwtIncompatible // java.util.concurrent.ScheduledExecutorService + public static ListenableFuture withTimeout( + ListenableFuture delegate, Duration time, ScheduledExecutorService scheduledExecutor) { + return withTimeout(delegate, toNanosSaturated(time), TimeUnit.NANOSECONDS, scheduledExecutor); + } + + /** + * Returns a future that delegates to another but will finish early (via a {@link + * TimeoutException} wrapped in an {@link ExecutionException}) if the specified duration expires. + * + *

The delegate future is interrupted and cancelled if it times out. + * + * @param delegate The future to delegate to. + * @param time when to timeout the future + * @param unit the time unit of the time parameter + * @param scheduledExecutor The executor service to enforce the timeout. + * @since 19.0 + */ + @Beta + @GwtIncompatible // java.util.concurrent.ScheduledExecutorService + @SuppressWarnings("GoodTime") // should accept a java.time.Duration + public static ListenableFuture withTimeout( + ListenableFuture delegate, + long time, + TimeUnit unit, + ScheduledExecutorService scheduledExecutor) { + if (delegate.isDone()) { + return delegate; + } + return TimeoutFuture.create(delegate, time, unit, scheduledExecutor); + } + + /** + * Returns a new {@code Future} whose result is asynchronously derived from the result of the + * given {@code Future}. If the given {@code Future} fails, the returned {@code Future} fails with + * the same exception (and the function is not invoked). + * + *

More precisely, the returned {@code Future} takes its result from a {@code Future} produced + * by applying the given {@code AsyncFunction} to the result of the original {@code Future}. + * Example usage: + * + *

{@code
+   * ListenableFuture rowKeyFuture = indexService.lookUp(query);
+   * ListenableFuture queryFuture =
+   *     transformAsync(rowKeyFuture, dataService::readFuture, executor);
+   * }
+ * + *

When selecting an executor, note that {@code directExecutor} is dangerous in some cases. See + * the discussion in the {@link ListenableFuture#addListener ListenableFuture.addListener} + * documentation. All its warnings about heavyweight listeners are also applicable to heavyweight + * functions passed to this method. (Specifically, {@code directExecutor} functions should avoid + * heavyweight operations inside {@code AsyncFunction.apply}. Any heavyweight operations should + * occur in other threads responsible for completing the returned {@code Future}.) + * + *

The returned {@code Future} attempts to keep its cancellation state in sync with that of the + * input future and that of the future returned by the chain function. That is, if the returned + * {@code Future} is cancelled, it will attempt to cancel the other two, and if either of the + * other two is cancelled, the returned {@code Future} will receive a callback in which it will + * attempt to cancel itself. + * + * @param input The future to transform + * @param function A function to transform the result of the input future to the result of the + * output future + * @param executor Executor to run the function in. + * @return A future that holds result of the function (if the input succeeded) or the original + * input's failure (if not) + * @since 19.0 (in 11.0 as {@code transform}) + */ + @Beta + public static ListenableFuture transformAsync( + ListenableFuture input, + AsyncFunction function, + Executor executor) { + return AbstractTransformFuture.create(input, function, executor); + } + + /** + * Returns a new {@code Future} whose result is derived from the result of the given {@code + * Future}. If {@code input} fails, the returned {@code Future} fails with the same exception (and + * the function is not invoked). Example usage: + * + *

{@code
+   * ListenableFuture queryFuture = ...;
+   * ListenableFuture> rowsFuture =
+   *     transform(queryFuture, QueryResult::getRows, executor);
+   * }
+ * + *

When selecting an executor, note that {@code directExecutor} is dangerous in some cases. See + * the discussion in the {@link ListenableFuture#addListener ListenableFuture.addListener} + * documentation. All its warnings about heavyweight listeners are also applicable to heavyweight + * functions passed to this method. + * + *

The returned {@code Future} attempts to keep its cancellation state in sync with that of the + * input future. That is, if the returned {@code Future} is cancelled, it will attempt to cancel + * the input, and if the input is cancelled, the returned {@code Future} will receive a callback + * in which it will attempt to cancel itself. + * + *

An example use of this method is to convert a serializable object returned from an RPC into + * a POJO. + * + * @param input The future to transform + * @param function A Function to transform the results of the provided future to the results of + * the returned future. + * @param executor Executor to run the function in. + * @return A future that holds result of the transformation. + * @since 9.0 (in 2.0 as {@code compose}) + */ + @Beta + public static ListenableFuture transform( + ListenableFuture input, Function function, Executor executor) { + return AbstractTransformFuture.create(input, function, executor); + } + + /** + * Like {@link #transform(ListenableFuture, Function, Executor)} except that the transformation + * {@code function} is invoked on each call to {@link Future#get() get()} on the returned future. + * + *

The returned {@code Future} reflects the input's cancellation state directly, and any + * attempt to cancel the returned Future is likewise passed through to the input Future. + * + *

Note that calls to {@linkplain Future#get(long, TimeUnit) timed get} only apply the timeout + * to the execution of the underlying {@code Future}, not to the execution of the + * transformation function. + * + *

The primary audience of this method is callers of {@code transform} who don't have a {@code + * ListenableFuture} available and do not mind repeated, lazy function evaluation. + * + * @param input The future to transform + * @param function A Function to transform the results of the provided future to the results of + * the returned future. + * @return A future that returns the result of the transformation. + * @since 10.0 + */ + @Beta + @GwtIncompatible // TODO + public static Future lazyTransform( + final Future input, final Function function) { + checkNotNull(input); + checkNotNull(function); + return new Future() { + + @Override + public boolean cancel(boolean mayInterruptIfRunning) { + return input.cancel(mayInterruptIfRunning); + } + + @Override + public boolean isCancelled() { + return input.isCancelled(); + } + + @Override + public boolean isDone() { + return input.isDone(); + } + + @Override + public O get() throws InterruptedException, ExecutionException { + return applyTransformation(input.get()); + } + + @Override + public O get(long timeout, TimeUnit unit) + throws InterruptedException, ExecutionException, TimeoutException { + return applyTransformation(input.get(timeout, unit)); + } + + private O applyTransformation(I input) throws ExecutionException { + try { + return function.apply(input); + } catch (Throwable t) { + throw new ExecutionException(t); + } + } + }; + } + + /** + * Creates a new {@code ListenableFuture} whose value is a list containing the values of all its + * input futures, if all succeed. + * + *

The list of results is in the same order as the input list. + * + *

This differs from {@link #successfulAsList(ListenableFuture[])} in that it will return a + * failed future if any of the items fails. + * + *

Canceling this future will attempt to cancel all the component futures, and if any of the + * provided futures fails or is canceled, this one is, too. + * + * @param futures futures to combine + * @return a future that provides a list of the results of the component futures + * @since 10.0 + */ + @Beta + @SafeVarargs + public static ListenableFuture> allAsList(ListenableFuture... futures) { + return new ListFuture(ImmutableList.copyOf(futures), true); + } + + /** + * Creates a new {@code ListenableFuture} whose value is a list containing the values of all its + * input futures, if all succeed. + * + *

The list of results is in the same order as the input list. + * + *

This differs from {@link #successfulAsList(Iterable)} in that it will return a failed future + * if any of the items fails. + * + *

Canceling this future will attempt to cancel all the component futures, and if any of the + * provided futures fails or is canceled, this one is, too. + * + * @param futures futures to combine + * @return a future that provides a list of the results of the component futures + * @since 10.0 + */ + @Beta + public static ListenableFuture> allAsList( + Iterable> futures) { + return new ListFuture(ImmutableList.copyOf(futures), true); + } + + /** + * Creates a {@link FutureCombiner} that processes the completed futures whether or not they're + * successful. + * + *

Any failures from the input futures will not be propagated to the returned future. + * + * @since 20.0 + */ + @Beta + @SafeVarargs + public static FutureCombiner whenAllComplete(ListenableFuture... futures) { + return new FutureCombiner(false, ImmutableList.copyOf(futures)); + } + + /** + * Creates a {@link FutureCombiner} that processes the completed futures whether or not they're + * successful. + * + *

Any failures from the input futures will not be propagated to the returned future. + * + * @since 20.0 + */ + @Beta + public static FutureCombiner whenAllComplete( + Iterable> futures) { + return new FutureCombiner(false, ImmutableList.copyOf(futures)); + } + + /** + * Creates a {@link FutureCombiner} requiring that all passed in futures are successful. + * + *

If any input fails, the returned future fails immediately. + * + * @since 20.0 + */ + @Beta + @SafeVarargs + public static FutureCombiner whenAllSucceed(ListenableFuture... futures) { + return new FutureCombiner(true, ImmutableList.copyOf(futures)); + } + + /** + * Creates a {@link FutureCombiner} requiring that all passed in futures are successful. + * + *

If any input fails, the returned future fails immediately. + * + * @since 20.0 + */ + @Beta + public static FutureCombiner whenAllSucceed( + Iterable> futures) { + return new FutureCombiner(true, ImmutableList.copyOf(futures)); + } + + /** + * A helper to create a new {@code ListenableFuture} whose result is generated from a combination + * of input futures. + * + *

See {@link #whenAllComplete} and {@link #whenAllSucceed} for how to instantiate this class. + * + *

Example: + * + *

{@code
+   * final ListenableFuture loginDateFuture =
+   *     loginService.findLastLoginDate(username);
+   * final ListenableFuture> recentCommandsFuture =
+   *     recentCommandsService.findRecentCommands(username);
+   * ListenableFuture usageFuture =
+   *     Futures.whenAllSucceed(loginDateFuture, recentCommandsFuture)
+   *         .call(
+   *             () ->
+   *                 new UsageHistory(
+   *                     username,
+   *                     Futures.getDone(loginDateFuture),
+   *                     Futures.getDone(recentCommandsFuture)),
+   *             executor);
+   * }
+ * + * @since 20.0 + */ + @Beta + // TODO(cpovirk): Consider removing, especially if we provide run(Runnable) + @GwtCompatible + public static final class FutureCombiner { + private final boolean allMustSucceed; + private final ImmutableList> futures; + + private FutureCombiner( + boolean allMustSucceed, ImmutableList> futures) { + this.allMustSucceed = allMustSucceed; + this.futures = futures; + } + + /** + * Creates the {@link ListenableFuture} which will return the result of calling {@link + * AsyncCallable#call} in {@code combiner} when all futures complete, using the specified {@code + * executor}. + * + *

If the combiner throws a {@code CancellationException}, the returned future will be + * cancelled. + * + *

If the combiner throws an {@code ExecutionException}, the cause of the thrown {@code + * ExecutionException} will be extracted and returned as the cause of the new {@code + * ExecutionException} that gets thrown by the returned combined future. + * + *

Canceling this future will attempt to cancel all the component futures. + */ + public ListenableFuture callAsync(AsyncCallable combiner, Executor executor) { + return new CombinedFuture(futures, allMustSucceed, executor, combiner); + } + + /** + * Creates the {@link ListenableFuture} which will return the result of calling {@link + * Callable#call} in {@code combiner} when all futures complete, using the specified {@code + * executor}. + * + *

If the combiner throws a {@code CancellationException}, the returned future will be + * cancelled. + * + *

If the combiner throws an {@code ExecutionException}, the cause of the thrown {@code + * ExecutionException} will be extracted and returned as the cause of the new {@code + * ExecutionException} that gets thrown by the returned combined future. + * + *

Canceling this future will attempt to cancel all the component futures. + */ + // TODO(cpovirk): Remove this + public ListenableFuture call(Callable combiner, Executor executor) { + return new CombinedFuture(futures, allMustSucceed, executor, combiner); + } + + /** + * Creates the {@link ListenableFuture} which will return the result of running {@code combiner} + * when all Futures complete. {@code combiner} will run using {@code executor}. + * + *

If the combiner throws a {@code CancellationException}, the returned future will be + * cancelled. + * + *

Canceling this Future will attempt to cancel all the component futures. + * + * @since 23.6 + */ + public ListenableFuture run(final Runnable combiner, Executor executor) { + return call( + new Callable() { + @Override + public Void call() throws Exception { + combiner.run(); + return null; + } + }, + executor); + } + } + + /** + * Returns a {@code ListenableFuture} whose result is set from the supplied future when it + * completes. Cancelling the supplied future will also cancel the returned future, but cancelling + * the returned future will have no effect on the supplied future. + * + * @since 15.0 + */ + @Beta + public static ListenableFuture nonCancellationPropagating(ListenableFuture future) { + if (future.isDone()) { + return future; + } + NonCancellationPropagatingFuture output = new NonCancellationPropagatingFuture<>(future); + future.addListener(output, directExecutor()); + return output; + } + + /** A wrapped future that does not propagate cancellation to its delegate. */ + private static final class NonCancellationPropagatingFuture + extends AbstractFuture.TrustedFuture implements Runnable { + private ListenableFuture delegate; + + NonCancellationPropagatingFuture(final ListenableFuture delegate) { + this.delegate = delegate; + } + + @Override + public void run() { + // This prevents cancellation from propagating because we don't call setFuture(delegate) until + // delegate is already done, so calling cancel() on this future won't affect it. + ListenableFuture localDelegate = delegate; + if (localDelegate != null) { + setFuture(localDelegate); + } + } + + @Override + protected String pendingToString() { + ListenableFuture localDelegate = delegate; + if (localDelegate != null) { + return "delegate=[" + localDelegate + "]"; + } + return null; + } + + @Override + protected void afterDone() { + delegate = null; + } + } + + /** + * Creates a new {@code ListenableFuture} whose value is a list containing the values of all its + * successful input futures. The list of results is in the same order as the input list, and if + * any of the provided futures fails or is canceled, its corresponding position will contain + * {@code null} (which is indistinguishable from the future having a successful value of {@code + * null}). + * + *

The list of results is in the same order as the input list. + * + *

This differs from {@link #allAsList(ListenableFuture[])} in that it's tolerant of failed + * futures for any of the items, representing them as {@code null} in the result list. + * + *

Canceling this future will attempt to cancel all the component futures. + * + * @param futures futures to combine + * @return a future that provides a list of the results of the component futures + * @since 10.0 + */ + @Beta + @SafeVarargs + public static ListenableFuture> successfulAsList( + ListenableFuture... futures) { + return new ListFuture(ImmutableList.copyOf(futures), false); + } + + /** + * Creates a new {@code ListenableFuture} whose value is a list containing the values of all its + * successful input futures. The list of results is in the same order as the input list, and if + * any of the provided futures fails or is canceled, its corresponding position will contain + * {@code null} (which is indistinguishable from the future having a successful value of {@code + * null}). + * + *

The list of results is in the same order as the input list. + * + *

This differs from {@link #allAsList(Iterable)} in that it's tolerant of failed futures for + * any of the items, representing them as {@code null} in the result list. + * + *

Canceling this future will attempt to cancel all the component futures. + * + * @param futures futures to combine + * @return a future that provides a list of the results of the component futures + * @since 10.0 + */ + @Beta + public static ListenableFuture> successfulAsList( + Iterable> futures) { + return new ListFuture(ImmutableList.copyOf(futures), false); + } + + /** + * Returns a list of delegate futures that correspond to the futures received in the order that + * they complete. Delegate futures return the same value or throw the same exception as the + * corresponding input future returns/throws. + * + *

"In the order that they complete" means, for practical purposes, about what you would + * expect, but there are some subtleties. First, we do guarantee that, if the output future at + * index n is done, the output future at index n-1 is also done. (But as usual with futures, some + * listeners for future n may complete before some for future n-1.) However, it is possible, if + * one input completes with result X and another later with result Y, for Y to come before X in + * the output future list. (Such races are impossible to solve without global synchronization of + * all future completions. And they should have little practical impact.) + * + *

Cancelling a delegate future propagates to input futures once all the delegates complete, + * either from cancellation or because an input future has completed. If N futures are passed in, + * and M delegates are cancelled, the remaining M input futures will be cancelled once N - M of + * the input futures complete. If all the delegates are cancelled, all the input futures will be + * too. + * + * @since 17.0 + */ + @Beta + public static ImmutableList> inCompletionOrder( + Iterable> futures) { + // Can't use Iterables.toArray because it's not gwt compatible + final Collection> collection; + if (futures instanceof Collection) { + collection = (Collection>) futures; + } else { + collection = ImmutableList.copyOf(futures); + } + @SuppressWarnings("unchecked") + ListenableFuture[] copy = + (ListenableFuture[]) + collection.toArray(new ListenableFuture[collection.size()]); + final InCompletionOrderState state = new InCompletionOrderState<>(copy); + ImmutableList.Builder> delegatesBuilder = ImmutableList.builder(); + for (int i = 0; i < copy.length; i++) { + delegatesBuilder.add(new InCompletionOrderFuture(state)); + } + + final ImmutableList> delegates = delegatesBuilder.build(); + for (int i = 0; i < copy.length; i++) { + final int localI = i; + copy[i].addListener( + new Runnable() { + @Override + public void run() { + state.recordInputCompletion(delegates, localI); + } + }, + directExecutor()); + } + + @SuppressWarnings("unchecked") + ImmutableList> delegatesCast = (ImmutableList) delegates; + return delegatesCast; + } + + // This can't be a TrustedFuture, because TrustedFuture has clever optimizations that + // mean cancel won't be called if this Future is passed into setFuture, and then + // cancelled. + private static final class InCompletionOrderFuture extends AbstractFuture { + private InCompletionOrderState state; + + private InCompletionOrderFuture(InCompletionOrderState state) { + this.state = state; + } + + @Override + public boolean cancel(boolean interruptIfRunning) { + InCompletionOrderState localState = state; + if (super.cancel(interruptIfRunning)) { + localState.recordOutputCancellation(interruptIfRunning); + return true; + } + return false; + } + + @Override + protected void afterDone() { + state = null; + } + + @Override + protected String pendingToString() { + InCompletionOrderState localState = state; + if (localState != null) { + // Don't print the actual array! We don't want inCompletionOrder(list).toString() to have + // quadratic output. + return "inputCount=[" + + localState.inputFutures.length + + "], remaining=[" + + localState.incompleteOutputCount.get() + + "]"; + } + return null; + } + } + + private static final class InCompletionOrderState { + // A happens-before edge between the writes of these fields and their reads exists, because + // in order to read these fields, the corresponding write to incompleteOutputCount must have + // been read. + private boolean wasCancelled = false; + private boolean shouldInterrupt = true; + private final AtomicInteger incompleteOutputCount; + private final ListenableFuture[] inputFutures; + private volatile int delegateIndex = 0; + + private InCompletionOrderState(ListenableFuture[] inputFutures) { + this.inputFutures = inputFutures; + incompleteOutputCount = new AtomicInteger(inputFutures.length); + } + + private void recordOutputCancellation(boolean interruptIfRunning) { + wasCancelled = true; + // If all the futures were cancelled with interruption, cancel the input futures + // with interruption; otherwise cancel without + if (!interruptIfRunning) { + shouldInterrupt = false; + } + recordCompletion(); + } + + private void recordInputCompletion( + ImmutableList> delegates, int inputFutureIndex) { + ListenableFuture inputFuture = inputFutures[inputFutureIndex]; + // Null out our reference to this future, so it can be GCed + inputFutures[inputFutureIndex] = null; + for (int i = delegateIndex; i < delegates.size(); i++) { + if (delegates.get(i).setFuture(inputFuture)) { + recordCompletion(); + // this is technically unnecessary, but should speed up later accesses + delegateIndex = i + 1; + return; + } + } + // If all the delegates were complete, no reason for the next listener to have to + // go through the whole list. Avoids O(n^2) behavior when the entire output list is + // cancelled. + delegateIndex = delegates.size(); + } + + private void recordCompletion() { + if (incompleteOutputCount.decrementAndGet() == 0 && wasCancelled) { + for (ListenableFuture toCancel : inputFutures) { + if (toCancel != null) { + toCancel.cancel(shouldInterrupt); + } + } + } + } + } + + /** + * Registers separate success and failure callbacks to be run when the {@code Future}'s + * computation is {@linkplain java.util.concurrent.Future#isDone() complete} or, if the + * computation is already complete, immediately. + * + *

The callback is run on {@code executor}. There is no guaranteed ordering of execution of + * callbacks, but any callback added through this method is guaranteed to be called once the + * computation is complete. + * + *

Example: + * + *

{@code
+   * ListenableFuture future = ...;
+   * Executor e = ...
+   * addCallback(future,
+   *     new FutureCallback() {
+   *       public void onSuccess(QueryResult result) {
+   *         storeInCache(result);
+   *       }
+   *       public void onFailure(Throwable t) {
+   *         reportError(t);
+   *       }
+   *     }, e);
+   * }
+ * + *

When selecting an executor, note that {@code directExecutor} is dangerous in some cases. See + * the discussion in the {@link ListenableFuture#addListener ListenableFuture.addListener} + * documentation. All its warnings about heavyweight listeners are also applicable to heavyweight + * callbacks passed to this method. + * + *

For a more general interface to attach a completion listener to a {@code Future}, see {@link + * ListenableFuture#addListener addListener}. + * + * @param future The future attach the callback to. + * @param callback The callback to invoke when {@code future} is completed. + * @param executor The executor to run {@code callback} when the future completes. + * @since 10.0 + */ + public static void addCallback( + final ListenableFuture future, + final FutureCallback callback, + Executor executor) { + Preconditions.checkNotNull(callback); + future.addListener(new CallbackListener(future, callback), executor); + } + + /** See {@link #addCallback(ListenableFuture, FutureCallback, Executor)} for behavioral notes. */ + private static final class CallbackListener implements Runnable { + final Future future; + final FutureCallback callback; + + CallbackListener(Future future, FutureCallback callback) { + this.future = future; + this.callback = callback; + } + + @Override + public void run() { + final V value; + try { + value = getDone(future); + } catch (ExecutionException e) { + callback.onFailure(e.getCause()); + return; + } catch (RuntimeException | Error e) { + callback.onFailure(e); + return; + } + callback.onSuccess(value); + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this).addValue(callback).toString(); + } + } + + /** + * Returns the result of the input {@code Future}, which must have already completed. + * + *

The benefits of this method are twofold. First, the name "getDone" suggests to readers that + * the {@code Future} is already done. Second, if buggy code calls {@code getDone} on a {@code + * Future} that is still pending, the program will throw instead of block. This can be important + * for APIs like {@link #whenAllComplete whenAllComplete(...)}{@code .}{@link + * FutureCombiner#call(Callable, Executor) call(...)}, where it is easy to use a new input from + * the {@code call} implementation but forget to add it to the arguments of {@code + * whenAllComplete}. + * + *

If you are looking for a method to determine whether a given {@code Future} is done, use the + * instance method {@link Future#isDone()}. + * + * @throws ExecutionException if the {@code Future} failed with an exception + * @throws CancellationException if the {@code Future} was cancelled + * @throws IllegalStateException if the {@code Future} is not done + * @since 20.0 + */ + + // TODO(cpovirk): Consider calling getDone() in our own code. + public static V getDone(Future future) throws ExecutionException { + /* + * We throw IllegalStateException, since the call could succeed later. Perhaps we "should" throw + * IllegalArgumentException, since the call could succeed with a different argument. Those + * exceptions' docs suggest that either is acceptable. Google's Java Practices page recommends + * IllegalArgumentException here, in part to keep its recommendation simple: Static methods + * should throw IllegalStateException only when they use static state. + * + * + * Why do we deviate here? The answer: We want for fluentFuture.getDone() to throw the same + * exception as Futures.getDone(fluentFuture). + */ + checkState(future.isDone(), "Future was expected to be done: %s", future); + return getUninterruptibly(future); + } + + /** + * Returns the result of {@link Future#get()}, converting most exceptions to a new instance of the + * given checked exception type. This reduces boilerplate for a common use of {@code Future} in + * which it is unnecessary to programmatically distinguish between exception types or to extract + * other information from the exception instance. + * + *

Exceptions from {@code Future.get} are treated as follows: + * + *

    + *
  • Any {@link ExecutionException} has its cause wrapped in an {@code X} if the cause + * is a checked exception, an {@link UncheckedExecutionException} if the cause is a {@code + * RuntimeException}, or an {@link ExecutionError} if the cause is an {@code Error}. + *
  • Any {@link InterruptedException} is wrapped in an {@code X} (after restoring the + * interrupt). + *
  • Any {@link CancellationException} is propagated untouched, as is any other {@link + * RuntimeException} (though {@code get} implementations are discouraged from throwing such + * exceptions). + *
+ * + *

The overall principle is to continue to treat every checked exception as a checked + * exception, every unchecked exception as an unchecked exception, and every error as an error. In + * addition, the cause of any {@code ExecutionException} is wrapped in order to ensure that the + * new stack trace matches that of the current thread. + * + *

Instances of {@code exceptionClass} are created by choosing an arbitrary public constructor + * that accepts zero or more arguments, all of type {@code String} or {@code Throwable} + * (preferring constructors with at least one {@code String}) and calling the constructor via + * reflection. If the exception did not already have a cause, one is set by calling {@link + * Throwable#initCause(Throwable)} on it. If no such constructor exists, an {@code + * IllegalArgumentException} is thrown. + * + * @throws X if {@code get} throws any checked exception except for an {@code ExecutionException} + * whose cause is not itself a checked exception + * @throws UncheckedExecutionException if {@code get} throws an {@code ExecutionException} with a + * {@code RuntimeException} as its cause + * @throws ExecutionError if {@code get} throws an {@code ExecutionException} with an {@code + * Error} as its cause + * @throws CancellationException if {@code get} throws a {@code CancellationException} + * @throws IllegalArgumentException if {@code exceptionClass} extends {@code RuntimeException} or + * does not have a suitable constructor + * @since 19.0 (in 10.0 as {@code get}) + */ + @Beta + + @GwtIncompatible // reflection + public static V getChecked(Future future, Class exceptionClass) + throws X { + return FuturesGetChecked.getChecked(future, exceptionClass); + } + + /** + * Returns the result of {@link Future#get(long, TimeUnit)}, converting most exceptions to a new + * instance of the given checked exception type. This reduces boilerplate for a common use of + * {@code Future} in which it is unnecessary to programmatically distinguish between exception + * types or to extract other information from the exception instance. + * + *

Exceptions from {@code Future.get} are treated as follows: + * + *

    + *
  • Any {@link ExecutionException} has its cause wrapped in an {@code X} if the cause + * is a checked exception, an {@link UncheckedExecutionException} if the cause is a {@code + * RuntimeException}, or an {@link ExecutionError} if the cause is an {@code Error}. + *
  • Any {@link InterruptedException} is wrapped in an {@code X} (after restoring the + * interrupt). + *
  • Any {@link TimeoutException} is wrapped in an {@code X}. + *
  • Any {@link CancellationException} is propagated untouched, as is any other {@link + * RuntimeException} (though {@code get} implementations are discouraged from throwing such + * exceptions). + *
+ * + *

The overall principle is to continue to treat every checked exception as a checked + * exception, every unchecked exception as an unchecked exception, and every error as an error. In + * addition, the cause of any {@code ExecutionException} is wrapped in order to ensure that the + * new stack trace matches that of the current thread. + * + *

Instances of {@code exceptionClass} are created by choosing an arbitrary public constructor + * that accepts zero or more arguments, all of type {@code String} or {@code Throwable} + * (preferring constructors with at least one {@code String}) and calling the constructor via + * reflection. If the exception did not already have a cause, one is set by calling {@link + * Throwable#initCause(Throwable)} on it. If no such constructor exists, an {@code + * IllegalArgumentException} is thrown. + * + * @throws X if {@code get} throws any checked exception except for an {@code ExecutionException} + * whose cause is not itself a checked exception + * @throws UncheckedExecutionException if {@code get} throws an {@code ExecutionException} with a + * {@code RuntimeException} as its cause + * @throws ExecutionError if {@code get} throws an {@code ExecutionException} with an {@code + * Error} as its cause + * @throws CancellationException if {@code get} throws a {@code CancellationException} + * @throws IllegalArgumentException if {@code exceptionClass} extends {@code RuntimeException} or + * does not have a suitable constructor + * @since 28.0 + */ + @Beta + + @GwtIncompatible // reflection + public static V getChecked( + Future future, Class exceptionClass, Duration timeout) throws X { + return getChecked(future, exceptionClass, toNanosSaturated(timeout), TimeUnit.NANOSECONDS); + } + + /** + * Returns the result of {@link Future#get(long, TimeUnit)}, converting most exceptions to a new + * instance of the given checked exception type. This reduces boilerplate for a common use of + * {@code Future} in which it is unnecessary to programmatically distinguish between exception + * types or to extract other information from the exception instance. + * + *

Exceptions from {@code Future.get} are treated as follows: + * + *

    + *
  • Any {@link ExecutionException} has its cause wrapped in an {@code X} if the cause + * is a checked exception, an {@link UncheckedExecutionException} if the cause is a {@code + * RuntimeException}, or an {@link ExecutionError} if the cause is an {@code Error}. + *
  • Any {@link InterruptedException} is wrapped in an {@code X} (after restoring the + * interrupt). + *
  • Any {@link TimeoutException} is wrapped in an {@code X}. + *
  • Any {@link CancellationException} is propagated untouched, as is any other {@link + * RuntimeException} (though {@code get} implementations are discouraged from throwing such + * exceptions). + *
+ * + *

The overall principle is to continue to treat every checked exception as a checked + * exception, every unchecked exception as an unchecked exception, and every error as an error. In + * addition, the cause of any {@code ExecutionException} is wrapped in order to ensure that the + * new stack trace matches that of the current thread. + * + *

Instances of {@code exceptionClass} are created by choosing an arbitrary public constructor + * that accepts zero or more arguments, all of type {@code String} or {@code Throwable} + * (preferring constructors with at least one {@code String}) and calling the constructor via + * reflection. If the exception did not already have a cause, one is set by calling {@link + * Throwable#initCause(Throwable)} on it. If no such constructor exists, an {@code + * IllegalArgumentException} is thrown. + * + * @throws X if {@code get} throws any checked exception except for an {@code ExecutionException} + * whose cause is not itself a checked exception + * @throws UncheckedExecutionException if {@code get} throws an {@code ExecutionException} with a + * {@code RuntimeException} as its cause + * @throws ExecutionError if {@code get} throws an {@code ExecutionException} with an {@code + * Error} as its cause + * @throws CancellationException if {@code get} throws a {@code CancellationException} + * @throws IllegalArgumentException if {@code exceptionClass} extends {@code RuntimeException} or + * does not have a suitable constructor + * @since 19.0 (in 10.0 as {@code get} and with different parameter order) + */ + @Beta + + @GwtIncompatible // reflection + @SuppressWarnings("GoodTime") // should accept a java.time.Duration + public static V getChecked( + Future future, Class exceptionClass, long timeout, TimeUnit unit) throws X { + return FuturesGetChecked.getChecked(future, exceptionClass, timeout, unit); + } + + /** + * Returns the result of calling {@link Future#get()} uninterruptibly on a task known not to throw + * a checked exception. This makes {@code Future} more suitable for lightweight, fast-running + * tasks that, barring bugs in the code, will not fail. This gives it exception-handling behavior + * similar to that of {@code ForkJoinTask.join}. + * + *

Exceptions from {@code Future.get} are treated as follows: + * + *

    + *
  • Any {@link ExecutionException} has its cause wrapped in an {@link + * UncheckedExecutionException} (if the cause is an {@code Exception}) or {@link + * ExecutionError} (if the cause is an {@code Error}). + *
  • Any {@link InterruptedException} causes a retry of the {@code get} call. The interrupt is + * restored before {@code getUnchecked} returns. + *
  • Any {@link CancellationException} is propagated untouched. So is any other {@link + * RuntimeException} ({@code get} implementations are discouraged from throwing such + * exceptions). + *
+ * + *

The overall principle is to eliminate all checked exceptions: to loop to avoid {@code + * InterruptedException}, to pass through {@code CancellationException}, and to wrap any exception + * from the underlying computation in an {@code UncheckedExecutionException} or {@code + * ExecutionError}. + * + *

For an uninterruptible {@code get} that preserves other exceptions, see {@link + * Uninterruptibles#getUninterruptibly(Future)}. + * + * @throws UncheckedExecutionException if {@code get} throws an {@code ExecutionException} with an + * {@code Exception} as its cause + * @throws ExecutionError if {@code get} throws an {@code ExecutionException} with an {@code + * Error} as its cause + * @throws CancellationException if {@code get} throws a {@code CancellationException} + * @since 10.0 + */ + + public static V getUnchecked(Future future) { + checkNotNull(future); + try { + return getUninterruptibly(future); + } catch (ExecutionException e) { + wrapAndThrowUnchecked(e.getCause()); + throw new AssertionError(); + } + } + + private static void wrapAndThrowUnchecked(Throwable cause) { + if (cause instanceof Error) { + throw new ExecutionError((Error) cause); + } + /* + * It's an Exception. (Or it's a non-Error, non-Exception Throwable. From my survey of such + * classes, I believe that most users intended to extend Exception, so we'll treat it like an + * Exception.) + */ + throw new UncheckedExecutionException(cause); + } + + /* + * Arguably we don't need a timed getUnchecked because any operation slow enough to require a + * timeout is heavyweight enough to throw a checked exception and therefore be inappropriate to + * use with getUnchecked. Further, it's not clear that converting the checked TimeoutException to + * a RuntimeException -- especially to an UncheckedExecutionException, since it wasn't thrown by + * the computation -- makes sense, and if we don't convert it, the user still has to write a + * try-catch block. + * + * If you think you would use this method, let us know. You might also also look into the + * Fork-Join framework: http://docs.oracle.com/javase/tutorial/essential/concurrency/forkjoin.html + */ +} diff --git a/src/main/java/com/google/common/util/concurrent/FuturesGetChecked.java b/src/main/java/com/google/common/util/concurrent/FuturesGetChecked.java new file mode 100644 index 0000000..d2b6b9e --- /dev/null +++ b/src/main/java/com/google/common/util/concurrent/FuturesGetChecked.java @@ -0,0 +1,301 @@ +/* + * Copyright (C) 2006 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.util.concurrent; + +import static com.google.common.base.Preconditions.checkArgument; +import static java.lang.Thread.currentThread; +import static java.util.Arrays.asList; + +import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.Function; +import com.google.common.collect.Ordering; + + +import java.lang.ref.WeakReference; +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.util.Arrays; +import java.util.List; +import java.util.Set; +import java.util.concurrent.CopyOnWriteArraySet; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + + +/** Static methods used to implement {@link Futures#getChecked(Future, Class)}. */ +@GwtIncompatible +final class FuturesGetChecked { + + static V getChecked(Future future, Class exceptionClass) throws X { + return getChecked(bestGetCheckedTypeValidator(), future, exceptionClass); + } + + /** Implementation of {@link Futures#getChecked(Future, Class)}. */ + + @VisibleForTesting + static V getChecked( + GetCheckedTypeValidator validator, Future future, Class exceptionClass) throws X { + validator.validateClass(exceptionClass); + try { + return future.get(); + } catch (InterruptedException e) { + currentThread().interrupt(); + throw newWithCause(exceptionClass, e); + } catch (ExecutionException e) { + wrapAndThrowExceptionOrError(e.getCause(), exceptionClass); + throw new AssertionError(); + } + } + + /** Implementation of {@link Futures#getChecked(Future, Class, long, TimeUnit)}. */ + + static V getChecked( + Future future, Class exceptionClass, long timeout, TimeUnit unit) throws X { + // TODO(cpovirk): benchmark a version of this method that accepts a GetCheckedTypeValidator + bestGetCheckedTypeValidator().validateClass(exceptionClass); + try { + return future.get(timeout, unit); + } catch (InterruptedException e) { + currentThread().interrupt(); + throw newWithCause(exceptionClass, e); + } catch (TimeoutException e) { + throw newWithCause(exceptionClass, e); + } catch (ExecutionException e) { + wrapAndThrowExceptionOrError(e.getCause(), exceptionClass); + throw new AssertionError(); + } + } + + @VisibleForTesting + interface GetCheckedTypeValidator { + void validateClass(Class exceptionClass); + } + + private static GetCheckedTypeValidator bestGetCheckedTypeValidator() { + return GetCheckedTypeValidatorHolder.BEST_VALIDATOR; + } + + @VisibleForTesting + static GetCheckedTypeValidator weakSetValidator() { + return GetCheckedTypeValidatorHolder.WeakSetValidator.INSTANCE; + } + + // ClassValue + @VisibleForTesting + static GetCheckedTypeValidator classValueValidator() { + return GetCheckedTypeValidatorHolder.ClassValueValidator.INSTANCE; + } + + /** + * Provides a check of whether an exception type is valid for use with {@link + * FuturesGetChecked#getChecked(Future, Class)}, possibly using caching. + * + *

Uses reflection to gracefully fall back to when certain implementations aren't available. + */ + @VisibleForTesting + static class GetCheckedTypeValidatorHolder { + static final String CLASS_VALUE_VALIDATOR_NAME = + GetCheckedTypeValidatorHolder.class.getName() + "$ClassValueValidator"; + + static final GetCheckedTypeValidator BEST_VALIDATOR = getBestValidator(); + + @IgnoreJRERequirement // getChecked falls back to another implementation if necessary + // ClassValue + enum ClassValueValidator implements GetCheckedTypeValidator { + INSTANCE; + + /* + * Static final fields are presumed to be fastest, based on our experience with + * UnsignedBytesBenchmark. TODO(cpovirk): benchmark this + */ + private static final ClassValue isValidClass = + new ClassValue() { + @Override + protected Boolean computeValue(Class type) { + checkExceptionClassValidity(type.asSubclass(Exception.class)); + return true; + } + }; + + @Override + public void validateClass(Class exceptionClass) { + isValidClass.get(exceptionClass); // throws if invalid; returns safely (and caches) if valid + } + } + + enum WeakSetValidator implements GetCheckedTypeValidator { + INSTANCE; + + /* + * Static final fields are presumed to be fastest, based on our experience with + * UnsignedBytesBenchmark. TODO(cpovirk): benchmark this + */ + /* + * A CopyOnWriteArraySet is faster than a newSetFromMap of a MapMaker map with + * weakKeys() and concurrencyLevel(1), even up to at least 12 cached exception types. + */ + private static final Set>> validClasses = + new CopyOnWriteArraySet<>(); + + @Override + public void validateClass(Class exceptionClass) { + for (WeakReference> knownGood : validClasses) { + if (exceptionClass.equals(knownGood.get())) { + return; + } + // TODO(cpovirk): if reference has been cleared, remove it? + } + checkExceptionClassValidity(exceptionClass); + + /* + * It's very unlikely that any loaded Futures class will see getChecked called with more + * than a handful of exceptions. But it seems prudent to set a cap on how many we'll cache. + * This avoids out-of-control memory consumption, and it keeps the cache from growing so + * large that doing the lookup is noticeably slower than redoing the work would be. + * + * Ideally we'd have a real eviction policy, but until we see a problem in practice, I hope + * that this will suffice. I have not even benchmarked with different size limits. + */ + if (validClasses.size() > 1000) { + validClasses.clear(); + } + + validClasses.add(new WeakReference>(exceptionClass)); + } + } + + /** + * Returns the ClassValue-using validator, or falls back to the "weak Set" implementation if + * unable to do so. + */ + static GetCheckedTypeValidator getBestValidator() { + try { + Class theClass = Class.forName(CLASS_VALUE_VALIDATOR_NAME); + return (GetCheckedTypeValidator) theClass.getEnumConstants()[0]; + } catch (Throwable t) { // ensure we really catch *everything* + return weakSetValidator(); + } + } + } + + // TODO(cpovirk): change parameter order to match other helper methods (Class, Throwable)? + private static void wrapAndThrowExceptionOrError( + Throwable cause, Class exceptionClass) throws X { + if (cause instanceof Error) { + throw new ExecutionError((Error) cause); + } + if (cause instanceof RuntimeException) { + throw new UncheckedExecutionException(cause); + } + throw newWithCause(exceptionClass, cause); + } + + /* + * TODO(user): FutureChecker interface for these to be static methods on? If so, refer to it in + * the (static-method) Futures.getChecked documentation + */ + + private static boolean hasConstructorUsableByGetChecked( + Class exceptionClass) { + try { + Exception unused = newWithCause(exceptionClass, new Exception()); + return true; + } catch (Exception e) { + return false; + } + } + + private static X newWithCause(Class exceptionClass, Throwable cause) { + // getConstructors() guarantees this as long as we don't modify the array. + @SuppressWarnings({"unchecked", "rawtypes"}) + List> constructors = (List) Arrays.asList(exceptionClass.getConstructors()); + for (Constructor constructor : preferringStrings(constructors)) { + X instance = newFromConstructor(constructor, cause); + if (instance != null) { + if (instance.getCause() == null) { + instance.initCause(cause); + } + return instance; + } + } + throw new IllegalArgumentException( + "No appropriate constructor for exception of type " + + exceptionClass + + " in response to chained exception", + cause); + } + + private static List> preferringStrings( + List> constructors) { + return WITH_STRING_PARAM_FIRST.sortedCopy(constructors); + } + + private static final Ordering> WITH_STRING_PARAM_FIRST = + Ordering.natural() + .onResultOf( + new Function, Boolean>() { + @Override + public Boolean apply(Constructor input) { + return asList(input.getParameterTypes()).contains(String.class); + } + }) + .reverse(); + + private static X newFromConstructor(Constructor constructor, Throwable cause) { + Class[] paramTypes = constructor.getParameterTypes(); + Object[] params = new Object[paramTypes.length]; + for (int i = 0; i < paramTypes.length; i++) { + Class paramType = paramTypes[i]; + if (paramType.equals(String.class)) { + params[i] = cause.toString(); + } else if (paramType.equals(Throwable.class)) { + params[i] = cause; + } else { + return null; + } + } + try { + return constructor.newInstance(params); + } catch (IllegalArgumentException + | InstantiationException + | IllegalAccessException + | InvocationTargetException e) { + return null; + } + } + + @VisibleForTesting + static boolean isCheckedException(Class type) { + return !RuntimeException.class.isAssignableFrom(type); + } + + @VisibleForTesting + static void checkExceptionClassValidity(Class exceptionClass) { + checkArgument( + isCheckedException(exceptionClass), + "Futures.getChecked exception type (%s) must not be a RuntimeException", + exceptionClass); + checkArgument( + hasConstructorUsableByGetChecked(exceptionClass), + "Futures.getChecked exception type (%s) must be an accessible class with an accessible " + + "constructor whose parameters (if any) must be of type String and/or Throwable", + exceptionClass); + } + + private FuturesGetChecked() {} +} diff --git a/src/main/java/com/google/common/util/concurrent/GwtFluentFutureCatchingSpecialization.java b/src/main/java/com/google/common/util/concurrent/GwtFluentFutureCatchingSpecialization.java new file mode 100644 index 0000000..e8acf62 --- /dev/null +++ b/src/main/java/com/google/common/util/concurrent/GwtFluentFutureCatchingSpecialization.java @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2006 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.util.concurrent; + +import com.google.common.annotations.GwtCompatible; + +/** + * Hidden superclass of {@link FluentFuture} that provides us a place to declare special GWT + * versions of the {@link FluentFuture#catching(Class, com.google.common.base.Function) + * FluentFuture.catching} family of methods. Those versions have slightly different signatures. + */ +@GwtCompatible(emulated = true) +abstract class GwtFluentFutureCatchingSpecialization extends AbstractFuture { + /* + * This server copy of the class is empty. The corresponding GWT copy contains alternative + * versions of catching() and catchingAsync() with slightly different signatures from the ones + * found in FluentFuture.java. + */ +} diff --git a/src/main/java/com/google/common/util/concurrent/GwtFuturesCatchingSpecialization.java b/src/main/java/com/google/common/util/concurrent/GwtFuturesCatchingSpecialization.java new file mode 100644 index 0000000..4626ce9 --- /dev/null +++ b/src/main/java/com/google/common/util/concurrent/GwtFuturesCatchingSpecialization.java @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2006 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.util.concurrent; + +import com.google.common.annotations.GwtCompatible; + +/** + * Hidden superclass of {@link Futures} that provides us a place to declare special GWT versions of + * the {@link Futures#catching(ListenableFuture, Class, com.google.common.base.Function, + * java.util.concurrent.Executor) Futures.catching} family of methods. Those versions have slightly + * different signatures. + */ +@GwtCompatible(emulated = true) +abstract class GwtFuturesCatchingSpecialization { + /* + * This server copy of the class is empty. The corresponding GWT copy contains alternative + * versions of catching() and catchingAsync() with slightly different signatures from the ones + * found in Futures.java. + */ +} diff --git a/src/main/java/com/google/common/util/concurrent/IgnoreJRERequirement.java b/src/main/java/com/google/common/util/concurrent/IgnoreJRERequirement.java new file mode 100644 index 0000000..b557ac6 --- /dev/null +++ b/src/main/java/com/google/common/util/concurrent/IgnoreJRERequirement.java @@ -0,0 +1,17 @@ +/* + * Copyright 2019 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.util.concurrent; + +@interface IgnoreJRERequirement {} diff --git a/src/main/java/com/google/common/util/concurrent/ImmediateFuture.java b/src/main/java/com/google/common/util/concurrent/ImmediateFuture.java new file mode 100644 index 0000000..5a940ea --- /dev/null +++ b/src/main/java/com/google/common/util/concurrent/ImmediateFuture.java @@ -0,0 +1,102 @@ +/* + * Copyright (C) 2006 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.util.concurrent; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.annotations.GwtCompatible; +import com.google.common.util.concurrent.AbstractFuture.TrustedFuture; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Executor; +import java.util.concurrent.TimeUnit; +import java.util.logging.Level; +import java.util.logging.Logger; + + +/** Implementation of {@link Futures#immediateFuture}. */ +@GwtCompatible +// TODO(cpovirk): Make this final (but that may break Mockito spy calls). +class ImmediateFuture implements ListenableFuture { + static final ListenableFuture NULL = new ImmediateFuture<>(null); + + private static final Logger log = Logger.getLogger(ImmediateFuture.class.getName()); + + private final V value; + + ImmediateFuture(V value) { + this.value = value; + } + + @Override + public void addListener(Runnable listener, Executor executor) { + checkNotNull(listener, "Runnable was null."); + checkNotNull(executor, "Executor was null."); + try { + executor.execute(listener); + } catch (RuntimeException e) { + // ListenableFuture's contract is that it will not throw unchecked exceptions, so log the bad + // runnable and/or executor and swallow it. + log.log( + Level.SEVERE, + "RuntimeException while executing runnable " + listener + " with executor " + executor, + e); + } + } + + @Override + public boolean cancel(boolean mayInterruptIfRunning) { + return false; + } + + // TODO(lukes): Consider throwing InterruptedException when appropriate. + @Override + public V get() { + return value; + } + + @Override + public V get(long timeout, TimeUnit unit) throws ExecutionException { + checkNotNull(unit); + return get(); + } + + @Override + public boolean isCancelled() { + return false; + } + + @Override + public boolean isDone() { + return true; + } + + @Override + public String toString() { + // Behaviour analogous to AbstractFuture#toString(). + return super.toString() + "[status=SUCCESS, result=[" + value + "]]"; + } + + static final class ImmediateFailedFuture extends TrustedFuture { + ImmediateFailedFuture(Throwable thrown) { + setException(thrown); + } + } + + static final class ImmediateCancelledFuture extends TrustedFuture { + ImmediateCancelledFuture() { + cancel(false); + } + } +} diff --git a/src/main/java/com/google/common/util/concurrent/Internal.java b/src/main/java/com/google/common/util/concurrent/Internal.java new file mode 100644 index 0000000..8f83823 --- /dev/null +++ b/src/main/java/com/google/common/util/concurrent/Internal.java @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2019 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.util.concurrent; + +import com.google.common.annotations.GwtIncompatible; +import java.time.Duration; + +/** This class is for {@code com.google.common.util.concurrent} use only! */ +@GwtIncompatible // java.time.Duration +final class Internal { + + /** + * Returns the number of nanoseconds of the given duration without throwing or overflowing. + * + *

Instead of throwing {@link ArithmeticException}, this method silently saturates to either + * {@link Long#MAX_VALUE} or {@link Long#MIN_VALUE}. This behavior can be useful when decomposing + * a duration in order to call a legacy API which requires a {@code long, TimeUnit} pair. + */ + static long toNanosSaturated(Duration duration) { + // Using a try/catch seems lazy, but the catch block will rarely get invoked (except for + // durations longer than approximately +/- 292 years). + try { + return duration.toNanos(); + } catch (ArithmeticException tooBig) { + return duration.isNegative() ? Long.MIN_VALUE : Long.MAX_VALUE; + } + } + + private Internal() {} +} diff --git a/src/main/java/com/google/common/util/concurrent/InterruptibleTask.java b/src/main/java/com/google/common/util/concurrent/InterruptibleTask.java new file mode 100644 index 0000000..d9ffd1f --- /dev/null +++ b/src/main/java/com/google/common/util/concurrent/InterruptibleTask.java @@ -0,0 +1,198 @@ +/* + * Copyright (C) 2015 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.util.concurrent; + +import com.google.common.annotations.GwtCompatible; + +import java.util.concurrent.atomic.AtomicReference; +import java.util.concurrent.locks.LockSupport; + + +@GwtCompatible(emulated = true) +// Some Android 5.0.x Samsung devices have bugs in JDK reflection APIs that cause +// getDeclaredField to throw a NoSuchFieldException when the field is definitely there. +// Since this class only needs CAS on one field, we can avoid this bug by extending AtomicReference +// instead of using an AtomicReferenceFieldUpdater. This reference stores Thread instances +// and DONE/INTERRUPTED - they have a common ancestor of Runnable. +abstract class InterruptibleTask extends AtomicReference implements Runnable { + static { + // Prevent rare disastrous classloading in first call to LockSupport.park. + // See: https://bugs.openjdk.java.net/browse/JDK-8074773 + @SuppressWarnings("unused") + Class ensureLoaded = LockSupport.class; + } + + private static final class DoNothingRunnable implements Runnable { + @Override + public void run() {} + } + // The thread executing the task publishes itself to the superclass' reference and the thread + // interrupting sets DONE when it has finished interrupting. + private static final Runnable DONE = new DoNothingRunnable(); + private static final Runnable INTERRUPTING = new DoNothingRunnable(); + private static final Runnable PARKED = new DoNothingRunnable(); + // Why 1000? WHY NOT! + private static final int MAX_BUSY_WAIT_SPINS = 1000; + + @SuppressWarnings("ThreadPriorityCheck") // The cow told me to + @Override + public final void run() { + /* + * Set runner thread before checking isDone(). If we were to check isDone() first, the task + * might be cancelled before we set the runner thread. That would make it impossible to + * interrupt, yet it will still run, since interruptTask will leave the runner value null, + * allowing the CAS below to succeed. + */ + Thread currentThread = Thread.currentThread(); + if (!compareAndSet(null, currentThread)) { + return; // someone else has run or is running. + } + + boolean run = !isDone(); + T result = null; + Throwable error = null; + try { + if (run) { + result = runInterruptibly(); + } + } catch (Throwable t) { + error = t; + } finally { + // Attempt to set the task as done so that further attempts to interrupt will fail. + if (!compareAndSet(currentThread, DONE)) { + // If we were interrupted, it is possible that the interrupted bit hasn't been set yet. Wait + // for the interrupting thread to set DONE. See interruptTask(). + // We want to wait so that we don't interrupt the _next_ thing run on the thread. + // Note: We don't reset the interrupted bit, just wait for it to be set. + // If this is a thread pool thread, the thread pool will reset it for us. Otherwise, the + // interrupted bit may have been intended for something else, so don't clear it. + boolean restoreInterruptedBit = false; + int spinCount = 0; + // Interrupting Cow Says: + // ______ + // < Spin > + // ------ + // \ ^__^ + // \ (oo)\_______ + // (__)\ )\/\ + // ||----w | + // || || + Runnable state = get(); + while (state == INTERRUPTING || state == PARKED) { + spinCount++; + if (spinCount > MAX_BUSY_WAIT_SPINS) { + // If we have spun a lot just park ourselves. + // This will save CPU while we wait for a slow interrupting thread. In theory + // interruptTask() should be very fast but due to InterruptibleChannel and + // JavaLangAccess.blockedOn(Thread, Interruptible), it isn't predictable what work might + // be done. (e.g. close a file and flush buffers to disk). To protect ourselve from + // this we park ourselves and tell our interrupter that we did so. + if (state == PARKED || compareAndSet(INTERRUPTING, PARKED)) { + // Interrupting Cow Says: + // ______ + // < Park > + // ------ + // \ ^__^ + // \ (oo)\_______ + // (__)\ )\/\ + // ||----w | + // || || + // We need to clear the interrupted bit prior to calling park and maintain it in case + // we wake up spuriously. + restoreInterruptedBit = Thread.interrupted() || restoreInterruptedBit; + LockSupport.park(this); + } + } else { + Thread.yield(); + } + state = get(); + } + if (restoreInterruptedBit) { + currentThread.interrupt(); + } + /* + * TODO(cpovirk): Clear interrupt status here? We currently don't, which means that an + * interrupt before, during, or after runInterruptibly() (unless it produced an + * InterruptedException caught above) can linger and affect listeners. + */ + } + if (run) { + afterRanInterruptibly(result, error); + } + } + } + + /** + * Called before runInterruptibly - if true, runInterruptibly and afterRanInterruptibly will not + * be called. + */ + abstract boolean isDone(); + + /** + * Do interruptible work here - do not complete Futures here, as their listeners could be + * interrupted. + */ + abstract T runInterruptibly() throws Exception; + + /** + * Any interruption that happens as a result of calling interruptTask will arrive before this + * method is called. Complete Futures here. + */ + abstract void afterRanInterruptibly(T result, Throwable error); + + /** + * Interrupts the running task. Because this internally calls {@link Thread#interrupt()} which can + * in turn invoke arbitrary code it is not safe to call while holding a lock. + */ + final void interruptTask() { + // Since the Thread is replaced by DONE before run() invokes listeners or returns, if we succeed + // in this CAS, there's no risk of interrupting the wrong thread or interrupting a thread that + // isn't currently executing this task. + Runnable currentRunner = get(); + if (currentRunner instanceof Thread && compareAndSet(currentRunner, INTERRUPTING)) { + // Thread.interrupt can throw aribitrary exceptions due to the nio InterruptibleChannel API + // This will make sure that tasks don't get stuck busy waiting. + // Some of this is fixed in jdk11 (see https://bugs.openjdk.java.net/browse/JDK-8198692) but + // not all. See the test cases for examples on how this can happen. + try { + ((Thread) currentRunner).interrupt(); + } finally { + Runnable prev = getAndSet(DONE); + if (prev == PARKED) { + LockSupport.unpark((Thread) currentRunner); + } + } + } + } + + @Override + public final String toString() { + Runnable state = get(); + final String result; + if (state == DONE) { + result = "running=[DONE]"; + } else if (state == INTERRUPTING) { + result = "running=[INTERRUPTED]"; + } else if (state instanceof Thread) { + // getName is final on Thread, no need to worry about exceptions + result = "running=[RUNNING ON " + ((Thread) state).getName() + "]"; + } else { + result = "running=[NOT STARTED YET]"; + } + return result + ", " + toPendingString(); + } + + abstract String toPendingString(); +} diff --git a/src/main/java/com/google/common/util/concurrent/JdkFutureAdapters.java b/src/main/java/com/google/common/util/concurrent/JdkFutureAdapters.java new file mode 100644 index 0000000..1b84302 --- /dev/null +++ b/src/main/java/com/google/common/util/concurrent/JdkFutureAdapters.java @@ -0,0 +1,171 @@ +/* + * Copyright (C) 2009 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.util.concurrent; + +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.util.concurrent.Uninterruptibles.getUninterruptibly; + +import com.google.common.annotations.Beta; +import com.google.common.annotations.GwtIncompatible; +import java.util.concurrent.Executor; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.atomic.AtomicBoolean; + +/** + * Utilities necessary for working with libraries that supply plain {@link Future} instances. Note + * that, whenever possible, it is strongly preferred to modify those libraries to return {@code + * ListenableFuture} directly. + * + * @author Sven Mawson + * @since 10.0 (replacing {@code Futures.makeListenable}, which existed in 1.0) + */ +@Beta +@GwtIncompatible +public final class JdkFutureAdapters { + /** + * Assigns a thread to the given {@link Future} to provide {@link ListenableFuture} functionality. + * + *

Warning: If the input future does not already implement {@code ListenableFuture}, the + * returned future will emulate {@link ListenableFuture#addListener} by taking a thread from an + * internal, unbounded pool at the first call to {@code addListener} and holding it until the + * future is {@linkplain Future#isDone() done}. + * + *

Prefer to create {@code ListenableFuture} instances with {@link SettableFuture}, {@link + * MoreExecutors#listeningDecorator( java.util.concurrent.ExecutorService)}, {@link + * ListenableFutureTask}, {@link AbstractFuture}, and other utilities over creating plain {@code + * Future} instances to be upgraded to {@code ListenableFuture} after the fact. + */ + public static ListenableFuture listenInPoolThread(Future future) { + if (future instanceof ListenableFuture) { + return (ListenableFuture) future; + } + return new ListenableFutureAdapter(future); + } + + /** + * Submits a blocking task for the given {@link Future} to provide {@link ListenableFuture} + * functionality. + * + *

Warning: If the input future does not already implement {@code ListenableFuture}, the + * returned future will emulate {@link ListenableFuture#addListener} by submitting a task to the + * given executor at the first call to {@code addListener}. The task must be started by the + * executor promptly, or else the returned {@code ListenableFuture} may fail to work. The task's + * execution consists of blocking until the input future is {@linkplain Future#isDone() done}, so + * each call to this method may claim and hold a thread for an arbitrary length of time. Use of + * bounded executors or other executors that may fail to execute a task promptly may result in + * deadlocks. + * + *

Prefer to create {@code ListenableFuture} instances with {@link SettableFuture}, {@link + * MoreExecutors#listeningDecorator( java.util.concurrent.ExecutorService)}, {@link + * ListenableFutureTask}, {@link AbstractFuture}, and other utilities over creating plain {@code + * Future} instances to be upgraded to {@code ListenableFuture} after the fact. + * + * @since 12.0 + */ + public static ListenableFuture listenInPoolThread(Future future, Executor executor) { + checkNotNull(executor); + if (future instanceof ListenableFuture) { + return (ListenableFuture) future; + } + return new ListenableFutureAdapter(future, executor); + } + + /** + * An adapter to turn a {@link Future} into a {@link ListenableFuture}. This will wait on the + * future to finish, and when it completes, run the listeners. This implementation will wait on + * the source future indefinitely, so if the source future never completes, the adapter will never + * complete either. + * + *

If the delegate future is interrupted or throws an unexpected unchecked exception, the + * listeners will not be invoked. + */ + private static class ListenableFutureAdapter extends ForwardingFuture + implements ListenableFuture { + + private static final ThreadFactory threadFactory = + new ThreadFactoryBuilder() + .setDaemon(true) + .setNameFormat("ListenableFutureAdapter-thread-%d") + .build(); + private static final Executor defaultAdapterExecutor = + Executors.newCachedThreadPool(threadFactory); + + private final Executor adapterExecutor; + + // The execution list to hold our listeners. + private final ExecutionList executionList = new ExecutionList(); + + // This allows us to only start up a thread waiting on the delegate future when the first + // listener is added. + private final AtomicBoolean hasListeners = new AtomicBoolean(false); + + // The delegate future. + private final Future delegate; + + ListenableFutureAdapter(Future delegate) { + this(delegate, defaultAdapterExecutor); + } + + ListenableFutureAdapter(Future delegate, Executor adapterExecutor) { + this.delegate = checkNotNull(delegate); + this.adapterExecutor = checkNotNull(adapterExecutor); + } + + @Override + protected Future delegate() { + return delegate; + } + + @Override + public void addListener(Runnable listener, Executor exec) { + executionList.add(listener, exec); + + // When a listener is first added, we run a task that will wait for the delegate to finish, + // and when it is done will run the listeners. + if (hasListeners.compareAndSet(false, true)) { + if (delegate.isDone()) { + // If the delegate is already done, run the execution list immediately on the current + // thread. + executionList.execute(); + return; + } + + // TODO(lukes): handle RejectedExecutionException + adapterExecutor.execute( + new Runnable() { + @Override + public void run() { + try { + /* + * Threads from our private pool are never interrupted. Threads from a + * user-supplied executor might be, but... what can we do? This is another reason + * to return a proper ListenableFuture instead of using listenInPoolThread. + */ + getUninterruptibly(delegate); + } catch (Throwable e) { + // ExecutionException / CancellationException / RuntimeException / Error + // The task is presumably done, run the listeners. + } + executionList.execute(); + } + }); + } + } + } + + private JdkFutureAdapters() {} +} diff --git a/src/main/java/com/google/common/util/concurrent/ListenableFuture.java b/src/main/java/com/google/common/util/concurrent/ListenableFuture.java new file mode 100644 index 0000000..371c5b5 --- /dev/null +++ b/src/main/java/com/google/common/util/concurrent/ListenableFuture.java @@ -0,0 +1,147 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.util.concurrent; + +import java.util.concurrent.Executor; +import java.util.concurrent.Future; +import java.util.concurrent.RejectedExecutionException; + +/** + * A {@link Future} that accepts completion listeners. Each listener has an associated executor, and + * it is invoked using this executor once the future's computation is {@linkplain Future#isDone() + * complete}. If the computation has already completed when the listener is added, the listener will + * execute immediately. + * + *

See the Guava User Guide article on {@code + * ListenableFuture}. + * + *

This class is GWT-compatible. + * + *

Purpose

+ * + *

The main purpose of {@code ListenableFuture} is to help you chain together a graph of + * asynchronous operations. You can chain them together manually with calls to methods like {@link + * Futures#transform(ListenableFuture, com.google.common.base.Function, Executor) + * Futures.transform}, but you will often find it easier to use a framework. Frameworks automate the + * process, often adding features like monitoring, debugging, and cancellation. Examples of + * frameworks include: + * + *

+ * + *

The main purpose of {@link #addListener addListener} is to support this chaining. You will + * rarely use it directly, in part because it does not provide direct access to the {@code Future} + * result. (If you want such access, you may prefer {@link Futures#addCallback + * Futures.addCallback}.) Still, direct {@code addListener} calls are occasionally useful: + * + *

{@code
+ * final String name = ...;
+ * inFlight.add(name);
+ * ListenableFuture future = service.query(name);
+ * future.addListener(new Runnable() {
+ *   public void run() {
+ *     processedCount.incrementAndGet();
+ *     inFlight.remove(name);
+ *     lastProcessed.set(name);
+ *     logger.info("Done with {0}", name);
+ *   }
+ * }, executor);
+ * }
+ * + *

How to get an instance

+ * + *

We encourage you to return {@code ListenableFuture} from your methods so that your users can + * take advantage of the {@linkplain Futures utilities built atop the class}. The way that you will + * create {@code ListenableFuture} instances depends on how you currently create {@code Future} + * instances: + * + *

    + *
  • If you receive them from an {@code java.util.concurrent.ExecutorService}, convert that + * service to a {@link ListeningExecutorService}, usually by calling {@link + * MoreExecutors#listeningDecorator(java.util.concurrent.ExecutorService) + * MoreExecutors.listeningDecorator}. + *
  • If you manually call {@link java.util.concurrent.FutureTask#set} or a similar method, + * create a {@link SettableFuture} instead. (If your needs are more complex, you may prefer + * {@link AbstractFuture}.) + *
+ * + *

Test doubles: If you need a {@code ListenableFuture} for your test, try a {@link + * SettableFuture} or one of the methods in the {@link Futures#immediateFuture Futures.immediate*} + * family. Avoid creating a mock or stub {@code Future}. Mock and stub implementations are + * fragile because they assume that only certain methods will be called and because they often + * implement subtleties of the API improperly. + * + *

Custom implementation: Avoid implementing {@code ListenableFuture} from scratch. If you + * can't get by with the standard implementations, prefer to derive a new {@code Future} instance + * with the methods in {@link Futures} or, if necessary, to extend {@link AbstractFuture}. + * + *

Occasionally, an API will return a plain {@code Future} and it will be impossible to change + * the return type. For this case, we provide a more expensive workaround in {@code + * JdkFutureAdapters}. However, when possible, it is more efficient and reliable to create a {@code + * ListenableFuture} directly. + * + * @author Sven Mawson + * @author Nishant Thakkar + * @since 1.0 + */ +public interface ListenableFuture extends Future { + /** + * Registers a listener to be {@linkplain Executor#execute(Runnable) run} on the given executor. + * The listener will run when the {@code Future}'s computation is {@linkplain Future#isDone() + * complete} or, if the computation is already complete, immediately. + * + *

There is no guaranteed ordering of execution of listeners, but any listener added through + * this method is guaranteed to be called once the computation is complete. + * + *

Exceptions thrown by a listener will be propagated up to the executor. Any exception thrown + * during {@code Executor.execute} (e.g., a {@code RejectedExecutionException} or an exception + * thrown by {@linkplain MoreExecutors#directExecutor direct execution}) will be caught and + * logged. + * + *

Note: For fast, lightweight listeners that would be safe to execute in any thread, consider + * {@link MoreExecutors#directExecutor}. Otherwise, avoid it. Heavyweight {@code directExecutor} + * listeners can cause problems, and these problems can be difficult to reproduce because they + * depend on timing. For example: + * + *

    + *
  • The listener may be executed by the caller of {@code addListener}. That caller may be a + * UI thread or other latency-sensitive thread. This can harm UI responsiveness. + *
  • The listener may be executed by the thread that completes this {@code Future}. That + * thread may be an internal system thread such as an RPC network thread. Blocking that + * thread may stall progress of the whole system. It may even cause a deadlock. + *
  • The listener may delay other listeners, even listeners that are not themselves {@code + * directExecutor} listeners. + *
+ * + *

This is the most general listener interface. For common operations performed using + * listeners, see {@link Futures}. For a simplified but general listener interface, see {@link + * Futures#addCallback addCallback()}. + * + *

Memory consistency effects: Actions in a thread prior to adding a listener + * happen-before its execution begins, perhaps in another thread. + * + *

Guava implementations of {@code ListenableFuture} promptly release references to listeners + * after executing them. + * + * @param listener the listener to run when the computation is complete + * @param executor the executor to run the listener in + * @throws RejectedExecutionException if we tried to execute the listener immediately but the + * executor rejected it. + */ + void addListener(Runnable listener, Executor executor); +} diff --git a/src/main/java/com/google/common/util/concurrent/ListenableFutureTask.java b/src/main/java/com/google/common/util/concurrent/ListenableFutureTask.java new file mode 100644 index 0000000..e423275 --- /dev/null +++ b/src/main/java/com/google/common/util/concurrent/ListenableFutureTask.java @@ -0,0 +1,88 @@ +/* + * Copyright (C) 2008 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.util.concurrent; + +import com.google.common.annotations.GwtIncompatible; +import java.util.concurrent.Callable; +import java.util.concurrent.Executor; +import java.util.concurrent.FutureTask; + + +/** + * A {@link FutureTask} that also implements the {@link ListenableFuture} interface. Unlike {@code + * FutureTask}, {@code ListenableFutureTask} does not provide an overrideable {@link + * FutureTask#done() done()} method. For similar functionality, call {@link #addListener}. + * + *

Few users should use this class. It is intended primarily for those who are implementing an + * {@code ExecutorService}. Most users should call {@link ListeningExecutorService#submit(Callable) + * ListeningExecutorService.submit} on a service obtained from {@link + * MoreExecutors#listeningDecorator}. + * + * @author Sven Mawson + * @since 1.0 + */ +@GwtIncompatible +public class ListenableFutureTask extends FutureTask implements ListenableFuture { + // TODO(cpovirk): explore ways of making ListenableFutureTask final. There are some valid reasons + // such as BoundedQueueExecutorService to allow extends but it would be nice to make it final to + // avoid unintended usage. + + // The execution list to hold our listeners. + private final ExecutionList executionList = new ExecutionList(); + + /** + * Creates a {@code ListenableFutureTask} that will upon running, execute the given {@code + * Callable}. + * + * @param callable the callable task + * @since 10.0 + */ + public static ListenableFutureTask create(Callable callable) { + return new ListenableFutureTask(callable); + } + + /** + * Creates a {@code ListenableFutureTask} that will upon running, execute the given {@code + * Runnable}, and arrange that {@code get} will return the given result on successful completion. + * + * @param runnable the runnable task + * @param result the result to return on successful completion. If you don't need a particular + * result, consider using constructions of the form: {@code ListenableFuture f = + * ListenableFutureTask.create(runnable, null)} + * @since 10.0 + */ + public static ListenableFutureTask create(Runnable runnable, V result) { + return new ListenableFutureTask(runnable, result); + } + + ListenableFutureTask(Callable callable) { + super(callable); + } + + ListenableFutureTask(Runnable runnable, V result) { + super(runnable, result); + } + + @Override + public void addListener(Runnable listener, Executor exec) { + executionList.add(listener, exec); + } + + /** Internal implementation detail used to invoke the listeners. */ + @Override + protected void done() { + executionList.execute(); + } +} diff --git a/src/main/java/com/google/common/util/concurrent/ListenableScheduledFuture.java b/src/main/java/com/google/common/util/concurrent/ListenableScheduledFuture.java new file mode 100644 index 0000000..77fa5f7 --- /dev/null +++ b/src/main/java/com/google/common/util/concurrent/ListenableScheduledFuture.java @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2012 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.util.concurrent; + +import com.google.common.annotations.Beta; +import com.google.common.annotations.GwtCompatible; +import java.util.concurrent.ScheduledFuture; + +/** + * Helper interface to implement both {@link ListenableFuture} and {@link ScheduledFuture}. + * + * @author Anthony Zana + * @since 15.0 + */ +@Beta +@GwtCompatible +public interface ListenableScheduledFuture extends ScheduledFuture, ListenableFuture {} diff --git a/src/main/java/com/google/common/util/concurrent/ListenerCallQueue.java b/src/main/java/com/google/common/util/concurrent/ListenerCallQueue.java new file mode 100644 index 0000000..0b3eeb5 --- /dev/null +++ b/src/main/java/com/google/common/util/concurrent/ListenerCallQueue.java @@ -0,0 +1,225 @@ +/* + * Copyright (C) 2014 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.util.concurrent; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.annotations.GwtIncompatible; +import com.google.common.base.Preconditions; +import com.google.common.collect.Queues; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Queue; +import java.util.concurrent.Executor; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * A list of listeners for implementing a concurrency friendly observable object. + * + *

Listeners are registered once via {@link #addListener} and then may be invoked by {@linkplain + * #enqueue enqueueing} and then {@linkplain #dispatch dispatching} events. + * + *

The API of this class is designed to make it easy to achieve the following properties + * + *

    + *
  • Multiple events for the same listener are never dispatched concurrently. + *
  • Events for the different listeners are dispatched concurrently. + *
  • All events for a given listener dispatch on the provided {@link #executor}. + *
  • It is easy for the user to ensure that listeners are never invoked while holding locks. + *
+ * + * The last point is subtle. Often the observable object will be managing its own internal state + * using a lock, however it is dangerous to dispatch listeners while holding a lock because they + * might run on the {@code directExecutor()} or be otherwise re-entrant (call back into your + * object). So it is important to not call {@link #dispatch} while holding any locks. This is why + * {@link #enqueue} and {@link #dispatch} are 2 different methods. It is expected that the decision + * to run a particular event is made during the state change, but the decision to actually invoke + * the listeners can be delayed slightly so that locks can be dropped. Also, because {@link + * #dispatch} is expected to be called concurrently, it is idempotent. + */ +@GwtIncompatible +final class ListenerCallQueue { + // TODO(cpovirk): consider using the logger associated with listener.getClass(). + private static final Logger logger = Logger.getLogger(ListenerCallQueue.class.getName()); + + // TODO(chrisn): promote AppendOnlyCollection for use here. + private final List> listeners = + Collections.synchronizedList(new ArrayList>()); + + /** Method reference-compatible listener event. */ + interface Event { + /** Call a method on the listener. */ + void call(L listener); + } + + /** + * Adds a listener that will be called using the given executor when events are later {@link + * #enqueue enqueued} and {@link #dispatch dispatched}. + */ + public void addListener(L listener, Executor executor) { + checkNotNull(listener, "listener"); + checkNotNull(executor, "executor"); + listeners.add(new PerListenerQueue<>(listener, executor)); + } + + /** + * Enqueues an event to be run on currently known listeners. + * + *

The {@code toString} method of the Event itself will be used to describe the event in the + * case of an error. + * + * @param event the callback to execute on {@link #dispatch} + */ + public void enqueue(Event event) { + enqueueHelper(event, event); + } + + /** + * Enqueues an event to be run on currently known listeners, with a label. + * + * @param event the callback to execute on {@link #dispatch} + * @param label a description of the event to use in the case of an error + */ + public void enqueue(Event event, String label) { + enqueueHelper(event, label); + } + + private void enqueueHelper(Event event, Object label) { + checkNotNull(event, "event"); + checkNotNull(label, "label"); + synchronized (listeners) { + for (PerListenerQueue queue : listeners) { + queue.add(event, label); + } + } + } + + /** + * Dispatches all events enqueued prior to this call, serially and in order, for every listener. + * + *

Note: this method is idempotent and safe to call from any thread + */ + public void dispatch() { + // iterate by index to avoid concurrent modification exceptions + for (int i = 0; i < listeners.size(); i++) { + listeners.get(i).dispatch(); + } + } + + /** + * A special purpose queue/executor that dispatches listener events serially on a configured + * executor. Each event event can be added and dispatched as separate phases. + * + *

This class is very similar to {@link SequentialExecutor} with the exception that events can + * be added without necessarily executing immediately. + */ + private static final class PerListenerQueue implements Runnable { + final L listener; + final Executor executor; + + + final Queue> waitQueue = Queues.newArrayDeque(); + + + final Queue labelQueue = Queues.newArrayDeque(); + + + boolean isThreadScheduled; + + PerListenerQueue(L listener, Executor executor) { + this.listener = checkNotNull(listener); + this.executor = checkNotNull(executor); + } + + /** Enqueues a event to be run. */ + synchronized void add(ListenerCallQueue.Event event, Object label) { + waitQueue.add(event); + labelQueue.add(label); + } + + /** + * Dispatches all listeners {@linkplain #enqueue enqueued} prior to this call, serially and in + * order. + */ + void dispatch() { + boolean scheduleEventRunner = false; + synchronized (this) { + if (!isThreadScheduled) { + isThreadScheduled = true; + scheduleEventRunner = true; + } + } + if (scheduleEventRunner) { + try { + executor.execute(this); + } catch (RuntimeException e) { + // reset state in case of an error so that later dispatch calls will actually do something + synchronized (this) { + isThreadScheduled = false; + } + // Log it and keep going. + logger.log( + Level.SEVERE, + "Exception while running callbacks for " + listener + " on " + executor, + e); + throw e; + } + } + } + + @Override + public void run() { + boolean stillRunning = true; + try { + while (true) { + ListenerCallQueue.Event nextToRun; + Object nextLabel; + synchronized (PerListenerQueue.this) { + Preconditions.checkState(isThreadScheduled); + nextToRun = waitQueue.poll(); + nextLabel = labelQueue.poll(); + if (nextToRun == null) { + isThreadScheduled = false; + stillRunning = false; + break; + } + } + + // Always run while _not_ holding the lock, to avoid deadlocks. + try { + nextToRun.call(listener); + } catch (RuntimeException e) { + // Log it and keep going. + logger.log( + Level.SEVERE, + "Exception while executing callback: " + listener + " " + nextLabel, + e); + } + } + } finally { + if (stillRunning) { + // An Error is bubbling up. We should mark ourselves as no longer running. That way, if + // anyone tries to keep using us, we won't be corrupted. + synchronized (PerListenerQueue.this) { + isThreadScheduled = false; + } + } + } + } + } +} diff --git a/src/main/java/com/google/common/util/concurrent/ListeningExecutorService.java b/src/main/java/com/google/common/util/concurrent/ListeningExecutorService.java new file mode 100644 index 0000000..e684d71 --- /dev/null +++ b/src/main/java/com/google/common/util/concurrent/ListeningExecutorService.java @@ -0,0 +1,102 @@ +/* + * Copyright (C) 2010 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.util.concurrent; + +import com.google.common.annotations.GwtIncompatible; +import java.util.Collection; +import java.util.List; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Future; +import java.util.concurrent.RejectedExecutionException; +import java.util.concurrent.TimeUnit; + +/** + * An {@link ExecutorService} that returns {@link ListenableFuture} instances. To create an instance + * from an existing {@link ExecutorService}, call {@link + * MoreExecutors#listeningDecorator(ExecutorService)}. + * + * @author Chris Povirk + * @since 10.0 + */ +@GwtIncompatible +public interface ListeningExecutorService extends ExecutorService { + /** + * @return a {@code ListenableFuture} representing pending completion of the task + * @throws RejectedExecutionException {@inheritDoc} + */ + @Override + ListenableFuture submit(Callable task); + + /** + * @return a {@code ListenableFuture} representing pending completion of the task + * @throws RejectedExecutionException {@inheritDoc} + */ + @Override + ListenableFuture submit(Runnable task); + + /** + * @return a {@code ListenableFuture} representing pending completion of the task + * @throws RejectedExecutionException {@inheritDoc} + */ + @Override + ListenableFuture submit(Runnable task, T result); + + /** + * {@inheritDoc} + * + *

All elements in the returned list must be {@link ListenableFuture} instances. The easiest + * way to obtain a {@code List>} from this method is an unchecked (but safe) + * cast: + * + *

+   *   {@code @SuppressWarnings("unchecked") // guaranteed by invokeAll contract}
+   *   {@code List> futures = (List) executor.invokeAll(tasks);}
+   * 
+ * + * @return A list of {@code ListenableFuture} instances representing the tasks, in the same + * sequential order as produced by the iterator for the given task list, each of which has + * completed. + * @throws RejectedExecutionException {@inheritDoc} + * @throws NullPointerException if any task is null + */ + @Override + List> invokeAll(Collection> tasks) + throws InterruptedException; + + /** + * {@inheritDoc} + * + *

All elements in the returned list must be {@link ListenableFuture} instances. The easiest + * way to obtain a {@code List>} from this method is an unchecked (but safe) + * cast: + * + *

+   *   {@code @SuppressWarnings("unchecked") // guaranteed by invokeAll contract}
+   *   {@code List> futures = (List) executor.invokeAll(tasks, timeout, unit);}
+   * 
+ * + * @return a list of {@code ListenableFuture} instances representing the tasks, in the same + * sequential order as produced by the iterator for the given task list. If the operation did + * not time out, each task will have completed. If it did time out, some of these tasks will + * not have completed. + * @throws RejectedExecutionException {@inheritDoc} + * @throws NullPointerException if any task is null + */ + @Override + List> invokeAll( + Collection> tasks, long timeout, TimeUnit unit) + throws InterruptedException; +} diff --git a/src/main/java/com/google/common/util/concurrent/ListeningScheduledExecutorService.java b/src/main/java/com/google/common/util/concurrent/ListeningScheduledExecutorService.java new file mode 100644 index 0000000..959a2fc --- /dev/null +++ b/src/main/java/com/google/common/util/concurrent/ListeningScheduledExecutorService.java @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2011 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.util.concurrent; + +import com.google.common.annotations.GwtIncompatible; +import java.util.concurrent.Callable; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; + +/** + * A {@link ScheduledExecutorService} that returns {@link ListenableFuture} instances from its + * {@code ExecutorService} methods. To create an instance from an existing {@link + * ScheduledExecutorService}, call {@link + * MoreExecutors#listeningDecorator(ScheduledExecutorService)}. + * + * @author Chris Povirk + * @since 10.0 + */ +@GwtIncompatible +public interface ListeningScheduledExecutorService + extends ScheduledExecutorService, ListeningExecutorService { + + /** @since 15.0 (previously returned ScheduledFuture) */ + @Override + ListenableScheduledFuture schedule(Runnable command, long delay, TimeUnit unit); + + /** @since 15.0 (previously returned ScheduledFuture) */ + @Override + ListenableScheduledFuture schedule(Callable callable, long delay, TimeUnit unit); + + /** @since 15.0 (previously returned ScheduledFuture) */ + @Override + ListenableScheduledFuture scheduleAtFixedRate( + Runnable command, long initialDelay, long period, TimeUnit unit); + + /** @since 15.0 (previously returned ScheduledFuture) */ + @Override + ListenableScheduledFuture scheduleWithFixedDelay( + Runnable command, long initialDelay, long delay, TimeUnit unit); +} diff --git a/src/main/java/com/google/common/util/concurrent/Monitor.java b/src/main/java/com/google/common/util/concurrent/Monitor.java new file mode 100644 index 0000000..2a06290 --- /dev/null +++ b/src/main/java/com/google/common/util/concurrent/Monitor.java @@ -0,0 +1,1230 @@ +/* + * Copyright (C) 2010 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.util.concurrent; + +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.util.concurrent.Internal.toNanosSaturated; + +import com.google.common.annotations.Beta; +import com.google.common.annotations.GwtIncompatible; +import com.google.common.primitives.Longs; + + +import java.time.Duration; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.locks.Condition; +import java.util.concurrent.locks.ReentrantLock; +import java.util.function.BooleanSupplier; + + +/** + * A synchronization abstraction supporting waiting on arbitrary boolean conditions. + * + *

This class is intended as a replacement for {@link ReentrantLock}. Code using {@code Monitor} + * is less error-prone and more readable than code using {@code ReentrantLock}, without significant + * performance loss. {@code Monitor} even has the potential for performance gain by optimizing the + * evaluation and signaling of conditions. Signaling is entirely implicit. By + * eliminating explicit signaling, this class can guarantee that only one thread is awakened when a + * condition becomes true (no "signaling storms" due to use of {@link + * java.util.concurrent.locks.Condition#signalAll Condition.signalAll}) and that no signals are lost + * (no "hangs" due to incorrect use of {@link java.util.concurrent.locks.Condition#signal + * Condition.signal}). + * + *

A thread is said to occupy a monitor if it has entered the monitor but not yet + * left. Only one thread may occupy a given monitor at any moment. A monitor is also + * reentrant, so a thread may enter a monitor any number of times, and then must leave the same + * number of times. The enter and leave operations have the same synchronization + * semantics as the built-in Java language synchronization primitives. + * + *

A call to any of the enter methods with void return type should always be + * followed immediately by a try/finally block to ensure that the current thread leaves the + * monitor cleanly: + * + *

{@code
+ * monitor.enter();
+ * try {
+ *   // do things while occupying the monitor
+ * } finally {
+ *   monitor.leave();
+ * }
+ * }
+ * + *

A call to any of the enter methods with boolean return type should always appear + * as the condition of an if statement containing a try/finally block to ensure that + * the current thread leaves the monitor cleanly: + * + *

{@code
+ * if (monitor.tryEnter()) {
+ *   try {
+ *     // do things while occupying the monitor
+ *   } finally {
+ *     monitor.leave();
+ *   }
+ * } else {
+ *   // do other things since the monitor was not available
+ * }
+ * }
+ * + *

Comparison with {@code synchronized} and {@code ReentrantLock}

+ * + *

The following examples show a simple threadsafe holder expressed using {@code synchronized}, + * {@link ReentrantLock}, and {@code Monitor}. + * + *

{@code synchronized}

+ * + *

This version is the fewest lines of code, largely because the synchronization mechanism used + * is built into the language and runtime. But the programmer has to remember to avoid a couple of + * common bugs: The {@code wait()} must be inside a {@code while} instead of an {@code if}, and + * {@code notifyAll()} must be used instead of {@code notify()} because there are two different + * logical conditions being awaited. + * + *

{@code
+ * public class SafeBox {
+ *   private V value;
+ *
+ *   public synchronized V get() throws InterruptedException {
+ *     while (value == null) {
+ *       wait();
+ *     }
+ *     V result = value;
+ *     value = null;
+ *     notifyAll();
+ *     return result;
+ *   }
+ *
+ *   public synchronized void set(V newValue) throws InterruptedException {
+ *     while (value != null) {
+ *       wait();
+ *     }
+ *     value = newValue;
+ *     notifyAll();
+ *   }
+ * }
+ * }
+ * + *

{@code ReentrantLock}

+ * + *

This version is much more verbose than the {@code synchronized} version, and still suffers + * from the need for the programmer to remember to use {@code while} instead of {@code if}. However, + * one advantage is that we can introduce two separate {@code Condition} objects, which allows us to + * use {@code signal()} instead of {@code signalAll()}, which may be a performance benefit. + * + *

{@code
+ * public class SafeBox {
+ *   private V value;
+ *   private final ReentrantLock lock = new ReentrantLock();
+ *   private final Condition valuePresent = lock.newCondition();
+ *   private final Condition valueAbsent = lock.newCondition();
+ *
+ *   public V get() throws InterruptedException {
+ *     lock.lock();
+ *     try {
+ *       while (value == null) {
+ *         valuePresent.await();
+ *       }
+ *       V result = value;
+ *       value = null;
+ *       valueAbsent.signal();
+ *       return result;
+ *     } finally {
+ *       lock.unlock();
+ *     }
+ *   }
+ *
+ *   public void set(V newValue) throws InterruptedException {
+ *     lock.lock();
+ *     try {
+ *       while (value != null) {
+ *         valueAbsent.await();
+ *       }
+ *       value = newValue;
+ *       valuePresent.signal();
+ *     } finally {
+ *       lock.unlock();
+ *     }
+ *   }
+ * }
+ * }
+ * + *

{@code Monitor}

+ * + *

This version adds some verbosity around the {@code Guard} objects, but removes that same + * verbosity, and more, from the {@code get} and {@code set} methods. {@code Monitor} implements the + * same efficient signaling as we had to hand-code in the {@code ReentrantLock} version above. + * Finally, the programmer no longer has to hand-code the wait loop, and therefore doesn't have to + * remember to use {@code while} instead of {@code if}. + * + *

{@code
+ * public class SafeBox {
+ *   private V value;
+ *   private final Monitor monitor = new Monitor();
+ *   private final Monitor.Guard valuePresent = monitor.newGuard(() -> value != null);
+ *   private final Monitor.Guard valueAbsent = monitor.newGuard(() -> value == null);
+ *
+ *   public V get() throws InterruptedException {
+ *     monitor.enterWhen(valuePresent);
+ *     try {
+ *       V result = value;
+ *       value = null;
+ *       return result;
+ *     } finally {
+ *       monitor.leave();
+ *     }
+ *   }
+ *
+ *   public void set(V newValue) throws InterruptedException {
+ *     monitor.enterWhen(valueAbsent);
+ *     try {
+ *       value = newValue;
+ *     } finally {
+ *       monitor.leave();
+ *     }
+ *   }
+ * }
+ * }
+ * + * @author Justin T. Sampson + * @author Martin Buchholz + * @since 10.0 + */ +@Beta +@GwtIncompatible +@SuppressWarnings("GuardedBy") // TODO(b/35466881): Fix or suppress. +public final class Monitor { + // TODO(user): Use raw LockSupport or AbstractQueuedSynchronizer instead of ReentrantLock. + // TODO(user): "Port" jsr166 tests for ReentrantLock. + // + // TODO(user): Change API to make it impossible to use a Guard with the "wrong" monitor, + // by making the monitor implicit, and to eliminate other sources of IMSE. + // Imagine: + // guard.lock(); + // try { /* monitor locked and guard satisfied here */ } + // finally { guard.unlock(); } + // Here are Justin's design notes about this: + // + // This idea has come up from time to time, and I think one of my + // earlier versions of Monitor even did something like this. I ended + // up strongly favoring the current interface. + // + // I probably can't remember all the reasons (it's possible you + // could find them in the code review archives), but here are a few: + // + // 1. What about leaving/unlocking? Are you going to do + // guard.enter() paired with monitor.leave()? That might get + // confusing. It's nice for the finally block to look as close as + // possible to the thing right before the try. You could have + // guard.leave(), but that's a little odd as well because the + // guard doesn't have anything to do with leaving. You can't + // really enforce that the guard you're leaving is the same one + // you entered with, and it doesn't actually matter. + // + // 2. Since you can enter the monitor without a guard at all, some + // places you'll have monitor.enter()/monitor.leave() and other + // places you'll have guard.enter()/guard.leave() even though + // it's the same lock being acquired underneath. Always using + // monitor.enterXXX()/monitor.leave() will make it really clear + // which lock is held at any point in the code. + // + // 3. I think "enterWhen(notEmpty)" reads better than "notEmpty.enter()". + // + // TODO(user): Implement ReentrantLock features: + // - toString() method + // - getOwner() method + // - getQueuedThreads() method + // - getWaitingThreads(Guard) method + // - implement Serializable + // - redo the API to be as close to identical to ReentrantLock as possible, + // since, after all, this class is also a reentrant mutual exclusion lock!? + + /* + * One of the key challenges of this class is to prevent lost signals, while trying hard to + * minimize unnecessary signals. One simple and correct algorithm is to signal some other waiter + * with a satisfied guard (if one exists) whenever any thread occupying the monitor exits the + * monitor, either by unlocking all of its held locks, or by starting to wait for a guard. This + * includes exceptional exits, so all control paths involving signalling must be protected by a + * finally block. + * + * Further optimizations of this algorithm become increasingly subtle. A wait that terminates + * without the guard being satisfied (due to timeout, but not interrupt) can then immediately exit + * the monitor without signalling. If it timed out without being signalled, it does not need to + * "pass on" the signal to another thread. If it *was* signalled, then its guard must have been + * satisfied at the time of signal, and has since been modified by some other thread to be + * non-satisfied before reacquiring the lock, and that other thread takes over the responsibility + * of signaling the next waiter. + * + * Unlike the underlying Condition, if we are not careful, an interrupt *can* cause a signal to be + * lost, because the signal may be sent to a condition whose sole waiter has just been + * interrupted. + * + * Imagine a monitor with multiple guards. A thread enters the monitor, satisfies all the guards, + * and leaves, calling signalNextWaiter. With traditional locks and conditions, all the conditions + * need to be signalled because it is not known which if any of them have waiters (and hasWaiters + * can't be used reliably because of a check-then-act race). With our Monitor guards, we only + * signal the first active guard that is satisfied. But the corresponding thread may have already + * been interrupted and is waiting to reacquire the lock while still registered in activeGuards, + * in which case the signal is a no-op, and the bigger-picture signal is lost unless interrupted + * threads take special action by participating in the signal-passing game. + */ + + /* + * Timeout handling is intricate, especially given our ambitious goals: + * - Avoid underflow and overflow of timeout values when specified timeouts are close to + * Long.MIN_VALUE or Long.MAX_VALUE. + * - Favor responding to interrupts over timeouts. + * - System.nanoTime() is expensive enough that we want to call it the minimum required number of + * times, typically once before invoking a blocking method. This often requires keeping track of + * the first time in a method that nanoTime() has been invoked, for which the special value 0L + * is reserved to mean "uninitialized". If timeout is non-positive, then nanoTime need never be + * called. + * - Keep behavior of fair and non-fair instances consistent. + */ + + /** + * A boolean condition for which a thread may wait. A {@code Guard} is associated with a single + * {@code Monitor}. The monitor may check the guard at arbitrary times from any thread occupying + * the monitor, so code should not be written to rely on how often a guard might or might not be + * checked. + * + *

If a {@code Guard} is passed into any method of a {@code Monitor} other than the one it is + * associated with, an {@link IllegalMonitorStateException} is thrown. + * + * @since 10.0 + */ + @Beta + public abstract static class Guard { + + final Monitor monitor; + final Condition condition; + + int waiterCount = 0; + + /** The next active guard */ + + Guard next; + + protected Guard(Monitor monitor) { + this.monitor = checkNotNull(monitor, "monitor"); + this.condition = monitor.lock.newCondition(); + } + + /** + * Evaluates this guard's boolean condition. This method is always called with the associated + * monitor already occupied. Implementations of this method must depend only on state protected + * by the associated monitor, and must not modify that state. + */ + public abstract boolean isSatisfied(); + } + + /** Whether this monitor is fair. */ + private final boolean fair; + + /** The lock underlying this monitor. */ + private final ReentrantLock lock; + + /** + * The guards associated with this monitor that currently have waiters ({@code waiterCount > 0}). + * A linked list threaded through the Guard.next field. + */ + + private Guard activeGuards = null; + + /** + * Creates a monitor with a non-fair (but fast) ordering policy. Equivalent to {@code + * Monitor(false)}. + */ + public Monitor() { + this(false); + } + + /** + * Creates a monitor with the given ordering policy. + * + * @param fair whether this monitor should use a fair ordering policy rather than a non-fair (but + * fast) one + */ + public Monitor(boolean fair) { + this.fair = fair; + this.lock = new ReentrantLock(fair); + } + + /** + * Creates a new {@linkplain Guard guard} for this monitor. + * + * @param isSatisfied the new guard's boolean condition (see {@link Guard#isSatisfied + * isSatisfied()}) + * @since 21.0 + */ + public Guard newGuard(final BooleanSupplier isSatisfied) { + checkNotNull(isSatisfied, "isSatisfied"); + return new Guard(this) { + @Override + public boolean isSatisfied() { + return isSatisfied.getAsBoolean(); + } + }; + } + + /** Enters this monitor. Blocks indefinitely. */ + public void enter() { + lock.lock(); + } + + /** + * Enters this monitor. Blocks at most the given time. + * + * @return whether the monitor was entered + * @since 28.0 + */ + public boolean enter(Duration time) { + return enter(toNanosSaturated(time), TimeUnit.NANOSECONDS); + } + + /** + * Enters this monitor. Blocks at most the given time. + * + * @return whether the monitor was entered + */ + @SuppressWarnings("GoodTime") // should accept a java.time.Duration + public boolean enter(long time, TimeUnit unit) { + final long timeoutNanos = toSafeNanos(time, unit); + final ReentrantLock lock = this.lock; + if (!fair && lock.tryLock()) { + return true; + } + boolean interrupted = Thread.interrupted(); + try { + final long startTime = System.nanoTime(); + for (long remainingNanos = timeoutNanos; ; ) { + try { + return lock.tryLock(remainingNanos, TimeUnit.NANOSECONDS); + } catch (InterruptedException interrupt) { + interrupted = true; + remainingNanos = remainingNanos(startTime, timeoutNanos); + } + } + } finally { + if (interrupted) { + Thread.currentThread().interrupt(); + } + } + } + + /** + * Enters this monitor. Blocks indefinitely, but may be interrupted. + * + * @throws InterruptedException if interrupted while waiting + */ + public void enterInterruptibly() throws InterruptedException { + lock.lockInterruptibly(); + } + + /** + * Enters this monitor. Blocks at most the given time, and may be interrupted. + * + * @return whether the monitor was entered + * @throws InterruptedException if interrupted while waiting + * @since 28.0 + */ + public boolean enterInterruptibly(Duration time) throws InterruptedException { + return enterInterruptibly(toNanosSaturated(time), TimeUnit.NANOSECONDS); + } + + /** + * Enters this monitor. Blocks at most the given time, and may be interrupted. + * + * @return whether the monitor was entered + * @throws InterruptedException if interrupted while waiting + */ + @SuppressWarnings("GoodTime") // should accept a java.time.Duration + public boolean enterInterruptibly(long time, TimeUnit unit) throws InterruptedException { + return lock.tryLock(time, unit); + } + + /** + * Enters this monitor if it is possible to do so immediately. Does not block. + * + *

Note: This method disregards the fairness setting of this monitor. + * + * @return whether the monitor was entered + */ + public boolean tryEnter() { + return lock.tryLock(); + } + + /** + * Enters this monitor when the guard is satisfied. Blocks indefinitely, but may be interrupted. + * + * @throws InterruptedException if interrupted while waiting + */ + public void enterWhen(Guard guard) throws InterruptedException { + if (guard.monitor != this) { + throw new IllegalMonitorStateException(); + } + final ReentrantLock lock = this.lock; + boolean signalBeforeWaiting = lock.isHeldByCurrentThread(); + lock.lockInterruptibly(); + + boolean satisfied = false; + try { + if (!guard.isSatisfied()) { + await(guard, signalBeforeWaiting); + } + satisfied = true; + } finally { + if (!satisfied) { + leave(); + } + } + } + + /** + * Enters this monitor when the guard is satisfied. Blocks at most the given time, including both + * the time to acquire the lock and the time to wait for the guard to be satisfied, and may be + * interrupted. + * + * @return whether the monitor was entered, which guarantees that the guard is now satisfied + * @throws InterruptedException if interrupted while waiting + * @since 28.0 + */ + public boolean enterWhen(Guard guard, Duration time) throws InterruptedException { + return enterWhen(guard, toNanosSaturated(time), TimeUnit.NANOSECONDS); + } + + /** + * Enters this monitor when the guard is satisfied. Blocks at most the given time, including both + * the time to acquire the lock and the time to wait for the guard to be satisfied, and may be + * interrupted. + * + * @return whether the monitor was entered, which guarantees that the guard is now satisfied + * @throws InterruptedException if interrupted while waiting + */ + @SuppressWarnings("GoodTime") // should accept a java.time.Duration + public boolean enterWhen(Guard guard, long time, TimeUnit unit) throws InterruptedException { + final long timeoutNanos = toSafeNanos(time, unit); + if (guard.monitor != this) { + throw new IllegalMonitorStateException(); + } + final ReentrantLock lock = this.lock; + boolean reentrant = lock.isHeldByCurrentThread(); + long startTime = 0L; + + locked: + { + if (!fair) { + // Check interrupt status to get behavior consistent with fair case. + if (Thread.interrupted()) { + throw new InterruptedException(); + } + if (lock.tryLock()) { + break locked; + } + } + startTime = initNanoTime(timeoutNanos); + if (!lock.tryLock(time, unit)) { + return false; + } + } + + boolean satisfied = false; + boolean threw = true; + try { + satisfied = + guard.isSatisfied() + || awaitNanos( + guard, + (startTime == 0L) ? timeoutNanos : remainingNanos(startTime, timeoutNanos), + reentrant); + threw = false; + return satisfied; + } finally { + if (!satisfied) { + try { + // Don't need to signal if timed out, but do if interrupted + if (threw && !reentrant) { + signalNextWaiter(); + } + } finally { + lock.unlock(); + } + } + } + } + + /** Enters this monitor when the guard is satisfied. Blocks indefinitely. */ + public void enterWhenUninterruptibly(Guard guard) { + if (guard.monitor != this) { + throw new IllegalMonitorStateException(); + } + final ReentrantLock lock = this.lock; + boolean signalBeforeWaiting = lock.isHeldByCurrentThread(); + lock.lock(); + + boolean satisfied = false; + try { + if (!guard.isSatisfied()) { + awaitUninterruptibly(guard, signalBeforeWaiting); + } + satisfied = true; + } finally { + if (!satisfied) { + leave(); + } + } + } + + /** + * Enters this monitor when the guard is satisfied. Blocks at most the given time, including both + * the time to acquire the lock and the time to wait for the guard to be satisfied. + * + * @return whether the monitor was entered, which guarantees that the guard is now satisfied + * @since 28.0 + */ + public boolean enterWhenUninterruptibly(Guard guard, Duration time) { + return enterWhenUninterruptibly(guard, toNanosSaturated(time), TimeUnit.NANOSECONDS); + } + + /** + * Enters this monitor when the guard is satisfied. Blocks at most the given time, including both + * the time to acquire the lock and the time to wait for the guard to be satisfied. + * + * @return whether the monitor was entered, which guarantees that the guard is now satisfied + */ + @SuppressWarnings("GoodTime") // should accept a java.time.Duration + public boolean enterWhenUninterruptibly(Guard guard, long time, TimeUnit unit) { + final long timeoutNanos = toSafeNanos(time, unit); + if (guard.monitor != this) { + throw new IllegalMonitorStateException(); + } + final ReentrantLock lock = this.lock; + long startTime = 0L; + boolean signalBeforeWaiting = lock.isHeldByCurrentThread(); + boolean interrupted = Thread.interrupted(); + try { + if (fair || !lock.tryLock()) { + startTime = initNanoTime(timeoutNanos); + for (long remainingNanos = timeoutNanos; ; ) { + try { + if (lock.tryLock(remainingNanos, TimeUnit.NANOSECONDS)) { + break; + } else { + return false; + } + } catch (InterruptedException interrupt) { + interrupted = true; + remainingNanos = remainingNanos(startTime, timeoutNanos); + } + } + } + + boolean satisfied = false; + try { + while (true) { + try { + if (guard.isSatisfied()) { + satisfied = true; + } else { + final long remainingNanos; + if (startTime == 0L) { + startTime = initNanoTime(timeoutNanos); + remainingNanos = timeoutNanos; + } else { + remainingNanos = remainingNanos(startTime, timeoutNanos); + } + satisfied = awaitNanos(guard, remainingNanos, signalBeforeWaiting); + } + return satisfied; + } catch (InterruptedException interrupt) { + interrupted = true; + signalBeforeWaiting = false; + } + } + } finally { + if (!satisfied) { + lock.unlock(); // No need to signal if timed out + } + } + } finally { + if (interrupted) { + Thread.currentThread().interrupt(); + } + } + } + + /** + * Enters this monitor if the guard is satisfied. Blocks indefinitely acquiring the lock, but does + * not wait for the guard to be satisfied. + * + * @return whether the monitor was entered, which guarantees that the guard is now satisfied + */ + public boolean enterIf(Guard guard) { + if (guard.monitor != this) { + throw new IllegalMonitorStateException(); + } + final ReentrantLock lock = this.lock; + lock.lock(); + + boolean satisfied = false; + try { + return satisfied = guard.isSatisfied(); + } finally { + if (!satisfied) { + lock.unlock(); + } + } + } + + /** + * Enters this monitor if the guard is satisfied. Blocks at most the given time acquiring the + * lock, but does not wait for the guard to be satisfied. + * + * @return whether the monitor was entered, which guarantees that the guard is now satisfied + * @since 28.0 + */ + public boolean enterIf(Guard guard, Duration time) { + return enterIf(guard, toNanosSaturated(time), TimeUnit.NANOSECONDS); + } + + /** + * Enters this monitor if the guard is satisfied. Blocks at most the given time acquiring the + * lock, but does not wait for the guard to be satisfied. + * + * @return whether the monitor was entered, which guarantees that the guard is now satisfied + */ + @SuppressWarnings("GoodTime") // should accept a java.time.Duration + public boolean enterIf(Guard guard, long time, TimeUnit unit) { + if (guard.monitor != this) { + throw new IllegalMonitorStateException(); + } + if (!enter(time, unit)) { + return false; + } + + boolean satisfied = false; + try { + return satisfied = guard.isSatisfied(); + } finally { + if (!satisfied) { + lock.unlock(); + } + } + } + + /** + * Enters this monitor if the guard is satisfied. Blocks indefinitely acquiring the lock, but does + * not wait for the guard to be satisfied, and may be interrupted. + * + * @return whether the monitor was entered, which guarantees that the guard is now satisfied + * @throws InterruptedException if interrupted while waiting + */ + public boolean enterIfInterruptibly(Guard guard) throws InterruptedException { + if (guard.monitor != this) { + throw new IllegalMonitorStateException(); + } + final ReentrantLock lock = this.lock; + lock.lockInterruptibly(); + + boolean satisfied = false; + try { + return satisfied = guard.isSatisfied(); + } finally { + if (!satisfied) { + lock.unlock(); + } + } + } + + /** + * Enters this monitor if the guard is satisfied. Blocks at most the given time acquiring the + * lock, but does not wait for the guard to be satisfied, and may be interrupted. + * + * @return whether the monitor was entered, which guarantees that the guard is now satisfied + * @since 28.0 + */ + public boolean enterIfInterruptibly(Guard guard, Duration time) throws InterruptedException { + return enterIfInterruptibly(guard, toNanosSaturated(time), TimeUnit.NANOSECONDS); + } + + /** + * Enters this monitor if the guard is satisfied. Blocks at most the given time acquiring the + * lock, but does not wait for the guard to be satisfied, and may be interrupted. + * + * @return whether the monitor was entered, which guarantees that the guard is now satisfied + */ + @SuppressWarnings("GoodTime") // should accept a java.time.Duration + public boolean enterIfInterruptibly(Guard guard, long time, TimeUnit unit) + throws InterruptedException { + if (guard.monitor != this) { + throw new IllegalMonitorStateException(); + } + final ReentrantLock lock = this.lock; + if (!lock.tryLock(time, unit)) { + return false; + } + + boolean satisfied = false; + try { + return satisfied = guard.isSatisfied(); + } finally { + if (!satisfied) { + lock.unlock(); + } + } + } + + /** + * Enters this monitor if it is possible to do so immediately and the guard is satisfied. Does not + * block acquiring the lock and does not wait for the guard to be satisfied. + * + *

Note: This method disregards the fairness setting of this monitor. + * + * @return whether the monitor was entered, which guarantees that the guard is now satisfied + */ + public boolean tryEnterIf(Guard guard) { + if (guard.monitor != this) { + throw new IllegalMonitorStateException(); + } + final ReentrantLock lock = this.lock; + if (!lock.tryLock()) { + return false; + } + + boolean satisfied = false; + try { + return satisfied = guard.isSatisfied(); + } finally { + if (!satisfied) { + lock.unlock(); + } + } + } + + /** + * Waits for the guard to be satisfied. Waits indefinitely, but may be interrupted. May be called + * only by a thread currently occupying this monitor. + * + * @throws InterruptedException if interrupted while waiting + */ + public void waitFor(Guard guard) throws InterruptedException { + if (!((guard.monitor == this) & lock.isHeldByCurrentThread())) { + throw new IllegalMonitorStateException(); + } + if (!guard.isSatisfied()) { + await(guard, true); + } + } + + /** + * Waits for the guard to be satisfied. Waits at most the given time, and may be interrupted. May + * be called only by a thread currently occupying this monitor. + * + * @return whether the guard is now satisfied + * @throws InterruptedException if interrupted while waiting + * @since 28.0 + */ + public boolean waitFor(Guard guard, Duration time) throws InterruptedException { + return waitFor(guard, toNanosSaturated(time), TimeUnit.NANOSECONDS); + } + + /** + * Waits for the guard to be satisfied. Waits at most the given time, and may be interrupted. May + * be called only by a thread currently occupying this monitor. + * + * @return whether the guard is now satisfied + * @throws InterruptedException if interrupted while waiting + */ + @SuppressWarnings("GoodTime") // should accept a java.time.Duration + public boolean waitFor(Guard guard, long time, TimeUnit unit) throws InterruptedException { + final long timeoutNanos = toSafeNanos(time, unit); + if (!((guard.monitor == this) & lock.isHeldByCurrentThread())) { + throw new IllegalMonitorStateException(); + } + if (guard.isSatisfied()) { + return true; + } + if (Thread.interrupted()) { + throw new InterruptedException(); + } + return awaitNanos(guard, timeoutNanos, true); + } + + /** + * Waits for the guard to be satisfied. Waits indefinitely. May be called only by a thread + * currently occupying this monitor. + */ + public void waitForUninterruptibly(Guard guard) { + if (!((guard.monitor == this) & lock.isHeldByCurrentThread())) { + throw new IllegalMonitorStateException(); + } + if (!guard.isSatisfied()) { + awaitUninterruptibly(guard, true); + } + } + + /** + * Waits for the guard to be satisfied. Waits at most the given time. May be called only by a + * thread currently occupying this monitor. + * + * @return whether the guard is now satisfied + * @since 28.0 + */ + public boolean waitForUninterruptibly(Guard guard, Duration time) { + return waitForUninterruptibly(guard, toNanosSaturated(time), TimeUnit.NANOSECONDS); + } + + /** + * Waits for the guard to be satisfied. Waits at most the given time. May be called only by a + * thread currently occupying this monitor. + * + * @return whether the guard is now satisfied + */ + @SuppressWarnings("GoodTime") // should accept a java.time.Duration + public boolean waitForUninterruptibly(Guard guard, long time, TimeUnit unit) { + final long timeoutNanos = toSafeNanos(time, unit); + if (!((guard.monitor == this) & lock.isHeldByCurrentThread())) { + throw new IllegalMonitorStateException(); + } + if (guard.isSatisfied()) { + return true; + } + boolean signalBeforeWaiting = true; + final long startTime = initNanoTime(timeoutNanos); + boolean interrupted = Thread.interrupted(); + try { + for (long remainingNanos = timeoutNanos; ; ) { + try { + return awaitNanos(guard, remainingNanos, signalBeforeWaiting); + } catch (InterruptedException interrupt) { + interrupted = true; + if (guard.isSatisfied()) { + return true; + } + signalBeforeWaiting = false; + remainingNanos = remainingNanos(startTime, timeoutNanos); + } + } + } finally { + if (interrupted) { + Thread.currentThread().interrupt(); + } + } + } + + /** Leaves this monitor. May be called only by a thread currently occupying this monitor. */ + public void leave() { + final ReentrantLock lock = this.lock; + try { + // No need to signal if we will still be holding the lock when we return + if (lock.getHoldCount() == 1) { + signalNextWaiter(); + } + } finally { + lock.unlock(); // Will throw IllegalMonitorStateException if not held + } + } + + /** Returns whether this monitor is using a fair ordering policy. */ + public boolean isFair() { + return fair; + } + + /** + * Returns whether this monitor is occupied by any thread. This method is designed for use in + * monitoring of the system state, not for synchronization control. + */ + public boolean isOccupied() { + return lock.isLocked(); + } + + /** + * Returns whether the current thread is occupying this monitor (has entered more times than it + * has left). + */ + public boolean isOccupiedByCurrentThread() { + return lock.isHeldByCurrentThread(); + } + + /** + * Returns the number of times the current thread has entered this monitor in excess of the number + * of times it has left. Returns 0 if the current thread is not occupying this monitor. + */ + public int getOccupiedDepth() { + return lock.getHoldCount(); + } + + /** + * Returns an estimate of the number of threads waiting to enter this monitor. The value is only + * an estimate because the number of threads may change dynamically while this method traverses + * internal data structures. This method is designed for use in monitoring of the system state, + * not for synchronization control. + */ + public int getQueueLength() { + return lock.getQueueLength(); + } + + /** + * Returns whether any threads are waiting to enter this monitor. Note that because cancellations + * may occur at any time, a {@code true} return does not guarantee that any other thread will ever + * enter this monitor. This method is designed primarily for use in monitoring of the system + * state. + */ + public boolean hasQueuedThreads() { + return lock.hasQueuedThreads(); + } + + /** + * Queries whether the given thread is waiting to enter this monitor. Note that because + * cancellations may occur at any time, a {@code true} return does not guarantee that this thread + * will ever enter this monitor. This method is designed primarily for use in monitoring of the + * system state. + */ + public boolean hasQueuedThread(Thread thread) { + return lock.hasQueuedThread(thread); + } + + /** + * Queries whether any threads are waiting for the given guard to become satisfied. Note that + * because timeouts and interrupts may occur at any time, a {@code true} return does not guarantee + * that the guard becoming satisfied in the future will awaken any threads. This method is + * designed primarily for use in monitoring of the system state. + */ + public boolean hasWaiters(Guard guard) { + return getWaitQueueLength(guard) > 0; + } + + /** + * Returns an estimate of the number of threads waiting for the given guard to become satisfied. + * Note that because timeouts and interrupts may occur at any time, the estimate serves only as an + * upper bound on the actual number of waiters. This method is designed for use in monitoring of + * the system state, not for synchronization control. + */ + public int getWaitQueueLength(Guard guard) { + if (guard.monitor != this) { + throw new IllegalMonitorStateException(); + } + lock.lock(); + try { + return guard.waiterCount; + } finally { + lock.unlock(); + } + } + + /** + * Returns unit.toNanos(time), additionally ensuring the returned value is not at risk of + * overflowing or underflowing, by bounding the value between 0 and (Long.MAX_VALUE / 4) * 3. + * Actually waiting for more than 219 years is not supported! + */ + private static long toSafeNanos(long time, TimeUnit unit) { + long timeoutNanos = unit.toNanos(time); + return Longs.constrainToRange(timeoutNanos, 0L, (Long.MAX_VALUE / 4) * 3); + } + + /** + * Returns System.nanoTime() unless the timeout has already elapsed. Returns 0L if and only if the + * timeout has already elapsed. + */ + private static long initNanoTime(long timeoutNanos) { + if (timeoutNanos <= 0L) { + return 0L; + } else { + long startTime = System.nanoTime(); + return (startTime == 0L) ? 1L : startTime; + } + } + + /** + * Returns the remaining nanos until the given timeout, or 0L if the timeout has already elapsed. + * Caller must have previously sanitized timeoutNanos using toSafeNanos. + */ + private static long remainingNanos(long startTime, long timeoutNanos) { + // assert timeoutNanos == 0L || startTime != 0L; + + // TODO : NOT CORRECT, BUT TESTS PASS ANYWAYS! + // if (true) return timeoutNanos; + // ONLY 2 TESTS FAIL IF WE DO: + // if (true) return 0; + + return (timeoutNanos <= 0L) ? 0L : timeoutNanos - (System.nanoTime() - startTime); + } + + /** + * Signals some other thread waiting on a satisfied guard, if one exists. + * + *

We manage calls to this method carefully, to signal only when necessary, but never losing a + * signal, which is the classic problem of this kind of concurrency construct. We must signal if + * the current thread is about to relinquish the lock and may have changed the state protected by + * the monitor, thereby causing some guard to be satisfied. + * + *

In addition, any thread that has been signalled when its guard was satisfied acquires the + * responsibility of signalling the next thread when it again relinquishes the lock. Unlike a + * normal Condition, there is no guarantee that an interrupted thread has not been signalled, + * since the concurrency control must manage multiple Conditions. So this method must generally be + * called when waits are interrupted. + * + *

On the other hand, if a signalled thread wakes up to discover that its guard is still not + * satisfied, it does *not* need to call this method before returning to wait. This can only + * happen due to spurious wakeup (ignorable) or another thread acquiring the lock before the + * current thread can and returning the guard to the unsatisfied state. In the latter case the + * other thread (last thread modifying the state protected by the monitor) takes over the + * responsibility of signalling the next waiter. + * + *

This method must not be called from within a beginWaitingFor/endWaitingFor block, or else + * the current thread's guard might be mistakenly signalled, leading to a lost signal. + */ + + private void signalNextWaiter() { + for (Guard guard = activeGuards; guard != null; guard = guard.next) { + if (isSatisfied(guard)) { + guard.condition.signal(); + break; + } + } + } + + /** + * Exactly like signalNextWaiter, but caller guarantees that guardToSkip need not be considered, + * because caller has previously checked that guardToSkip.isSatisfied() returned false. An + * optimization for the case that guardToSkip.isSatisfied() may be expensive. + * + *

We decided against using this method, since in practice, isSatisfied() is likely to be very + * cheap (typically one field read). Resurrect this method if you find that not to be true. + */ + // + // private void signalNextWaiterSkipping(Guard guardToSkip) { + // for (Guard guard = activeGuards; guard != null; guard = guard.next) { + // if (guard != guardToSkip && isSatisfied(guard)) { + // guard.condition.signal(); + // break; + // } + // } + // } + + /** + * Exactly like guard.isSatisfied(), but in addition signals all waiting threads in the (hopefully + * unlikely) event that isSatisfied() throws. + */ + + private boolean isSatisfied(Guard guard) { + try { + return guard.isSatisfied(); + } catch (Throwable throwable) { + signalAllWaiters(); + throw throwable; + } + } + + /** Signals all threads waiting on guards. */ + + private void signalAllWaiters() { + for (Guard guard = activeGuards; guard != null; guard = guard.next) { + guard.condition.signalAll(); + } + } + + /** Records that the current thread is about to wait on the specified guard. */ + + private void beginWaitingFor(Guard guard) { + int waiters = guard.waiterCount++; + if (waiters == 0) { + // push guard onto activeGuards + guard.next = activeGuards; + activeGuards = guard; + } + } + + /** Records that the current thread is no longer waiting on the specified guard. */ + + private void endWaitingFor(Guard guard) { + int waiters = --guard.waiterCount; + if (waiters == 0) { + // unlink guard from activeGuards + for (Guard p = activeGuards, pred = null; ; pred = p, p = p.next) { + if (p == guard) { + if (pred == null) { + activeGuards = p.next; + } else { + pred.next = p.next; + } + p.next = null; // help GC + break; + } + } + } + } + + /* + * Methods that loop waiting on a guard's condition until the guard is satisfied, while recording + * this fact so that other threads know to check our guard and signal us. It's caller's + * responsibility to ensure that the guard is *not* currently satisfied. + */ + + + private void await(Guard guard, boolean signalBeforeWaiting) throws InterruptedException { + if (signalBeforeWaiting) { + signalNextWaiter(); + } + beginWaitingFor(guard); + try { + do { + guard.condition.await(); + } while (!guard.isSatisfied()); + } finally { + endWaitingFor(guard); + } + } + + + private void awaitUninterruptibly(Guard guard, boolean signalBeforeWaiting) { + if (signalBeforeWaiting) { + signalNextWaiter(); + } + beginWaitingFor(guard); + try { + do { + guard.condition.awaitUninterruptibly(); + } while (!guard.isSatisfied()); + } finally { + endWaitingFor(guard); + } + } + + /** Caller should check before calling that guard is not satisfied. */ + + private boolean awaitNanos(Guard guard, long nanos, boolean signalBeforeWaiting) + throws InterruptedException { + boolean firstTime = true; + try { + do { + if (nanos <= 0L) { + return false; + } + if (firstTime) { + if (signalBeforeWaiting) { + signalNextWaiter(); + } + beginWaitingFor(guard); + firstTime = false; + } + nanos = guard.condition.awaitNanos(nanos); + } while (!guard.isSatisfied()); + return true; + } finally { + if (!firstTime) { + endWaitingFor(guard); + } + } + } +} diff --git a/src/main/java/com/google/common/util/concurrent/MoreExecutors.java b/src/main/java/com/google/common/util/concurrent/MoreExecutors.java new file mode 100644 index 0000000..bb319f8 --- /dev/null +++ b/src/main/java/com/google/common/util/concurrent/MoreExecutors.java @@ -0,0 +1,1105 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.util.concurrent; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.util.concurrent.Internal.toNanosSaturated; + +import com.google.common.annotations.Beta; +import com.google.common.annotations.GwtCompatible; +import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.Supplier; +import com.google.common.base.Throwables; +import com.google.common.collect.Lists; +import com.google.common.collect.Queues; +import com.google.common.util.concurrent.ForwardingListenableFuture.SimpleForwardingListenableFuture; + + +import java.lang.reflect.InvocationTargetException; +import java.time.Duration; +import java.util.Collection; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.Callable; +import java.util.concurrent.Delayed; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Executor; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.RejectedExecutionException; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.ScheduledThreadPoolExecutor; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.ThreadPoolExecutor.CallerRunsPolicy; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +/** + * Factory and utility methods for {@link java.util.concurrent.Executor}, {@link ExecutorService}, + * and {@link ThreadFactory}. + * + * @author Eric Fellheimer + * @author Kyle Littlefield + * @author Justin Mahoney + * @since 3.0 + */ +@GwtCompatible(emulated = true) +public final class MoreExecutors { + private MoreExecutors() {} + + /** + * Converts the given ThreadPoolExecutor into an ExecutorService that exits when the application + * is complete. It does so by using daemon threads and adding a shutdown hook to wait for their + * completion. + * + *

This is mainly for fixed thread pools. See {@link Executors#newFixedThreadPool(int)}. + * + * @param executor the executor to modify to make sure it exits when the application is finished + * @param terminationTimeout how long to wait for the executor to finish before terminating the + * JVM + * @return an unmodifiable version of the input which will not hang the JVM + * @since 28.0 + */ + @Beta + @GwtIncompatible // TODO + public static ExecutorService getExitingExecutorService( + ThreadPoolExecutor executor, Duration terminationTimeout) { + return getExitingExecutorService( + executor, toNanosSaturated(terminationTimeout), TimeUnit.NANOSECONDS); + } + + /** + * Converts the given ThreadPoolExecutor into an ExecutorService that exits when the application + * is complete. It does so by using daemon threads and adding a shutdown hook to wait for their + * completion. + * + *

This is mainly for fixed thread pools. See {@link Executors#newFixedThreadPool(int)}. + * + * @param executor the executor to modify to make sure it exits when the application is finished + * @param terminationTimeout how long to wait for the executor to finish before terminating the + * JVM + * @param timeUnit unit of time for the time parameter + * @return an unmodifiable version of the input which will not hang the JVM + */ + @Beta + @GwtIncompatible // TODO + @SuppressWarnings("GoodTime") // should accept a java.time.Duration + public static ExecutorService getExitingExecutorService( + ThreadPoolExecutor executor, long terminationTimeout, TimeUnit timeUnit) { + return new Application().getExitingExecutorService(executor, terminationTimeout, timeUnit); + } + + /** + * Converts the given ThreadPoolExecutor into an ExecutorService that exits when the application + * is complete. It does so by using daemon threads and adding a shutdown hook to wait for their + * completion. + * + *

This method waits 120 seconds before continuing with JVM termination, even if the executor + * has not finished its work. + * + *

This is mainly for fixed thread pools. See {@link Executors#newFixedThreadPool(int)}. + * + * @param executor the executor to modify to make sure it exits when the application is finished + * @return an unmodifiable version of the input which will not hang the JVM + */ + @Beta + @GwtIncompatible // concurrency + public static ExecutorService getExitingExecutorService(ThreadPoolExecutor executor) { + return new Application().getExitingExecutorService(executor); + } + + /** + * Converts the given ScheduledThreadPoolExecutor into a ScheduledExecutorService that exits when + * the application is complete. It does so by using daemon threads and adding a shutdown hook to + * wait for their completion. + * + *

This is mainly for fixed thread pools. See {@link Executors#newScheduledThreadPool(int)}. + * + * @param executor the executor to modify to make sure it exits when the application is finished + * @param terminationTimeout how long to wait for the executor to finish before terminating the + * JVM + * @return an unmodifiable version of the input which will not hang the JVM + * @since 28.0 + */ + @Beta + @GwtIncompatible // java.time.Duration + public static ScheduledExecutorService getExitingScheduledExecutorService( + ScheduledThreadPoolExecutor executor, Duration terminationTimeout) { + return getExitingScheduledExecutorService( + executor, toNanosSaturated(terminationTimeout), TimeUnit.NANOSECONDS); + } + + /** + * Converts the given ScheduledThreadPoolExecutor into a ScheduledExecutorService that exits when + * the application is complete. It does so by using daemon threads and adding a shutdown hook to + * wait for their completion. + * + *

This is mainly for fixed thread pools. See {@link Executors#newScheduledThreadPool(int)}. + * + * @param executor the executor to modify to make sure it exits when the application is finished + * @param terminationTimeout how long to wait for the executor to finish before terminating the + * JVM + * @param timeUnit unit of time for the time parameter + * @return an unmodifiable version of the input which will not hang the JVM + */ + @Beta + @GwtIncompatible // TODO + @SuppressWarnings("GoodTime") // should accept a java.time.Duration + public static ScheduledExecutorService getExitingScheduledExecutorService( + ScheduledThreadPoolExecutor executor, long terminationTimeout, TimeUnit timeUnit) { + return new Application() + .getExitingScheduledExecutorService(executor, terminationTimeout, timeUnit); + } + + /** + * Converts the given ScheduledThreadPoolExecutor into a ScheduledExecutorService that exits when + * the application is complete. It does so by using daemon threads and adding a shutdown hook to + * wait for their completion. + * + *

This method waits 120 seconds before continuing with JVM termination, even if the executor + * has not finished its work. + * + *

This is mainly for fixed thread pools. See {@link Executors#newScheduledThreadPool(int)}. + * + * @param executor the executor to modify to make sure it exits when the application is finished + * @return an unmodifiable version of the input which will not hang the JVM + */ + @Beta + @GwtIncompatible // TODO + public static ScheduledExecutorService getExitingScheduledExecutorService( + ScheduledThreadPoolExecutor executor) { + return new Application().getExitingScheduledExecutorService(executor); + } + + /** + * Add a shutdown hook to wait for thread completion in the given {@link ExecutorService service}. + * This is useful if the given service uses daemon threads, and we want to keep the JVM from + * exiting immediately on shutdown, instead giving these daemon threads a chance to terminate + * normally. + * + * @param service ExecutorService which uses daemon threads + * @param terminationTimeout how long to wait for the executor to finish before terminating the + * JVM + * @since 28.0 + */ + @Beta + @GwtIncompatible // java.time.Duration + public static void addDelayedShutdownHook(ExecutorService service, Duration terminationTimeout) { + addDelayedShutdownHook(service, toNanosSaturated(terminationTimeout), TimeUnit.NANOSECONDS); + } + + /** + * Add a shutdown hook to wait for thread completion in the given {@link ExecutorService service}. + * This is useful if the given service uses daemon threads, and we want to keep the JVM from + * exiting immediately on shutdown, instead giving these daemon threads a chance to terminate + * normally. + * + * @param service ExecutorService which uses daemon threads + * @param terminationTimeout how long to wait for the executor to finish before terminating the + * JVM + * @param timeUnit unit of time for the time parameter + */ + @Beta + @GwtIncompatible // TODO + @SuppressWarnings("GoodTime") // should accept a java.time.Duration + public static void addDelayedShutdownHook( + ExecutorService service, long terminationTimeout, TimeUnit timeUnit) { + new Application().addDelayedShutdownHook(service, terminationTimeout, timeUnit); + } + + /** Represents the current application to register shutdown hooks. */ + @GwtIncompatible // TODO + @VisibleForTesting + static class Application { + + final ExecutorService getExitingExecutorService( + ThreadPoolExecutor executor, long terminationTimeout, TimeUnit timeUnit) { + useDaemonThreadFactory(executor); + ExecutorService service = Executors.unconfigurableExecutorService(executor); + addDelayedShutdownHook(executor, terminationTimeout, timeUnit); + return service; + } + + final ExecutorService getExitingExecutorService(ThreadPoolExecutor executor) { + return getExitingExecutorService(executor, 120, TimeUnit.SECONDS); + } + + final ScheduledExecutorService getExitingScheduledExecutorService( + ScheduledThreadPoolExecutor executor, long terminationTimeout, TimeUnit timeUnit) { + useDaemonThreadFactory(executor); + ScheduledExecutorService service = Executors.unconfigurableScheduledExecutorService(executor); + addDelayedShutdownHook(executor, terminationTimeout, timeUnit); + return service; + } + + final ScheduledExecutorService getExitingScheduledExecutorService( + ScheduledThreadPoolExecutor executor) { + return getExitingScheduledExecutorService(executor, 120, TimeUnit.SECONDS); + } + + final void addDelayedShutdownHook( + final ExecutorService service, final long terminationTimeout, final TimeUnit timeUnit) { + checkNotNull(service); + checkNotNull(timeUnit); + addShutdownHook( + MoreExecutors.newThread( + "DelayedShutdownHook-for-" + service, + new Runnable() { + @Override + public void run() { + try { + // We'd like to log progress and failures that may arise in the + // following code, but unfortunately the behavior of logging + // is undefined in shutdown hooks. + // This is because the logging code installs a shutdown hook of its + // own. See Cleaner class inside {@link LogManager}. + service.shutdown(); + service.awaitTermination(terminationTimeout, timeUnit); + } catch (InterruptedException ignored) { + // We're shutting down anyway, so just ignore. + } + } + })); + } + + @VisibleForTesting + void addShutdownHook(Thread hook) { + Runtime.getRuntime().addShutdownHook(hook); + } + } + + @GwtIncompatible // TODO + private static void useDaemonThreadFactory(ThreadPoolExecutor executor) { + executor.setThreadFactory( + new ThreadFactoryBuilder() + .setDaemon(true) + .setThreadFactory(executor.getThreadFactory()) + .build()); + } + + // See newDirectExecutorService javadoc for behavioral notes. + @GwtIncompatible // TODO + private static final class DirectExecutorService extends AbstractListeningExecutorService { + /** Lock used whenever accessing the state variables (runningTasks, shutdown) of the executor */ + private final Object lock = new Object(); + + /* + * Conceptually, these two variables describe the executor being in + * one of three states: + * - Active: shutdown == false + * - Shutdown: runningTasks > 0 and shutdown == true + * - Terminated: runningTasks == 0 and shutdown == true + */ + + private int runningTasks = 0; + + + private boolean shutdown = false; + + @Override + public void execute(Runnable command) { + startTask(); + try { + command.run(); + } finally { + endTask(); + } + } + + @Override + public boolean isShutdown() { + synchronized (lock) { + return shutdown; + } + } + + @Override + public void shutdown() { + synchronized (lock) { + shutdown = true; + if (runningTasks == 0) { + lock.notifyAll(); + } + } + } + + // See newDirectExecutorService javadoc for unusual behavior of this method. + @Override + public List shutdownNow() { + shutdown(); + return Collections.emptyList(); + } + + @Override + public boolean isTerminated() { + synchronized (lock) { + return shutdown && runningTasks == 0; + } + } + + @Override + public boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException { + long nanos = unit.toNanos(timeout); + synchronized (lock) { + while (true) { + if (shutdown && runningTasks == 0) { + return true; + } else if (nanos <= 0) { + return false; + } else { + long now = System.nanoTime(); + TimeUnit.NANOSECONDS.timedWait(lock, nanos); + nanos -= System.nanoTime() - now; // subtract the actual time we waited + } + } + } + } + + /** + * Checks if the executor has been shut down and increments the running task count. + * + * @throws RejectedExecutionException if the executor has been previously shutdown + */ + private void startTask() { + synchronized (lock) { + if (shutdown) { + throw new RejectedExecutionException("Executor already shutdown"); + } + runningTasks++; + } + } + + /** Decrements the running task count. */ + private void endTask() { + synchronized (lock) { + int numRunning = --runningTasks; + if (numRunning == 0) { + lock.notifyAll(); + } + } + } + } + + /** + * Creates an executor service that runs each task in the thread that invokes {@code + * execute/submit}, as in {@link CallerRunsPolicy} This applies both to individually submitted + * tasks and to collections of tasks submitted via {@code invokeAll} or {@code invokeAny}. In the + * latter case, tasks will run serially on the calling thread. Tasks are run to completion before + * a {@code Future} is returned to the caller (unless the executor has been shutdown). + * + *

Although all tasks are immediately executed in the thread that submitted the task, this + * {@code ExecutorService} imposes a small locking overhead on each task submission in order to + * implement shutdown and termination behavior. + * + *

The implementation deviates from the {@code ExecutorService} specification with regards to + * the {@code shutdownNow} method. First, "best-effort" with regards to canceling running tasks is + * implemented as "no-effort". No interrupts or other attempts are made to stop threads executing + * tasks. Second, the returned list will always be empty, as any submitted task is considered to + * have started execution. This applies also to tasks given to {@code invokeAll} or {@code + * invokeAny} which are pending serial execution, even the subset of the tasks that have not yet + * started execution. It is unclear from the {@code ExecutorService} specification if these should + * be included, and it's much easier to implement the interpretation that they not be. Finally, a + * call to {@code shutdown} or {@code shutdownNow} may result in concurrent calls to {@code + * invokeAll/invokeAny} throwing RejectedExecutionException, although a subset of the tasks may + * already have been executed. + * + * @since 18.0 (present as MoreExecutors.sameThreadExecutor() since 10.0) + */ + @GwtIncompatible // TODO + public static ListeningExecutorService newDirectExecutorService() { + return new DirectExecutorService(); + } + + /** + * Returns an {@link Executor} that runs each task in the thread that invokes {@link + * Executor#execute execute}, as in {@link CallerRunsPolicy}. + * + *

This instance is equivalent to: + * + *

{@code
+   * final class DirectExecutor implements Executor {
+   *   public void execute(Runnable r) {
+   *     r.run();
+   *   }
+   * }
+   * }
+ * + *

This should be preferred to {@link #newDirectExecutorService()} because implementing the + * {@link ExecutorService} subinterface necessitates significant performance overhead. + * + * + * @since 18.0 + */ + public static Executor directExecutor() { + return DirectExecutor.INSTANCE; + } + + /** + * Returns an {@link Executor} that runs each task executed sequentially, such that no two tasks + * are running concurrently. Submitted tasks have a happens-before order as defined in the Java + * Language Specification. + * + *

The executor uses {@code delegate} in order to {@link Executor#execute execute} each task in + * turn, and does not create any threads of its own. + * + *

After execution begins on a thread from the {@code delegate} {@link Executor}, tasks are + * polled and executed from a task queue until there are no more tasks. The thread will not be + * released until there are no more tasks to run. + * + *

If a task is submitted while a thread is executing tasks from the task queue, the thread + * will not be released until that submitted task is also complete. + * + *

If a task is {@linkplain Thread#interrupt interrupted} while a task is running: + * + *

    + *
  1. execution will not stop until the task queue is empty. + *
  2. tasks will begin execution with the thread marked as not interrupted - any interruption + * applies only to the task that was running at the point of interruption. + *
  3. if the thread was interrupted before the SequentialExecutor's worker begins execution, + * the interrupt will be restored to the thread after it completes so that its {@code + * delegate} Executor may process the interrupt. + *
  4. subtasks are run with the thread uninterrupted and interrupts received during execution + * of a task are ignored. + *
+ * + *

{@code RuntimeException}s thrown by tasks are simply logged and the executor keeps trucking. + * If an {@code Error} is thrown, the error will propagate and execution will stop until the next + * time a task is submitted. + * + *

When an {@code Error} is thrown by an executed task, previously submitted tasks may never + * run. An attempt will be made to restart execution on the next call to {@code execute}. If the + * {@code delegate} has begun to reject execution, the previously submitted tasks may never run, + * despite not throwing a RejectedExecutionException synchronously with the call to {@code + * execute}. If this behaviour is problematic, use an Executor with a single thread (e.g. {@link + * Executors#newSingleThreadExecutor}). + * + * @since 23.3 (since 23.1 as {@code sequentialExecutor}) + */ + @Beta + @GwtIncompatible + public static Executor newSequentialExecutor(Executor delegate) { + return new SequentialExecutor(delegate); + } + + /** + * Creates an {@link ExecutorService} whose {@code submit} and {@code invokeAll} methods submit + * {@link ListenableFutureTask} instances to the given delegate executor. Those methods, as well + * as {@code execute} and {@code invokeAny}, are implemented in terms of calls to {@code + * delegate.execute}. All other methods are forwarded unchanged to the delegate. This implies that + * the returned {@code ListeningExecutorService} never calls the delegate's {@code submit}, {@code + * invokeAll}, and {@code invokeAny} methods, so any special handling of tasks must be implemented + * in the delegate's {@code execute} method or by wrapping the returned {@code + * ListeningExecutorService}. + * + *

If the delegate executor was already an instance of {@code ListeningExecutorService}, it is + * returned untouched, and the rest of this documentation does not apply. + * + * @since 10.0 + */ + @GwtIncompatible // TODO + public static ListeningExecutorService listeningDecorator(ExecutorService delegate) { + return (delegate instanceof ListeningExecutorService) + ? (ListeningExecutorService) delegate + : (delegate instanceof ScheduledExecutorService) + ? new ScheduledListeningDecorator((ScheduledExecutorService) delegate) + : new ListeningDecorator(delegate); + } + + /** + * Creates a {@link ScheduledExecutorService} whose {@code submit} and {@code invokeAll} methods + * submit {@link ListenableFutureTask} instances to the given delegate executor. Those methods, as + * well as {@code execute} and {@code invokeAny}, are implemented in terms of calls to {@code + * delegate.execute}. All other methods are forwarded unchanged to the delegate. This implies that + * the returned {@code ListeningScheduledExecutorService} never calls the delegate's {@code + * submit}, {@code invokeAll}, and {@code invokeAny} methods, so any special handling of tasks + * must be implemented in the delegate's {@code execute} method or by wrapping the returned {@code + * ListeningScheduledExecutorService}. + * + *

If the delegate executor was already an instance of {@code + * ListeningScheduledExecutorService}, it is returned untouched, and the rest of this + * documentation does not apply. + * + * @since 10.0 + */ + @GwtIncompatible // TODO + public static ListeningScheduledExecutorService listeningDecorator( + ScheduledExecutorService delegate) { + return (delegate instanceof ListeningScheduledExecutorService) + ? (ListeningScheduledExecutorService) delegate + : new ScheduledListeningDecorator(delegate); + } + + @GwtIncompatible // TODO + private static class ListeningDecorator extends AbstractListeningExecutorService { + private final ExecutorService delegate; + + ListeningDecorator(ExecutorService delegate) { + this.delegate = checkNotNull(delegate); + } + + @Override + public final boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException { + return delegate.awaitTermination(timeout, unit); + } + + @Override + public final boolean isShutdown() { + return delegate.isShutdown(); + } + + @Override + public final boolean isTerminated() { + return delegate.isTerminated(); + } + + @Override + public final void shutdown() { + delegate.shutdown(); + } + + @Override + public final List shutdownNow() { + return delegate.shutdownNow(); + } + + @Override + public final void execute(Runnable command) { + delegate.execute(command); + } + } + + @GwtIncompatible // TODO + private static final class ScheduledListeningDecorator extends ListeningDecorator + implements ListeningScheduledExecutorService { + @SuppressWarnings("hiding") + final ScheduledExecutorService delegate; + + ScheduledListeningDecorator(ScheduledExecutorService delegate) { + super(delegate); + this.delegate = checkNotNull(delegate); + } + + @Override + public ListenableScheduledFuture schedule(Runnable command, long delay, TimeUnit unit) { + TrustedListenableFutureTask task = TrustedListenableFutureTask.create(command, null); + ScheduledFuture scheduled = delegate.schedule(task, delay, unit); + return new ListenableScheduledTask<>(task, scheduled); + } + + @Override + public ListenableScheduledFuture schedule( + Callable callable, long delay, TimeUnit unit) { + TrustedListenableFutureTask task = TrustedListenableFutureTask.create(callable); + ScheduledFuture scheduled = delegate.schedule(task, delay, unit); + return new ListenableScheduledTask(task, scheduled); + } + + @Override + public ListenableScheduledFuture scheduleAtFixedRate( + Runnable command, long initialDelay, long period, TimeUnit unit) { + NeverSuccessfulListenableFutureTask task = new NeverSuccessfulListenableFutureTask(command); + ScheduledFuture scheduled = delegate.scheduleAtFixedRate(task, initialDelay, period, unit); + return new ListenableScheduledTask<>(task, scheduled); + } + + @Override + public ListenableScheduledFuture scheduleWithFixedDelay( + Runnable command, long initialDelay, long delay, TimeUnit unit) { + NeverSuccessfulListenableFutureTask task = new NeverSuccessfulListenableFutureTask(command); + ScheduledFuture scheduled = + delegate.scheduleWithFixedDelay(task, initialDelay, delay, unit); + return new ListenableScheduledTask<>(task, scheduled); + } + + private static final class ListenableScheduledTask + extends SimpleForwardingListenableFuture implements ListenableScheduledFuture { + + private final ScheduledFuture scheduledDelegate; + + public ListenableScheduledTask( + ListenableFuture listenableDelegate, ScheduledFuture scheduledDelegate) { + super(listenableDelegate); + this.scheduledDelegate = scheduledDelegate; + } + + @Override + public boolean cancel(boolean mayInterruptIfRunning) { + boolean cancelled = super.cancel(mayInterruptIfRunning); + if (cancelled) { + // Unless it is cancelled, the delegate may continue being scheduled + scheduledDelegate.cancel(mayInterruptIfRunning); + + // TODO(user): Cancel "this" if "scheduledDelegate" is cancelled. + } + return cancelled; + } + + @Override + public long getDelay(TimeUnit unit) { + return scheduledDelegate.getDelay(unit); + } + + @Override + public int compareTo(Delayed other) { + return scheduledDelegate.compareTo(other); + } + } + + @GwtIncompatible // TODO + private static final class NeverSuccessfulListenableFutureTask + extends AbstractFuture.TrustedFuture implements Runnable { + private final Runnable delegate; + + public NeverSuccessfulListenableFutureTask(Runnable delegate) { + this.delegate = checkNotNull(delegate); + } + + @Override + public void run() { + try { + delegate.run(); + } catch (Throwable t) { + setException(t); + throw Throwables.propagate(t); + } + } + } + } + + /* + * This following method is a modified version of one found in + * http://gee.cs.oswego.edu/cgi-bin/viewcvs.cgi/jsr166/src/test/tck/AbstractExecutorServiceTest.java?revision=1.30 + * which contained the following notice: + * + * 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. + */ + + /** + * An implementation of {@link ExecutorService#invokeAny} for {@link ListeningExecutorService} + * implementations. + */ + @GwtIncompatible static T invokeAnyImpl( + ListeningExecutorService executorService, + Collection> tasks, + boolean timed, + Duration timeout) + throws InterruptedException, ExecutionException, TimeoutException { + return invokeAnyImpl( + executorService, tasks, timed, toNanosSaturated(timeout), TimeUnit.NANOSECONDS); + } + + /** + * An implementation of {@link ExecutorService#invokeAny} for {@link ListeningExecutorService} + * implementations. + */ + @SuppressWarnings("GoodTime") // should accept a java.time.Duration + @GwtIncompatible static T invokeAnyImpl( + ListeningExecutorService executorService, + Collection> tasks, + boolean timed, + long timeout, + TimeUnit unit) + throws InterruptedException, ExecutionException, TimeoutException { + checkNotNull(executorService); + checkNotNull(unit); + int ntasks = tasks.size(); + checkArgument(ntasks > 0); + List> futures = Lists.newArrayListWithCapacity(ntasks); + BlockingQueue> futureQueue = Queues.newLinkedBlockingQueue(); + long timeoutNanos = unit.toNanos(timeout); + + // For efficiency, especially in executors with limited + // parallelism, check to see if previously submitted tasks are + // done before submitting more of them. This interleaving + // plus the exception mechanics account for messiness of main + // loop. + + try { + // Record exceptions so that if we fail to obtain any + // result, we can throw the last exception we got. + ExecutionException ee = null; + long lastTime = timed ? System.nanoTime() : 0; + Iterator> it = tasks.iterator(); + + futures.add(submitAndAddQueueListener(executorService, it.next(), futureQueue)); + --ntasks; + int active = 1; + + while (true) { + Future f = futureQueue.poll(); + if (f == null) { + if (ntasks > 0) { + --ntasks; + futures.add(submitAndAddQueueListener(executorService, it.next(), futureQueue)); + ++active; + } else if (active == 0) { + break; + } else if (timed) { + f = futureQueue.poll(timeoutNanos, TimeUnit.NANOSECONDS); + if (f == null) { + throw new TimeoutException(); + } + long now = System.nanoTime(); + timeoutNanos -= now - lastTime; + lastTime = now; + } else { + f = futureQueue.take(); + } + } + if (f != null) { + --active; + try { + return f.get(); + } catch (ExecutionException eex) { + ee = eex; + } catch (RuntimeException rex) { + ee = new ExecutionException(rex); + } + } + } + + if (ee == null) { + ee = new ExecutionException(null); + } + throw ee; + } finally { + for (Future f : futures) { + f.cancel(true); + } + } + } + + /** + * Submits the task and adds a listener that adds the future to {@code queue} when it completes. + */ + @GwtIncompatible // TODO + private static ListenableFuture submitAndAddQueueListener( + ListeningExecutorService executorService, + Callable task, + final BlockingQueue> queue) { + final ListenableFuture future = executorService.submit(task); + future.addListener( + new Runnable() { + @Override + public void run() { + queue.add(future); + } + }, + directExecutor()); + return future; + } + + /** + * Returns a default thread factory used to create new threads. + * + *

When running on AppEngine with access to AppEngine legacy + * APIs, this method returns {@code ThreadManager.currentRequestThreadFactory()}. Otherwise, + * it returns {@link Executors#defaultThreadFactory()}. + * + * @since 14.0 + */ + @Beta + @GwtIncompatible // concurrency + public static ThreadFactory platformThreadFactory() { + if (!isAppEngineWithApiClasses()) { + return Executors.defaultThreadFactory(); + } + try { + return (ThreadFactory) + Class.forName("com.google.appengine.api.ThreadManager") + .getMethod("currentRequestThreadFactory") + .invoke(null); + /* + * Do not merge the 3 catch blocks below. javac would infer a type of + * ReflectiveOperationException, which Animal Sniffer would reject. (Old versions of Android + * don't *seem* to mind, but there might be edge cases of which we're unaware.) + */ + } catch (IllegalAccessException e) { + throw new RuntimeException("Couldn't invoke ThreadManager.currentRequestThreadFactory", e); + } catch (ClassNotFoundException e) { + throw new RuntimeException("Couldn't invoke ThreadManager.currentRequestThreadFactory", e); + } catch (NoSuchMethodException e) { + throw new RuntimeException("Couldn't invoke ThreadManager.currentRequestThreadFactory", e); + } catch (InvocationTargetException e) { + throw Throwables.propagate(e.getCause()); + } + } + + @GwtIncompatible // TODO + private static boolean isAppEngineWithApiClasses() { + if (System.getProperty("com.google.appengine.runtime.environment") == null) { + return false; + } + try { + Class.forName("com.google.appengine.api.utils.SystemProperty"); + } catch (ClassNotFoundException e) { + return false; + } + try { + // If the current environment is null, we're not inside AppEngine. + return Class.forName("com.google.apphosting.api.ApiProxy") + .getMethod("getCurrentEnvironment") + .invoke(null) + != null; + } catch (ClassNotFoundException e) { + // If ApiProxy doesn't exist, we're not on AppEngine at all. + return false; + } catch (InvocationTargetException e) { + // If ApiProxy throws an exception, we're not in a proper AppEngine environment. + return false; + } catch (IllegalAccessException e) { + // If the method isn't accessible, we're not on a supported version of AppEngine; + return false; + } catch (NoSuchMethodException e) { + // If the method doesn't exist, we're not on a supported version of AppEngine; + return false; + } + } + + /** + * Creates a thread using {@link #platformThreadFactory}, and sets its name to {@code name} unless + * changing the name is forbidden by the security manager. + */ + @GwtIncompatible // concurrency + static Thread newThread(String name, Runnable runnable) { + checkNotNull(name); + checkNotNull(runnable); + Thread result = platformThreadFactory().newThread(runnable); + try { + result.setName(name); + } catch (SecurityException e) { + // OK if we can't set the name in this environment. + } + return result; + } + + // TODO(lukes): provide overloads for ListeningExecutorService? ListeningScheduledExecutorService? + // TODO(lukes): provide overloads that take constant strings? Functions to + // calculate names? + + /** + * Creates an {@link Executor} that renames the {@link Thread threads} that its tasks run in. + * + *

The names are retrieved from the {@code nameSupplier} on the thread that is being renamed + * right before each task is run. The renaming is best effort, if a {@link SecurityManager} + * prevents the renaming then it will be skipped but the tasks will still execute. + * + * + * @param executor The executor to decorate + * @param nameSupplier The source of names for each task + */ + @GwtIncompatible // concurrency + static Executor renamingDecorator(final Executor executor, final Supplier nameSupplier) { + checkNotNull(executor); + checkNotNull(nameSupplier); + return new Executor() { + @Override + public void execute(Runnable command) { + executor.execute(Callables.threadRenaming(command, nameSupplier)); + } + }; + } + + /** + * Creates an {@link ExecutorService} that renames the {@link Thread threads} that its tasks run + * in. + * + *

The names are retrieved from the {@code nameSupplier} on the thread that is being renamed + * right before each task is run. The renaming is best effort, if a {@link SecurityManager} + * prevents the renaming then it will be skipped but the tasks will still execute. + * + * + * @param service The executor to decorate + * @param nameSupplier The source of names for each task + */ + @GwtIncompatible // concurrency + static ExecutorService renamingDecorator( + final ExecutorService service, final Supplier nameSupplier) { + checkNotNull(service); + checkNotNull(nameSupplier); + return new WrappingExecutorService(service) { + @Override + protected Callable wrapTask(Callable callable) { + return Callables.threadRenaming(callable, nameSupplier); + } + + @Override + protected Runnable wrapTask(Runnable command) { + return Callables.threadRenaming(command, nameSupplier); + } + }; + } + + /** + * Creates a {@link ScheduledExecutorService} that renames the {@link Thread threads} that its + * tasks run in. + * + *

The names are retrieved from the {@code nameSupplier} on the thread that is being renamed + * right before each task is run. The renaming is best effort, if a {@link SecurityManager} + * prevents the renaming then it will be skipped but the tasks will still execute. + * + * + * @param service The executor to decorate + * @param nameSupplier The source of names for each task + */ + @GwtIncompatible // concurrency + static ScheduledExecutorService renamingDecorator( + final ScheduledExecutorService service, final Supplier nameSupplier) { + checkNotNull(service); + checkNotNull(nameSupplier); + return new WrappingScheduledExecutorService(service) { + @Override + protected Callable wrapTask(Callable callable) { + return Callables.threadRenaming(callable, nameSupplier); + } + + @Override + protected Runnable wrapTask(Runnable command) { + return Callables.threadRenaming(command, nameSupplier); + } + }; + } + + /** + * Shuts down the given executor service gradually, first disabling new submissions and later, if + * necessary, cancelling remaining tasks. + * + *

The method takes the following steps: + * + *

    + *
  1. calls {@link ExecutorService#shutdown()}, disabling acceptance of new submitted tasks. + *
  2. awaits executor service termination for half of the specified timeout. + *
  3. if the timeout expires, it calls {@link ExecutorService#shutdownNow()}, cancelling + * pending tasks and interrupting running tasks. + *
  4. awaits executor service termination for the other half of the specified timeout. + *
+ * + *

If, at any step of the process, the calling thread is interrupted, the method calls {@link + * ExecutorService#shutdownNow()} and returns. + * + * @param service the {@code ExecutorService} to shut down + * @param timeout the maximum time to wait for the {@code ExecutorService} to terminate + * @return {@code true} if the {@code ExecutorService} was terminated successfully, {@code false} + * if the call timed out or was interrupted + * @since 28.0 + */ + @Beta + + @GwtIncompatible // java.time.Duration + public static boolean shutdownAndAwaitTermination(ExecutorService service, Duration timeout) { + return shutdownAndAwaitTermination(service, toNanosSaturated(timeout), TimeUnit.NANOSECONDS); + } + + /** + * Shuts down the given executor service gradually, first disabling new submissions and later, if + * necessary, cancelling remaining tasks. + * + *

The method takes the following steps: + * + *

    + *
  1. calls {@link ExecutorService#shutdown()}, disabling acceptance of new submitted tasks. + *
  2. awaits executor service termination for half of the specified timeout. + *
  3. if the timeout expires, it calls {@link ExecutorService#shutdownNow()}, cancelling + * pending tasks and interrupting running tasks. + *
  4. awaits executor service termination for the other half of the specified timeout. + *
+ * + *

If, at any step of the process, the calling thread is interrupted, the method calls {@link + * ExecutorService#shutdownNow()} and returns. + * + * @param service the {@code ExecutorService} to shut down + * @param timeout the maximum time to wait for the {@code ExecutorService} to terminate + * @param unit the time unit of the timeout argument + * @return {@code true} if the {@code ExecutorService} was terminated successfully, {@code false} + * if the call timed out or was interrupted + * @since 17.0 + */ + @Beta + + @GwtIncompatible // concurrency + @SuppressWarnings("GoodTime") // should accept a java.time.Duration + public static boolean shutdownAndAwaitTermination( + ExecutorService service, long timeout, TimeUnit unit) { + long halfTimeoutNanos = unit.toNanos(timeout) / 2; + // Disable new tasks from being submitted + service.shutdown(); + try { + // Wait for half the duration of the timeout for existing tasks to terminate + if (!service.awaitTermination(halfTimeoutNanos, TimeUnit.NANOSECONDS)) { + // Cancel currently executing tasks + service.shutdownNow(); + // Wait the other half of the timeout for tasks to respond to being cancelled + service.awaitTermination(halfTimeoutNanos, TimeUnit.NANOSECONDS); + } + } catch (InterruptedException ie) { + // Preserve interrupt status + Thread.currentThread().interrupt(); + // (Re-)Cancel if current thread also interrupted + service.shutdownNow(); + } + return service.isTerminated(); + } + + /** + * Returns an Executor that will propagate {@link RejectedExecutionException} from the delegate + * executor to the given {@code future}. + * + *

Note, the returned executor can only be used once. + */ + static Executor rejectionPropagatingExecutor( + final Executor delegate, final AbstractFuture future) { + checkNotNull(delegate); + checkNotNull(future); + if (delegate == directExecutor()) { + // directExecutor() cannot throw RejectedExecutionException + return delegate; + } + return new Executor() { + boolean thrownFromDelegate = true; + + @Override + public void execute(final Runnable command) { + try { + delegate.execute( + new Runnable() { + @Override + public void run() { + thrownFromDelegate = false; + command.run(); + } + }); + } catch (RejectedExecutionException e) { + if (thrownFromDelegate) { + // wrap exception? + future.setException(e); + } + // otherwise it must have been thrown from a transitive call and the delegate runnable + // should have handled it. + } + } + }; + } +} diff --git a/src/main/java/com/google/common/util/concurrent/Partially.java b/src/main/java/com/google/common/util/concurrent/Partially.java new file mode 100644 index 0000000..c40ab4e --- /dev/null +++ b/src/main/java/com/google/common/util/concurrent/Partially.java @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2009 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.util.concurrent; + +import com.google.common.annotations.GwtCompatible; +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Outer class that exists solely to let us write {@code Partially.GwtIncompatible} instead of plain + * {@code GwtIncompatible}. This is more accurate for {@link Futures#catching}, which is available + * under GWT but with a slightly different signature. + * + *

We can't use {@code PartiallyGwtIncompatible} because then the GWT compiler wouldn't recognize + * it as a {@code GwtIncompatible} annotation. And for {@code Futures.catching}, we need the GWT + * compiler to autostrip the normal server method in order to expose the special, inherited GWT + * version. + */ +@GwtCompatible +final class Partially { + /** + * The presence of this annotation on an API indicates that the method may be used with the + * Google Web Toolkit (GWT) but that it has some + * restrictions. + */ + @Retention(RetentionPolicy.CLASS) + @Target({ElementType.TYPE, ElementType.METHOD, ElementType.CONSTRUCTOR, ElementType.FIELD}) + @Documented + @interface GwtIncompatible { + String value(); + } + + private Partially() {} +} diff --git a/src/main/java/com/google/common/util/concurrent/Platform.java b/src/main/java/com/google/common/util/concurrent/Platform.java new file mode 100644 index 0000000..8fa3a5b --- /dev/null +++ b/src/main/java/com/google/common/util/concurrent/Platform.java @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2015 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.util.concurrent; + +import com.google.common.annotations.GwtCompatible; + + +/** Methods factored out so that they can be emulated differently in GWT. */ +@GwtCompatible(emulated = true) +final class Platform { + static boolean isInstanceOfThrowableClass( + Throwable t, Class expectedClass) { + return expectedClass.isInstance(t); + } + + private Platform() {} +} diff --git a/src/main/java/com/google/common/util/concurrent/RateLimiter.java b/src/main/java/com/google/common/util/concurrent/RateLimiter.java new file mode 100644 index 0000000..9aba070 --- /dev/null +++ b/src/main/java/com/google/common/util/concurrent/RateLimiter.java @@ -0,0 +1,495 @@ +/* + * Copyright (C) 2012 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.util.concurrent; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.util.concurrent.Internal.toNanosSaturated; +import static java.lang.Math.max; +import static java.util.concurrent.TimeUnit.MICROSECONDS; +import static java.util.concurrent.TimeUnit.SECONDS; + +import com.google.common.annotations.Beta; +import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.Stopwatch; +import com.google.common.util.concurrent.SmoothRateLimiter.SmoothBursty; +import com.google.common.util.concurrent.SmoothRateLimiter.SmoothWarmingUp; + +import java.time.Duration; +import java.util.Locale; +import java.util.concurrent.TimeUnit; + + +/** + * A rate limiter. Conceptually, a rate limiter distributes permits at a configurable rate. Each + * {@link #acquire()} blocks if necessary until a permit is available, and then takes it. Once + * acquired, permits need not be released. + * + *

{@code RateLimiter} is safe for concurrent use: It will restrict the total rate of calls from + * all threads. Note, however, that it does not guarantee fairness. + * + *

Rate limiters are often used to restrict the rate at which some physical or logical resource + * is accessed. This is in contrast to {@link java.util.concurrent.Semaphore} which restricts the + * number of concurrent accesses instead of the rate (note though that concurrency and rate are + * closely related, e.g. see Little's + * Law). + * + *

A {@code RateLimiter} is defined primarily by the rate at which permits are issued. Absent + * additional configuration, permits will be distributed at a fixed rate, defined in terms of + * permits per second. Permits will be distributed smoothly, with the delay between individual + * permits being adjusted to ensure that the configured rate is maintained. + * + *

It is possible to configure a {@code RateLimiter} to have a warmup period during which time + * the permits issued each second steadily increases until it hits the stable rate. + * + *

As an example, imagine that we have a list of tasks to execute, but we don't want to submit + * more than 2 per second: + * + *

{@code
+ * final RateLimiter rateLimiter = RateLimiter.create(2.0); // rate is "2 permits per second"
+ * void submitTasks(List tasks, Executor executor) {
+ *   for (Runnable task : tasks) {
+ *     rateLimiter.acquire(); // may wait
+ *     executor.execute(task);
+ *   }
+ * }
+ * }
+ * + *

As another example, imagine that we produce a stream of data, and we want to cap it at 5kb per + * second. This could be accomplished by requiring a permit per byte, and specifying a rate of 5000 + * permits per second: + * + *

{@code
+ * final RateLimiter rateLimiter = RateLimiter.create(5000.0); // rate = 5000 permits per second
+ * void submitPacket(byte[] packet) {
+ *   rateLimiter.acquire(packet.length);
+ *   networkService.send(packet);
+ * }
+ * }
+ * + *

It is important to note that the number of permits requested never affects the + * throttling of the request itself (an invocation to {@code acquire(1)} and an invocation to {@code + * acquire(1000)} will result in exactly the same throttling, if any), but it affects the throttling + * of the next request. I.e., if an expensive task arrives at an idle RateLimiter, it will be + * granted immediately, but it is the next request that will experience extra throttling, + * thus paying for the cost of the expensive task. + * + * @author Dimitris Andreou + * @since 13.0 + */ +// TODO(user): switch to nano precision. A natural unit of cost is "bytes", and a micro precision +// would mean a maximum rate of "1MB/s", which might be small in some cases. +@Beta +@GwtIncompatible +public abstract class RateLimiter { + /** + * Creates a {@code RateLimiter} with the specified stable throughput, given as "permits per + * second" (commonly referred to as QPS, queries per second). + * + *

The returned {@code RateLimiter} ensures that on average no more than {@code + * permitsPerSecond} are issued during any given second, with sustained requests being smoothly + * spread over each second. When the incoming request rate exceeds {@code permitsPerSecond} the + * rate limiter will release one permit every {@code (1.0 / permitsPerSecond)} seconds. When the + * rate limiter is unused, bursts of up to {@code permitsPerSecond} permits will be allowed, with + * subsequent requests being smoothly limited at the stable rate of {@code permitsPerSecond}. + * + * @param permitsPerSecond the rate of the returned {@code RateLimiter}, measured in how many + * permits become available per second + * @throws IllegalArgumentException if {@code permitsPerSecond} is negative or zero + */ + // TODO(user): "This is equivalent to + // {@code createWithCapacity(permitsPerSecond, 1, TimeUnit.SECONDS)}". + public static RateLimiter create(double permitsPerSecond) { + /* + * The default RateLimiter configuration can save the unused permits of up to one second. This + * is to avoid unnecessary stalls in situations like this: A RateLimiter of 1qps, and 4 threads, + * all calling acquire() at these moments: + * + * T0 at 0 seconds + * T1 at 1.05 seconds + * T2 at 2 seconds + * T3 at 3 seconds + * + * Due to the slight delay of T1, T2 would have to sleep till 2.05 seconds, and T3 would also + * have to sleep till 3.05 seconds. + */ + return create(permitsPerSecond, SleepingStopwatch.createFromSystemTimer()); + } + + @VisibleForTesting + static RateLimiter create(double permitsPerSecond, SleepingStopwatch stopwatch) { + RateLimiter rateLimiter = new SmoothBursty(stopwatch, 1.0 /* maxBurstSeconds */); + rateLimiter.setRate(permitsPerSecond); + return rateLimiter; + } + + /** + * Creates a {@code RateLimiter} with the specified stable throughput, given as "permits per + * second" (commonly referred to as QPS, queries per second), and a warmup period, + * during which the {@code RateLimiter} smoothly ramps up its rate, until it reaches its maximum + * rate at the end of the period (as long as there are enough requests to saturate it). Similarly, + * if the {@code RateLimiter} is left unused for a duration of {@code warmupPeriod}, it + * will gradually return to its "cold" state, i.e. it will go through the same warming up process + * as when it was first created. + * + *

The returned {@code RateLimiter} is intended for cases where the resource that actually + * fulfills the requests (e.g., a remote server) needs "warmup" time, rather than being + * immediately accessed at the stable (maximum) rate. + * + *

The returned {@code RateLimiter} starts in a "cold" state (i.e. the warmup period will + * follow), and if it is left unused for long enough, it will return to that state. + * + * @param permitsPerSecond the rate of the returned {@code RateLimiter}, measured in how many + * permits become available per second + * @param warmupPeriod the duration of the period where the {@code RateLimiter} ramps up its rate, + * before reaching its stable (maximum) rate + * @throws IllegalArgumentException if {@code permitsPerSecond} is negative or zero or {@code + * warmupPeriod} is negative + * @since 28.0 + */ + public static RateLimiter create(double permitsPerSecond, Duration warmupPeriod) { + return create(permitsPerSecond, toNanosSaturated(warmupPeriod), TimeUnit.NANOSECONDS); + } + + /** + * Creates a {@code RateLimiter} with the specified stable throughput, given as "permits per + * second" (commonly referred to as QPS, queries per second), and a warmup period, + * during which the {@code RateLimiter} smoothly ramps up its rate, until it reaches its maximum + * rate at the end of the period (as long as there are enough requests to saturate it). Similarly, + * if the {@code RateLimiter} is left unused for a duration of {@code warmupPeriod}, it + * will gradually return to its "cold" state, i.e. it will go through the same warming up process + * as when it was first created. + * + *

The returned {@code RateLimiter} is intended for cases where the resource that actually + * fulfills the requests (e.g., a remote server) needs "warmup" time, rather than being + * immediately accessed at the stable (maximum) rate. + * + *

The returned {@code RateLimiter} starts in a "cold" state (i.e. the warmup period will + * follow), and if it is left unused for long enough, it will return to that state. + * + * @param permitsPerSecond the rate of the returned {@code RateLimiter}, measured in how many + * permits become available per second + * @param warmupPeriod the duration of the period where the {@code RateLimiter} ramps up its rate, + * before reaching its stable (maximum) rate + * @param unit the time unit of the warmupPeriod argument + * @throws IllegalArgumentException if {@code permitsPerSecond} is negative or zero or {@code + * warmupPeriod} is negative + */ + @SuppressWarnings("GoodTime") // should accept a java.time.Duration + public static RateLimiter create(double permitsPerSecond, long warmupPeriod, TimeUnit unit) { + checkArgument(warmupPeriod >= 0, "warmupPeriod must not be negative: %s", warmupPeriod); + return create( + permitsPerSecond, warmupPeriod, unit, 3.0, SleepingStopwatch.createFromSystemTimer()); + } + + @VisibleForTesting + static RateLimiter create( + double permitsPerSecond, + long warmupPeriod, + TimeUnit unit, + double coldFactor, + SleepingStopwatch stopwatch) { + RateLimiter rateLimiter = new SmoothWarmingUp(stopwatch, warmupPeriod, unit, coldFactor); + rateLimiter.setRate(permitsPerSecond); + return rateLimiter; + } + + /** + * The underlying timer; used both to measure elapsed time and sleep as necessary. A separate + * object to facilitate testing. + */ + private final SleepingStopwatch stopwatch; + + // Can't be initialized in the constructor because mocks don't call the constructor. + private volatile Object mutexDoNotUseDirectly; + + private Object mutex() { + Object mutex = mutexDoNotUseDirectly; + if (mutex == null) { + synchronized (this) { + mutex = mutexDoNotUseDirectly; + if (mutex == null) { + mutexDoNotUseDirectly = mutex = new Object(); + } + } + } + return mutex; + } + + RateLimiter(SleepingStopwatch stopwatch) { + this.stopwatch = checkNotNull(stopwatch); + } + + /** + * Updates the stable rate of this {@code RateLimiter}, that is, the {@code permitsPerSecond} + * argument provided in the factory method that constructed the {@code RateLimiter}. Currently + * throttled threads will not be awakened as a result of this invocation, thus they do not + * observe the new rate; only subsequent requests will. + * + *

Note though that, since each request repays (by waiting, if necessary) the cost of the + * previous request, this means that the very next request after an invocation to {@code + * setRate} will not be affected by the new rate; it will pay the cost of the previous request, + * which is in terms of the previous rate. + * + *

The behavior of the {@code RateLimiter} is not modified in any other way, e.g. if the {@code + * RateLimiter} was configured with a warmup period of 20 seconds, it still has a warmup period of + * 20 seconds after this method invocation. + * + * @param permitsPerSecond the new stable rate of this {@code RateLimiter} + * @throws IllegalArgumentException if {@code permitsPerSecond} is negative or zero + */ + public final void setRate(double permitsPerSecond) { + checkArgument( + permitsPerSecond > 0.0 && !Double.isNaN(permitsPerSecond), "rate must be positive"); + synchronized (mutex()) { + doSetRate(permitsPerSecond, stopwatch.readMicros()); + } + } + + abstract void doSetRate(double permitsPerSecond, long nowMicros); + + /** + * Returns the stable rate (as {@code permits per seconds}) with which this {@code RateLimiter} is + * configured with. The initial value of this is the same as the {@code permitsPerSecond} argument + * passed in the factory method that produced this {@code RateLimiter}, and it is only updated + * after invocations to {@linkplain #setRate}. + */ + public final double getRate() { + synchronized (mutex()) { + return doGetRate(); + } + } + + abstract double doGetRate(); + + /** + * Acquires a single permit from this {@code RateLimiter}, blocking until the request can be + * granted. Tells the amount of time slept, if any. + * + *

This method is equivalent to {@code acquire(1)}. + * + * @return time spent sleeping to enforce rate, in seconds; 0.0 if not rate-limited + * @since 16.0 (present in 13.0 with {@code void} return type}) + */ + + public double acquire() { + return acquire(1); + } + + /** + * Acquires the given number of permits from this {@code RateLimiter}, blocking until the request + * can be granted. Tells the amount of time slept, if any. + * + * @param permits the number of permits to acquire + * @return time spent sleeping to enforce rate, in seconds; 0.0 if not rate-limited + * @throws IllegalArgumentException if the requested number of permits is negative or zero + * @since 16.0 (present in 13.0 with {@code void} return type}) + */ + + public double acquire(int permits) { + long microsToWait = reserve(permits); + stopwatch.sleepMicrosUninterruptibly(microsToWait); + return 1.0 * microsToWait / SECONDS.toMicros(1L); + } + + /** + * Reserves the given number of permits from this {@code RateLimiter} for future use, returning + * the number of microseconds until the reservation can be consumed. + * + * @return time in microseconds to wait until the resource can be acquired, never negative + */ + final long reserve(int permits) { + checkPermits(permits); + synchronized (mutex()) { + return reserveAndGetWaitLength(permits, stopwatch.readMicros()); + } + } + + /** + * Acquires a permit from this {@code RateLimiter} if it can be obtained without exceeding the + * specified {@code timeout}, or returns {@code false} immediately (without waiting) if the permit + * would not have been granted before the timeout expired. + * + *

This method is equivalent to {@code tryAcquire(1, timeout)}. + * + * @param timeout the maximum time to wait for the permit. Negative values are treated as zero. + * @return {@code true} if the permit was acquired, {@code false} otherwise + * @throws IllegalArgumentException if the requested number of permits is negative or zero + * @since 28.0 + */ + public boolean tryAcquire(Duration timeout) { + return tryAcquire(1, toNanosSaturated(timeout), TimeUnit.NANOSECONDS); + } + + /** + * Acquires a permit from this {@code RateLimiter} if it can be obtained without exceeding the + * specified {@code timeout}, or returns {@code false} immediately (without waiting) if the permit + * would not have been granted before the timeout expired. + * + *

This method is equivalent to {@code tryAcquire(1, timeout, unit)}. + * + * @param timeout the maximum time to wait for the permit. Negative values are treated as zero. + * @param unit the time unit of the timeout argument + * @return {@code true} if the permit was acquired, {@code false} otherwise + * @throws IllegalArgumentException if the requested number of permits is negative or zero + */ + @SuppressWarnings("GoodTime") // should accept a java.time.Duration + public boolean tryAcquire(long timeout, TimeUnit unit) { + return tryAcquire(1, timeout, unit); + } + + /** + * Acquires permits from this {@link RateLimiter} if it can be acquired immediately without delay. + * + *

This method is equivalent to {@code tryAcquire(permits, 0, anyUnit)}. + * + * @param permits the number of permits to acquire + * @return {@code true} if the permits were acquired, {@code false} otherwise + * @throws IllegalArgumentException if the requested number of permits is negative or zero + * @since 14.0 + */ + public boolean tryAcquire(int permits) { + return tryAcquire(permits, 0, MICROSECONDS); + } + + /** + * Acquires a permit from this {@link RateLimiter} if it can be acquired immediately without + * delay. + * + *

This method is equivalent to {@code tryAcquire(1)}. + * + * @return {@code true} if the permit was acquired, {@code false} otherwise + * @since 14.0 + */ + public boolean tryAcquire() { + return tryAcquire(1, 0, MICROSECONDS); + } + + /** + * Acquires the given number of permits from this {@code RateLimiter} if it can be obtained + * without exceeding the specified {@code timeout}, or returns {@code false} immediately (without + * waiting) if the permits would not have been granted before the timeout expired. + * + * @param permits the number of permits to acquire + * @param timeout the maximum time to wait for the permits. Negative values are treated as zero. + * @return {@code true} if the permits were acquired, {@code false} otherwise + * @throws IllegalArgumentException if the requested number of permits is negative or zero + * @since 28.0 + */ + public boolean tryAcquire(int permits, Duration timeout) { + return tryAcquire(permits, toNanosSaturated(timeout), TimeUnit.NANOSECONDS); + } + + /** + * Acquires the given number of permits from this {@code RateLimiter} if it can be obtained + * without exceeding the specified {@code timeout}, or returns {@code false} immediately (without + * waiting) if the permits would not have been granted before the timeout expired. + * + * @param permits the number of permits to acquire + * @param timeout the maximum time to wait for the permits. Negative values are treated as zero. + * @param unit the time unit of the timeout argument + * @return {@code true} if the permits were acquired, {@code false} otherwise + * @throws IllegalArgumentException if the requested number of permits is negative or zero + */ + @SuppressWarnings("GoodTime") // should accept a java.time.Duration + public boolean tryAcquire(int permits, long timeout, TimeUnit unit) { + long timeoutMicros = max(unit.toMicros(timeout), 0); + checkPermits(permits); + long microsToWait; + synchronized (mutex()) { + long nowMicros = stopwatch.readMicros(); + if (!canAcquire(nowMicros, timeoutMicros)) { + return false; + } else { + microsToWait = reserveAndGetWaitLength(permits, nowMicros); + } + } + stopwatch.sleepMicrosUninterruptibly(microsToWait); + return true; + } + + private boolean canAcquire(long nowMicros, long timeoutMicros) { + return queryEarliestAvailable(nowMicros) - timeoutMicros <= nowMicros; + } + + /** + * Reserves next ticket and returns the wait time that the caller must wait for. + * + * @return the required wait time, never negative + */ + final long reserveAndGetWaitLength(int permits, long nowMicros) { + long momentAvailable = reserveEarliestAvailable(permits, nowMicros); + return max(momentAvailable - nowMicros, 0); + } + + /** + * Returns the earliest time that permits are available (with one caveat). + * + * @return the time that permits are available, or, if permits are available immediately, an + * arbitrary past or present time + */ + abstract long queryEarliestAvailable(long nowMicros); + + /** + * Reserves the requested number of permits and returns the time that those permits can be used + * (with one caveat). + * + * @return the time that the permits may be used, or, if the permits may be used immediately, an + * arbitrary past or present time + */ + abstract long reserveEarliestAvailable(int permits, long nowMicros); + + @Override + public String toString() { + return String.format(Locale.ROOT, "RateLimiter[stableRate=%3.1fqps]", getRate()); + } + + abstract static class SleepingStopwatch { + /** Constructor for use by subclasses. */ + protected SleepingStopwatch() {} + + /* + * We always hold the mutex when calling this. TODO(cpovirk): Is that important? Perhaps we need + * to guarantee that each call to reserveEarliestAvailable, etc. sees a value >= the previous? + * Also, is it OK that we don't hold the mutex when sleeping? + */ + protected abstract long readMicros(); + + protected abstract void sleepMicrosUninterruptibly(long micros); + + public static SleepingStopwatch createFromSystemTimer() { + return new SleepingStopwatch() { + final Stopwatch stopwatch = Stopwatch.createStarted(); + + @Override + protected long readMicros() { + return stopwatch.elapsed(MICROSECONDS); + } + + @Override + protected void sleepMicrosUninterruptibly(long micros) { + if (micros > 0) { + Uninterruptibles.sleepUninterruptibly(micros, MICROSECONDS); + } + } + }; + } + } + + private static void checkPermits(int permits) { + checkArgument(permits > 0, "Requested permits (%s) must be positive", permits); + } +} diff --git a/src/main/java/com/google/common/util/concurrent/Runnables.java b/src/main/java/com/google/common/util/concurrent/Runnables.java new file mode 100644 index 0000000..e1ebd23 --- /dev/null +++ b/src/main/java/com/google/common/util/concurrent/Runnables.java @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2013 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.util.concurrent; + +import com.google.common.annotations.Beta; +import com.google.common.annotations.GwtCompatible; + +/** + * Static utility methods pertaining to the {@link Runnable} interface. + * + * @since 16.0 + */ +@Beta +@GwtCompatible +public final class Runnables { + + private static final Runnable EMPTY_RUNNABLE = + new Runnable() { + @Override + public void run() {} + }; + + /** Returns a {@link Runnable} instance that does nothing when run. */ + public static Runnable doNothing() { + return EMPTY_RUNNABLE; + } + + private Runnables() {} +} diff --git a/src/main/java/com/google/common/util/concurrent/SequentialExecutor.java b/src/main/java/com/google/common/util/concurrent/SequentialExecutor.java new file mode 100644 index 0000000..6b1d564 --- /dev/null +++ b/src/main/java/com/google/common/util/concurrent/SequentialExecutor.java @@ -0,0 +1,239 @@ +/* + * Copyright (C) 2008 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.util.concurrent; + +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.util.concurrent.SequentialExecutor.WorkerRunningState.IDLE; +import static com.google.common.util.concurrent.SequentialExecutor.WorkerRunningState.QUEUED; +import static com.google.common.util.concurrent.SequentialExecutor.WorkerRunningState.QUEUING; +import static com.google.common.util.concurrent.SequentialExecutor.WorkerRunningState.RUNNING; + +import com.google.common.annotations.GwtIncompatible; +import com.google.common.base.Preconditions; + + +import java.util.ArrayDeque; +import java.util.Deque; +import java.util.concurrent.Executor; +import java.util.concurrent.RejectedExecutionException; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * Executor ensuring that all Runnables submitted are executed in order, using the provided + * Executor, and sequentially such that no two will ever be running at the same time. + * + *

Tasks submitted to {@link #execute(Runnable)} are executed in FIFO order. + * + *

The execution of tasks is done by one thread as long as there are tasks left in the queue. + * When a task is {@linkplain Thread#interrupt interrupted}, execution of subsequent tasks + * continues. See {@link QueueWorker#workOnQueue} for details. + * + *

{@code RuntimeException}s thrown by tasks are simply logged and the executor keeps trucking. + * If an {@code Error} is thrown, the error will propagate and execution will stop until it is + * restarted by a call to {@link #execute}. + */ +@GwtIncompatible +final class SequentialExecutor implements Executor { + private static final Logger log = Logger.getLogger(SequentialExecutor.class.getName()); + + enum WorkerRunningState { + /** Runnable is not running and not queued for execution */ + IDLE, + /** Runnable is not running, but is being queued for execution */ + QUEUING, + /** runnable has been submitted but has not yet begun execution */ + QUEUED, + RUNNING, + } + + /** Underlying executor that all submitted Runnable objects are run on. */ + private final Executor executor; + + private final Deque queue = new ArrayDeque<>(); + + /** see {@link WorkerRunningState} */ + private WorkerRunningState workerRunningState = IDLE; + + /** + * This counter prevents an ABA issue where a thread may successfully schedule the worker, the + * worker runs and exhausts the queue, another thread enqueues a task and fails to schedule the + * worker, and then the first thread's call to delegate.execute() returns. Without this counter, + * it would observe the QUEUING state and set it to QUEUED, and the worker would never be + * scheduled again for future submissions. + */ + private long workerRunCount = 0; + + private final QueueWorker worker = new QueueWorker(); + + /** Use {@link MoreExecutors#newSequentialExecutor} */ + SequentialExecutor(Executor executor) { + this.executor = Preconditions.checkNotNull(executor); + } + + /** + * Adds a task to the queue and makes sure a worker thread is running. + * + *

If this method throws, e.g. a {@code RejectedExecutionException} from the delegate executor, + * execution of tasks will stop until a call to this method or to {@link #resume()} is made. + */ + @Override + public void execute(final Runnable task) { + checkNotNull(task); + final Runnable submittedTask; + final long oldRunCount; + synchronized (queue) { + // If the worker is already running (or execute() on the delegate returned successfully, and + // the worker has yet to start) then we don't need to start the worker. + if (workerRunningState == RUNNING || workerRunningState == QUEUED) { + queue.add(task); + return; + } + + oldRunCount = workerRunCount; + + // If the worker is not yet running, the delegate Executor might reject our attempt to start + // it. To preserve FIFO order and failure atomicity of rejected execution when the same + // Runnable is executed more than once, allocate a wrapper that we know is safe to remove by + // object identity. + // A data structure that returned a removal handle from add() would allow eliminating this + // allocation. + submittedTask = + new Runnable() { + @Override + public void run() { + task.run(); + } + }; + queue.add(submittedTask); + workerRunningState = QUEUING; + } + + try { + executor.execute(worker); + } catch (RuntimeException | Error t) { + synchronized (queue) { + boolean removed = + (workerRunningState == IDLE || workerRunningState == QUEUING) + && queue.removeLastOccurrence(submittedTask); + // If the delegate is directExecutor(), the submitted runnable could have thrown a REE. But + // that's handled by the log check that catches RuntimeExceptions in the queue worker. + if (!(t instanceof RejectedExecutionException) || removed) { + throw t; + } + } + return; + } + + /* + * This is an unsynchronized read! After the read, the function returns immediately or acquires + * the lock to check again. Since an IDLE state was observed inside the preceding synchronized + * block, and reference field assignment is atomic, this may save reacquiring the lock when + * another thread or the worker task has cleared the count and set the state. + * + *

When {@link #executor} is a directExecutor(), the value written to + * {@code workerRunningState} will be available synchronously, and behaviour will be + * deterministic. + */ + @SuppressWarnings("GuardedBy") + boolean alreadyMarkedQueued = workerRunningState != QUEUING; + if (alreadyMarkedQueued) { + return; + } + synchronized (queue) { + if (workerRunCount == oldRunCount && workerRunningState == QUEUING) { + workerRunningState = QUEUED; + } + } + } + + /** Worker that runs tasks from {@link #queue} until it is empty. */ + + private final class QueueWorker implements Runnable { + @Override + public void run() { + try { + workOnQueue(); + } catch (Error e) { + synchronized (queue) { + workerRunningState = IDLE; + } + throw e; + // The execution of a task has ended abnormally. + // We could have tasks left in the queue, so should perhaps try to restart a worker, + // but then the Error will get delayed if we are using a direct (same thread) executor. + } + } + + /** + * Continues executing tasks from {@link #queue} until it is empty. + * + *

The thread's interrupt bit is cleared before execution of each task. + * + *

If the Thread in use is interrupted before or during execution of the tasks in {@link + * #queue}, the Executor will complete its tasks, and then restore the interruption. This means + * that once the Thread returns to the Executor that this Executor composes, the interruption + * will still be present. If the composed Executor is an ExecutorService, it can respond to + * shutdown() by returning tasks queued on that Thread after {@link #worker} drains the queue. + */ + private void workOnQueue() { + boolean interruptedDuringTask = false; + boolean hasSetRunning = false; + try { + while (true) { + Runnable task; + synchronized (queue) { + // Choose whether this thread will run or not after acquiring the lock on the first + // iteration + if (!hasSetRunning) { + if (workerRunningState == RUNNING) { + // Don't want to have two workers pulling from the queue. + return; + } else { + // Increment the run counter to avoid the ABA problem of a submitter marking the + // thread as QUEUED after it already ran and exhausted the queue before returning + // from execute(). + workerRunCount++; + workerRunningState = RUNNING; + hasSetRunning = true; + } + } + task = queue.poll(); + if (task == null) { + workerRunningState = IDLE; + return; + } + } + // Remove the interrupt bit before each task. The interrupt is for the "current task" when + // it is sent, so subsequent tasks in the queue should not be caused to be interrupted + // by a previous one in the queue being interrupted. + interruptedDuringTask |= Thread.interrupted(); + try { + task.run(); + } catch (RuntimeException e) { + log.log(Level.SEVERE, "Exception while executing runnable " + task, e); + } + } + } finally { + // Ensure that if the thread was interrupted at all while processing the task queue, it + // is returned to the delegate Executor interrupted so that it may handle the + // interruption if it likes. + if (interruptedDuringTask) { + Thread.currentThread().interrupt(); + } + } + } + } +} diff --git a/src/main/java/com/google/common/util/concurrent/Service.java b/src/main/java/com/google/common/util/concurrent/Service.java new file mode 100644 index 0000000..1300485 --- /dev/null +++ b/src/main/java/com/google/common/util/concurrent/Service.java @@ -0,0 +1,323 @@ +/* + * Copyright (C) 2009 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.util.concurrent; + +import static com.google.common.util.concurrent.Internal.toNanosSaturated; + +import com.google.common.annotations.Beta; +import com.google.common.annotations.GwtIncompatible; + +import java.time.Duration; +import java.util.concurrent.Executor; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +/** + * An object with an operational state, plus asynchronous {@link #startAsync()} and {@link + * #stopAsync()} lifecycle methods to transition between states. Example services include + * webservers, RPC servers and timers. + * + *

The normal lifecycle of a service is: + * + *

    + *
  • {@linkplain State#NEW NEW} -> + *
  • {@linkplain State#STARTING STARTING} -> + *
  • {@linkplain State#RUNNING RUNNING} -> + *
  • {@linkplain State#STOPPING STOPPING} -> + *
  • {@linkplain State#TERMINATED TERMINATED} + *
+ * + *

There are deviations from this if there are failures or if {@link Service#stopAsync} is called + * before the {@link Service} reaches the {@linkplain State#RUNNING RUNNING} state. The set of legal + * transitions form a DAG, + * therefore every method of the listener will be called at most once. N.B. The {@link State#FAILED} + * and {@link State#TERMINATED} states are terminal states, once a service enters either of these + * states it cannot ever leave them. + * + *

Implementors of this interface are strongly encouraged to extend one of the abstract classes + * in this package which implement this interface and make the threading and state management + * easier. + * + * @author Jesse Wilson + * @author Luke Sandberg + * @since 9.0 (in 1.0 as {@code com.google.common.base.Service}) + */ +@Beta +@GwtIncompatible +public interface Service { + /** + * If the service state is {@link State#NEW}, this initiates service startup and returns + * immediately. A stopped service may not be restarted. + * + * @return this + * @throws IllegalStateException if the service is not {@link State#NEW} + * @since 15.0 + */ + + Service startAsync(); + + /** Returns {@code true} if this service is {@linkplain State#RUNNING running}. */ + boolean isRunning(); + + /** Returns the lifecycle state of the service. */ + State state(); + + /** + * If the service is {@linkplain State#STARTING starting} or {@linkplain State#RUNNING running}, + * this initiates service shutdown and returns immediately. If the service is {@linkplain + * State#NEW new}, it is {@linkplain State#TERMINATED terminated} without having been started nor + * stopped. If the service has already been stopped, this method returns immediately without + * taking action. + * + * @return this + * @since 15.0 + */ + + Service stopAsync(); + + /** + * Waits for the {@link Service} to reach the {@linkplain State#RUNNING running state}. + * + * @throws IllegalStateException if the service reaches a state from which it is not possible to + * enter the {@link State#RUNNING} state. e.g. if the {@code state} is {@code + * State#TERMINATED} when this method is called then this will throw an IllegalStateException. + * @since 15.0 + */ + void awaitRunning(); + + /** + * Waits for the {@link Service} to reach the {@linkplain State#RUNNING running state} for no more + * than the given time. + * + * @param timeout the maximum time to wait + * @throws TimeoutException if the service has not reached the given state within the deadline + * @throws IllegalStateException if the service reaches a state from which it is not possible to + * enter the {@link State#RUNNING RUNNING} state. e.g. if the {@code state} is {@code + * State#TERMINATED} when this method is called then this will throw an IllegalStateException. + * @since 28.0 + */ + default void awaitRunning(Duration timeout) throws TimeoutException { + awaitRunning(toNanosSaturated(timeout), TimeUnit.NANOSECONDS); + } + + /** + * Waits for the {@link Service} to reach the {@linkplain State#RUNNING running state} for no more + * than the given time. + * + * @param timeout the maximum time to wait + * @param unit the time unit of the timeout argument + * @throws TimeoutException if the service has not reached the given state within the deadline + * @throws IllegalStateException if the service reaches a state from which it is not possible to + * enter the {@link State#RUNNING RUNNING} state. e.g. if the {@code state} is {@code + * State#TERMINATED} when this method is called then this will throw an IllegalStateException. + * @since 15.0 + */ + @SuppressWarnings("GoodTime") // should accept a java.time.Duration + void awaitRunning(long timeout, TimeUnit unit) throws TimeoutException; + + /** + * Waits for the {@link Service} to reach the {@linkplain State#TERMINATED terminated state}. + * + * @throws IllegalStateException if the service {@linkplain State#FAILED fails}. + * @since 15.0 + */ + void awaitTerminated(); + + /** + * Waits for the {@link Service} to reach a terminal state (either {@link Service.State#TERMINATED + * terminated} or {@link Service.State#FAILED failed}) for no more than the given time. + * + * @param timeout the maximum time to wait + * @throws TimeoutException if the service has not reached the given state within the deadline + * @throws IllegalStateException if the service {@linkplain State#FAILED fails}. + * @since 28.0 + */ + default void awaitTerminated(Duration timeout) throws TimeoutException { + awaitTerminated(toNanosSaturated(timeout), TimeUnit.NANOSECONDS); + } + + /** + * Waits for the {@link Service} to reach a terminal state (either {@link Service.State#TERMINATED + * terminated} or {@link Service.State#FAILED failed}) for no more than the given time. + * + * @param timeout the maximum time to wait + * @param unit the time unit of the timeout argument + * @throws TimeoutException if the service has not reached the given state within the deadline + * @throws IllegalStateException if the service {@linkplain State#FAILED fails}. + * @since 15.0 + */ + @SuppressWarnings("GoodTime") // should accept a java.time.Duration + void awaitTerminated(long timeout, TimeUnit unit) throws TimeoutException; + + /** + * Returns the {@link Throwable} that caused this service to fail. + * + * @throws IllegalStateException if this service's state isn't {@linkplain State#FAILED FAILED}. + * @since 14.0 + */ + Throwable failureCause(); + + /** + * Registers a {@link Listener} to be {@linkplain Executor#execute executed} on the given + * executor. The listener will have the corresponding transition method called whenever the + * service changes state. The listener will not have previous state changes replayed, so it is + * suggested that listeners are added before the service starts. + * + *

{@code addListener} guarantees execution ordering across calls to a given listener but not + * across calls to multiple listeners. Specifically, a given listener will have its callbacks + * invoked in the same order as the underlying service enters those states. Additionally, at most + * one of the listener's callbacks will execute at once. However, multiple listeners' callbacks + * may execute concurrently, and listeners may execute in an order different from the one in which + * they were registered. + * + *

RuntimeExceptions thrown by a listener will be caught and logged. Any exception thrown + * during {@code Executor.execute} (e.g., a {@code RejectedExecutionException}) will be caught and + * logged. + * + * @param listener the listener to run when the service changes state is complete + * @param executor the executor in which the listeners callback methods will be run. For fast, + * lightweight listeners that would be safe to execute in any thread, consider {@link + * MoreExecutors#directExecutor}. + * @since 13.0 + */ + void addListener(Listener listener, Executor executor); + + /** + * The lifecycle states of a service. + * + *

The ordering of the {@link State} enum is defined such that if there is a state transition + * from {@code A -> B} then {@code A.compareTo(B) < 0}. N.B. The converse is not true, i.e. if + * {@code A.compareTo(B) < 0} then there is not guaranteed to be a valid state transition + * {@code A -> B}. + * + * @since 9.0 (in 1.0 as {@code com.google.common.base.Service.State}) + */ + @Beta // should come out of Beta when Service does + enum State { + /** A service in this state is inactive. It does minimal work and consumes minimal resources. */ + NEW { + @Override + boolean isTerminal() { + return false; + } + }, + + /** A service in this state is transitioning to {@link #RUNNING}. */ + STARTING { + @Override + boolean isTerminal() { + return false; + } + }, + + /** A service in this state is operational. */ + RUNNING { + @Override + boolean isTerminal() { + return false; + } + }, + + /** A service in this state is transitioning to {@link #TERMINATED}. */ + STOPPING { + @Override + boolean isTerminal() { + return false; + } + }, + + /** + * A service in this state has completed execution normally. It does minimal work and consumes + * minimal resources. + */ + TERMINATED { + @Override + boolean isTerminal() { + return true; + } + }, + + /** + * A service in this state has encountered a problem and may not be operational. It cannot be + * started nor stopped. + */ + FAILED { + @Override + boolean isTerminal() { + return true; + } + }; + + /** Returns true if this state is terminal. */ + abstract boolean isTerminal(); + } + + /** + * A listener for the various state changes that a {@link Service} goes through in its lifecycle. + * + *

All methods are no-ops by default, implementors should override the ones they care about. + * + * @author Luke Sandberg + * @since 15.0 (present as an interface in 13.0) + */ + @Beta // should come out of Beta when Service does + abstract class Listener { + /** + * Called when the service transitions from {@linkplain State#NEW NEW} to {@linkplain + * State#STARTING STARTING}. This occurs when {@link Service#startAsync} is called the first + * time. + */ + public void starting() {} + + /** + * Called when the service transitions from {@linkplain State#STARTING STARTING} to {@linkplain + * State#RUNNING RUNNING}. This occurs when a service has successfully started. + */ + public void running() {} + + /** + * Called when the service transitions to the {@linkplain State#STOPPING STOPPING} state. The + * only valid values for {@code from} are {@linkplain State#STARTING STARTING} or {@linkplain + * State#RUNNING RUNNING}. This occurs when {@link Service#stopAsync} is called. + * + * @param from The previous state that is being transitioned from. + */ + public void stopping(State from) {} + + /** + * Called when the service transitions to the {@linkplain State#TERMINATED TERMINATED} state. + * The {@linkplain State#TERMINATED TERMINATED} state is a terminal state in the transition + * diagram. Therefore, if this method is called, no other methods will be called on the {@link + * Listener}. + * + * @param from The previous state that is being transitioned from. Failure can occur in any + * state with the exception of {@linkplain State#FAILED FAILED} and {@linkplain + * State#TERMINATED TERMINATED}. + */ + public void terminated(State from) {} + + /** + * Called when the service transitions to the {@linkplain State#FAILED FAILED} state. The + * {@linkplain State#FAILED FAILED} state is a terminal state in the transition diagram. + * Therefore, if this method is called, no other methods will be called on the {@link Listener}. + * + * @param from The previous state that is being transitioned from. Failure can occur in any + * state with the exception of {@linkplain State#NEW NEW} or {@linkplain State#TERMINATED + * TERMINATED}. + * @param failure The exception that caused the failure. + */ + public void failed(State from, Throwable failure) {} + } +} diff --git a/src/main/java/com/google/common/util/concurrent/ServiceManager.java b/src/main/java/com/google/common/util/concurrent/ServiceManager.java new file mode 100644 index 0000000..65536ae --- /dev/null +++ b/src/main/java/com/google/common/util/concurrent/ServiceManager.java @@ -0,0 +1,886 @@ +/* + * Copyright (C) 2012 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.util.concurrent; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.base.Preconditions.checkState; +import static com.google.common.base.Predicates.equalTo; +import static com.google.common.base.Predicates.in; +import static com.google.common.base.Predicates.instanceOf; +import static com.google.common.base.Predicates.not; +import static com.google.common.util.concurrent.Internal.toNanosSaturated; +import static com.google.common.util.concurrent.MoreExecutors.directExecutor; +import static com.google.common.util.concurrent.Service.State.FAILED; +import static com.google.common.util.concurrent.Service.State.NEW; +import static com.google.common.util.concurrent.Service.State.RUNNING; +import static com.google.common.util.concurrent.Service.State.STARTING; +import static com.google.common.util.concurrent.Service.State.STOPPING; +import static com.google.common.util.concurrent.Service.State.TERMINATED; +import static java.util.concurrent.TimeUnit.MILLISECONDS; + +import com.google.common.annotations.Beta; +import com.google.common.annotations.GwtIncompatible; +import com.google.common.base.Function; +import com.google.common.base.MoreObjects; +import com.google.common.base.Stopwatch; +import com.google.common.collect.Collections2; +import com.google.common.collect.ImmutableCollection; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableMultimap; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.ImmutableSetMultimap; +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; +import com.google.common.collect.MultimapBuilder; +import com.google.common.collect.Multimaps; +import com.google.common.collect.Multiset; +import com.google.common.collect.Ordering; +import com.google.common.collect.SetMultimap; +import com.google.common.util.concurrent.Service.State; + + + +import java.lang.ref.WeakReference; +import java.time.Duration; +import java.util.Collections; +import java.util.EnumSet; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.concurrent.Executor; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * A manager for monitoring and controlling a set of {@linkplain Service services}. This class + * provides methods for {@linkplain #startAsync() starting}, {@linkplain #stopAsync() stopping} and + * {@linkplain #servicesByState inspecting} a collection of {@linkplain Service services}. + * Additionally, users can monitor state transitions with the {@linkplain Listener listener} + * mechanism. + * + *

While it is recommended that service lifecycles be managed via this class, state transitions + * initiated via other mechanisms do not impact the correctness of its methods. For example, if the + * services are started by some mechanism besides {@link #startAsync}, the listeners will be invoked + * when appropriate and {@link #awaitHealthy} will still work as expected. + * + *

Here is a simple example of how to use a {@code ServiceManager} to start a server. + * + *

{@code
+ * class Server {
+ *   public static void main(String[] args) {
+ *     Set services = ...;
+ *     ServiceManager manager = new ServiceManager(services);
+ *     manager.addListener(new Listener() {
+ *         public void stopped() {}
+ *         public void healthy() {
+ *           // Services have been initialized and are healthy, start accepting requests...
+ *         }
+ *         public void failure(Service service) {
+ *           // Something failed, at this point we could log it, notify a load balancer, or take
+ *           // some other action.  For now we will just exit.
+ *           System.exit(1);
+ *         }
+ *       },
+ *       MoreExecutors.directExecutor());
+ *
+ *     Runtime.getRuntime().addShutdownHook(new Thread() {
+ *       public void run() {
+ *         // Give the services 5 seconds to stop to ensure that we are responsive to shutdown
+ *         // requests.
+ *         try {
+ *           manager.stopAsync().awaitStopped(5, TimeUnit.SECONDS);
+ *         } catch (TimeoutException timeout) {
+ *           // stopping timed out
+ *         }
+ *       }
+ *     });
+ *     manager.startAsync();  // start all the services asynchronously
+ *   }
+ * }
+ * }
+ * + *

This class uses the ServiceManager's methods to start all of its services, to respond to + * service failure and to ensure that when the JVM is shutting down all the services are stopped. + * + * @author Luke Sandberg + * @since 14.0 + */ +@Beta +@GwtIncompatible +public final class ServiceManager { + private static final Logger logger = Logger.getLogger(ServiceManager.class.getName()); + private static final ListenerCallQueue.Event HEALTHY_EVENT = + new ListenerCallQueue.Event() { + @Override + public void call(Listener listener) { + listener.healthy(); + } + + @Override + public String toString() { + return "healthy()"; + } + }; + private static final ListenerCallQueue.Event STOPPED_EVENT = + new ListenerCallQueue.Event() { + @Override + public void call(Listener listener) { + listener.stopped(); + } + + @Override + public String toString() { + return "stopped()"; + } + }; + + /** + * A listener for the aggregate state changes of the services that are under management. Users + * that need to listen to more fine-grained events (such as when each particular {@linkplain + * Service service} starts, or terminates), should attach {@linkplain Service.Listener service + * listeners} to each individual service. + * + * @author Luke Sandberg + * @since 15.0 (present as an interface in 14.0) + */ + @Beta // Should come out of Beta when ServiceManager does + public abstract static class Listener { + /** + * Called when the service initially becomes healthy. + * + *

This will be called at most once after all the services have entered the {@linkplain + * State#RUNNING running} state. If any services fail during start up or {@linkplain + * State#FAILED fail}/{@linkplain State#TERMINATED terminate} before all other services have + * started {@linkplain State#RUNNING running} then this method will not be called. + */ + public void healthy() {} + + /** + * Called when the all of the component services have reached a terminal state, either + * {@linkplain State#TERMINATED terminated} or {@linkplain State#FAILED failed}. + */ + public void stopped() {} + + /** + * Called when a component service has {@linkplain State#FAILED failed}. + * + * @param service The service that failed. + */ + public void failure(Service service) {} + } + + /** + * An encapsulation of all of the state that is accessed by the {@linkplain ServiceListener + * service listeners}. This is extracted into its own object so that {@link ServiceListener} could + * be made {@code static} and its instances can be safely constructed and added in the {@link + * ServiceManager} constructor without having to close over the partially constructed {@link + * ServiceManager} instance (i.e. avoid leaking a pointer to {@code this}). + */ + private final ServiceManagerState state; + + private final ImmutableList services; + + /** + * Constructs a new instance for managing the given services. + * + * @param services The services to manage + * @throws IllegalArgumentException if not all services are {@linkplain State#NEW new} or if there + * are any duplicate services. + */ + public ServiceManager(Iterable services) { + ImmutableList copy = ImmutableList.copyOf(services); + if (copy.isEmpty()) { + // Having no services causes the manager to behave strangely. Notably, listeners are never + // fired. To avoid this we substitute a placeholder service. + logger.log( + Level.WARNING, + "ServiceManager configured with no services. Is your application configured properly?", + new EmptyServiceManagerWarning()); + copy = ImmutableList.of(new NoOpService()); + } + this.state = new ServiceManagerState(copy); + this.services = copy; + WeakReference stateReference = new WeakReference<>(state); + for (Service service : copy) { + service.addListener(new ServiceListener(service, stateReference), directExecutor()); + // We check the state after adding the listener as a way to ensure that our listener was added + // to a NEW service. + checkArgument(service.state() == NEW, "Can only manage NEW services, %s", service); + } + // We have installed all of our listeners and after this point any state transition should be + // correct. + this.state.markReady(); + } + + /** + * Registers a {@link Listener} to be {@linkplain Executor#execute executed} on the given + * executor. The listener will not have previous state changes replayed, so it is suggested that + * listeners are added before any of the managed services are {@linkplain Service#startAsync + * started}. + * + *

{@code addListener} guarantees execution ordering across calls to a given listener but not + * across calls to multiple listeners. Specifically, a given listener will have its callbacks + * invoked in the same order as the underlying service enters those states. Additionally, at most + * one of the listener's callbacks will execute at once. However, multiple listeners' callbacks + * may execute concurrently, and listeners may execute in an order different from the one in which + * they were registered. + * + *

RuntimeExceptions thrown by a listener will be caught and logged. Any exception thrown + * during {@code Executor.execute} (e.g., a {@code RejectedExecutionException}) will be caught and + * logged. + * + *

For fast, lightweight listeners that would be safe to execute in any thread, consider + * calling {@link #addListener(Listener)}. + * + * @param listener the listener to run when the manager changes state + * @param executor the executor in which the listeners callback methods will be run. + */ + public void addListener(Listener listener, Executor executor) { + state.addListener(listener, executor); + } + + /** + * Registers a {@link Listener} to be run when this {@link ServiceManager} changes state. The + * listener will not have previous state changes replayed, so it is suggested that listeners are + * added before any of the managed services are {@linkplain Service#startAsync started}. + * + *

{@code addListener} guarantees execution ordering across calls to a given listener but not + * across calls to multiple listeners. Specifically, a given listener will have its callbacks + * invoked in the same order as the underlying service enters those states. Additionally, at most + * one of the listener's callbacks will execute at once. However, multiple listeners' callbacks + * may execute concurrently, and listeners may execute in an order different from the one in which + * they were registered. + * + *

RuntimeExceptions thrown by a listener will be caught and logged. + * + * @param listener the listener to run when the manager changes state + */ + public void addListener(Listener listener) { + state.addListener(listener, directExecutor()); + } + + /** + * Initiates service {@linkplain Service#startAsync startup} on all the services being managed. It + * is only valid to call this method if all of the services are {@linkplain State#NEW new}. + * + * @return this + * @throws IllegalStateException if any of the Services are not {@link State#NEW new} when the + * method is called. + */ + + public ServiceManager startAsync() { + for (Service service : services) { + State state = service.state(); + checkState(state == NEW, "Service %s is %s, cannot start it.", service, state); + } + for (Service service : services) { + try { + state.tryStartTiming(service); + service.startAsync(); + } catch (IllegalStateException e) { + // This can happen if the service has already been started or stopped (e.g. by another + // service or listener). Our contract says it is safe to call this method if + // all services were NEW when it was called, and this has already been verified above, so we + // don't propagate the exception. + logger.log(Level.WARNING, "Unable to start Service " + service, e); + } + } + return this; + } + + /** + * Waits for the {@link ServiceManager} to become {@linkplain #isHealthy() healthy}. The manager + * will become healthy after all the component services have reached the {@linkplain State#RUNNING + * running} state. + * + * @throws IllegalStateException if the service manager reaches a state from which it cannot + * become {@linkplain #isHealthy() healthy}. + */ + public void awaitHealthy() { + state.awaitHealthy(); + } + + /** + * Waits for the {@link ServiceManager} to become {@linkplain #isHealthy() healthy} for no more + * than the given time. The manager will become healthy after all the component services have + * reached the {@linkplain State#RUNNING running} state. + * + * @param timeout the maximum time to wait + * @throws TimeoutException if not all of the services have finished starting within the deadline + * @throws IllegalStateException if the service manager reaches a state from which it cannot + * become {@linkplain #isHealthy() healthy}. + * @since 28.0 + */ + public void awaitHealthy(Duration timeout) throws TimeoutException { + awaitHealthy(toNanosSaturated(timeout), TimeUnit.NANOSECONDS); + } + + /** + * Waits for the {@link ServiceManager} to become {@linkplain #isHealthy() healthy} for no more + * than the given time. The manager will become healthy after all the component services have + * reached the {@linkplain State#RUNNING running} state. + * + * @param timeout the maximum time to wait + * @param unit the time unit of the timeout argument + * @throws TimeoutException if not all of the services have finished starting within the deadline + * @throws IllegalStateException if the service manager reaches a state from which it cannot + * become {@linkplain #isHealthy() healthy}. + */ + @SuppressWarnings("GoodTime") // should accept a java.time.Duration + public void awaitHealthy(long timeout, TimeUnit unit) throws TimeoutException { + state.awaitHealthy(timeout, unit); + } + + /** + * Initiates service {@linkplain Service#stopAsync shutdown} if necessary on all the services + * being managed. + * + * @return this + */ + + public ServiceManager stopAsync() { + for (Service service : services) { + service.stopAsync(); + } + return this; + } + + /** + * Waits for the all the services to reach a terminal state. After this method returns all + * services will either be {@linkplain Service.State#TERMINATED terminated} or {@linkplain + * Service.State#FAILED failed}. + */ + public void awaitStopped() { + state.awaitStopped(); + } + + /** + * Waits for the all the services to reach a terminal state for no more than the given time. After + * this method returns all services will either be {@linkplain Service.State#TERMINATED + * terminated} or {@linkplain Service.State#FAILED failed}. + * + * @param timeout the maximum time to wait + * @throws TimeoutException if not all of the services have stopped within the deadline + * @since 28.0 + */ + public void awaitStopped(Duration timeout) throws TimeoutException { + awaitStopped(toNanosSaturated(timeout), TimeUnit.NANOSECONDS); + } + + /** + * Waits for the all the services to reach a terminal state for no more than the given time. After + * this method returns all services will either be {@linkplain Service.State#TERMINATED + * terminated} or {@linkplain Service.State#FAILED failed}. + * + * @param timeout the maximum time to wait + * @param unit the time unit of the timeout argument + * @throws TimeoutException if not all of the services have stopped within the deadline + */ + @SuppressWarnings("GoodTime") // should accept a java.time.Duration + public void awaitStopped(long timeout, TimeUnit unit) throws TimeoutException { + state.awaitStopped(timeout, unit); + } + + /** + * Returns true if all services are currently in the {@linkplain State#RUNNING running} state. + * + *

Users who want more detailed information should use the {@link #servicesByState} method to + * get detailed information about which services are not running. + */ + public boolean isHealthy() { + for (Service service : services) { + if (!service.isRunning()) { + return false; + } + } + return true; + } + + /** + * Provides a snapshot of the current state of all the services under management. + * + *

N.B. This snapshot is guaranteed to be consistent, i.e. the set of states returned will + * correspond to a point in time view of the services. + */ + public ImmutableMultimap servicesByState() { + return state.servicesByState(); + } + + /** + * Returns the service load times. This value will only return startup times for services that + * have finished starting. + * + * @return Map of services and their corresponding startup time in millis, the map entries will be + * ordered by startup time. + */ + public ImmutableMap startupTimes() { + return state.startupTimes(); + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(ServiceManager.class) + .add("services", Collections2.filter(services, not(instanceOf(NoOpService.class)))) + .toString(); + } + + /** + * An encapsulation of all the mutable state of the {@link ServiceManager} that needs to be + * accessed by instances of {@link ServiceListener}. + */ + private static final class ServiceManagerState { + final Monitor monitor = new Monitor(); + + final SetMultimap servicesByState = + MultimapBuilder.enumKeys(State.class).linkedHashSetValues().build(); + + final Multiset states = servicesByState.keys(); + + final Map startupTimers = Maps.newIdentityHashMap(); + + /** + * These two booleans are used to mark the state as ready to start. + * + *

{@link #ready}: is set by {@link #markReady} to indicate that all listeners have been + * correctly installed + * + *

{@link #transitioned}: is set by {@link #transitionService} to indicate that some + * transition has been performed. + * + *

Together, they allow us to enforce that all services have their listeners installed prior + * to any service performing a transition, then we can fail in the ServiceManager constructor + * rather than in a Service.Listener callback. + */ + boolean ready; + + boolean transitioned; + + final int numberOfServices; + + /** + * Controls how long to wait for all the services to either become healthy or reach a state from + * which it is guaranteed that it can never become healthy. + */ + final Monitor.Guard awaitHealthGuard = new AwaitHealthGuard(); + + + final class AwaitHealthGuard extends Monitor.Guard { + AwaitHealthGuard() { + super(ServiceManagerState.this.monitor); + } + + @Override + public boolean isSatisfied() { + // All services have started or some service has terminated/failed. + return states.count(RUNNING) == numberOfServices + || states.contains(STOPPING) + || states.contains(TERMINATED) + || states.contains(FAILED); + } + } + + /** Controls how long to wait for all services to reach a terminal state. */ + final Monitor.Guard stoppedGuard = new StoppedGuard(); + + + final class StoppedGuard extends Monitor.Guard { + StoppedGuard() { + super(ServiceManagerState.this.monitor); + } + + @Override + public boolean isSatisfied() { + return states.count(TERMINATED) + states.count(FAILED) == numberOfServices; + } + } + + /** The listeners to notify during a state transition. */ + final ListenerCallQueue listeners = new ListenerCallQueue<>(); + + /** + * It is implicitly assumed that all the services are NEW and that they will all remain NEW + * until all the Listeners are installed and {@link #markReady()} is called. It is our caller's + * responsibility to only call {@link #markReady()} if all services were new at the time this + * method was called and when all the listeners were installed. + */ + ServiceManagerState(ImmutableCollection services) { + this.numberOfServices = services.size(); + servicesByState.putAll(NEW, services); + } + + /** + * Attempts to start the timer immediately prior to the service being started via {@link + * Service#startAsync()}. + */ + void tryStartTiming(Service service) { + monitor.enter(); + try { + Stopwatch stopwatch = startupTimers.get(service); + if (stopwatch == null) { + startupTimers.put(service, Stopwatch.createStarted()); + } + } finally { + monitor.leave(); + } + } + + /** + * Marks the {@link State} as ready to receive transitions. Returns true if no transitions have + * been observed yet. + */ + void markReady() { + monitor.enter(); + try { + if (!transitioned) { + // nothing has transitioned since construction, good. + ready = true; + } else { + // This should be an extremely rare race condition. + List servicesInBadStates = Lists.newArrayList(); + for (Service service : servicesByState().values()) { + if (service.state() != NEW) { + servicesInBadStates.add(service); + } + } + throw new IllegalArgumentException( + "Services started transitioning asynchronously before " + + "the ServiceManager was constructed: " + + servicesInBadStates); + } + } finally { + monitor.leave(); + } + } + + void addListener(Listener listener, Executor executor) { + listeners.addListener(listener, executor); + } + + void awaitHealthy() { + monitor.enterWhenUninterruptibly(awaitHealthGuard); + try { + checkHealthy(); + } finally { + monitor.leave(); + } + } + + void awaitHealthy(long timeout, TimeUnit unit) throws TimeoutException { + monitor.enter(); + try { + if (!monitor.waitForUninterruptibly(awaitHealthGuard, timeout, unit)) { + throw new TimeoutException( + "Timeout waiting for the services to become healthy. The " + + "following services have not started: " + + Multimaps.filterKeys(servicesByState, in(ImmutableSet.of(NEW, STARTING)))); + } + checkHealthy(); + } finally { + monitor.leave(); + } + } + + void awaitStopped() { + monitor.enterWhenUninterruptibly(stoppedGuard); + monitor.leave(); + } + + void awaitStopped(long timeout, TimeUnit unit) throws TimeoutException { + monitor.enter(); + try { + if (!monitor.waitForUninterruptibly(stoppedGuard, timeout, unit)) { + throw new TimeoutException( + "Timeout waiting for the services to stop. The following " + + "services have not stopped: " + + Multimaps.filterKeys(servicesByState, not(in(EnumSet.of(TERMINATED, FAILED))))); + } + } finally { + monitor.leave(); + } + } + + ImmutableMultimap servicesByState() { + ImmutableSetMultimap.Builder builder = ImmutableSetMultimap.builder(); + monitor.enter(); + try { + for (Entry entry : servicesByState.entries()) { + if (!(entry.getValue() instanceof NoOpService)) { + builder.put(entry); + } + } + } finally { + monitor.leave(); + } + return builder.build(); + } + + ImmutableMap startupTimes() { + List> loadTimes; + monitor.enter(); + try { + loadTimes = Lists.newArrayListWithCapacity(startupTimers.size()); + // N.B. There will only be an entry in the map if the service has started + for (Entry entry : startupTimers.entrySet()) { + Service service = entry.getKey(); + Stopwatch stopWatch = entry.getValue(); + if (!stopWatch.isRunning() && !(service instanceof NoOpService)) { + loadTimes.add(Maps.immutableEntry(service, stopWatch.elapsed(MILLISECONDS))); + } + } + } finally { + monitor.leave(); + } + Collections.sort( + loadTimes, + Ordering.natural() + .onResultOf( + new Function, Long>() { + @Override + public Long apply(Entry input) { + return input.getValue(); + } + })); + return ImmutableMap.copyOf(loadTimes); + } + + /** + * Updates the state with the given service transition. + * + *

This method performs the main logic of ServiceManager in the following steps. + * + *

    + *
  1. Update the {@link #servicesByState()} + *
  2. Update the {@link #startupTimers} + *
  3. Based on the new state queue listeners to run + *
  4. Run the listeners (outside of the lock) + *
+ */ + void transitionService(final Service service, State from, State to) { + checkNotNull(service); + checkArgument(from != to); + monitor.enter(); + try { + transitioned = true; + if (!ready) { + return; + } + // Update state. + checkState( + servicesByState.remove(from, service), + "Service %s not at the expected location in the state map %s", + service, + from); + checkState( + servicesByState.put(to, service), + "Service %s in the state map unexpectedly at %s", + service, + to); + // Update the timer + Stopwatch stopwatch = startupTimers.get(service); + if (stopwatch == null) { + // This means the service was started by some means other than ServiceManager.startAsync + stopwatch = Stopwatch.createStarted(); + startupTimers.put(service, stopwatch); + } + if (to.compareTo(RUNNING) >= 0 && stopwatch.isRunning()) { + // N.B. if we miss the STARTING event then we may never record a startup time. + stopwatch.stop(); + if (!(service instanceof NoOpService)) { + logger.log(Level.FINE, "Started {0} in {1}.", new Object[] {service, stopwatch}); + } + } + // Queue our listeners + + // Did a service fail? + if (to == FAILED) { + enqueueFailedEvent(service); + } + + if (states.count(RUNNING) == numberOfServices) { + // This means that the manager is currently healthy. N.B. If other threads call isHealthy + // they are not guaranteed to get 'true', because any service could fail right now. + enqueueHealthyEvent(); + } else if (states.count(TERMINATED) + states.count(FAILED) == numberOfServices) { + enqueueStoppedEvent(); + } + } finally { + monitor.leave(); + // Run our executors outside of the lock + dispatchListenerEvents(); + } + } + + void enqueueStoppedEvent() { + listeners.enqueue(STOPPED_EVENT); + } + + void enqueueHealthyEvent() { + listeners.enqueue(HEALTHY_EVENT); + } + + void enqueueFailedEvent(final Service service) { + listeners.enqueue( + new ListenerCallQueue.Event() { + @Override + public void call(Listener listener) { + listener.failure(service); + } + + @Override + public String toString() { + return "failed({service=" + service + "})"; + } + }); + } + + /** Attempts to execute all the listeners in {@link #listeners}. */ + void dispatchListenerEvents() { + checkState( + !monitor.isOccupiedByCurrentThread(), + "It is incorrect to execute listeners with the monitor held."); + listeners.dispatch(); + } + + void checkHealthy() { + if (states.count(RUNNING) != numberOfServices) { + IllegalStateException exception = + new IllegalStateException( + "Expected to be healthy after starting. The following services are not running: " + + Multimaps.filterKeys(servicesByState, not(equalTo(RUNNING)))); + for (Service service : servicesByState.get(State.FAILED)) { + exception.addSuppressed(new FailedService(service)); + } + throw exception; + } + } + } + + /** + * A {@link Service} that wraps another service and times how long it takes for it to start and + * also calls the {@link ServiceManagerState#transitionService(Service, State, State)}, to record + * the state transitions. + */ + private static final class ServiceListener extends Service.Listener { + final Service service; + // We store the state in a weak reference to ensure that if something went wrong while + // constructing the ServiceManager we don't pointlessly keep updating the state. + final WeakReference state; + + ServiceListener(Service service, WeakReference state) { + this.service = service; + this.state = state; + } + + @Override + public void starting() { + ServiceManagerState state = this.state.get(); + if (state != null) { + state.transitionService(service, NEW, STARTING); + if (!(service instanceof NoOpService)) { + logger.log(Level.FINE, "Starting {0}.", service); + } + } + } + + @Override + public void running() { + ServiceManagerState state = this.state.get(); + if (state != null) { + state.transitionService(service, STARTING, RUNNING); + } + } + + @Override + public void stopping(State from) { + ServiceManagerState state = this.state.get(); + if (state != null) { + state.transitionService(service, from, STOPPING); + } + } + + @Override + public void terminated(State from) { + ServiceManagerState state = this.state.get(); + if (state != null) { + if (!(service instanceof NoOpService)) { + logger.log( + Level.FINE, + "Service {0} has terminated. Previous state was: {1}", + new Object[] {service, from}); + } + state.transitionService(service, from, TERMINATED); + } + } + + @Override + public void failed(State from, Throwable failure) { + ServiceManagerState state = this.state.get(); + if (state != null) { + // Log before the transition, so that if the process exits in response to server failure, + // there is a higher likelihood that the cause will be in the logs. + boolean log = !(service instanceof NoOpService); + /* + * We have already exposed startup exceptions to the user in the form of suppressed + * exceptions. We don't need to log those exceptions again. + */ + log &= from != State.STARTING; + if (log) { + logger.log( + Level.SEVERE, + "Service " + service + " has failed in the " + from + " state.", + failure); + } + state.transitionService(service, from, FAILED); + } + } + } + + /** + * A {@link Service} instance that does nothing. This is only useful as a placeholder to ensure + * that the {@link ServiceManager} functions properly even when it is managing no services. + * + *

The use of this class is considered an implementation detail of ServiceManager and as such + * it is excluded from {@link #servicesByState}, {@link #startupTimes}, {@link #toString} and all + * logging statements. + */ + private static final class NoOpService extends AbstractService { + @Override + protected void doStart() { + notifyStarted(); + } + + @Override + protected void doStop() { + notifyStopped(); + } + } + + /** This is never thrown but only used for logging. */ + private static final class EmptyServiceManagerWarning extends Throwable {} + + private static final class FailedService extends Throwable { + FailedService(Service service) { + super( + service.toString(), + service.failureCause(), + false /* don't enable suppression */, + false /* don't calculate a stack trace. */); + } + } +} diff --git a/src/main/java/com/google/common/util/concurrent/SettableFuture.java b/src/main/java/com/google/common/util/concurrent/SettableFuture.java new file mode 100644 index 0000000..b64322d --- /dev/null +++ b/src/main/java/com/google/common/util/concurrent/SettableFuture.java @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2009 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.util.concurrent; + +import com.google.common.annotations.GwtCompatible; + + + +/** + * A {@link ListenableFuture} whose result can be set by a {@link #set(Object)}, {@link + * #setException(Throwable)} or {@link #setFuture(ListenableFuture)} call. It can also, like any + * other {@code Future}, be {@linkplain #cancel cancelled}. + * + *

{@code SettableFuture} is the recommended {@code ListenableFuture} implementation when your + * task cannot be implemented with {@link ListeningExecutorService}, the various {@link Futures} + * utility methods, or {@link ListenableFutureTask}. Those APIs have less opportunity for developer + * error. If your needs are more complex than {@code SettableFuture} supports, use {@link + * AbstractFuture}, which offers an extensible version of the API. + * + * @author Sven Mawson + * @since 9.0 (in 1.0 as {@code ValueFuture}) + */ +@GwtCompatible +public final class SettableFuture extends AbstractFuture.TrustedFuture { + /** + * Creates a new {@code SettableFuture} that can be completed or cancelled by a later method call. + */ + public static SettableFuture create() { + return new SettableFuture(); + } + + + @Override + public boolean set(V value) { + return super.set(value); + } + + + @Override + public boolean setException(Throwable throwable) { + return super.setException(throwable); + } + + + @Override + public boolean setFuture(ListenableFuture future) { + return super.setFuture(future); + } + + private SettableFuture() {} +} diff --git a/src/main/java/com/google/common/util/concurrent/SimpleTimeLimiter.java b/src/main/java/com/google/common/util/concurrent/SimpleTimeLimiter.java new file mode 100644 index 0000000..7b85f66 --- /dev/null +++ b/src/main/java/com/google/common/util/concurrent/SimpleTimeLimiter.java @@ -0,0 +1,288 @@ +/* + * Copyright (C) 2006 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.util.concurrent; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.annotations.Beta; +import com.google.common.annotations.GwtIncompatible; +import com.google.common.collect.ObjectArrays; +import com.google.common.collect.Sets; + +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; +import java.util.Set; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +/** + * A TimeLimiter that runs method calls in the background using an {@link ExecutorService}. If the + * time limit expires for a given method call, the thread running the call will be interrupted. + * + * @author Kevin Bourrillion + * @author Jens Nyman + * @since 1.0 + */ +@Beta +@GwtIncompatible +public final class SimpleTimeLimiter implements TimeLimiter { + + private final ExecutorService executor; + + private SimpleTimeLimiter(ExecutorService executor) { + this.executor = checkNotNull(executor); + } + + /** + * Creates a TimeLimiter instance using the given executor service to execute method calls. + * + *

Warning: using a bounded executor may be counterproductive! If the thread pool fills + * up, any time callers spend waiting for a thread may count toward their time limit, and in this + * case the call may even time out before the target method is ever invoked. + * + * @param executor the ExecutorService that will execute the method calls on the target objects; + * for example, a {@link Executors#newCachedThreadPool()}. + * @since 22.0 + */ + public static SimpleTimeLimiter create(ExecutorService executor) { + return new SimpleTimeLimiter(executor); + } + + @Override + public T newProxy( + final T target, + Class interfaceType, + final long timeoutDuration, + final TimeUnit timeoutUnit) { + checkNotNull(target); + checkNotNull(interfaceType); + checkNotNull(timeoutUnit); + checkPositiveTimeout(timeoutDuration); + checkArgument(interfaceType.isInterface(), "interfaceType must be an interface type"); + + final Set interruptibleMethods = findInterruptibleMethods(interfaceType); + + InvocationHandler handler = + new InvocationHandler() { + @Override + public Object invoke(Object obj, final Method method, final Object[] args) + throws Throwable { + Callable callable = + new Callable() { + @Override + public Object call() throws Exception { + try { + return method.invoke(target, args); + } catch (InvocationTargetException e) { + throw throwCause(e, false /* combineStackTraces */); + } + } + }; + return callWithTimeout( + callable, timeoutDuration, timeoutUnit, interruptibleMethods.contains(method)); + } + }; + return newProxy(interfaceType, handler); + } + + // TODO: replace with version in common.reflect if and when it's open-sourced + private static T newProxy(Class interfaceType, InvocationHandler handler) { + Object object = + Proxy.newProxyInstance( + interfaceType.getClassLoader(), new Class[] {interfaceType}, handler); + return interfaceType.cast(object); + } + + private T callWithTimeout( + Callable callable, long timeoutDuration, TimeUnit timeoutUnit, boolean amInterruptible) + throws Exception { + checkNotNull(callable); + checkNotNull(timeoutUnit); + checkPositiveTimeout(timeoutDuration); + + Future future = executor.submit(callable); + + try { + if (amInterruptible) { + try { + return future.get(timeoutDuration, timeoutUnit); + } catch (InterruptedException e) { + future.cancel(true); + throw e; + } + } else { + return Uninterruptibles.getUninterruptibly(future, timeoutDuration, timeoutUnit); + } + } catch (ExecutionException e) { + throw throwCause(e, true /* combineStackTraces */); + } catch (TimeoutException e) { + future.cancel(true); + throw new UncheckedTimeoutException(e); + } + } + + + @Override + public T callWithTimeout(Callable callable, long timeoutDuration, TimeUnit timeoutUnit) + throws TimeoutException, InterruptedException, ExecutionException { + checkNotNull(callable); + checkNotNull(timeoutUnit); + checkPositiveTimeout(timeoutDuration); + + Future future = executor.submit(callable); + + try { + return future.get(timeoutDuration, timeoutUnit); + } catch (InterruptedException | TimeoutException e) { + future.cancel(true /* mayInterruptIfRunning */); + throw e; + } catch (ExecutionException e) { + wrapAndThrowExecutionExceptionOrError(e.getCause()); + throw new AssertionError(); + } + } + + + @Override + public T callUninterruptiblyWithTimeout( + Callable callable, long timeoutDuration, TimeUnit timeoutUnit) + throws TimeoutException, ExecutionException { + checkNotNull(callable); + checkNotNull(timeoutUnit); + checkPositiveTimeout(timeoutDuration); + + Future future = executor.submit(callable); + + try { + return Uninterruptibles.getUninterruptibly(future, timeoutDuration, timeoutUnit); + } catch (TimeoutException e) { + future.cancel(true /* mayInterruptIfRunning */); + throw e; + } catch (ExecutionException e) { + wrapAndThrowExecutionExceptionOrError(e.getCause()); + throw new AssertionError(); + } + } + + @Override + public void runWithTimeout(Runnable runnable, long timeoutDuration, TimeUnit timeoutUnit) + throws TimeoutException, InterruptedException { + checkNotNull(runnable); + checkNotNull(timeoutUnit); + checkPositiveTimeout(timeoutDuration); + + Future future = executor.submit(runnable); + + try { + future.get(timeoutDuration, timeoutUnit); + } catch (InterruptedException | TimeoutException e) { + future.cancel(true /* mayInterruptIfRunning */); + throw e; + } catch (ExecutionException e) { + wrapAndThrowRuntimeExecutionExceptionOrError(e.getCause()); + throw new AssertionError(); + } + } + + @Override + public void runUninterruptiblyWithTimeout( + Runnable runnable, long timeoutDuration, TimeUnit timeoutUnit) throws TimeoutException { + checkNotNull(runnable); + checkNotNull(timeoutUnit); + checkPositiveTimeout(timeoutDuration); + + Future future = executor.submit(runnable); + + try { + Uninterruptibles.getUninterruptibly(future, timeoutDuration, timeoutUnit); + } catch (TimeoutException e) { + future.cancel(true /* mayInterruptIfRunning */); + throw e; + } catch (ExecutionException e) { + wrapAndThrowRuntimeExecutionExceptionOrError(e.getCause()); + throw new AssertionError(); + } + } + + private static Exception throwCause(Exception e, boolean combineStackTraces) throws Exception { + Throwable cause = e.getCause(); + if (cause == null) { + throw e; + } + if (combineStackTraces) { + StackTraceElement[] combined = + ObjectArrays.concat(cause.getStackTrace(), e.getStackTrace(), StackTraceElement.class); + cause.setStackTrace(combined); + } + if (cause instanceof Exception) { + throw (Exception) cause; + } + if (cause instanceof Error) { + throw (Error) cause; + } + // The cause is a weird kind of Throwable, so throw the outer exception. + throw e; + } + + private static Set findInterruptibleMethods(Class interfaceType) { + Set set = Sets.newHashSet(); + for (Method m : interfaceType.getMethods()) { + if (declaresInterruptedEx(m)) { + set.add(m); + } + } + return set; + } + + private static boolean declaresInterruptedEx(Method method) { + for (Class exType : method.getExceptionTypes()) { + // debate: == or isAssignableFrom? + if (exType == InterruptedException.class) { + return true; + } + } + return false; + } + + private void wrapAndThrowExecutionExceptionOrError(Throwable cause) throws ExecutionException { + if (cause instanceof Error) { + throw new ExecutionError((Error) cause); + } else if (cause instanceof RuntimeException) { + throw new UncheckedExecutionException(cause); + } else { + throw new ExecutionException(cause); + } + } + + private void wrapAndThrowRuntimeExecutionExceptionOrError(Throwable cause) { + if (cause instanceof Error) { + throw new ExecutionError((Error) cause); + } else { + throw new UncheckedExecutionException(cause); + } + } + + private static void checkPositiveTimeout(long timeoutDuration) { + checkArgument(timeoutDuration > 0, "timeout must be positive: %s", timeoutDuration); + } +} diff --git a/src/main/java/com/google/common/util/concurrent/SmoothRateLimiter.java b/src/main/java/com/google/common/util/concurrent/SmoothRateLimiter.java new file mode 100644 index 0000000..d78d7da --- /dev/null +++ b/src/main/java/com/google/common/util/concurrent/SmoothRateLimiter.java @@ -0,0 +1,391 @@ +/* + * Copyright (C) 2012 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.util.concurrent; + +import static java.lang.Math.min; +import static java.util.concurrent.TimeUnit.SECONDS; + +import com.google.common.annotations.GwtIncompatible; +import com.google.common.math.LongMath; +import java.util.concurrent.TimeUnit; + +@GwtIncompatible +abstract class SmoothRateLimiter extends RateLimiter { + /* + * How is the RateLimiter designed, and why? + * + * The primary feature of a RateLimiter is its "stable rate", the maximum rate that is should + * allow at normal conditions. This is enforced by "throttling" incoming requests as needed, i.e. + * compute, for an incoming request, the appropriate throttle time, and make the calling thread + * wait as much. + * + * The simplest way to maintain a rate of QPS is to keep the timestamp of the last granted + * request, and ensure that (1/QPS) seconds have elapsed since then. For example, for a rate of + * QPS=5 (5 tokens per second), if we ensure that a request isn't granted earlier than 200ms after + * the last one, then we achieve the intended rate. If a request comes and the last request was + * granted only 100ms ago, then we wait for another 100ms. At this rate, serving 15 fresh permits + * (i.e. for an acquire(15) request) naturally takes 3 seconds. + * + * It is important to realize that such a RateLimiter has a very superficial memory of the past: + * it only remembers the last request. What if the RateLimiter was unused for a long period of + * time, then a request arrived and was immediately granted? This RateLimiter would immediately + * forget about that past underutilization. This may result in either underutilization or + * overflow, depending on the real world consequences of not using the expected rate. + * + * Past underutilization could mean that excess resources are available. Then, the RateLimiter + * should speed up for a while, to take advantage of these resources. This is important when the + * rate is applied to networking (limiting bandwidth), where past underutilization typically + * translates to "almost empty buffers", which can be filled immediately. + * + * On the other hand, past underutilization could mean that "the server responsible for handling + * the request has become less ready for future requests", i.e. its caches become stale, and + * requests become more likely to trigger expensive operations (a more extreme case of this + * example is when a server has just booted, and it is mostly busy with getting itself up to + * speed). + * + * To deal with such scenarios, we add an extra dimension, that of "past underutilization", + * modeled by "storedPermits" variable. This variable is zero when there is no underutilization, + * and it can grow up to maxStoredPermits, for sufficiently large underutilization. So, the + * requested permits, by an invocation acquire(permits), are served from: + * + * - stored permits (if available) + * + * - fresh permits (for any remaining permits) + * + * How this works is best explained with an example: + * + * For a RateLimiter that produces 1 token per second, every second that goes by with the + * RateLimiter being unused, we increase storedPermits by 1. Say we leave the RateLimiter unused + * for 10 seconds (i.e., we expected a request at time X, but we are at time X + 10 seconds before + * a request actually arrives; this is also related to the point made in the last paragraph), thus + * storedPermits becomes 10.0 (assuming maxStoredPermits >= 10.0). At that point, a request of + * acquire(3) arrives. We serve this request out of storedPermits, and reduce that to 7.0 (how + * this is translated to throttling time is discussed later). Immediately after, assume that an + * acquire(10) request arriving. We serve the request partly from storedPermits, using all the + * remaining 7.0 permits, and the remaining 3.0, we serve them by fresh permits produced by the + * rate limiter. + * + * We already know how much time it takes to serve 3 fresh permits: if the rate is + * "1 token per second", then this will take 3 seconds. But what does it mean to serve 7 stored + * permits? As explained above, there is no unique answer. If we are primarily interested to deal + * with underutilization, then we want stored permits to be given out /faster/ than fresh ones, + * because underutilization = free resources for the taking. If we are primarily interested to + * deal with overflow, then stored permits could be given out /slower/ than fresh ones. Thus, we + * require a (different in each case) function that translates storedPermits to throttling time. + * + * This role is played by storedPermitsToWaitTime(double storedPermits, double permitsToTake). The + * underlying model is a continuous function mapping storedPermits (from 0.0 to maxStoredPermits) + * onto the 1/rate (i.e. intervals) that is effective at the given storedPermits. "storedPermits" + * essentially measure unused time; we spend unused time buying/storing permits. Rate is + * "permits / time", thus "1 / rate = time / permits". Thus, "1/rate" (time / permits) times + * "permits" gives time, i.e., integrals on this function (which is what storedPermitsToWaitTime() + * computes) correspond to minimum intervals between subsequent requests, for the specified number + * of requested permits. + * + * Here is an example of storedPermitsToWaitTime: If storedPermits == 10.0, and we want 3 permits, + * we take them from storedPermits, reducing them to 7.0, and compute the throttling for these as + * a call to storedPermitsToWaitTime(storedPermits = 10.0, permitsToTake = 3.0), which will + * evaluate the integral of the function from 7.0 to 10.0. + * + * Using integrals guarantees that the effect of a single acquire(3) is equivalent to { + * acquire(1); acquire(1); acquire(1); }, or { acquire(2); acquire(1); }, etc, since the integral + * of the function in [7.0, 10.0] is equivalent to the sum of the integrals of [7.0, 8.0], [8.0, + * 9.0], [9.0, 10.0] (and so on), no matter what the function is. This guarantees that we handle + * correctly requests of varying weight (permits), /no matter/ what the actual function is - so we + * can tweak the latter freely. (The only requirement, obviously, is that we can compute its + * integrals). + * + * Note well that if, for this function, we chose a horizontal line, at height of exactly (1/QPS), + * then the effect of the function is non-existent: we serve storedPermits at exactly the same + * cost as fresh ones (1/QPS is the cost for each). We use this trick later. + * + * If we pick a function that goes /below/ that horizontal line, it means that we reduce the area + * of the function, thus time. Thus, the RateLimiter becomes /faster/ after a period of + * underutilization. If, on the other hand, we pick a function that goes /above/ that horizontal + * line, then it means that the area (time) is increased, thus storedPermits are more costly than + * fresh permits, thus the RateLimiter becomes /slower/ after a period of underutilization. + * + * Last, but not least: consider a RateLimiter with rate of 1 permit per second, currently + * completely unused, and an expensive acquire(100) request comes. It would be nonsensical to just + * wait for 100 seconds, and /then/ start the actual task. Why wait without doing anything? A much + * better approach is to /allow/ the request right away (as if it was an acquire(1) request + * instead), and postpone /subsequent/ requests as needed. In this version, we allow starting the + * task immediately, and postpone by 100 seconds future requests, thus we allow for work to get + * done in the meantime instead of waiting idly. + * + * This has important consequences: it means that the RateLimiter doesn't remember the time of the + * _last_ request, but it remembers the (expected) time of the _next_ request. This also enables + * us to tell immediately (see tryAcquire(timeout)) whether a particular timeout is enough to get + * us to the point of the next scheduling time, since we always maintain that. And what we mean by + * "an unused RateLimiter" is also defined by that notion: when we observe that the + * "expected arrival time of the next request" is actually in the past, then the difference (now - + * past) is the amount of time that the RateLimiter was formally unused, and it is that amount of + * time which we translate to storedPermits. (We increase storedPermits with the amount of permits + * that would have been produced in that idle time). So, if rate == 1 permit per second, and + * arrivals come exactly one second after the previous, then storedPermits is _never_ increased -- + * we would only increase it for arrivals _later_ than the expected one second. + */ + + /** + * This implements the following function where coldInterval = coldFactor * stableInterval. + * + *
+   *          ^ throttling
+   *          |
+   *    cold  +                  /
+   * interval |                 /.
+   *          |                / .
+   *          |               /  .   ← "warmup period" is the area of the trapezoid between
+   *          |              /   .     thresholdPermits and maxPermits
+   *          |             /    .
+   *          |            /     .
+   *          |           /      .
+   *   stable +----------/  WARM .
+   * interval |          .   UP  .
+   *          |          . PERIOD.
+   *          |          .       .
+   *        0 +----------+-------+--------------→ storedPermits
+   *          0 thresholdPermits maxPermits
+   * 
+ * + * Before going into the details of this particular function, let's keep in mind the basics: + * + *
    + *
  1. The state of the RateLimiter (storedPermits) is a vertical line in this figure. + *
  2. When the RateLimiter is not used, this goes right (up to maxPermits) + *
  3. When the RateLimiter is used, this goes left (down to zero), since if we have + * storedPermits, we serve from those first + *
  4. When _unused_, we go right at a constant rate! The rate at which we move to the right is + * chosen as maxPermits / warmupPeriod. This ensures that the time it takes to go from 0 to + * maxPermits is equal to warmupPeriod. + *
  5. When _used_, the time it takes, as explained in the introductory class note, is equal to + * the integral of our function, between X permits and X-K permits, assuming we want to + * spend K saved permits. + *
+ * + *

In summary, the time it takes to move to the left (spend K permits), is equal to the area of + * the function of width == K. + * + *

Assuming we have saturated demand, the time to go from maxPermits to thresholdPermits is + * equal to warmupPeriod. And the time to go from thresholdPermits to 0 is warmupPeriod/2. (The + * reason that this is warmupPeriod/2 is to maintain the behavior of the original implementation + * where coldFactor was hard coded as 3.) + * + *

It remains to calculate thresholdsPermits and maxPermits. + * + *

    + *
  • The time to go from thresholdPermits to 0 is equal to the integral of the function + * between 0 and thresholdPermits. This is thresholdPermits * stableIntervals. By (5) it is + * also equal to warmupPeriod/2. Therefore + *
    + * thresholdPermits = 0.5 * warmupPeriod / stableInterval + *
    + *
  • The time to go from maxPermits to thresholdPermits is equal to the integral of the + * function between thresholdPermits and maxPermits. This is the area of the pictured + * trapezoid, and it is equal to 0.5 * (stableInterval + coldInterval) * (maxPermits - + * thresholdPermits). It is also equal to warmupPeriod, so + *
    + * maxPermits = thresholdPermits + 2 * warmupPeriod / (stableInterval + coldInterval) + *
    + *
+ */ + static final class SmoothWarmingUp extends SmoothRateLimiter { + private final long warmupPeriodMicros; + /** + * The slope of the line from the stable interval (when permits == 0), to the cold interval + * (when permits == maxPermits) + */ + private double slope; + + private double thresholdPermits; + private double coldFactor; + + SmoothWarmingUp( + SleepingStopwatch stopwatch, long warmupPeriod, TimeUnit timeUnit, double coldFactor) { + super(stopwatch); + this.warmupPeriodMicros = timeUnit.toMicros(warmupPeriod); + this.coldFactor = coldFactor; + } + + @Override + void doSetRate(double permitsPerSecond, double stableIntervalMicros) { + double oldMaxPermits = maxPermits; + double coldIntervalMicros = stableIntervalMicros * coldFactor; + thresholdPermits = 0.5 * warmupPeriodMicros / stableIntervalMicros; + maxPermits = + thresholdPermits + 2.0 * warmupPeriodMicros / (stableIntervalMicros + coldIntervalMicros); + slope = (coldIntervalMicros - stableIntervalMicros) / (maxPermits - thresholdPermits); + if (oldMaxPermits == Double.POSITIVE_INFINITY) { + // if we don't special-case this, we would get storedPermits == NaN, below + storedPermits = 0.0; + } else { + storedPermits = + (oldMaxPermits == 0.0) + ? maxPermits // initial state is cold + : storedPermits * maxPermits / oldMaxPermits; + } + } + + @Override + long storedPermitsToWaitTime(double storedPermits, double permitsToTake) { + double availablePermitsAboveThreshold = storedPermits - thresholdPermits; + long micros = 0; + // measuring the integral on the right part of the function (the climbing line) + if (availablePermitsAboveThreshold > 0.0) { + double permitsAboveThresholdToTake = min(availablePermitsAboveThreshold, permitsToTake); + // TODO(cpovirk): Figure out a good name for this variable. + double length = + permitsToTime(availablePermitsAboveThreshold) + + permitsToTime(availablePermitsAboveThreshold - permitsAboveThresholdToTake); + micros = (long) (permitsAboveThresholdToTake * length / 2.0); + permitsToTake -= permitsAboveThresholdToTake; + } + // measuring the integral on the left part of the function (the horizontal line) + micros += (long) (stableIntervalMicros * permitsToTake); + return micros; + } + + private double permitsToTime(double permits) { + return stableIntervalMicros + permits * slope; + } + + @Override + double coolDownIntervalMicros() { + return warmupPeriodMicros / maxPermits; + } + } + + /** + * This implements a "bursty" RateLimiter, where storedPermits are translated to zero throttling. + * The maximum number of permits that can be saved (when the RateLimiter is unused) is defined in + * terms of time, in this sense: if a RateLimiter is 2qps, and this time is specified as 10 + * seconds, we can save up to 2 * 10 = 20 permits. + */ + static final class SmoothBursty extends SmoothRateLimiter { + /** The work (permits) of how many seconds can be saved up if this RateLimiter is unused? */ + final double maxBurstSeconds; + + SmoothBursty(SleepingStopwatch stopwatch, double maxBurstSeconds) { + super(stopwatch); + this.maxBurstSeconds = maxBurstSeconds; + } + + @Override + void doSetRate(double permitsPerSecond, double stableIntervalMicros) { + double oldMaxPermits = this.maxPermits; + maxPermits = maxBurstSeconds * permitsPerSecond; + if (oldMaxPermits == Double.POSITIVE_INFINITY) { + // if we don't special-case this, we would get storedPermits == NaN, below + storedPermits = maxPermits; + } else { + storedPermits = + (oldMaxPermits == 0.0) + ? 0.0 // initial state + : storedPermits * maxPermits / oldMaxPermits; + } + } + + @Override + long storedPermitsToWaitTime(double storedPermits, double permitsToTake) { + return 0L; + } + + @Override + double coolDownIntervalMicros() { + return stableIntervalMicros; + } + } + + /** The currently stored permits. */ + double storedPermits; + + /** The maximum number of stored permits. */ + double maxPermits; + + /** + * The interval between two unit requests, at our stable rate. E.g., a stable rate of 5 permits + * per second has a stable interval of 200ms. + */ + double stableIntervalMicros; + + /** + * The time when the next request (no matter its size) will be granted. After granting a request, + * this is pushed further in the future. Large requests push this further than small requests. + */ + private long nextFreeTicketMicros = 0L; // could be either in the past or future + + private SmoothRateLimiter(SleepingStopwatch stopwatch) { + super(stopwatch); + } + + @Override + final void doSetRate(double permitsPerSecond, long nowMicros) { + resync(nowMicros); + double stableIntervalMicros = SECONDS.toMicros(1L) / permitsPerSecond; + this.stableIntervalMicros = stableIntervalMicros; + doSetRate(permitsPerSecond, stableIntervalMicros); + } + + abstract void doSetRate(double permitsPerSecond, double stableIntervalMicros); + + @Override + final double doGetRate() { + return SECONDS.toMicros(1L) / stableIntervalMicros; + } + + @Override + final long queryEarliestAvailable(long nowMicros) { + return nextFreeTicketMicros; + } + + @Override + final long reserveEarliestAvailable(int requiredPermits, long nowMicros) { + resync(nowMicros); + long returnValue = nextFreeTicketMicros; + double storedPermitsToSpend = min(requiredPermits, this.storedPermits); + double freshPermits = requiredPermits - storedPermitsToSpend; + long waitMicros = + storedPermitsToWaitTime(this.storedPermits, storedPermitsToSpend) + + (long) (freshPermits * stableIntervalMicros); + + this.nextFreeTicketMicros = LongMath.saturatedAdd(nextFreeTicketMicros, waitMicros); + this.storedPermits -= storedPermitsToSpend; + return returnValue; + } + + /** + * Translates a specified portion of our currently stored permits which we want to spend/acquire, + * into a throttling time. Conceptually, this evaluates the integral of the underlying function we + * use, for the range of [(storedPermits - permitsToTake), storedPermits]. + * + *

This always holds: {@code 0 <= permitsToTake <= storedPermits} + */ + abstract long storedPermitsToWaitTime(double storedPermits, double permitsToTake); + + /** + * Returns the number of microseconds during cool down that we have to wait to get a new permit. + */ + abstract double coolDownIntervalMicros(); + + /** Updates {@code storedPermits} and {@code nextFreeTicketMicros} based on the current time. */ + void resync(long nowMicros) { + // if nextFreeTicket is in the past, resync to now + if (nowMicros > nextFreeTicketMicros) { + double newPermits = (nowMicros - nextFreeTicketMicros) / coolDownIntervalMicros(); + storedPermits = min(maxPermits, storedPermits + newPermits); + nextFreeTicketMicros = nowMicros; + } + } +} diff --git a/src/main/java/com/google/common/util/concurrent/Striped.java b/src/main/java/com/google/common/util/concurrent/Striped.java new file mode 100644 index 0000000..c9ba772 --- /dev/null +++ b/src/main/java/com/google/common/util/concurrent/Striped.java @@ -0,0 +1,587 @@ +/* + * Copyright (C) 2011 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.util.concurrent; + +import com.google.common.annotations.Beta; +import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.MoreObjects; +import com.google.common.base.Preconditions; +import com.google.common.base.Supplier; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Iterables; +import com.google.common.collect.MapMaker; +import com.google.common.math.IntMath; +import com.google.common.primitives.Ints; +import java.lang.ref.Reference; +import java.lang.ref.ReferenceQueue; +import java.lang.ref.WeakReference; +import java.math.RoundingMode; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.Semaphore; +import java.util.concurrent.atomic.AtomicReferenceArray; +import java.util.concurrent.locks.Condition; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReadWriteLock; +import java.util.concurrent.locks.ReentrantLock; +import java.util.concurrent.locks.ReentrantReadWriteLock; + +/** + * A striped {@code Lock/Semaphore/ReadWriteLock}. This offers the underlying lock striping similar + * to that of {@code ConcurrentHashMap} in a reusable form, and extends it for semaphores and + * read-write locks. Conceptually, lock striping is the technique of dividing a lock into many + * stripes, increasing the granularity of a single lock and allowing independent operations + * to lock different stripes and proceed concurrently, instead of creating contention for a single + * lock. + * + *

The guarantee provided by this class is that equal keys lead to the same lock (or semaphore), + * i.e. {@code if (key1.equals(key2))} then {@code striped.get(key1) == striped.get(key2)} (assuming + * {@link Object#hashCode()} is correctly implemented for the keys). Note that if {@code key1} is + * not equal to {@code key2}, it is not guaranteed that {@code + * striped.get(key1) != striped.get(key2)}; the elements might nevertheless be mapped to the same + * lock. The lower the number of stripes, the higher the probability of this happening. + * + *

There are three flavors of this class: {@code Striped}, {@code Striped}, and + * {@code Striped}. For each type, two implementations are offered: {@linkplain + * #lock(int) strong} and {@linkplain #lazyWeakLock(int) weak} {@code Striped}, {@linkplain + * #semaphore(int, int) strong} and {@linkplain #lazyWeakSemaphore(int, int) weak} {@code + * Striped}, and {@linkplain #readWriteLock(int) strong} and {@linkplain + * #lazyWeakReadWriteLock(int) weak} {@code Striped}. Strong means that all + * stripes (locks/semaphores) are initialized eagerly, and are not reclaimed unless {@code Striped} + * itself is reclaimable. Weak means that locks/semaphores are created lazily, and they are + * allowed to be reclaimed if nobody is holding on to them. This is useful, for example, if one + * wants to create a {@code Striped} of many locks, but worries that in most cases only a + * small portion of these would be in use. + * + *

Prior to this class, one might be tempted to use {@code Map}, where {@code K} + * represents the task. This maximizes concurrency by having each unique key mapped to a unique + * lock, but also maximizes memory footprint. On the other extreme, one could use a single lock for + * all tasks, which minimizes memory footprint but also minimizes concurrency. Instead of choosing + * either of these extremes, {@code Striped} allows the user to trade between required concurrency + * and memory footprint. For example, if a set of tasks are CPU-bound, one could easily create a + * very compact {@code Striped} of {@code availableProcessors() * 4} stripes, instead of + * possibly thousands of locks which could be created in a {@code Map} structure. + * + * @author Dimitris Andreou + * @since 13.0 + */ +@Beta +@GwtIncompatible +public abstract class Striped { + /** + * If there are at least this many stripes, we assume the memory usage of a ConcurrentMap will be + * smaller than a large array. (This assumes that in the lazy case, most stripes are unused. As + * always, if many stripes are in use, a non-lazy striped makes more sense.) + */ + private static final int LARGE_LAZY_CUTOFF = 1024; + + private Striped() {} + + /** + * Returns the stripe that corresponds to the passed key. It is always guaranteed that if {@code + * key1.equals(key2)}, then {@code get(key1) == get(key2)}. + * + * @param key an arbitrary, non-null key + * @return the stripe that the passed key corresponds to + */ + public abstract L get(Object key); + + /** + * Returns the stripe at the specified index. Valid indexes are 0, inclusively, to {@code size()}, + * exclusively. + * + * @param index the index of the stripe to return; must be in {@code [0...size())} + * @return the stripe at the specified index + */ + public abstract L getAt(int index); + + /** + * Returns the index to which the given key is mapped, so that getAt(indexFor(key)) == get(key). + */ + abstract int indexFor(Object key); + + /** Returns the total number of stripes in this instance. */ + public abstract int size(); + + /** + * Returns the stripes that correspond to the passed objects, in ascending (as per {@link + * #getAt(int)}) order. Thus, threads that use the stripes in the order returned by this method + * are guaranteed to not deadlock each other. + * + *

It should be noted that using a {@code Striped} with relatively few stripes, and {@code + * bulkGet(keys)} with a relative large number of keys can cause an excessive number of shared + * stripes (much like the birthday paradox, where much fewer than anticipated birthdays are needed + * for a pair of them to match). Please consider carefully the implications of the number of + * stripes, the intended concurrency level, and the typical number of keys used in a {@code + * bulkGet(keys)} operation. See Balls in + * Bins model for mathematical formulas that can be used to estimate the probability of + * collisions. + * + * @param keys arbitrary non-null keys + * @return the stripes corresponding to the objects (one per each object, derived by delegating to + * {@link #get(Object)}; may contain duplicates), in an increasing index order. + */ + public Iterable bulkGet(Iterable keys) { + // Initially using the array to store the keys, then reusing it to store the respective L's + final Object[] array = Iterables.toArray(keys, Object.class); + if (array.length == 0) { + return ImmutableList.of(); + } + int[] stripes = new int[array.length]; + for (int i = 0; i < array.length; i++) { + stripes[i] = indexFor(array[i]); + } + Arrays.sort(stripes); + // optimize for runs of identical stripes + int previousStripe = stripes[0]; + array[0] = getAt(previousStripe); + for (int i = 1; i < array.length; i++) { + int currentStripe = stripes[i]; + if (currentStripe == previousStripe) { + array[i] = array[i - 1]; + } else { + array[i] = getAt(currentStripe); + previousStripe = currentStripe; + } + } + /* + * Note that the returned Iterable holds references to the returned stripes, to avoid + * error-prone code like: + * + * Striped stripedLock = Striped.lazyWeakXXX(...)' + * Iterable locks = stripedLock.bulkGet(keys); + * for (Lock lock : locks) { + * lock.lock(); + * } + * operation(); + * for (Lock lock : locks) { + * lock.unlock(); + * } + * + * If we only held the int[] stripes, translating it on the fly to L's, the original locks might + * be garbage collected after locking them, ending up in a huge mess. + */ + @SuppressWarnings("unchecked") // we carefully replaced all keys with their respective L's + List asList = (List) Arrays.asList(array); + return Collections.unmodifiableList(asList); + } + + // Static factories + + /** + * Creates a {@code Striped} with eagerly initialized, strongly referenced locks. Every lock is + * obtained from the passed supplier. + * + * @param stripes the minimum number of stripes (locks) required + * @param supplier a {@code Supplier} object to obtain locks from + * @return a new {@code Striped} + */ + static Striped custom(int stripes, Supplier supplier) { + return new CompactStriped<>(stripes, supplier); + } + + /** + * Creates a {@code Striped} with eagerly initialized, strongly referenced locks. Every lock + * is reentrant. + * + * @param stripes the minimum number of stripes (locks) required + * @return a new {@code Striped} + */ + public static Striped lock(int stripes) { + return custom( + stripes, + new Supplier() { + @Override + public Lock get() { + return new PaddedLock(); + } + }); + } + + /** + * Creates a {@code Striped} with lazily initialized, weakly referenced locks. Every lock is + * reentrant. + * + * @param stripes the minimum number of stripes (locks) required + * @return a new {@code Striped} + */ + public static Striped lazyWeakLock(int stripes) { + return lazy( + stripes, + new Supplier() { + @Override + public Lock get() { + return new ReentrantLock(false); + } + }); + } + + private static Striped lazy(int stripes, Supplier supplier) { + return stripes < LARGE_LAZY_CUTOFF + ? new SmallLazyStriped(stripes, supplier) + : new LargeLazyStriped(stripes, supplier); + } + + /** + * Creates a {@code Striped} with eagerly initialized, strongly referenced semaphores, + * with the specified number of permits. + * + * @param stripes the minimum number of stripes (semaphores) required + * @param permits the number of permits in each semaphore + * @return a new {@code Striped} + */ + public static Striped semaphore(int stripes, final int permits) { + return custom( + stripes, + new Supplier() { + @Override + public Semaphore get() { + return new PaddedSemaphore(permits); + } + }); + } + + /** + * Creates a {@code Striped} with lazily initialized, weakly referenced semaphores, + * with the specified number of permits. + * + * @param stripes the minimum number of stripes (semaphores) required + * @param permits the number of permits in each semaphore + * @return a new {@code Striped} + */ + public static Striped lazyWeakSemaphore(int stripes, final int permits) { + return lazy( + stripes, + new Supplier() { + @Override + public Semaphore get() { + return new Semaphore(permits, false); + } + }); + } + + /** + * Creates a {@code Striped} with eagerly initialized, strongly referenced + * read-write locks. Every lock is reentrant. + * + * @param stripes the minimum number of stripes (locks) required + * @return a new {@code Striped} + */ + public static Striped readWriteLock(int stripes) { + return custom(stripes, READ_WRITE_LOCK_SUPPLIER); + } + + /** + * Creates a {@code Striped} with lazily initialized, weakly referenced read-write + * locks. Every lock is reentrant. + * + * @param stripes the minimum number of stripes (locks) required + * @return a new {@code Striped} + */ + public static Striped lazyWeakReadWriteLock(int stripes) { + return lazy(stripes, WEAK_SAFE_READ_WRITE_LOCK_SUPPLIER); + } + + private static final Supplier READ_WRITE_LOCK_SUPPLIER = + new Supplier() { + @Override + public ReadWriteLock get() { + return new ReentrantReadWriteLock(); + } + }; + + private static final Supplier WEAK_SAFE_READ_WRITE_LOCK_SUPPLIER = + new Supplier() { + @Override + public ReadWriteLock get() { + return new WeakSafeReadWriteLock(); + } + }; + + /** + * ReadWriteLock implementation whose read and write locks retain a reference back to this lock. + * Otherwise, a reference to just the read lock or just the write lock would not suffice to ensure + * the {@code ReadWriteLock} is retained. + */ + private static final class WeakSafeReadWriteLock implements ReadWriteLock { + private final ReadWriteLock delegate; + + WeakSafeReadWriteLock() { + this.delegate = new ReentrantReadWriteLock(); + } + + @Override + public Lock readLock() { + return new WeakSafeLock(delegate.readLock(), this); + } + + @Override + public Lock writeLock() { + return new WeakSafeLock(delegate.writeLock(), this); + } + } + + /** Lock object that ensures a strong reference is retained to a specified object. */ + private static final class WeakSafeLock extends ForwardingLock { + private final Lock delegate; + + @SuppressWarnings("unused") + private final WeakSafeReadWriteLock strongReference; + + WeakSafeLock(Lock delegate, WeakSafeReadWriteLock strongReference) { + this.delegate = delegate; + this.strongReference = strongReference; + } + + @Override + Lock delegate() { + return delegate; + } + + @Override + public Condition newCondition() { + return new WeakSafeCondition(delegate.newCondition(), strongReference); + } + } + + /** Condition object that ensures a strong reference is retained to a specified object. */ + private static final class WeakSafeCondition extends ForwardingCondition { + private final Condition delegate; + + @SuppressWarnings("unused") + private final WeakSafeReadWriteLock strongReference; + + WeakSafeCondition(Condition delegate, WeakSafeReadWriteLock strongReference) { + this.delegate = delegate; + this.strongReference = strongReference; + } + + @Override + Condition delegate() { + return delegate; + } + } + + private abstract static class PowerOfTwoStriped extends Striped { + /** Capacity (power of two) minus one, for fast mod evaluation */ + final int mask; + + PowerOfTwoStriped(int stripes) { + Preconditions.checkArgument(stripes > 0, "Stripes must be positive"); + this.mask = stripes > Ints.MAX_POWER_OF_TWO ? ALL_SET : ceilToPowerOfTwo(stripes) - 1; + } + + @Override + final int indexFor(Object key) { + int hash = smear(key.hashCode()); + return hash & mask; + } + + @Override + public final L get(Object key) { + return getAt(indexFor(key)); + } + } + + /** + * Implementation of Striped where 2^k stripes are represented as an array of the same length, + * eagerly initialized. + */ + private static class CompactStriped extends PowerOfTwoStriped { + /** Size is a power of two. */ + private final Object[] array; + + private CompactStriped(int stripes, Supplier supplier) { + super(stripes); + Preconditions.checkArgument(stripes <= Ints.MAX_POWER_OF_TWO, "Stripes must be <= 2^30)"); + + this.array = new Object[mask + 1]; + for (int i = 0; i < array.length; i++) { + array[i] = supplier.get(); + } + } + + @SuppressWarnings("unchecked") // we only put L's in the array + @Override + public L getAt(int index) { + return (L) array[index]; + } + + @Override + public int size() { + return array.length; + } + } + + /** + * Implementation of Striped where up to 2^k stripes can be represented, using an + * AtomicReferenceArray of size 2^k. To map a user key into a stripe, we take a k-bit slice of the + * user key's (smeared) hashCode(). The stripes are lazily initialized and are weakly referenced. + */ + @VisibleForTesting + static class SmallLazyStriped extends PowerOfTwoStriped { + final AtomicReferenceArray> locks; + final Supplier supplier; + final int size; + final ReferenceQueue queue = new ReferenceQueue(); + + SmallLazyStriped(int stripes, Supplier supplier) { + super(stripes); + this.size = (mask == ALL_SET) ? Integer.MAX_VALUE : mask + 1; + this.locks = new AtomicReferenceArray<>(size); + this.supplier = supplier; + } + + @Override + public L getAt(int index) { + if (size != Integer.MAX_VALUE) { + Preconditions.checkElementIndex(index, size()); + } // else no check necessary, all index values are valid + ArrayReference existingRef = locks.get(index); + L existing = existingRef == null ? null : existingRef.get(); + if (existing != null) { + return existing; + } + L created = supplier.get(); + ArrayReference newRef = new ArrayReference(created, index, queue); + while (!locks.compareAndSet(index, existingRef, newRef)) { + // we raced, we need to re-read and try again + existingRef = locks.get(index); + existing = existingRef == null ? null : existingRef.get(); + if (existing != null) { + return existing; + } + } + drainQueue(); + return created; + } + + // N.B. Draining the queue is only necessary to ensure that we don't accumulate empty references + // in the array. We could skip this if we decide we don't care about holding on to Reference + // objects indefinitely. + private void drainQueue() { + Reference ref; + while ((ref = queue.poll()) != null) { + // We only ever register ArrayReferences with the queue so this is always safe. + ArrayReference arrayRef = (ArrayReference) ref; + // Try to clear out the array slot, n.b. if we fail that is fine, in either case the + // arrayRef will be out of the array after this step. + locks.compareAndSet(arrayRef.index, arrayRef, null); + } + } + + @Override + public int size() { + return size; + } + + private static final class ArrayReference extends WeakReference { + final int index; + + ArrayReference(L referent, int index, ReferenceQueue queue) { + super(referent, queue); + this.index = index; + } + } + } + + /** + * Implementation of Striped where up to 2^k stripes can be represented, using a ConcurrentMap + * where the key domain is [0..2^k). To map a user key into a stripe, we take a k-bit slice of the + * user key's (smeared) hashCode(). The stripes are lazily initialized and are weakly referenced. + */ + @VisibleForTesting + static class LargeLazyStriped extends PowerOfTwoStriped { + final ConcurrentMap locks; + final Supplier supplier; + final int size; + + LargeLazyStriped(int stripes, Supplier supplier) { + super(stripes); + this.size = (mask == ALL_SET) ? Integer.MAX_VALUE : mask + 1; + this.supplier = supplier; + this.locks = new MapMaker().weakValues().makeMap(); + } + + @Override + public L getAt(int index) { + if (size != Integer.MAX_VALUE) { + Preconditions.checkElementIndex(index, size()); + } // else no check necessary, all index values are valid + L existing = locks.get(index); + if (existing != null) { + return existing; + } + L created = supplier.get(); + existing = locks.putIfAbsent(index, created); + return MoreObjects.firstNonNull(existing, created); + } + + @Override + public int size() { + return size; + } + } + + /** A bit mask were all bits are set. */ + private static final int ALL_SET = ~0; + + private static int ceilToPowerOfTwo(int x) { + return 1 << IntMath.log2(x, RoundingMode.CEILING); + } + + /* + * This method was 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/licenses/publicdomain + * + * As of 2010/06/11, this method is identical to the (package private) hash method in OpenJDK 7's + * java.util.HashMap class. + */ + // Copied from java/com/google/common/collect/Hashing.java + private static int smear(int hashCode) { + hashCode ^= (hashCode >>> 20) ^ (hashCode >>> 12); + return hashCode ^ (hashCode >>> 7) ^ (hashCode >>> 4); + } + + private static class PaddedLock extends ReentrantLock { + /* + * Padding from 40 into 64 bytes, same size as cache line. Might be beneficial to add a fourth + * long here, to minimize chance of interference between consecutive locks, but I couldn't + * observe any benefit from that. + */ + long unused1; + long unused2; + long unused3; + + PaddedLock() { + super(false); + } + } + + private static class PaddedSemaphore extends Semaphore { + // See PaddedReentrantLock comment + long unused1; + long unused2; + long unused3; + + PaddedSemaphore(int permits) { + super(permits, false); + } + } +} diff --git a/src/main/java/com/google/common/util/concurrent/ThreadFactoryBuilder.java b/src/main/java/com/google/common/util/concurrent/ThreadFactoryBuilder.java new file mode 100644 index 0000000..257dd37 --- /dev/null +++ b/src/main/java/com/google/common/util/concurrent/ThreadFactoryBuilder.java @@ -0,0 +1,184 @@ +/* + * Copyright (C) 2010 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.util.concurrent; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.annotations.GwtIncompatible; + + +import java.lang.Thread.UncaughtExceptionHandler; +import java.util.Locale; +import java.util.concurrent.Executors; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.atomic.AtomicLong; + +/** + * A ThreadFactory builder, providing any combination of these features: + * + *

    + *
  • whether threads should be marked as {@linkplain Thread#setDaemon daemon} threads + *
  • a {@linkplain ThreadFactoryBuilder#setNameFormat naming format} + *
  • a {@linkplain Thread#setPriority thread priority} + *
  • an {@linkplain Thread#setUncaughtExceptionHandler uncaught exception handler} + *
  • a {@linkplain ThreadFactory#newThread backing thread factory} + *
+ * + *

If no backing thread factory is provided, a default backing thread factory is used as if by + * calling {@code setThreadFactory(}{@link Executors#defaultThreadFactory()}{@code )}. + * + * @author Kurt Alfred Kluever + * @since 4.0 + */ + +@GwtIncompatible +public final class ThreadFactoryBuilder { + private String nameFormat = null; + private Boolean daemon = null; + private Integer priority = null; + private UncaughtExceptionHandler uncaughtExceptionHandler = null; + private ThreadFactory backingThreadFactory = null; + + /** Creates a new {@link ThreadFactory} builder. */ + public ThreadFactoryBuilder() {} + + /** + * Sets the naming format to use when naming threads ({@link Thread#setName}) which are created + * with this ThreadFactory. + * + * @param nameFormat a {@link String#format(String, Object...)}-compatible format String, to which + * a unique integer (0, 1, etc.) will be supplied as the single parameter. This integer will + * be unique to the built instance of the ThreadFactory and will be assigned sequentially. For + * example, {@code "rpc-pool-%d"} will generate thread names like {@code "rpc-pool-0"}, {@code + * "rpc-pool-1"}, {@code "rpc-pool-2"}, etc. + * @return this for the builder pattern + */ + public ThreadFactoryBuilder setNameFormat(String nameFormat) { + String unused = format(nameFormat, 0); // fail fast if the format is bad or null + this.nameFormat = nameFormat; + return this; + } + + /** + * Sets daemon or not for new threads created with this ThreadFactory. + * + * @param daemon whether or not new Threads created with this ThreadFactory will be daemon threads + * @return this for the builder pattern + */ + public ThreadFactoryBuilder setDaemon(boolean daemon) { + this.daemon = daemon; + return this; + } + + /** + * Sets the priority for new threads created with this ThreadFactory. + * + * @param priority the priority for new Threads created with this ThreadFactory + * @return this for the builder pattern + */ + public ThreadFactoryBuilder setPriority(int priority) { + // Thread#setPriority() already checks for validity. These error messages + // are nicer though and will fail-fast. + checkArgument( + priority >= Thread.MIN_PRIORITY, + "Thread priority (%s) must be >= %s", + priority, + Thread.MIN_PRIORITY); + checkArgument( + priority <= Thread.MAX_PRIORITY, + "Thread priority (%s) must be <= %s", + priority, + Thread.MAX_PRIORITY); + this.priority = priority; + return this; + } + + /** + * Sets the {@link UncaughtExceptionHandler} for new threads created with this ThreadFactory. + * + * @param uncaughtExceptionHandler the uncaught exception handler for new Threads created with + * this ThreadFactory + * @return this for the builder pattern + */ + public ThreadFactoryBuilder setUncaughtExceptionHandler( + UncaughtExceptionHandler uncaughtExceptionHandler) { + this.uncaughtExceptionHandler = checkNotNull(uncaughtExceptionHandler); + return this; + } + + /** + * Sets the backing {@link ThreadFactory} for new threads created with this ThreadFactory. Threads + * will be created by invoking #newThread(Runnable) on this backing {@link ThreadFactory}. + * + * @param backingThreadFactory the backing {@link ThreadFactory} which will be delegated to during + * thread creation. + * @return this for the builder pattern + * @see MoreExecutors + */ + public ThreadFactoryBuilder setThreadFactory(ThreadFactory backingThreadFactory) { + this.backingThreadFactory = checkNotNull(backingThreadFactory); + return this; + } + + /** + * Returns a new thread factory using the options supplied during the building process. After + * building, it is still possible to change the options used to build the ThreadFactory and/or + * build again. State is not shared amongst built instances. + * + * @return the fully constructed {@link ThreadFactory} + */ + + public ThreadFactory build() { + return doBuild(this); + } + + // Split out so that the anonymous ThreadFactory can't contain a reference back to the builder. + // At least, I assume that's why. TODO(cpovirk): Check, and maybe add a test for this. + private static ThreadFactory doBuild(ThreadFactoryBuilder builder) { + final String nameFormat = builder.nameFormat; + final Boolean daemon = builder.daemon; + final Integer priority = builder.priority; + final UncaughtExceptionHandler uncaughtExceptionHandler = builder.uncaughtExceptionHandler; + final ThreadFactory backingThreadFactory = + (builder.backingThreadFactory != null) + ? builder.backingThreadFactory + : Executors.defaultThreadFactory(); + final AtomicLong count = (nameFormat != null) ? new AtomicLong(0) : null; + return new ThreadFactory() { + @Override + public Thread newThread(Runnable runnable) { + Thread thread = backingThreadFactory.newThread(runnable); + if (nameFormat != null) { + thread.setName(format(nameFormat, count.getAndIncrement())); + } + if (daemon != null) { + thread.setDaemon(daemon); + } + if (priority != null) { + thread.setPriority(priority); + } + if (uncaughtExceptionHandler != null) { + thread.setUncaughtExceptionHandler(uncaughtExceptionHandler); + } + return thread; + } + }; + } + + private static String format(String format, Object... args) { + return String.format(Locale.ROOT, format, args); + } +} diff --git a/src/main/java/com/google/common/util/concurrent/TimeLimiter.java b/src/main/java/com/google/common/util/concurrent/TimeLimiter.java new file mode 100644 index 0000000..d8deb77 --- /dev/null +++ b/src/main/java/com/google/common/util/concurrent/TimeLimiter.java @@ -0,0 +1,299 @@ +/* + * Copyright (C) 2006 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.util.concurrent; + +import static com.google.common.util.concurrent.Internal.toNanosSaturated; + +import com.google.common.annotations.Beta; +import com.google.common.annotations.GwtIncompatible; + +import java.time.Duration; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +/** + * Imposes a time limit on method calls. + * + * @author Kevin Bourrillion + * @author Jens Nyman + * @since 1.0 + */ +@Beta +@GwtIncompatible +public interface TimeLimiter { + + /** + * Returns an instance of {@code interfaceType} that delegates all method calls to the {@code + * target} object, enforcing the specified time limit on each call. This time-limited delegation + * is also performed for calls to {@link Object#equals}, {@link Object#hashCode}, and {@link + * Object#toString}. + * + *

If the target method call finishes before the limit is reached, the return value or + * exception is propagated to the caller exactly as-is. If, on the other hand, the time limit is + * reached, the proxy will attempt to abort the call to the target, and will throw an {@link + * UncheckedTimeoutException} to the caller. + * + *

It is important to note that the primary purpose of the proxy object is to return control to + * the caller when the timeout elapses; aborting the target method call is of secondary concern. + * The particular nature and strength of the guarantees made by the proxy is + * implementation-dependent. However, it is important that each of the methods on the target + * object behaves appropriately when its thread is interrupted. + * + *

For example, to return the value of {@code target.someMethod()}, but substitute {@code + * DEFAULT_VALUE} if this method call takes over 50 ms, you can use this code: + * + *

+   *   TimeLimiter limiter = . . .;
+   *   TargetType proxy = limiter.newProxy(
+   *       target, TargetType.class, 50, TimeUnit.MILLISECONDS);
+   *   try {
+   *     return proxy.someMethod();
+   *   } catch (UncheckedTimeoutException e) {
+   *     return DEFAULT_VALUE;
+   *   }
+   * 
+ * + * @param target the object to proxy + * @param interfaceType the interface you wish the returned proxy to implement + * @param timeoutDuration with timeoutUnit, the maximum length of time that callers are willing to + * wait on each method call to the proxy + * @param timeoutUnit with timeoutDuration, the maximum length of time that callers are willing to + * wait on each method call to the proxy + * @return a time-limiting proxy + * @throws IllegalArgumentException if {@code interfaceType} is a regular class, enum, or + * annotation type, rather than an interface + */ + @SuppressWarnings("GoodTime") // should accept a java.time.Duration + T newProxy(T target, Class interfaceType, long timeoutDuration, TimeUnit timeoutUnit); + + /** + * Returns an instance of {@code interfaceType} that delegates all method calls to the {@code + * target} object, enforcing the specified time limit on each call. This time-limited delegation + * is also performed for calls to {@link Object#equals}, {@link Object#hashCode}, and {@link + * Object#toString}. + * + *

If the target method call finishes before the limit is reached, the return value or + * exception is propagated to the caller exactly as-is. If, on the other hand, the time limit is + * reached, the proxy will attempt to abort the call to the target, and will throw an {@link + * UncheckedTimeoutException} to the caller. + * + *

It is important to note that the primary purpose of the proxy object is to return control to + * the caller when the timeout elapses; aborting the target method call is of secondary concern. + * The particular nature and strength of the guarantees made by the proxy is + * implementation-dependent. However, it is important that each of the methods on the target + * object behaves appropriately when its thread is interrupted. + * + *

For example, to return the value of {@code target.someMethod()}, but substitute {@code + * DEFAULT_VALUE} if this method call takes over 50 ms, you can use this code: + * + *

+   *   TimeLimiter limiter = . . .;
+   *   TargetType proxy = limiter.newProxy(target, TargetType.class, Duration.ofMillis(50));
+   *   try {
+   *     return proxy.someMethod();
+   *   } catch (UncheckedTimeoutException e) {
+   *     return DEFAULT_VALUE;
+   *   }
+   * 
+ * + * @param target the object to proxy + * @param interfaceType the interface you wish the returned proxy to implement + * @param timeout the maximum length of time that callers are willing to wait on each method call + * to the proxy + * @return a time-limiting proxy + * @throws IllegalArgumentException if {@code interfaceType} is a regular class, enum, or + * annotation type, rather than an interface + * @since 28.0 + */ + default T newProxy(T target, Class interfaceType, Duration timeout) { + return newProxy(target, interfaceType, toNanosSaturated(timeout), TimeUnit.NANOSECONDS); + } + + /** + * Invokes a specified Callable, timing out after the specified time limit. If the target method + * call finishes before the limit is reached, the return value or a wrapped exception is + * propagated. If, on the other hand, the time limit is reached, we attempt to abort the call to + * the target, and throw a {@link TimeoutException} to the caller. + * + * @param callable the Callable to execute + * @param timeoutDuration with timeoutUnit, the maximum length of time to wait + * @param timeoutUnit with timeoutDuration, the maximum length of time to wait + * @return the result returned by the Callable + * @throws TimeoutException if the time limit is reached + * @throws InterruptedException if the current thread was interrupted during execution + * @throws ExecutionException if {@code callable} throws a checked exception + * @throws UncheckedExecutionException if {@code callable} throws a {@code RuntimeException} + * @throws ExecutionError if {@code callable} throws an {@code Error} + * @since 22.0 + */ + @SuppressWarnings("GoodTime") // should accept a java.time.Duration + + T callWithTimeout(Callable callable, long timeoutDuration, TimeUnit timeoutUnit) + throws TimeoutException, InterruptedException, ExecutionException; + + /** + * Invokes a specified Callable, timing out after the specified time limit. If the target method + * call finishes before the limit is reached, the return value or a wrapped exception is + * propagated. If, on the other hand, the time limit is reached, we attempt to abort the call to + * the target, and throw a {@link TimeoutException} to the caller. + * + * @param callable the Callable to execute + * @param timeout the maximum length of time to wait + * @return the result returned by the Callable + * @throws TimeoutException if the time limit is reached + * @throws InterruptedException if the current thread was interrupted during execution + * @throws ExecutionException if {@code callable} throws a checked exception + * @throws UncheckedExecutionException if {@code callable} throws a {@code RuntimeException} + * @throws ExecutionError if {@code callable} throws an {@code Error} + * @since 28.0 + */ + + default T callWithTimeout(Callable callable, Duration timeout) + throws TimeoutException, InterruptedException, ExecutionException { + return callWithTimeout(callable, toNanosSaturated(timeout), TimeUnit.NANOSECONDS); + } + + /** + * Invokes a specified Callable, timing out after the specified time limit. If the target method + * call finishes before the limit is reached, the return value or a wrapped exception is + * propagated. If, on the other hand, the time limit is reached, we attempt to abort the call to + * the target, and throw a {@link TimeoutException} to the caller. + * + *

The difference with {@link #callWithTimeout(Callable, long, TimeUnit)} is that this method + * will ignore interrupts on the current thread. + * + * @param callable the Callable to execute + * @param timeoutDuration with timeoutUnit, the maximum length of time to wait + * @param timeoutUnit with timeoutDuration, the maximum length of time to wait + * @return the result returned by the Callable + * @throws TimeoutException if the time limit is reached + * @throws ExecutionException if {@code callable} throws a checked exception + * @throws UncheckedExecutionException if {@code callable} throws a {@code RuntimeException} + * @throws ExecutionError if {@code callable} throws an {@code Error} + * @since 22.0 + */ + @SuppressWarnings("GoodTime") // should accept a java.time.Duration + + T callUninterruptiblyWithTimeout( + Callable callable, long timeoutDuration, TimeUnit timeoutUnit) + throws TimeoutException, ExecutionException; + + /** + * Invokes a specified Callable, timing out after the specified time limit. If the target method + * call finishes before the limit is reached, the return value or a wrapped exception is + * propagated. If, on the other hand, the time limit is reached, we attempt to abort the call to + * the target, and throw a {@link TimeoutException} to the caller. + * + *

The difference with {@link #callWithTimeout(Callable, Duration)} is that this method will + * ignore interrupts on the current thread. + * + * @param callable the Callable to execute + * @param timeout the maximum length of time to wait + * @return the result returned by the Callable + * @throws TimeoutException if the time limit is reached + * @throws ExecutionException if {@code callable} throws a checked exception + * @throws UncheckedExecutionException if {@code callable} throws a {@code RuntimeException} + * @throws ExecutionError if {@code callable} throws an {@code Error} + * @since 28.0 + */ + + default T callUninterruptiblyWithTimeout(Callable callable, Duration timeout) + throws TimeoutException, ExecutionException { + return callUninterruptiblyWithTimeout( + callable, toNanosSaturated(timeout), TimeUnit.NANOSECONDS); + } + + /** + * Invokes a specified Runnable, timing out after the specified time limit. If the target method + * run finishes before the limit is reached, this method returns or a wrapped exception is + * propagated. If, on the other hand, the time limit is reached, we attempt to abort the run, and + * throw a {@link TimeoutException} to the caller. + * + * @param runnable the Runnable to execute + * @param timeoutDuration with timeoutUnit, the maximum length of time to wait + * @param timeoutUnit with timeoutDuration, the maximum length of time to wait + * @throws TimeoutException if the time limit is reached + * @throws InterruptedException if the current thread was interrupted during execution + * @throws UncheckedExecutionException if {@code runnable} throws a {@code RuntimeException} + * @throws ExecutionError if {@code runnable} throws an {@code Error} + * @since 22.0 + */ + @SuppressWarnings("GoodTime") // should accept a java.time.Duration + void runWithTimeout(Runnable runnable, long timeoutDuration, TimeUnit timeoutUnit) + throws TimeoutException, InterruptedException; + + /** + * Invokes a specified Runnable, timing out after the specified time limit. If the target method + * run finishes before the limit is reached, this method returns or a wrapped exception is + * propagated. If, on the other hand, the time limit is reached, we attempt to abort the run, and + * throw a {@link TimeoutException} to the caller. + * + * @param runnable the Runnable to execute + * @param timeout the maximum length of time to wait + * @throws TimeoutException if the time limit is reached + * @throws InterruptedException if the current thread was interrupted during execution + * @throws UncheckedExecutionException if {@code runnable} throws a {@code RuntimeException} + * @throws ExecutionError if {@code runnable} throws an {@code Error} + * @since 28.0 + */ + default void runWithTimeout(Runnable runnable, Duration timeout) + throws TimeoutException, InterruptedException { + runWithTimeout(runnable, toNanosSaturated(timeout), TimeUnit.NANOSECONDS); + } + + /** + * Invokes a specified Runnable, timing out after the specified time limit. If the target method + * run finishes before the limit is reached, this method returns or a wrapped exception is + * propagated. If, on the other hand, the time limit is reached, we attempt to abort the run, and + * throw a {@link TimeoutException} to the caller. + * + *

The difference with {@link #runWithTimeout(Runnable, long, TimeUnit)} is that this method + * will ignore interrupts on the current thread. + * + * @param runnable the Runnable to execute + * @param timeoutDuration with timeoutUnit, the maximum length of time to wait + * @param timeoutUnit with timeoutDuration, the maximum length of time to wait + * @throws TimeoutException if the time limit is reached + * @throws UncheckedExecutionException if {@code runnable} throws a {@code RuntimeException} + * @throws ExecutionError if {@code runnable} throws an {@code Error} + * @since 22.0 + */ + @SuppressWarnings("GoodTime") // should accept a java.time.Duration + void runUninterruptiblyWithTimeout(Runnable runnable, long timeoutDuration, TimeUnit timeoutUnit) + throws TimeoutException; + + /** + * Invokes a specified Runnable, timing out after the specified time limit. If the target method + * run finishes before the limit is reached, this method returns or a wrapped exception is + * propagated. If, on the other hand, the time limit is reached, we attempt to abort the run, and + * throw a {@link TimeoutException} to the caller. + * + *

The difference with {@link #runWithTimeout(Runnable, Duration)} is that this method will + * ignore interrupts on the current thread. + * + * @param runnable the Runnable to execute + * @param timeout the maximum length of time to wait + * @throws TimeoutException if the time limit is reached + * @throws UncheckedExecutionException if {@code runnable} throws a {@code RuntimeException} + * @throws ExecutionError if {@code runnable} throws an {@code Error} + * @since 28.0 + */ + default void runUninterruptiblyWithTimeout(Runnable runnable, Duration timeout) + throws TimeoutException { + runUninterruptiblyWithTimeout(runnable, toNanosSaturated(timeout), TimeUnit.NANOSECONDS); + } +} diff --git a/src/main/java/com/google/common/util/concurrent/TimeoutFuture.java b/src/main/java/com/google/common/util/concurrent/TimeoutFuture.java new file mode 100644 index 0000000..b8f95a3 --- /dev/null +++ b/src/main/java/com/google/common/util/concurrent/TimeoutFuture.java @@ -0,0 +1,187 @@ +/* + * Copyright (C) 2006 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.util.concurrent; + +import static com.google.common.util.concurrent.MoreExecutors.directExecutor; + +import com.google.common.annotations.GwtIncompatible; +import com.google.common.base.Preconditions; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + + +/** + * Implementation of {@code Futures#withTimeout}. + * + *

Future that delegates to another but will finish early (via a {@link TimeoutException} wrapped + * in an {@link ExecutionException}) if the specified duration expires. The delegate future is + * interrupted and cancelled if it times out. + */ +@GwtIncompatible +final class TimeoutFuture extends FluentFuture.TrustedFuture { + static ListenableFuture create( + ListenableFuture delegate, + long time, + TimeUnit unit, + ScheduledExecutorService scheduledExecutor) { + TimeoutFuture result = new TimeoutFuture<>(delegate); + Fire fire = new Fire<>(result); + result.timer = scheduledExecutor.schedule(fire, time, unit); + delegate.addListener(fire, directExecutor()); + return result; + } + + /* + * Memory visibility of these fields. There are two cases to consider. + * + * 1. visibility of the writes to these fields to Fire.run: + * + * The initial write to delegateRef is made definitely visible via the semantics of + * addListener/SES.schedule. The later racy write in cancel() is not guaranteed to be observed, + * however that is fine since the correctness is based on the atomic state in our base class. The + * initial write to timer is never definitely visible to Fire.run since it is assigned after + * SES.schedule is called. Therefore Fire.run has to check for null. However, it should be visible + * if Fire.run is called by delegate.addListener since addListener is called after the assignment + * to timer, and importantly this is the main situation in which we need to be able to see the + * write. + * + * 2. visibility of the writes to an afterDone() call triggered by cancel(): + * + * Since these fields are non-final that means that TimeoutFuture is not being 'safely published', + * thus a motivated caller may be able to expose the reference to another thread that would then + * call cancel() and be unable to cancel the delegate. + * There are a number of ways to solve this, none of which are very pretty, and it is currently + * believed to be a purely theoretical problem (since the other actions should supply sufficient + * write-barriers). + */ + + private ListenableFuture delegateRef; + private ScheduledFuture timer; + + private TimeoutFuture(ListenableFuture delegate) { + this.delegateRef = Preconditions.checkNotNull(delegate); + } + + /** A runnable that is called when the delegate or the timer completes. */ + private static final class Fire implements Runnable { + TimeoutFuture timeoutFutureRef; + + Fire(TimeoutFuture timeoutFuture) { + this.timeoutFutureRef = timeoutFuture; + } + + @Override + public void run() { + // If either of these reads return null then we must be after a successful cancel or another + // call to this method. + TimeoutFuture timeoutFuture = timeoutFutureRef; + if (timeoutFuture == null) { + return; + } + ListenableFuture delegate = timeoutFuture.delegateRef; + if (delegate == null) { + return; + } + + /* + * If we're about to complete the TimeoutFuture, we want to release our reference to it. + * Otherwise, we'll pin it (and its result) in memory until the timeout task is GCed. (The + * need to clear our reference to the TimeoutFuture is the reason we use a *static* nested + * class with a manual reference back to the "containing" class.) + * + * This has the nice-ish side effect of limiting reentrancy: run() calls + * timeoutFuture.setException() calls run(). That reentrancy would already be harmless, since + * timeoutFuture can be set (and delegate cancelled) only once. (And "set only once" is + * important for other reasons: run() can still be invoked concurrently in different threads, + * even with the above null checks.) + */ + timeoutFutureRef = null; + if (delegate.isDone()) { + timeoutFuture.setFuture(delegate); + } else { + try { + ScheduledFuture timer = timeoutFuture.timer; + timeoutFuture.timer = null; // Don't include already elapsed delay in delegate.toString() + String message = "Timed out"; + // This try-finally block ensures that we complete the timeout future, even if attempting + // to produce the message throws (probably StackOverflowError from delegate.toString()) + try { + if (timer != null) { + long overDelayMs = Math.abs(timer.getDelay(TimeUnit.MILLISECONDS)); + if (overDelayMs > 10) { // Not all timing drift is worth reporting + message += " (timeout delayed by " + overDelayMs + " ms after scheduled time)"; + } + } + message += ": " + delegate; + } finally { + timeoutFuture.setException(new TimeoutFutureException(message)); + } + } finally { + delegate.cancel(true); + } + } + } + } + + private static final class TimeoutFutureException extends TimeoutException { + private TimeoutFutureException(String message) { + super(message); + } + + @Override + public synchronized Throwable fillInStackTrace() { + setStackTrace(new StackTraceElement[0]); + return this; // no stack trace, wouldn't be useful anyway + } + } + + @Override + protected String pendingToString() { + ListenableFuture localInputFuture = delegateRef; + ScheduledFuture localTimer = timer; + if (localInputFuture != null) { + String message = "inputFuture=[" + localInputFuture + "]"; + if (localTimer != null) { + final long delay = localTimer.getDelay(TimeUnit.MILLISECONDS); + // Negative delays look confusing in an error message + if (delay > 0) { + message += ", remaining delay=[" + delay + " ms]"; + } + } + return message; + } + return null; + } + + @Override + protected void afterDone() { + maybePropagateCancellationTo(delegateRef); + + Future localTimer = timer; + // Try to cancel the timer as an optimization. + // timer may be null if this call to run was by the timer task since there is no happens-before + // edge between the assignment to timer and an execution of the timer task. + if (localTimer != null) { + localTimer.cancel(false); + } + + delegateRef = null; + timer = null; + } +} diff --git a/src/main/java/com/google/common/util/concurrent/TrustedListenableFutureTask.java b/src/main/java/com/google/common/util/concurrent/TrustedListenableFutureTask.java new file mode 100644 index 0000000..8381e97 --- /dev/null +++ b/src/main/java/com/google/common/util/concurrent/TrustedListenableFutureTask.java @@ -0,0 +1,180 @@ +/* + * Copyright (C) 2014 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.util.concurrent; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.annotations.GwtCompatible; + +import java.util.concurrent.Callable; +import java.util.concurrent.Executors; +import java.util.concurrent.RunnableFuture; + + +/** + * A {@link RunnableFuture} that also implements the {@link ListenableFuture} interface. + * + *

This should be used in preference to {@link ListenableFutureTask} when possible for + * performance reasons. + */ +@GwtCompatible +class TrustedListenableFutureTask extends FluentFuture.TrustedFuture + implements RunnableFuture { + + static TrustedListenableFutureTask create(AsyncCallable callable) { + return new TrustedListenableFutureTask(callable); + } + + static TrustedListenableFutureTask create(Callable callable) { + return new TrustedListenableFutureTask(callable); + } + + /** + * Creates a {@code ListenableFutureTask} that will upon running, execute the given {@code + * Runnable}, and arrange that {@code get} will return the given result on successful completion. + * + * @param runnable the runnable task + * @param result the result to return on successful completion. If you don't need a particular + * result, consider using constructions of the form: {@code ListenableFuture f = + * ListenableFutureTask.create(runnable, null)} + */ + static TrustedListenableFutureTask create(Runnable runnable, V result) { + return new TrustedListenableFutureTask(Executors.callable(runnable, result)); + } + + /* + * In certain circumstances, this field might theoretically not be visible to an afterDone() call + * triggered by cancel(). For details, see the comments on the fields of TimeoutFuture. + * + *

{@code volatile} is required for j2objc transpiling: + * https://developers.google.com/j2objc/guides/j2objc-memory-model#atomicity + */ + private volatile InterruptibleTask task; + + TrustedListenableFutureTask(Callable callable) { + this.task = new TrustedFutureInterruptibleTask(callable); + } + + TrustedListenableFutureTask(AsyncCallable callable) { + this.task = new TrustedFutureInterruptibleAsyncTask(callable); + } + + @Override + public void run() { + InterruptibleTask localTask = task; + if (localTask != null) { + localTask.run(); + } + /* + * In the Async case, we may have called setFuture(pendingFuture), in which case afterDone() + * won't have been called yet. + */ + this.task = null; + } + + @Override + protected void afterDone() { + super.afterDone(); + + if (wasInterrupted()) { + InterruptibleTask localTask = task; + if (localTask != null) { + localTask.interruptTask(); + } + } + + this.task = null; + } + + @Override + protected String pendingToString() { + InterruptibleTask localTask = task; + if (localTask != null) { + return "task=[" + localTask + "]"; + } + return super.pendingToString(); + } + + + private final class TrustedFutureInterruptibleTask extends InterruptibleTask { + private final Callable callable; + + TrustedFutureInterruptibleTask(Callable callable) { + this.callable = checkNotNull(callable); + } + + @Override + final boolean isDone() { + return TrustedListenableFutureTask.this.isDone(); + } + + @Override + V runInterruptibly() throws Exception { + return callable.call(); + } + + @Override + void afterRanInterruptibly(V result, Throwable error) { + if (error == null) { + TrustedListenableFutureTask.this.set(result); + } else { + setException(error); + } + } + + @Override + String toPendingString() { + return callable.toString(); + } + } + + + private final class TrustedFutureInterruptibleAsyncTask + extends InterruptibleTask> { + private final AsyncCallable callable; + + TrustedFutureInterruptibleAsyncTask(AsyncCallable callable) { + this.callable = checkNotNull(callable); + } + + @Override + final boolean isDone() { + return TrustedListenableFutureTask.this.isDone(); + } + + @Override + ListenableFuture runInterruptibly() throws Exception { + return checkNotNull( + callable.call(), + "AsyncCallable.call returned null instead of a Future. " + + "Did you mean to return immediateFuture(null)? %s", + callable); + } + + @Override + void afterRanInterruptibly(ListenableFuture result, Throwable error) { + if (error == null) { + setFuture(result); + } else { + setException(error); + } + } + + @Override + String toPendingString() { + return callable.toString(); + } + } +} diff --git a/src/main/java/com/google/common/util/concurrent/UncaughtExceptionHandlers.java b/src/main/java/com/google/common/util/concurrent/UncaughtExceptionHandlers.java new file mode 100644 index 0000000..6e8abba --- /dev/null +++ b/src/main/java/com/google/common/util/concurrent/UncaughtExceptionHandlers.java @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2010 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.util.concurrent; + +import static java.util.logging.Level.SEVERE; + +import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.VisibleForTesting; +import java.lang.Thread.UncaughtExceptionHandler; +import java.util.Locale; +import java.util.logging.Logger; + +/** + * Factories for {@link UncaughtExceptionHandler} instances. + * + * @author Gregory Kick + * @since 8.0 + */ +@GwtIncompatible +public final class UncaughtExceptionHandlers { + private UncaughtExceptionHandlers() {} + + /** + * Returns an exception handler that exits the system. This is particularly useful for the main + * thread, which may start up other, non-daemon threads, but fail to fully initialize the + * application successfully. + * + *

Example usage: + * + *

+   * public static void main(String[] args) {
+   *   Thread.currentThread().setUncaughtExceptionHandler(UncaughtExceptionHandlers.systemExit());
+   *   ...
+   * 
+ * + *

The returned handler logs any exception at severity {@code SEVERE} and then shuts down the + * process with an exit status of 1, indicating abnormal termination. + */ + public static UncaughtExceptionHandler systemExit() { + return new Exiter(Runtime.getRuntime()); + } + + @VisibleForTesting + static final class Exiter implements UncaughtExceptionHandler { + private static final Logger logger = Logger.getLogger(Exiter.class.getName()); + + private final Runtime runtime; + + Exiter(Runtime runtime) { + this.runtime = runtime; + } + + @Override + public void uncaughtException(Thread t, Throwable e) { + try { + // cannot use FormattingLogger due to a dependency loop + logger.log( + SEVERE, String.format(Locale.ROOT, "Caught an exception in %s. Shutting down.", t), e); + } catch (Throwable errorInLogging) { + // If logging fails, e.g. due to missing memory, at least try to log the + // message and the cause for the failed logging. + System.err.println(e.getMessage()); + System.err.println(errorInLogging.getMessage()); + } finally { + runtime.exit(1); + } + } + } +} diff --git a/src/main/java/com/google/common/util/concurrent/UncheckedExecutionException.java b/src/main/java/com/google/common/util/concurrent/UncheckedExecutionException.java new file mode 100644 index 0000000..c0fbade --- /dev/null +++ b/src/main/java/com/google/common/util/concurrent/UncheckedExecutionException.java @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2011 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.util.concurrent; + +import com.google.common.annotations.GwtCompatible; + + +/** + * Unchecked variant of {@link java.util.concurrent.ExecutionException}. As with {@code + * ExecutionException}, the exception's {@linkplain #getCause() cause} comes from a failed task, + * possibly run in another thread. + * + *

{@code UncheckedExecutionException} is intended as an alternative to {@code + * ExecutionException} when the exception thrown by a task is an unchecked exception. However, it + * may also wrap a checked exception in some cases. + * + *

When wrapping an {@code Error} from another thread, prefer {@link ExecutionError}. When + * wrapping a checked exception, prefer {@code ExecutionException}. + * + * @author Charles Fry + * @since 10.0 + */ +@GwtCompatible +public class UncheckedExecutionException extends RuntimeException { + /** Creates a new instance with {@code null} as its detail message. */ + protected UncheckedExecutionException() {} + + /** Creates a new instance with the given detail message. */ + protected UncheckedExecutionException(String message) { + super(message); + } + + /** Creates a new instance with the given detail message and cause. */ + public UncheckedExecutionException(String message, Throwable cause) { + super(message, cause); + } + + /** Creates a new instance with the given cause. */ + public UncheckedExecutionException(Throwable cause) { + super(cause); + } + + private static final long serialVersionUID = 0; +} diff --git a/src/main/java/com/google/common/util/concurrent/UncheckedTimeoutException.java b/src/main/java/com/google/common/util/concurrent/UncheckedTimeoutException.java new file mode 100644 index 0000000..3c2b61a --- /dev/null +++ b/src/main/java/com/google/common/util/concurrent/UncheckedTimeoutException.java @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2006 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.util.concurrent; + +import com.google.common.annotations.GwtIncompatible; + + +/** + * Unchecked version of {@link java.util.concurrent.TimeoutException}. + * + * @author Kevin Bourrillion + * @since 1.0 + */ +@GwtIncompatible +public class UncheckedTimeoutException extends RuntimeException { + public UncheckedTimeoutException() {} + + public UncheckedTimeoutException(String message) { + super(message); + } + + public UncheckedTimeoutException(Throwable cause) { + super(cause); + } + + public UncheckedTimeoutException(String message, Throwable cause) { + super(message, cause); + } + + private static final long serialVersionUID = 0; +} diff --git a/src/main/java/com/google/common/util/concurrent/Uninterruptibles.java b/src/main/java/com/google/common/util/concurrent/Uninterruptibles.java new file mode 100644 index 0000000..4ed6faf --- /dev/null +++ b/src/main/java/com/google/common/util/concurrent/Uninterruptibles.java @@ -0,0 +1,477 @@ +/* + * Copyright (C) 2011 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.util.concurrent; + +import static com.google.common.util.concurrent.Internal.toNanosSaturated; +import static java.util.concurrent.TimeUnit.NANOSECONDS; + +import com.google.common.annotations.Beta; +import com.google.common.annotations.GwtCompatible; +import com.google.common.annotations.GwtIncompatible; +import com.google.common.base.Preconditions; + +import java.time.Duration; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.CancellationException; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; +import java.util.concurrent.Semaphore; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.concurrent.locks.Condition; + +/** + * Utilities for treating interruptible operations as uninterruptible. In all cases, if a thread is + * interrupted during such a call, the call continues to block until the result is available or the + * timeout elapses, and only then re-interrupts the thread. + * + * @author Anthony Zana + * @since 10.0 + */ +@GwtCompatible(emulated = true) +public final class Uninterruptibles { + + // Implementation Note: As of 3-7-11, the logic for each blocking/timeout + // methods is identical, save for method being invoked. + + /** Invokes {@code latch.}{@link CountDownLatch#await() await()} uninterruptibly. */ + @GwtIncompatible // concurrency + public static void awaitUninterruptibly(CountDownLatch latch) { + boolean interrupted = false; + try { + while (true) { + try { + latch.await(); + return; + } catch (InterruptedException e) { + interrupted = true; + } + } + } finally { + if (interrupted) { + Thread.currentThread().interrupt(); + } + } + } + + /** + * Invokes {@code latch.}{@link CountDownLatch#await(long, TimeUnit) await(timeout, unit)} + * uninterruptibly. + * + * @since 28.0 + */ + // TODO(cpovirk): Consider being more strict. + @GwtIncompatible // concurrency + @Beta + public static boolean awaitUninterruptibly(CountDownLatch latch, Duration timeout) { + return awaitUninterruptibly(latch, toNanosSaturated(timeout), TimeUnit.NANOSECONDS); + } + + /** + * Invokes {@code latch.}{@link CountDownLatch#await(long, TimeUnit) await(timeout, unit)} + * uninterruptibly. + */ + // TODO(cpovirk): Consider being more strict. + @GwtIncompatible // concurrency + @SuppressWarnings("GoodTime") // should accept a java.time.Duration + public static boolean awaitUninterruptibly(CountDownLatch latch, long timeout, TimeUnit unit) { + boolean interrupted = false; + try { + long remainingNanos = unit.toNanos(timeout); + long end = System.nanoTime() + remainingNanos; + + while (true) { + try { + // CountDownLatch treats negative timeouts just like zero. + return latch.await(remainingNanos, NANOSECONDS); + } catch (InterruptedException e) { + interrupted = true; + remainingNanos = end - System.nanoTime(); + } + } + } finally { + if (interrupted) { + Thread.currentThread().interrupt(); + } + } + } + + /** + * Invokes {@code condition.}{@link Condition#await(long, TimeUnit) await(timeout, unit)} + * uninterruptibly. + * + * @since 28.0 + */ + @GwtIncompatible // concurrency + @Beta + public static boolean awaitUninterruptibly(Condition condition, Duration timeout) { + return awaitUninterruptibly(condition, toNanosSaturated(timeout), TimeUnit.NANOSECONDS); + } + + /** + * Invokes {@code condition.}{@link Condition#await(long, TimeUnit) await(timeout, unit)} + * uninterruptibly. + * + * @since 23.6 + */ + @GwtIncompatible // concurrency + @SuppressWarnings("GoodTime") // should accept a java.time.Duration + public static boolean awaitUninterruptibly(Condition condition, long timeout, TimeUnit unit) { + boolean interrupted = false; + try { + long remainingNanos = unit.toNanos(timeout); + long end = System.nanoTime() + remainingNanos; + + while (true) { + try { + return condition.await(remainingNanos, NANOSECONDS); + } catch (InterruptedException e) { + interrupted = true; + remainingNanos = end - System.nanoTime(); + } + } + } finally { + if (interrupted) { + Thread.currentThread().interrupt(); + } + } + } + + /** Invokes {@code toJoin.}{@link Thread#join() join()} uninterruptibly. */ + @GwtIncompatible // concurrency + public static void joinUninterruptibly(Thread toJoin) { + boolean interrupted = false; + try { + while (true) { + try { + toJoin.join(); + return; + } catch (InterruptedException e) { + interrupted = true; + } + } + } finally { + if (interrupted) { + Thread.currentThread().interrupt(); + } + } + } + + /** + * Invokes {@code unit.}{@link TimeUnit#timedJoin(Thread, long) timedJoin(toJoin, timeout)} + * uninterruptibly. + * + * @since 28.0 + */ + @GwtIncompatible // concurrency + @Beta + public static void joinUninterruptibly(Thread toJoin, Duration timeout) { + joinUninterruptibly(toJoin, toNanosSaturated(timeout), TimeUnit.NANOSECONDS); + } + + /** + * Invokes {@code unit.}{@link TimeUnit#timedJoin(Thread, long) timedJoin(toJoin, timeout)} + * uninterruptibly. + */ + @GwtIncompatible // concurrency + @SuppressWarnings("GoodTime") // should accept a java.time.Duration + public static void joinUninterruptibly(Thread toJoin, long timeout, TimeUnit unit) { + Preconditions.checkNotNull(toJoin); + boolean interrupted = false; + try { + long remainingNanos = unit.toNanos(timeout); + long end = System.nanoTime() + remainingNanos; + while (true) { + try { + // TimeUnit.timedJoin() treats negative timeouts just like zero. + NANOSECONDS.timedJoin(toJoin, remainingNanos); + return; + } catch (InterruptedException e) { + interrupted = true; + remainingNanos = end - System.nanoTime(); + } + } + } finally { + if (interrupted) { + Thread.currentThread().interrupt(); + } + } + } + + /** + * Invokes {@code future.}{@link Future#get() get()} uninterruptibly. + * + *

Similar methods: + * + *

    + *
  • To retrieve a result from a {@code Future} that is already done, use {@link + * Futures#getDone Futures.getDone}. + *
  • To treat {@link InterruptedException} uniformly with other exceptions, use {@link + * Futures#getChecked(Future, Class) Futures.getChecked}. + *
  • To get uninterruptibility and remove checked exceptions, use {@link + * Futures#getUnchecked}. + *
+ * + * @throws ExecutionException if the computation threw an exception + * @throws CancellationException if the computation was cancelled + */ + + public static V getUninterruptibly(Future future) throws ExecutionException { + boolean interrupted = false; + try { + while (true) { + try { + return future.get(); + } catch (InterruptedException e) { + interrupted = true; + } + } + } finally { + if (interrupted) { + Thread.currentThread().interrupt(); + } + } + } + + /** + * Invokes {@code future.}{@link Future#get(long, TimeUnit) get(timeout, unit)} uninterruptibly. + * + *

Similar methods: + * + *

    + *
  • To retrieve a result from a {@code Future} that is already done, use {@link + * Futures#getDone Futures.getDone}. + *
  • To treat {@link InterruptedException} uniformly with other exceptions, use {@link + * Futures#getChecked(Future, Class, long, TimeUnit) Futures.getChecked}. + *
  • To get uninterruptibility and remove checked exceptions, use {@link + * Futures#getUnchecked}. + *
+ * + * @throws ExecutionException if the computation threw an exception + * @throws CancellationException if the computation was cancelled + * @throws TimeoutException if the wait timed out + * @since 28.0 + */ + + @GwtIncompatible // java.time.Duration + @Beta + public static V getUninterruptibly(Future future, Duration timeout) + throws ExecutionException, TimeoutException { + return getUninterruptibly(future, toNanosSaturated(timeout), TimeUnit.NANOSECONDS); + } + + /** + * Invokes {@code future.}{@link Future#get(long, TimeUnit) get(timeout, unit)} uninterruptibly. + * + *

Similar methods: + * + *

    + *
  • To retrieve a result from a {@code Future} that is already done, use {@link + * Futures#getDone Futures.getDone}. + *
  • To treat {@link InterruptedException} uniformly with other exceptions, use {@link + * Futures#getChecked(Future, Class, long, TimeUnit) Futures.getChecked}. + *
  • To get uninterruptibility and remove checked exceptions, use {@link + * Futures#getUnchecked}. + *
+ * + * @throws ExecutionException if the computation threw an exception + * @throws CancellationException if the computation was cancelled + * @throws TimeoutException if the wait timed out + */ + + @GwtIncompatible // TODO + @SuppressWarnings("GoodTime") // should accept a java.time.Duration + public static V getUninterruptibly(Future future, long timeout, TimeUnit unit) + throws ExecutionException, TimeoutException { + boolean interrupted = false; + try { + long remainingNanos = unit.toNanos(timeout); + long end = System.nanoTime() + remainingNanos; + + while (true) { + try { + // Future treats negative timeouts just like zero. + return future.get(remainingNanos, NANOSECONDS); + } catch (InterruptedException e) { + interrupted = true; + remainingNanos = end - System.nanoTime(); + } + } + } finally { + if (interrupted) { + Thread.currentThread().interrupt(); + } + } + } + + /** Invokes {@code queue.}{@link BlockingQueue#take() take()} uninterruptibly. */ + @GwtIncompatible // concurrency + public static E takeUninterruptibly(BlockingQueue queue) { + boolean interrupted = false; + try { + while (true) { + try { + return queue.take(); + } catch (InterruptedException e) { + interrupted = true; + } + } + } finally { + if (interrupted) { + Thread.currentThread().interrupt(); + } + } + } + + /** + * Invokes {@code queue.}{@link BlockingQueue#put(Object) put(element)} uninterruptibly. + * + * @throws ClassCastException if the class of the specified element prevents it from being added + * to the given queue + * @throws IllegalArgumentException if some property of the specified element prevents it from + * being added to the given queue + */ + @GwtIncompatible // concurrency + public static void putUninterruptibly(BlockingQueue queue, E element) { + boolean interrupted = false; + try { + while (true) { + try { + queue.put(element); + return; + } catch (InterruptedException e) { + interrupted = true; + } + } + } finally { + if (interrupted) { + Thread.currentThread().interrupt(); + } + } + } + + // TODO(user): Support Sleeper somehow (wrapper or interface method)? + /** + * Invokes {@code unit.}{@link TimeUnit#sleep(long) sleep(sleepFor)} uninterruptibly. + * + * @since 28.0 + */ + @GwtIncompatible // concurrency + @Beta + public static void sleepUninterruptibly(Duration sleepFor) { + sleepUninterruptibly(toNanosSaturated(sleepFor), TimeUnit.NANOSECONDS); + } + + // TODO(user): Support Sleeper somehow (wrapper or interface method)? + /** Invokes {@code unit.}{@link TimeUnit#sleep(long) sleep(sleepFor)} uninterruptibly. */ + @GwtIncompatible // concurrency + @SuppressWarnings("GoodTime") // should accept a java.time.Duration + public static void sleepUninterruptibly(long sleepFor, TimeUnit unit) { + boolean interrupted = false; + try { + long remainingNanos = unit.toNanos(sleepFor); + long end = System.nanoTime() + remainingNanos; + while (true) { + try { + // TimeUnit.sleep() treats negative timeouts just like zero. + NANOSECONDS.sleep(remainingNanos); + return; + } catch (InterruptedException e) { + interrupted = true; + remainingNanos = end - System.nanoTime(); + } + } + } finally { + if (interrupted) { + Thread.currentThread().interrupt(); + } + } + } + + /** + * Invokes {@code semaphore.}{@link Semaphore#tryAcquire(int, long, TimeUnit) tryAcquire(1, + * timeout, unit)} uninterruptibly. + * + * @since 28.0 + */ + @GwtIncompatible // concurrency + @Beta + public static boolean tryAcquireUninterruptibly(Semaphore semaphore, Duration timeout) { + return tryAcquireUninterruptibly(semaphore, toNanosSaturated(timeout), TimeUnit.NANOSECONDS); + } + + /** + * Invokes {@code semaphore.}{@link Semaphore#tryAcquire(int, long, TimeUnit) tryAcquire(1, + * timeout, unit)} uninterruptibly. + * + * @since 18.0 + */ + @GwtIncompatible // concurrency + @SuppressWarnings("GoodTime") // should accept a java.time.Duration + public static boolean tryAcquireUninterruptibly( + Semaphore semaphore, long timeout, TimeUnit unit) { + return tryAcquireUninterruptibly(semaphore, 1, timeout, unit); + } + + /** + * Invokes {@code semaphore.}{@link Semaphore#tryAcquire(int, long, TimeUnit) tryAcquire(permits, + * timeout, unit)} uninterruptibly. + * + * @since 28.0 + */ + @GwtIncompatible // concurrency + @Beta + public static boolean tryAcquireUninterruptibly( + Semaphore semaphore, int permits, Duration timeout) { + return tryAcquireUninterruptibly( + semaphore, permits, toNanosSaturated(timeout), TimeUnit.NANOSECONDS); + } + + /** + * Invokes {@code semaphore.}{@link Semaphore#tryAcquire(int, long, TimeUnit) tryAcquire(permits, + * timeout, unit)} uninterruptibly. + * + * @since 18.0 + */ + @GwtIncompatible // concurrency + @SuppressWarnings("GoodTime") // should accept a java.time.Duration + public static boolean tryAcquireUninterruptibly( + Semaphore semaphore, int permits, long timeout, TimeUnit unit) { + boolean interrupted = false; + try { + long remainingNanos = unit.toNanos(timeout); + long end = System.nanoTime() + remainingNanos; + + while (true) { + try { + // Semaphore treats negative timeouts just like zero. + return semaphore.tryAcquire(permits, remainingNanos, NANOSECONDS); + } catch (InterruptedException e) { + interrupted = true; + remainingNanos = end - System.nanoTime(); + } + } + } finally { + if (interrupted) { + Thread.currentThread().interrupt(); + } + } + } + + // TODO(user): Add support for waitUninterruptibly. + + private Uninterruptibles() {} +} diff --git a/src/main/java/com/google/common/util/concurrent/WrappingExecutorService.java b/src/main/java/com/google/common/util/concurrent/WrappingExecutorService.java new file mode 100644 index 0000000..3f1c2ac --- /dev/null +++ b/src/main/java/com/google/common/util/concurrent/WrappingExecutorService.java @@ -0,0 +1,162 @@ +/* + * Copyright (C) 2011 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.util.concurrent; + +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.base.Throwables.throwIfUnchecked; + +import com.google.common.annotations.GwtIncompatible; +import com.google.common.collect.ImmutableList; + +import java.util.Collection; +import java.util.List; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +/** + * An abstract {@code ExecutorService} that allows subclasses to {@linkplain #wrapTask(Callable) + * wrap} tasks before they are submitted to the underlying executor. + * + *

Note that task wrapping may occur even if the task is never executed. + * + *

For delegation without task-wrapping, see {@link ForwardingExecutorService}. + * + * @author Chris Nokleberg + */ + // TODO(cpovirk): Consider being more strict. +@GwtIncompatible +abstract class WrappingExecutorService implements ExecutorService { + private final ExecutorService delegate; + + protected WrappingExecutorService(ExecutorService delegate) { + this.delegate = checkNotNull(delegate); + } + + /** + * Wraps a {@code Callable} for submission to the underlying executor. This method is also applied + * to any {@code Runnable} passed to the default implementation of {@link #wrapTask(Runnable)}. + */ + protected abstract Callable wrapTask(Callable callable); + + /** + * Wraps a {@code Runnable} for submission to the underlying executor. The default implementation + * delegates to {@link #wrapTask(Callable)}. + */ + protected Runnable wrapTask(Runnable command) { + final Callable wrapped = wrapTask(Executors.callable(command, null)); + return new Runnable() { + @Override + public void run() { + try { + wrapped.call(); + } catch (Exception e) { + throwIfUnchecked(e); + throw new RuntimeException(e); + } + } + }; + } + + /** + * Wraps a collection of tasks. + * + * @throws NullPointerException if any element of {@code tasks} is null + */ + private ImmutableList> wrapTasks(Collection> tasks) { + ImmutableList.Builder> builder = ImmutableList.builder(); + for (Callable task : tasks) { + builder.add(wrapTask(task)); + } + return builder.build(); + } + + // These methods wrap before delegating. + @Override + public final void execute(Runnable command) { + delegate.execute(wrapTask(command)); + } + + @Override + public final Future submit(Callable task) { + return delegate.submit(wrapTask(checkNotNull(task))); + } + + @Override + public final Future submit(Runnable task) { + return delegate.submit(wrapTask(task)); + } + + @Override + public final Future submit(Runnable task, T result) { + return delegate.submit(wrapTask(task), result); + } + + @Override + public final List> invokeAll(Collection> tasks) + throws InterruptedException { + return delegate.invokeAll(wrapTasks(tasks)); + } + + @Override + public final List> invokeAll( + Collection> tasks, long timeout, TimeUnit unit) + throws InterruptedException { + return delegate.invokeAll(wrapTasks(tasks), timeout, unit); + } + + @Override + public final T invokeAny(Collection> tasks) + throws InterruptedException, ExecutionException { + return delegate.invokeAny(wrapTasks(tasks)); + } + + @Override + public final T invokeAny(Collection> tasks, long timeout, TimeUnit unit) + throws InterruptedException, ExecutionException, TimeoutException { + return delegate.invokeAny(wrapTasks(tasks), timeout, unit); + } + + // The remaining methods just delegate. + + @Override + public final void shutdown() { + delegate.shutdown(); + } + + @Override + public final List shutdownNow() { + return delegate.shutdownNow(); + } + + @Override + public final boolean isShutdown() { + return delegate.isShutdown(); + } + + @Override + public final boolean isTerminated() { + return delegate.isTerminated(); + } + + @Override + public final boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException { + return delegate.awaitTermination(timeout, unit); + } +} diff --git a/src/main/java/com/google/common/util/concurrent/WrappingScheduledExecutorService.java b/src/main/java/com/google/common/util/concurrent/WrappingScheduledExecutorService.java new file mode 100644 index 0000000..0e82962 --- /dev/null +++ b/src/main/java/com/google/common/util/concurrent/WrappingScheduledExecutorService.java @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2013 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.util.concurrent; + +import com.google.common.annotations.GwtIncompatible; + +import java.util.concurrent.Callable; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; + +/** + * An abstract {@code ScheduledExecutorService} that allows subclasses to {@linkplain + * #wrapTask(Callable) wrap} tasks before they are submitted to the underlying executor. + * + *

Note that task wrapping may occur even if the task is never executed. + * + * @author Luke Sandberg + */ + // TODO(cpovirk): Consider being more strict. +@GwtIncompatible +abstract class WrappingScheduledExecutorService extends WrappingExecutorService + implements ScheduledExecutorService { + final ScheduledExecutorService delegate; + + protected WrappingScheduledExecutorService(ScheduledExecutorService delegate) { + super(delegate); + this.delegate = delegate; + } + + @Override + public final ScheduledFuture schedule(Runnable command, long delay, TimeUnit unit) { + return delegate.schedule(wrapTask(command), delay, unit); + } + + @Override + public final ScheduledFuture schedule(Callable task, long delay, TimeUnit unit) { + return delegate.schedule(wrapTask(task), delay, unit); + } + + @Override + public final ScheduledFuture scheduleAtFixedRate( + Runnable command, long initialDelay, long period, TimeUnit unit) { + return delegate.scheduleAtFixedRate(wrapTask(command), initialDelay, period, unit); + } + + @Override + public final ScheduledFuture scheduleWithFixedDelay( + Runnable command, long initialDelay, long delay, TimeUnit unit) { + return delegate.scheduleWithFixedDelay(wrapTask(command), initialDelay, delay, unit); + } +} diff --git a/src/main/java/com/google/common/util/concurrent/internal/InternalFutureFailureAccess.java b/src/main/java/com/google/common/util/concurrent/internal/InternalFutureFailureAccess.java new file mode 100644 index 0000000..7cc8489 --- /dev/null +++ b/src/main/java/com/google/common/util/concurrent/internal/InternalFutureFailureAccess.java @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2018 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.util.concurrent.internal; + +/** + * A future that, if it fails, may optionally provide access to the cause of the failure. + * + *

This class is used only for micro-optimization. Standard {@code Future} utilities benefit from + * this optimization, so there is no need to specialize methods to return or accept this type + * instead of {@code ListenableFuture}. + * + *

This class is GWT-compatible. + * + * @since {@code com.google.guava:failureaccess:1.0}, which was added as a dependency of Guava in + * Guava 27.0 + */ +public abstract class InternalFutureFailureAccess { + /** Constructor for use by subclasses. */ + protected InternalFutureFailureAccess() {} + + /** + * Usually returns {@code null} but, if this {@code Future} has failed, may optionally + * return the cause of the failure. "Failure" means specifically "completed with an exception"; it + * does not include "was cancelled." To be explicit: If this method returns a non-null value, + * then: + * + *

    + *
  • {@code isDone()} must return {@code true} + *
  • {@code isCancelled()} must return {@code false} + *
  • {@code get()} must not block, and it must throw an {@code ExecutionException} with the + * return value of this method as its cause + *
+ * + *

This method is {@code protected} so that classes like {@code + * com.google.common.util.concurrent.SettableFuture} do not expose it to their users as an + * instance method. In the unlikely event that you need to call this method, call {@link + * InternalFutures#tryInternalFastPathGetFailure(InternalFutureFailureAccess)}. + */ + protected abstract Throwable tryInternalFastPathGetFailure(); +} diff --git a/src/main/java/com/google/common/util/concurrent/internal/InternalFutures.java b/src/main/java/com/google/common/util/concurrent/internal/InternalFutures.java new file mode 100644 index 0000000..42df5ec --- /dev/null +++ b/src/main/java/com/google/common/util/concurrent/internal/InternalFutures.java @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2018 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.util.concurrent.internal; + +/** + * Static utilities for {@link InternalFutureFailureAccess}. Most users will never need to use this + * class. + * + *

This class is GWT-compatible. + * + * @since {@code com.google.guava:failureaccess:1.0}, which was added as a dependency of Guava in + * Guava 27.0 + */ +public final class InternalFutures { + /** + * Usually returns {@code null} but, if the given {@code Future} has failed, may optionally + * return the cause of the failure. "Failure" means specifically "completed with an exception"; it + * does not include "was cancelled." To be explicit: If this method returns a non-null value, + * then: + * + *

    + *
  • {@code isDone()} must return {@code true} + *
  • {@code isCancelled()} must return {@code false} + *
  • {@code get()} must not block, and it must throw an {@code ExecutionException} with the + * return value of this method as its cause + *
+ */ + public static Throwable tryInternalFastPathGetFailure(InternalFutureFailureAccess future) { + return future.tryInternalFastPathGetFailure(); + } + + private InternalFutures() {} +} diff --git a/src/main/java/com/google/common/xml/XmlEscapers.java b/src/main/java/com/google/common/xml/XmlEscapers.java new file mode 100644 index 0000000..b25fcfc --- /dev/null +++ b/src/main/java/com/google/common/xml/XmlEscapers.java @@ -0,0 +1,145 @@ +/* + * Copyright (C) 2009 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.xml; + +import com.google.common.annotations.Beta; +import com.google.common.annotations.GwtCompatible; +import com.google.common.escape.Escaper; +import com.google.common.escape.Escapers; + +/** + * {@code Escaper} instances suitable for strings to be included in XML attribute values and + * elements' text contents. When possible, avoid manual escaping by using templating systems and + * high-level APIs that provide autoescaping. For example, consider XOM or JDOM. + * + *

Note: Currently the escapers provided by this class do not escape any characters + * outside the ASCII character range. Unlike HTML escaping the XML escapers will not escape + * non-ASCII characters to their numeric entity replacements. These XML escapers provide the minimal + * level of escaping to ensure that the output can be safely included in a Unicode XML document. + * + * + *

For details on the behavior of the escapers in this class, see sections 2.2 and 2.4 of the XML specification. + * + * @author Alex Matevossian + * @author David Beaumont + * @since 15.0 + */ +@Beta +@GwtCompatible +public class XmlEscapers { + private XmlEscapers() {} + + private static final char MIN_ASCII_CONTROL_CHAR = 0x00; + private static final char MAX_ASCII_CONTROL_CHAR = 0x1F; + + // For each xxxEscaper() method, please add links to external reference pages + // that are considered authoritative for the behavior of that escaper. + + /** + * Returns an {@link Escaper} instance that escapes special characters in a string so it can + * safely be included in an XML document as element content. See section 2.4 of the XML specification. + * + *

Note: Double and single quotes are not escaped, so it is not safe to use this + * escaper to escape attribute values. Use {@link #xmlContentEscaper} if the output can appear in + * element content or {@link #xmlAttributeEscaper} in attribute values. + * + *

This escaper substitutes {@code 0xFFFD} for non-whitespace control characters and the + * character values {@code 0xFFFE} and {@code 0xFFFF} which are not permitted in XML. For more + * detail see section 2.2 of + * the XML specification. + * + *

This escaper does not escape non-ASCII characters to their numeric character references + * (NCR). Any non-ASCII characters appearing in the input will be preserved in the output. + * Specifically "\r" (carriage return) is preserved in the output, which may result in it being + * silently converted to "\n" when the XML is parsed. + * + *

This escaper does not treat surrogate pairs specially and does not perform Unicode + * validation on its input. + */ + public static Escaper xmlContentEscaper() { + return XML_CONTENT_ESCAPER; + } + + /** + * Returns an {@link Escaper} instance that escapes special characters in a string so it can + * safely be included in XML document as an attribute value. See section 3.3.3 of the XML + * specification. + * + *

This escaper substitutes {@code 0xFFFD} for non-whitespace control characters and the + * character values {@code 0xFFFE} and {@code 0xFFFF} which are not permitted in XML. For more + * detail see section 2.2 of + * the XML specification. + * + *

This escaper does not escape non-ASCII characters to their numeric character references + * (NCR). However, horizontal tab {@code '\t'}, line feed {@code '\n'} and carriage return {@code + * '\r'} are escaped to a corresponding NCR {@code " "}, {@code " "}, and {@code " "} + * respectively. Any other non-ASCII characters appearing in the input will be preserved in the + * output. + * + *

This escaper does not treat surrogate pairs specially and does not perform Unicode + * validation on its input. + */ + public static Escaper xmlAttributeEscaper() { + return XML_ATTRIBUTE_ESCAPER; + } + + private static final Escaper XML_ESCAPER; + private static final Escaper XML_CONTENT_ESCAPER; + private static final Escaper XML_ATTRIBUTE_ESCAPER; + + static { + Escapers.Builder builder = Escapers.builder(); + // The char values \uFFFE and \uFFFF are explicitly not allowed in XML + // (Unicode code points above \uFFFF are represented via surrogate pairs + // which means they are treated as pairs of safe characters). + builder.setSafeRange(Character.MIN_VALUE, '\uFFFD'); + // Unsafe characters are replaced with the Unicode replacement character. + builder.setUnsafeReplacement("\uFFFD"); + + /* + * Except for \n, \t, and \r, all ASCII control characters are replaced with the Unicode + * replacement character. + * + * Implementation note: An alternative to the following would be to make a map that simply + * replaces the allowed ASCII whitespace characters with themselves and to set the minimum safe + * character to 0x20. However this would slow down the escaping of simple strings that contain + * \t, \n, or \r. + */ + for (char c = MIN_ASCII_CONTROL_CHAR; c <= MAX_ASCII_CONTROL_CHAR; c++) { + if (c != '\t' && c != '\n' && c != '\r') { + builder.addEscape(c, "\uFFFD"); + } + } + + // Build the content escaper first and then add quote escaping for the + // general escaper. + builder.addEscape('&', "&"); + builder.addEscape('<', "<"); + builder.addEscape('>', ">"); + XML_CONTENT_ESCAPER = builder.build(); + builder.addEscape('\'', "'"); + builder.addEscape('"', """); + XML_ESCAPER = builder.build(); + builder.addEscape('\t', " "); + builder.addEscape('\n', " "); + builder.addEscape('\r', " "); + XML_ATTRIBUTE_ESCAPER = builder.build(); + } +} diff --git a/src/main/java/module-info.java b/src/main/java/module-info.java new file mode 100644 index 0000000..3d1e890 --- /dev/null +++ b/src/main/java/module-info.java @@ -0,0 +1,23 @@ +module org.xbib.guava { + + exports com.google.common.annotations; + exports com.google.common.base; + exports com.google.common.cache; + exports com.google.common.collect; + exports com.google.common.escape; + exports com.google.common.eventbus; + exports com.google.common.graph; + exports com.google.common.hash; + exports com.google.common.html; + exports com.google.common.io; + exports com.google.common.math; + exports com.google.common.net; + exports com.google.common.primitives; + exports com.google.common.reflect; + exports com.google.common.util.concurrent; + exports com.google.common.util.concurrent.internal; + exports com.google.common.xml; + + requires java.logging; + +}

This class compares primitive {@code double} values in methods such as + * {@link #compareAndSet} by comparing their bitwise representation using {@link + * Double#doubleToRawLongBits}, which differs from both the primitive double {@code ==} operator and + * from {@link Double#equals}, as if implemented by: + * + *

This workaround should be removed at a distant future time when we no longer support Java + * versions earlier than 8. + */ + private static final class TypeVariableInvocationHandler implements InvocationHandler { + private static final ImmutableMap typeVariableMethods; + + static { + ImmutableMap.Builder builder = ImmutableMap.builder(); + for (Method method : TypeVariableImpl.class.getMethods()) { + if (method.getDeclaringClass().equals(TypeVariableImpl.class)) { + try { + method.setAccessible(true); + } catch (AccessControlException e) { + // OK: the method is accessible to us anyway. The setAccessible call is only for + // unusual execution environments where that might not be true. + } + builder.put(method.getName(), method); + } + } + typeVariableMethods = builder.build(); + } + + private final TypeVariableImpl typeVariableImpl; + + TypeVariableInvocationHandler(TypeVariableImpl typeVariableImpl) { + this.typeVariableImpl = typeVariableImpl; + } + + @Override + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + String methodName = method.getName(); + Method typeVariableMethod = typeVariableMethods.get(methodName); + if (typeVariableMethod == null) { + throw new UnsupportedOperationException(methodName); + } else { + try { + return typeVariableMethod.invoke(typeVariableImpl, args); + } catch (InvocationTargetException e) { + throw e.getCause(); + } + } + } + } + + private static final class TypeVariableImpl { + + private final D genericDeclaration; + private final String name; + private final ImmutableList bounds; + + TypeVariableImpl(D genericDeclaration, String name, Type[] bounds) { + disallowPrimitiveType(bounds, "bound for type variable"); + this.genericDeclaration = checkNotNull(genericDeclaration); + this.name = checkNotNull(name); + this.bounds = ImmutableList.copyOf(bounds); + } + + public Type[] getBounds() { + return toArray(bounds); + } + + public D getGenericDeclaration() { + return genericDeclaration; + } + + public String getName() { + return name; + } + + public String getTypeName() { + return name; + } + + @Override + public String toString() { + return name; + } + + @Override + public int hashCode() { + return genericDeclaration.hashCode() ^ name.hashCode(); + } + + @Override + public boolean equals(Object obj) { + if (NativeTypeVariableEquals.NATIVE_TYPE_VARIABLE_ONLY) { + // equal only to our TypeVariable implementation with identical bounds + if (obj != null + && Proxy.isProxyClass(obj.getClass()) + && Proxy.getInvocationHandler(obj) instanceof TypeVariableInvocationHandler) { + TypeVariableInvocationHandler typeVariableInvocationHandler = + (TypeVariableInvocationHandler) Proxy.getInvocationHandler(obj); + TypeVariableImpl that = typeVariableInvocationHandler.typeVariableImpl; + return name.equals(that.getName()) + && genericDeclaration.equals(that.getGenericDeclaration()) + && bounds.equals(that.bounds); + } + return false; + } else { + // equal to any TypeVariable implementation regardless of bounds + if (obj instanceof TypeVariable) { + TypeVariable that = (TypeVariable) obj; + return name.equals(that.getName()) + && genericDeclaration.equals(that.getGenericDeclaration()); + } + return false; + } + } + } + + static final class WildcardTypeImpl implements WildcardType, Serializable { + + private final ImmutableList lowerBounds; + private final ImmutableList upperBounds; + + WildcardTypeImpl(Type[] lowerBounds, Type[] upperBounds) { + disallowPrimitiveType(lowerBounds, "lower bound for wildcard"); + disallowPrimitiveType(upperBounds, "upper bound for wildcard"); + this.lowerBounds = JavaVersion.CURRENT.usedInGenericType(lowerBounds); + this.upperBounds = JavaVersion.CURRENT.usedInGenericType(upperBounds); + } + + @Override + public Type[] getLowerBounds() { + return toArray(lowerBounds); + } + + @Override + public Type[] getUpperBounds() { + return toArray(upperBounds); + } + + @Override + public boolean equals(Object obj) { + if (obj instanceof WildcardType) { + WildcardType that = (WildcardType) obj; + return lowerBounds.equals(Arrays.asList(that.getLowerBounds())) + && upperBounds.equals(Arrays.asList(that.getUpperBounds())); + } + return false; + } + + @Override + public int hashCode() { + return lowerBounds.hashCode() ^ upperBounds.hashCode(); + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder("?"); + for (Type lowerBound : lowerBounds) { + builder.append(" super ").append(JavaVersion.CURRENT.typeName(lowerBound)); + } + for (Type upperBound : filterUpperBounds(upperBounds)) { + builder.append(" extends ").append(JavaVersion.CURRENT.typeName(upperBound)); + } + return builder.toString(); + } + + private static final long serialVersionUID = 0; + } + + private static Type[] toArray(Collection types) { + return types.toArray(new Type[0]); + } + + private static Iterable filterUpperBounds(Iterable bounds) { + return Iterables.filter(bounds, Predicates.not(Predicates.equalTo(Object.class))); + } + + private static void disallowPrimitiveType(Type[] types, String usedAs) { + for (Type type : types) { + if (type instanceof Class) { + Class cls = (Class) type; + checkArgument(!cls.isPrimitive(), "Primitive type '%s' used as %s", cls, usedAs); + } + } + } + + /** Returns the {@code Class} object of arrays with {@code componentType}. */ + static Class getArrayClass(Class componentType) { + // TODO(user): This is not the most efficient way to handle generic + // arrays, but is there another way to extract the array class in a + // non-hacky way (i.e. using String value class names- "[L...")? + return Array.newInstance(componentType, 0).getClass(); + } + + // TODO(benyu): Once behavior is the same for all Java versions we support, delete this. + enum JavaVersion { + JAVA6 { + @Override + GenericArrayType newArrayType(Type componentType) { + return new GenericArrayTypeImpl(componentType); + } + + @Override + Type usedInGenericType(Type type) { + checkNotNull(type); + if (type instanceof Class) { + Class cls = (Class) type; + if (cls.isArray()) { + return new GenericArrayTypeImpl(cls.getComponentType()); + } + } + return type; + } + }, + JAVA7 { + @Override + Type newArrayType(Type componentType) { + if (componentType instanceof Class) { + return getArrayClass((Class) componentType); + } else { + return new GenericArrayTypeImpl(componentType); + } + } + + @Override + Type usedInGenericType(Type type) { + return checkNotNull(type); + } + }, + JAVA8 { + @Override + Type newArrayType(Type componentType) { + return JAVA7.newArrayType(componentType); + } + + @Override + Type usedInGenericType(Type type) { + return JAVA7.usedInGenericType(type); + } + + @Override + String typeName(Type type) { + try { + Method getTypeName = Type.class.getMethod("getTypeName"); + return (String) getTypeName.invoke(type); + } catch (NoSuchMethodException e) { + throw new AssertionError("Type.getTypeName should be available in Java 8"); + /* + * Do not merge the 2 catch blocks below. javac would infer a type of + * ReflectiveOperationException, which Animal Sniffer would reject. (Old versions of + * Android don't *seem* to mind, but there might be edge cases of which we're unaware.) + */ + } catch (InvocationTargetException e) { + throw new RuntimeException(e); + } catch (IllegalAccessException e) { + throw new RuntimeException(e); + } + } + }, + JAVA9 { + @Override + Type newArrayType(Type componentType) { + return JAVA8.newArrayType(componentType); + } + + @Override + Type usedInGenericType(Type type) { + return JAVA8.usedInGenericType(type); + } + + @Override + String typeName(Type type) { + return JAVA8.typeName(type); + } + + @Override + boolean jdkTypeDuplicatesOwnerName() { + return false; + } + }; + + static final JavaVersion CURRENT; + + static { + if (AnnotatedElement.class.isAssignableFrom(TypeVariable.class)) { + if (new TypeCapture>() {}.capture() + .toString() + .contains("java.util.Map.java.util.Map")) { + CURRENT = JAVA8; + } else { + CURRENT = JAVA9; + } + } else if (new TypeCapture() {}.capture() instanceof Class) { + CURRENT = JAVA7; + } else { + CURRENT = JAVA6; + } + } + + abstract Type newArrayType(Type componentType); + + abstract Type usedInGenericType(Type type); + + final ImmutableList usedInGenericType(Type[] types) { + ImmutableList.Builder builder = ImmutableList.builder(); + for (Type type : types) { + builder.add(usedInGenericType(type)); + } + return builder.build(); + } + + String typeName(Type type) { + return Types.toString(type); + } + + boolean jdkTypeDuplicatesOwnerName() { + return true; + } + } + + /** + * Per issue 1635, + * In JDK 1.7.0_51-b13, {@link TypeVariableImpl#equals(Object)} is changed to no longer be equal + * to custom TypeVariable implementations. As a result, we need to make sure our TypeVariable + * implementation respects symmetry. Moreover, we don't want to reconstruct a native type variable + * {@code } using our implementation unless some of its bounds have changed in resolution. This + * avoids creating unequal TypeVariable implementation unnecessarily. When the bounds do change, + * however, it's fine for the synthetic TypeVariable to be unequal to any native TypeVariable + * anyway. + */ + static final class NativeTypeVariableEquals { + static final boolean NATIVE_TYPE_VARIABLE_ONLY = + !NativeTypeVariableEquals.class.getTypeParameters()[0].equals( + newArtificialTypeVariable(NativeTypeVariableEquals.class, "X")); + } + + private Types() {} +} diff --git a/src/main/java/com/google/common/util/concurrent/AbstractCatchingFuture.java b/src/main/java/com/google/common/util/concurrent/AbstractCatchingFuture.java new file mode 100644 index 0000000..9a9a888 --- /dev/null +++ b/src/main/java/com/google/common/util/concurrent/AbstractCatchingFuture.java @@ -0,0 +1,212 @@ +/* + * Copyright (C) 2006 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.util.concurrent; + +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.util.concurrent.Futures.getDone; +import static com.google.common.util.concurrent.MoreExecutors.rejectionPropagatingExecutor; +import static com.google.common.util.concurrent.Platform.isInstanceOfThrowableClass; + +import com.google.common.annotations.GwtCompatible; +import com.google.common.base.Function; + +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Executor; + + +/** Implementations of {@code Futures.catching*}. */ +@GwtCompatible +abstract class AbstractCatchingFuture + extends FluentFuture.TrustedFuture implements Runnable { + static ListenableFuture create( + ListenableFuture input, + Class exceptionType, + Function fallback, + Executor executor) { + CatchingFuture future = new CatchingFuture<>(input, exceptionType, fallback); + input.addListener(future, rejectionPropagatingExecutor(executor, future)); + return future; + } + + static ListenableFuture create( + ListenableFuture input, + Class exceptionType, + AsyncFunction fallback, + Executor executor) { + AsyncCatchingFuture future = new AsyncCatchingFuture<>(input, exceptionType, fallback); + input.addListener(future, rejectionPropagatingExecutor(executor, future)); + return future; + } + + /* + * In certain circumstances, this field might theoretically not be visible to an afterDone() call + * triggered by cancel(). For details, see the comments on the fields of TimeoutFuture. + */ + ListenableFuture inputFuture; + Class exceptionType; + F fallback; + + AbstractCatchingFuture( + ListenableFuture inputFuture, Class exceptionType, F fallback) { + this.inputFuture = checkNotNull(inputFuture); + this.exceptionType = checkNotNull(exceptionType); + this.fallback = checkNotNull(fallback); + } + + @Override + public final void run() { + ListenableFuture localInputFuture = inputFuture; + Class localExceptionType = exceptionType; + F localFallback = fallback; + if (localInputFuture == null + | localExceptionType == null + | localFallback == null + | isCancelled()) { + return; + } + inputFuture = null; + + // For an explanation of the cases here, see the comments on AbstractTransformFuture.run. + V sourceResult = null; + Throwable throwable = null; + try { + sourceResult = getDone(localInputFuture); + } catch (ExecutionException e) { + throwable = checkNotNull(e.getCause()); + } catch (Throwable e) { // this includes cancellation exception + throwable = e; + } + + if (throwable == null) { + set(sourceResult); + return; + } + + if (!isInstanceOfThrowableClass(throwable, localExceptionType)) { + setFuture(localInputFuture); + // TODO(cpovirk): Test that fallback is not run in this case. + return; + } + + @SuppressWarnings("unchecked") // verified safe by isInstanceOfThrowableClass + X castThrowable = (X) throwable; + T fallbackResult; + try { + fallbackResult = doFallback(localFallback, castThrowable); + } catch (Throwable t) { + setException(t); + return; + } finally { + exceptionType = null; + fallback = null; + } + + setResult(fallbackResult); + } + + @Override + protected String pendingToString() { + ListenableFuture localInputFuture = inputFuture; + Class localExceptionType = exceptionType; + F localFallback = fallback; + String superString = super.pendingToString(); + String resultString = ""; + if (localInputFuture != null) { + resultString = "inputFuture=[" + localInputFuture + "], "; + } + if (localExceptionType != null && localFallback != null) { + return resultString + + "exceptionType=[" + + localExceptionType + + "], fallback=[" + + localFallback + + "]"; + } else if (superString != null) { + return resultString + superString; + } + return null; + } + + /** Template method for subtypes to actually run the fallback. */ + abstract T doFallback(F fallback, X throwable) throws Exception; + + /** Template method for subtypes to actually set the result. */ + abstract void setResult(T result); + + @Override + protected final void afterDone() { + maybePropagateCancellationTo(inputFuture); + this.inputFuture = null; + this.exceptionType = null; + this.fallback = null; + } + + /** + * An {@link AbstractCatchingFuture} that delegates to an {@link AsyncFunction} and {@link + * #setFuture(ListenableFuture)}. + */ + private static final class AsyncCatchingFuture + extends AbstractCatchingFuture< + V, X, AsyncFunction, ListenableFuture> { + AsyncCatchingFuture( + ListenableFuture input, + Class exceptionType, + AsyncFunction fallback) { + super(input, exceptionType, fallback); + } + + @Override + ListenableFuture doFallback( + AsyncFunction fallback, X cause) throws Exception { + ListenableFuture replacement = fallback.apply(cause); + checkNotNull( + replacement, + "AsyncFunction.apply returned null instead of a Future. " + + "Did you mean to return immediateFuture(null)? %s", + fallback); + return replacement; + } + + @Override + void setResult(ListenableFuture result) { + setFuture(result); + } + } + + /** + * An {@link AbstractCatchingFuture} that delegates to a {@link Function} and {@link + * #set(Object)}. + */ + private static final class CatchingFuture + extends AbstractCatchingFuture, V> { + CatchingFuture( + ListenableFuture input, + Class exceptionType, + Function fallback) { + super(input, exceptionType, fallback); + } + + @Override + + V doFallback(Function fallback, X cause) throws Exception { + return fallback.apply(cause); + } + + @Override + void setResult(V result) { + set(result); + } + } +} diff --git a/src/main/java/com/google/common/util/concurrent/AbstractExecutionThreadService.java b/src/main/java/com/google/common/util/concurrent/AbstractExecutionThreadService.java new file mode 100644 index 0000000..4c788db --- /dev/null +++ b/src/main/java/com/google/common/util/concurrent/AbstractExecutionThreadService.java @@ -0,0 +1,243 @@ +/* + * Copyright (C) 2009 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.util.concurrent; + +import com.google.common.annotations.Beta; +import com.google.common.annotations.GwtIncompatible; +import com.google.common.base.Supplier; + +import java.util.concurrent.Executor; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * Base class for services that can implement {@link #startUp}, {@link #run} and {@link #shutDown} + * methods. This class uses a single thread to execute the service; consider {@link AbstractService} + * if you would like to manage any threading manually. + * + * @author Jesse Wilson + * @since 1.0 + */ +@Beta +@GwtIncompatible +public abstract class AbstractExecutionThreadService implements Service { + private static final Logger logger = + Logger.getLogger(AbstractExecutionThreadService.class.getName()); + + /* use AbstractService for state management */ + private final Service delegate = + new AbstractService() { + @Override + protected final void doStart() { + Executor executor = + MoreExecutors.renamingDecorator( + executor(), + new Supplier() { + @Override + public String get() { + return serviceName(); + } + }); + executor.execute( + new Runnable() { + @Override + public void run() { + try { + startUp(); + notifyStarted(); + // If stopAsync() is called while starting we may be in the STOPPING state in + // which case we should skip right down to shutdown. + if (isRunning()) { + try { + AbstractExecutionThreadService.this.run(); + } catch (Throwable t) { + try { + shutDown(); + } catch (Exception ignored) { + // TODO(lukes): if guava ever moves to java7, this would be a good + // candidate for a suppressed exception, or maybe we could generalize + // Closer.Suppressor + logger.log( + Level.WARNING, + "Error while attempting to shut down the service after failure.", + ignored); + } + notifyFailed(t); + return; + } + } + + shutDown(); + notifyStopped(); + } catch (Throwable t) { + notifyFailed(t); + } + } + }); + } + + @Override + protected void doStop() { + triggerShutdown(); + } + + @Override + public String toString() { + return AbstractExecutionThreadService.this.toString(); + } + }; + + /** Constructor for use by subclasses. */ + protected AbstractExecutionThreadService() {} + + /** + * Start the service. This method is invoked on the execution thread. + * + *

If this type is a type variable or wildcard, upper bounds that are themselves type variables + * aren't included (their super interfaces and superclasses are). + */ + public final TypeSet getTypes() { + return new TypeSet(); + } + + /** + * Returns the generic form of {@code superclass}. For example, if this is {@code + * ArrayList}, {@code Iterable} is returned given the input {@code + * Iterable.class}. + */ + public final TypeToken getSupertype(Class superclass) { + checkArgument( + this.someRawTypeIsSubclassOf(superclass), + "%s is not a super class of %s", + superclass, + this); + if (runtimeType instanceof TypeVariable) { + return getSupertypeFromUpperBounds(superclass, ((TypeVariable) runtimeType).getBounds()); + } + if (runtimeType instanceof WildcardType) { + return getSupertypeFromUpperBounds(superclass, ((WildcardType) runtimeType).getUpperBounds()); + } + if (superclass.isArray()) { + return getArraySupertype(superclass); + } + @SuppressWarnings("unchecked") // resolved supertype + TypeToken supertype = + (TypeToken) resolveSupertype(toGenericType(superclass).runtimeType); + return supertype; + } + + /** + * Returns subtype of {@code this} with {@code subclass} as the raw class. For example, if this is + * {@code Iterable} and {@code subclass} is {@code List}, {@code List} is + * returned. + */ + public final TypeToken getSubtype(Class subclass) { + checkArgument( + !(runtimeType instanceof TypeVariable), "Cannot get subtype of type variable <%s>", this); + if (runtimeType instanceof WildcardType) { + return getSubtypeFromLowerBounds(subclass, ((WildcardType) runtimeType).getLowerBounds()); + } + // unwrap array type if necessary + if (isArray()) { + return getArraySubtype(subclass); + } + // At this point, it's either a raw class or parameterized type. + checkArgument( + getRawType().isAssignableFrom(subclass), "%s isn't a subclass of %s", subclass, this); + Type resolvedTypeArgs = resolveTypeArgsForSubclass(subclass); + @SuppressWarnings("unchecked") // guarded by the isAssignableFrom() statement above + TypeToken subtype = (TypeToken) of(resolvedTypeArgs); + checkArgument( + subtype.isSubtypeOf(this), "%s does not appear to be a subtype of %s", subtype, this); + return subtype; + } + + /** + * Returns true if this type is a supertype of the given {@code type}. "Supertype" is defined + * according to the rules for type + * arguments introduced with Java generics. + * + * @since 19.0 + */ + public final boolean isSupertypeOf(TypeToken type) { + return type.isSubtypeOf(getType()); + } + + /** + * Returns true if this type is a supertype of the given {@code type}. "Supertype" is defined + * according to the rules for type + * arguments introduced with Java generics. + * + * @since 19.0 + */ + public final boolean isSupertypeOf(Type type) { + return of(type).isSubtypeOf(getType()); + } + + /** + * Returns true if this type is a subtype of the given {@code type}. "Subtype" is defined + * according to the rules for type + * arguments introduced with Java generics. + * + * @since 19.0 + */ + public final boolean isSubtypeOf(TypeToken type) { + return isSubtypeOf(type.getType()); + } + + /** + * Returns true if this type is a subtype of the given {@code type}. "Subtype" is defined + * according to the rules for type + * arguments introduced with Java generics. + * + * @since 19.0 + */ + public final boolean isSubtypeOf(Type supertype) { + checkNotNull(supertype); + if (supertype instanceof WildcardType) { + // if 'supertype' is , 'this' can be: + // Foo, SubFoo, . + // if 'supertype' is , nothing is a subtype. + return any(((WildcardType) supertype).getLowerBounds()).isSupertypeOf(runtimeType); + } + // if 'this' is wildcard, it's a suptype of to 'supertype' if any of its "extends" + // bounds is a subtype of 'supertype'. + if (runtimeType instanceof WildcardType) { + // is of no use in checking 'from' being a subtype of 'to'. + return any(((WildcardType) runtimeType).getUpperBounds()).isSubtypeOf(supertype); + } + // if 'this' is type variable, it's a subtype if any of its "extends" + // bounds is a subtype of 'supertype'. + if (runtimeType instanceof TypeVariable) { + return runtimeType.equals(supertype) + || any(((TypeVariable) runtimeType).getBounds()).isSubtypeOf(supertype); + } + if (runtimeType instanceof GenericArrayType) { + return of(supertype).isSupertypeOfArray((GenericArrayType) runtimeType); + } + // Proceed to regular Type subtype check + if (supertype instanceof Class) { + return this.someRawTypeIsSubclassOf((Class) supertype); + } else if (supertype instanceof ParameterizedType) { + return this.isSubtypeOfParameterizedType((ParameterizedType) supertype); + } else if (supertype instanceof GenericArrayType) { + return this.isSubtypeOfArrayType((GenericArrayType) supertype); + } else { // to instanceof TypeVariable + return false; + } + } + + /** + * Returns true if this type is known to be an array type, such as {@code int[]}, {@code T[]}, + * {@code []>} etc. + */ + public final boolean isArray() { + return getComponentType() != null; + } + + /** + * Returns true if this type is one of the nine primitive types (including {@code void}). + * + * @since 15.0 + */ + public final boolean isPrimitive() { + return (runtimeType instanceof Class) && ((Class) runtimeType).isPrimitive(); + } + + /** + * Returns the corresponding wrapper type if this is a primitive type; otherwise returns {@code + * this} itself. Idempotent. + * + * @since 15.0 + */ + public final TypeToken wrap() { + if (isPrimitive()) { + @SuppressWarnings("unchecked") // this is a primitive class + Class type = (Class) runtimeType; + return of(Primitives.wrap(type)); + } + return this; + } + + private boolean isWrapper() { + return Primitives.allWrapperTypes().contains(runtimeType); + } + + /** + * Returns the corresponding primitive type if this is a wrapper type; otherwise returns {@code + * this} itself. Idempotent. + * + * @since 15.0 + */ + public final TypeToken unwrap() { + if (isWrapper()) { + @SuppressWarnings("unchecked") // this is a wrapper class + Class type = (Class) runtimeType; + return of(Primitives.unwrap(type)); + } + return this; + } + + /** + * Returns the array component type if this type represents an array ({@code int[]}, {@code T[]}, + * {@code []>} etc.), or else {@code null} is returned. + */ + public final TypeToken getComponentType() { + Type componentType = Types.getComponentType(runtimeType); + if (componentType == null) { + return null; + } + return of(componentType); + } + + /** + * Returns the {@link Invokable} for {@code method}, which must be a member of {@code T}. + * + * @since 14.0 + */ + public final Invokable method(Method method) { + checkArgument( + this.someRawTypeIsSubclassOf(method.getDeclaringClass()), + "%s not declared by %s", + method, + this); + return new Invokable.MethodInvokable(method) { + @Override + Type getGenericReturnType() { + return getCovariantTypeResolver().resolveType(super.getGenericReturnType()); + } + + @Override + Type[] getGenericParameterTypes() { + return getInvariantTypeResolver().resolveTypesInPlace(super.getGenericParameterTypes()); + } + + @Override + Type[] getGenericExceptionTypes() { + return getCovariantTypeResolver().resolveTypesInPlace(super.getGenericExceptionTypes()); + } + + @Override + public TypeToken getOwnerType() { + return TypeToken.this; + } + + @Override + public String toString() { + return getOwnerType() + "." + super.toString(); + } + }; + } + + /** + * Returns the {@link Invokable} for {@code constructor}, which must be a member of {@code T}. + * + * @since 14.0 + */ + public final Invokable constructor(Constructor constructor) { + checkArgument( + constructor.getDeclaringClass() == getRawType(), + "%s not declared by %s", + constructor, + getRawType()); + return new Invokable.ConstructorInvokable(constructor) { + @Override + Type getGenericReturnType() { + return getCovariantTypeResolver().resolveType(super.getGenericReturnType()); + } + + @Override + Type[] getGenericParameterTypes() { + return getInvariantTypeResolver().resolveTypesInPlace(super.getGenericParameterTypes()); + } + + @Override + Type[] getGenericExceptionTypes() { + return getCovariantTypeResolver().resolveTypesInPlace(super.getGenericExceptionTypes()); + } + + @Override + public TypeToken getOwnerType() { + return TypeToken.this; + } + + @Override + public String toString() { + return getOwnerType() + "(" + Joiner.on(", ").join(getGenericParameterTypes()) + ")"; + } + }; + } + + /** + * The set of interfaces and classes that {@code T} is or is a subtype of. {@link Object} is not + * included in the set if this type is an interface. + * + * @since 13.0 + */ + public class TypeSet extends ForwardingSet> implements Serializable { + + private transient ImmutableSet> types; + + TypeSet() {} + + /** Returns the types that are interfaces implemented by this type. */ + public TypeSet interfaces() { + return new InterfaceSet(this); + } + + /** Returns the types that are classes. */ + public TypeSet classes() { + return new ClassSet(); + } + + @Override + protected Set> delegate() { + ImmutableSet> filteredTypes = types; + if (filteredTypes == null) { + // Java has no way to express ? super T when we parameterize TypeToken vs. Class. + @SuppressWarnings({"unchecked", "rawtypes"}) + ImmutableList> collectedTypes = + (ImmutableList) TypeCollector.FOR_GENERIC_TYPE.collectTypes(TypeToken.this); + return (types = + FluentIterable.from(collectedTypes) + .filter(TypeFilter.IGNORE_TYPE_VARIABLE_OR_WILDCARD) + .toSet()); + } else { + return filteredTypes; + } + } + + /** Returns the raw types of the types in this set, in the same order. */ + public Set> rawTypes() { + // Java has no way to express ? super T when we parameterize TypeToken vs. Class. + @SuppressWarnings({"unchecked", "rawtypes"}) + ImmutableList> collectedTypes = + (ImmutableList) TypeCollector.FOR_RAW_TYPE.collectTypes(getRawTypes()); + return ImmutableSet.copyOf(collectedTypes); + } + + private static final long serialVersionUID = 0; + } + + private final class InterfaceSet extends TypeSet { + + private final transient TypeSet allTypes; + private transient ImmutableSet> interfaces; + + InterfaceSet(TypeSet allTypes) { + this.allTypes = allTypes; + } + + @Override + protected Set> delegate() { + ImmutableSet> result = interfaces; + if (result == null) { + return (interfaces = + FluentIterable.from(allTypes).filter(TypeFilter.INTERFACE_ONLY).toSet()); + } else { + return result; + } + } + + @Override + public TypeSet interfaces() { + return this; + } + + @Override + public Set> rawTypes() { + // Java has no way to express ? super T when we parameterize TypeToken vs. Class. + @SuppressWarnings({"unchecked", "rawtypes"}) + ImmutableList> collectedTypes = + (ImmutableList) TypeCollector.FOR_RAW_TYPE.collectTypes(getRawTypes()); + return FluentIterable.from(collectedTypes) + .filter( + new Predicate>() { + @Override + public boolean apply(Class type) { + return type.isInterface(); + } + }) + .toSet(); + } + + @Override + public TypeSet classes() { + throw new UnsupportedOperationException("interfaces().classes() not supported."); + } + + private Object readResolve() { + return getTypes().interfaces(); + } + + private static final long serialVersionUID = 0; + } + + private final class ClassSet extends TypeSet { + + private transient ImmutableSet> classes; + + @Override + protected Set> delegate() { + ImmutableSet> result = classes; + if (result == null) { + @SuppressWarnings({"unchecked", "rawtypes"}) + ImmutableList> collectedTypes = + (ImmutableList) + TypeCollector.FOR_GENERIC_TYPE.classesOnly().collectTypes(TypeToken.this); + return (classes = + FluentIterable.from(collectedTypes) + .filter(TypeFilter.IGNORE_TYPE_VARIABLE_OR_WILDCARD) + .toSet()); + } else { + return result; + } + } + + @Override + public TypeSet classes() { + return this; + } + + @Override + public Set> rawTypes() { + // Java has no way to express ? super T when we parameterize TypeToken vs. Class. + @SuppressWarnings({"unchecked", "rawtypes"}) + ImmutableList> collectedTypes = + (ImmutableList) TypeCollector.FOR_RAW_TYPE.classesOnly().collectTypes(getRawTypes()); + return ImmutableSet.copyOf(collectedTypes); + } + + @Override + public TypeSet interfaces() { + throw new UnsupportedOperationException("classes().interfaces() not supported."); + } + + private Object readResolve() { + return getTypes().classes(); + } + + private static final long serialVersionUID = 0; + } + + private enum TypeFilter implements Predicate> { + IGNORE_TYPE_VARIABLE_OR_WILDCARD { + @Override + public boolean apply(TypeToken type) { + return !(type.runtimeType instanceof TypeVariable + || type.runtimeType instanceof WildcardType); + } + }, + INTERFACE_ONLY { + @Override + public boolean apply(TypeToken type) { + return type.getRawType().isInterface(); + } + } + } + + /** + * Returns true if {@code o} is another {@code TypeToken} that represents the same {@link Type}. + */ + @Override + public boolean equals(Object o) { + if (o instanceof TypeToken) { + TypeToken that = (TypeToken) o; + return runtimeType.equals(that.runtimeType); + } + return false; + } + + @Override + public int hashCode() { + return runtimeType.hashCode(); + } + + @Override + public String toString() { + return Types.toString(runtimeType); + } + + /** Implemented to support serialization of subclasses. */ + protected Object writeReplace() { + // TypeResolver just transforms the type to our own impls that are Serializable + // except TypeVariable. + return of(new TypeResolver().resolveType(runtimeType)); + } + + /** + * Ensures that this type token doesn't contain type variables, which can cause unchecked type + * errors for callers like {@link TypeToInstanceMap}. + */ + + final TypeToken rejectTypeVariables() { + new TypeVisitor() { + @Override + void visitTypeVariable(TypeVariable type) { + throw new IllegalArgumentException( + runtimeType + "contains a type variable and is not safe for the operation"); + } + + @Override + void visitWildcardType(WildcardType type) { + visit(type.getLowerBounds()); + visit(type.getUpperBounds()); + } + + @Override + void visitParameterizedType(ParameterizedType type) { + visit(type.getActualTypeArguments()); + visit(type.getOwnerType()); + } + + @Override + void visitGenericArrayType(GenericArrayType type) { + visit(type.getGenericComponentType()); + } + }.visit(runtimeType); + return this; + } + + private boolean someRawTypeIsSubclassOf(Class superclass) { + for (Class rawType : getRawTypes()) { + if (superclass.isAssignableFrom(rawType)) { + return true; + } + } + return false; + } + + private boolean isSubtypeOfParameterizedType(ParameterizedType supertype) { + Class matchedClass = of(supertype).getRawType(); + if (!someRawTypeIsSubclassOf(matchedClass)) { + return false; + } + TypeVariable[] typeVars = matchedClass.getTypeParameters(); + Type[] supertypeArgs = supertype.getActualTypeArguments(); + for (int i = 0; i < typeVars.length; i++) { + Type subtypeParam = getCovariantTypeResolver().resolveType(typeVars[i]); + // If 'supertype' is "List" + // and 'this' is StringArrayList, + // First step is to figure out StringArrayList "is-a" List where = String. + // String is then matched against , the supertypeArgs[0]. + if (!of(subtypeParam).is(supertypeArgs[i], typeVars[i])) { + return false; + } + } + // We only care about the case when the supertype is a non-static inner class + // in which case we need to make sure the subclass's owner type is a subtype of the + // supertype's owner. + return Modifier.isStatic(((Class) supertype.getRawType()).getModifiers()) + || supertype.getOwnerType() == null + || isOwnedBySubtypeOf(supertype.getOwnerType()); + } + + private boolean isSubtypeOfArrayType(GenericArrayType supertype) { + if (runtimeType instanceof Class) { + Class fromClass = (Class) runtimeType; + if (!fromClass.isArray()) { + return false; + } + return of(fromClass.getComponentType()).isSubtypeOf(supertype.getGenericComponentType()); + } else if (runtimeType instanceof GenericArrayType) { + GenericArrayType fromArrayType = (GenericArrayType) runtimeType; + return of(fromArrayType.getGenericComponentType()) + .isSubtypeOf(supertype.getGenericComponentType()); + } else { + return false; + } + } + + private boolean isSupertypeOfArray(GenericArrayType subtype) { + if (runtimeType instanceof Class) { + Class thisClass = (Class) runtimeType; + if (!thisClass.isArray()) { + return thisClass.isAssignableFrom(Object[].class); + } + return of(subtype.getGenericComponentType()).isSubtypeOf(thisClass.getComponentType()); + } else if (runtimeType instanceof GenericArrayType) { + return of(subtype.getGenericComponentType()) + .isSubtypeOf(((GenericArrayType) runtimeType).getGenericComponentType()); + } else { + return false; + } + } + + /** + * {@code A.is(B)} is defined as {@code Foo.isSubtypeOf(Foo)}. + * + *

{@code []} will be returned for ArrayList's constructor. When both the class and the + * constructor have type parameters, the class parameters are prepended before those of the + * constructor's. This is an arbitrary rule since no existing language spec mandates one way or + * the other. From the declaration syntax, the class type parameter appears first, but the call + * syntax may show up in opposite order such as {@code new Foo()}. + */ + @Override + public final TypeVariable[] getTypeParameters() { + TypeVariable[] declaredByClass = getDeclaringClass().getTypeParameters(); + TypeVariable[] declaredByConstructor = constructor.getTypeParameters(); + TypeVariable[] result = + new TypeVariable[declaredByClass.length + declaredByConstructor.length]; + System.arraycopy(declaredByClass, 0, result, 0, declaredByClass.length); + System.arraycopy( + declaredByConstructor, 0, result, declaredByClass.length, declaredByConstructor.length); + return result; + } + + @Override + public final boolean isOverridable() { + return false; + } + + @Override + public final boolean isVarArgs() { + return constructor.isVarArgs(); + } + + private boolean mayNeedHiddenThis() { + Class declaringClass = constructor.getDeclaringClass(); + if (declaringClass.getEnclosingConstructor() != null) { + // Enclosed in a constructor, needs hidden this + return true; + } + Method enclosingMethod = declaringClass.getEnclosingMethod(); + if (enclosingMethod != null) { + // Enclosed in a method, if it's not static, must need hidden this. + return !Modifier.isStatic(enclosingMethod.getModifiers()); + } else { + // Strictly, this doesn't necessarily indicate a hidden 'this' in the case of + // static initializer. But there seems no way to tell in that case. :( + // This may cause issues when an anonymous class is created inside a static initializer, + // and the class's constructor's first parameter happens to be the enclosing class. + // In such case, we may mistakenly think that the class is within a non-static context + // and the first parameter is the hidden 'this'. + return declaringClass.getEnclosingClass() != null + && !Modifier.isStatic(declaringClass.getModifiers()); + } + } + } +} diff --git a/src/main/java/com/google/common/reflect/MutableTypeToInstanceMap.java b/src/main/java/com/google/common/reflect/MutableTypeToInstanceMap.java new file mode 100644 index 0000000..a12a07c --- /dev/null +++ b/src/main/java/com/google/common/reflect/MutableTypeToInstanceMap.java @@ -0,0 +1,164 @@ +/* + * Copyright (C) 2012 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.reflect; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.annotations.Beta; +import com.google.common.base.Function; +import com.google.common.collect.ForwardingMap; +import com.google.common.collect.ForwardingMapEntry; +import com.google.common.collect.ForwardingSet; +import com.google.common.collect.Iterators; +import com.google.common.collect.Maps; + +import java.util.Iterator; +import java.util.Map; +import java.util.Set; + + +/** + * A mutable type-to-instance map. See also {@link ImmutableTypeToInstanceMap}. + * + * @author Ben Yu + * @since 13.0 + */ +@Beta +public final class MutableTypeToInstanceMap extends ForwardingMap, B> + implements TypeToInstanceMap { + + private final Map, B> backingMap = Maps.newHashMap(); + + @Override + public T getInstance(Class type) { + return trustedGet(TypeToken.of(type)); + } + + @Override + public T getInstance(TypeToken type) { + return trustedGet(type.rejectTypeVariables()); + } + + @Override + + public T putInstance(Class type, T value) { + return trustedPut(TypeToken.of(type), value); + } + + @Override + + public T putInstance(TypeToken type, T value) { + return trustedPut(type.rejectTypeVariables(), value); + } + + /** + * Not supported. Use {@link #putInstance} instead. + * + * @deprecated unsupported operation + * @throws UnsupportedOperationException always + */ + + @Deprecated + @Override + public B put(TypeToken key, B value) { + throw new UnsupportedOperationException("Please use putInstance() instead."); + } + + /** + * Not supported. Use {@link #putInstance} instead. + * + * @deprecated unsupported operation + * @throws UnsupportedOperationException always + */ + @Deprecated + @Override + public void putAll(Map, ? extends B> map) { + throw new UnsupportedOperationException("Please use putInstance() instead."); + } + + @Override + public Set, B>> entrySet() { + return UnmodifiableEntry.transformEntries(super.entrySet()); + } + + @Override + protected Map, B> delegate() { + return backingMap; + } + + @SuppressWarnings("unchecked") // value could not get in if not a T + private T trustedPut(TypeToken type, T value) { + return (T) backingMap.put(type, value); + } + + @SuppressWarnings("unchecked") // value could not get in if not a T + private T trustedGet(TypeToken type) { + return (T) backingMap.get(type); + } + + private static final class UnmodifiableEntry extends ForwardingMapEntry { + + private final Entry delegate; + + static Set> transformEntries(final Set> entries) { + return new ForwardingSet>() { + @Override + protected Set> delegate() { + return entries; + } + + @Override + public Iterator> iterator() { + return UnmodifiableEntry.transformEntries(super.iterator()); + } + + @Override + public Object[] toArray() { + return standardToArray(); + } + + @Override + public T[] toArray(T[] array) { + return standardToArray(array); + } + }; + } + + private static Iterator> transformEntries(Iterator> entries) { + return Iterators.transform( + entries, + new Function, Entry>() { + @Override + public Entry apply(Entry entry) { + return new UnmodifiableEntry<>(entry); + } + }); + } + + private UnmodifiableEntry(java.util.Map.Entry delegate) { + this.delegate = checkNotNull(delegate); + } + + @Override + protected Entry delegate() { + return delegate; + } + + @Override + public V setValue(V value) { + throw new UnsupportedOperationException(); + } + } +} diff --git a/src/main/java/com/google/common/reflect/Parameter.java b/src/main/java/com/google/common/reflect/Parameter.java new file mode 100644 index 0000000..9193661 --- /dev/null +++ b/src/main/java/com/google/common/reflect/Parameter.java @@ -0,0 +1,139 @@ +/* + * Copyright (C) 2012 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.reflect; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.annotations.Beta; +import com.google.common.collect.FluentIterable; +import com.google.common.collect.ImmutableList; +import java.lang.annotation.Annotation; +import java.lang.reflect.AnnotatedElement; +import java.lang.reflect.AnnotatedType; + + +/** + * Represents a method or constructor parameter. + * + * @author Ben Yu + * @since 14.0 + */ +@Beta +public final class Parameter implements AnnotatedElement { + + private final Invokable declaration; + private final int position; + private final TypeToken type; + private final ImmutableList annotations; + private final AnnotatedType annotatedType; + + Parameter( + Invokable declaration, + int position, + TypeToken type, + Annotation[] annotations, + AnnotatedType annotatedType) { + this.declaration = declaration; + this.position = position; + this.type = type; + this.annotations = ImmutableList.copyOf(annotations); + this.annotatedType = annotatedType; + } + + /** Returns the type of the parameter. */ + public TypeToken getType() { + return type; + } + + /** Returns the {@link Invokable} that declares this parameter. */ + public Invokable getDeclaringInvokable() { + return declaration; + } + + @Override + public boolean isAnnotationPresent(Class annotationType) { + return getAnnotation(annotationType) != null; + } + + @Override + public A getAnnotation(Class annotationType) { + checkNotNull(annotationType); + for (Annotation annotation : annotations) { + if (annotationType.isInstance(annotation)) { + return annotationType.cast(annotation); + } + } + return null; + } + + @Override + public Annotation[] getAnnotations() { + return getDeclaredAnnotations(); + } + + /** @since 18.0 */ + // @Override on JDK8 + @Override + public A[] getAnnotationsByType(Class annotationType) { + return getDeclaredAnnotationsByType(annotationType); + } + + /** @since 18.0 */ + // @Override on JDK8 + @Override + public Annotation[] getDeclaredAnnotations() { + return annotations.toArray(new Annotation[0]); + } + + /** @since 18.0 */ + // @Override on JDK8 + @Override + public A getDeclaredAnnotation(Class annotationType) { + checkNotNull(annotationType); + return FluentIterable.from(annotations).filter(annotationType).first().orNull(); + } + + /** @since 18.0 */ + // @Override on JDK8 + @Override + public A[] getDeclaredAnnotationsByType(Class annotationType) { + return FluentIterable.from(annotations).filter(annotationType).toArray(annotationType); + } + + /** @since 25.1 */ + // @Override on JDK8 + public AnnotatedType getAnnotatedType() { + return annotatedType; + } + + @Override + public boolean equals(Object obj) { + if (obj instanceof Parameter) { + Parameter that = (Parameter) obj; + return position == that.position && declaration.equals(that.declaration); + } + return false; + } + + @Override + public int hashCode() { + return position; + } + + @Override + public String toString() { + return type + " arg" + position; + } +} diff --git a/src/main/java/com/google/common/reflect/Reflection.java b/src/main/java/com/google/common/reflect/Reflection.java new file mode 100644 index 0000000..4ad5dff --- /dev/null +++ b/src/main/java/com/google/common/reflect/Reflection.java @@ -0,0 +1,91 @@ +/* + * Copyright (C) 2005 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.reflect; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.annotations.Beta; +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Proxy; + +/** + * Static utilities relating to Java reflection. + * + * @since 12.0 + */ +@Beta +public final class Reflection { + + /** + * Returns the package name of {@code clazz} according to the Java Language Specification (section + * 6.7). Unlike {@link Class#getPackage}, this method only parses the class name, without + * attempting to define the {@link Package} and hence load files. + */ + public static String getPackageName(Class clazz) { + return getPackageName(clazz.getName()); + } + + /** + * Returns the package name of {@code classFullName} according to the Java Language Specification + * (section 6.7). Unlike {@link Class#getPackage}, this method only parses the class name, without + * attempting to define the {@link Package} and hence load files. + */ + public static String getPackageName(String classFullName) { + int lastDot = classFullName.lastIndexOf('.'); + return (lastDot < 0) ? "" : classFullName.substring(0, lastDot); + } + + /** + * Ensures that the given classes are initialized, as described in JLS Section + * 12.4.2. + * + *

See the Guava User Guide article on {@code Splitter}. + * + * @author Julien Silland + * @author Jesse Wilson + * @author Kevin Bourrillion + * @author Louis Wasserman + * @since 1.0 + */ +@GwtCompatible(emulated = true) +public final class Splitter { + private final CharMatcher trimmer; + private final boolean omitEmptyStrings; + private final Strategy strategy; + private final int limit; + + private Splitter(Strategy strategy) { + this(strategy, false, CharMatcher.none(), Integer.MAX_VALUE); + } + + private Splitter(Strategy strategy, boolean omitEmptyStrings, CharMatcher trimmer, int limit) { + this.strategy = strategy; + this.omitEmptyStrings = omitEmptyStrings; + this.trimmer = trimmer; + this.limit = limit; + } + + /** + * Returns a splitter that uses the given single-character separator. For example, {@code + * Splitter.on(',').split("foo,,bar")} returns an iterable containing {@code ["foo", "", "bar"]}. + * + * @param separator the character to recognize as a separator + * @return a splitter, with default settings, that recognizes that separator + */ + public static Splitter on(char separator) { + return on(CharMatcher.is(separator)); + } + + /** + * Returns a splitter that considers any single character matched by the given {@code CharMatcher} + * to be a separator. For example, {@code + * Splitter.on(CharMatcher.anyOf(";,")).split("foo,;bar,quux")} returns an iterable containing + * {@code ["foo", "", "bar", "quux"]}. + * + * @param separatorMatcher a {@link CharMatcher} that determines whether a character is a + * separator + * @return a splitter, with default settings, that uses this matcher + */ + public static Splitter on(final CharMatcher separatorMatcher) { + checkNotNull(separatorMatcher); + + return new Splitter( + new Strategy() { + @Override + public SplittingIterator iterator(Splitter splitter, final CharSequence toSplit) { + return new SplittingIterator(splitter, toSplit) { + @Override + int separatorStart(int start) { + return separatorMatcher.indexIn(toSplit, start); + } + + @Override + int separatorEnd(int separatorPosition) { + return separatorPosition + 1; + } + }; + } + }); + } + + /** + * Returns a splitter that uses the given fixed string as a separator. For example, {@code + * Splitter.on(", ").split("foo, bar,baz")} returns an iterable containing {@code ["foo", + * "bar,baz"]}. + * + * @param separator the literal, nonempty string to recognize as a separator + * @return a splitter, with default settings, that recognizes that separator + */ + public static Splitter on(final String separator) { + checkArgument(separator.length() != 0, "The separator may not be the empty string."); + if (separator.length() == 1) { + return Splitter.on(separator.charAt(0)); + } + return new Splitter( + new Strategy() { + @Override + public SplittingIterator iterator(Splitter splitter, CharSequence toSplit) { + return new SplittingIterator(splitter, toSplit) { + @Override + public int separatorStart(int start) { + int separatorLength = separator.length(); + + positions: + for (int p = start, last = toSplit.length() - separatorLength; p <= last; p++) { + for (int i = 0; i < separatorLength; i++) { + if (toSplit.charAt(i + p) != separator.charAt(i)) { + continue positions; + } + } + return p; + } + return -1; + } + + @Override + public int separatorEnd(int separatorPosition) { + return separatorPosition + separator.length(); + } + }; + } + }); + } + + /** + * Returns a splitter that considers any subsequence matching {@code pattern} to be a separator. + * For example, {@code Splitter.on(Pattern.compile("\r?\n")).split(entireFile)} splits a string + * into lines whether it uses DOS-style or UNIX-style line terminators. + * + * @param separatorPattern the pattern that determines whether a subsequence is a separator. This + * pattern may not match the empty string. + * @return a splitter, with default settings, that uses this pattern + * @throws IllegalArgumentException if {@code separatorPattern} matches the empty string + */ + @GwtIncompatible // java.util.regex + public static Splitter on(Pattern separatorPattern) { + return on(new JdkPattern(separatorPattern)); + } + + private static Splitter on(final CommonPattern separatorPattern) { + checkArgument( + !separatorPattern.matcher("").matches(), + "The pattern may not match the empty string: %s", + separatorPattern); + + return new Splitter( + new Strategy() { + @Override + public SplittingIterator iterator(final Splitter splitter, CharSequence toSplit) { + final CommonMatcher matcher = separatorPattern.matcher(toSplit); + return new SplittingIterator(splitter, toSplit) { + @Override + public int separatorStart(int start) { + return matcher.find(start) ? matcher.start() : -1; + } + + @Override + public int separatorEnd(int separatorPosition) { + return matcher.end(); + } + }; + } + }); + } + + /** + * Returns a splitter that considers any subsequence matching a given pattern (regular expression) + * to be a separator. For example, {@code Splitter.onPattern("\r?\n").split(entireFile)} splits a + * string into lines whether it uses DOS-style or UNIX-style line terminators. This is equivalent + * to {@code Splitter.on(Pattern.compile(pattern))}. + * + * @param separatorPattern the pattern that determines whether a subsequence is a separator. This + * pattern may not match the empty string. + * @return a splitter, with default settings, that uses this pattern + * @throws IllegalArgumentException if {@code separatorPattern} matches the empty string or is a + * malformed expression + */ + @GwtIncompatible // java.util.regex + public static Splitter onPattern(String separatorPattern) { + return on(Platform.compilePattern(separatorPattern)); + } + + /** + * Returns a splitter that divides strings into pieces of the given length. For example, {@code + * Splitter.fixedLength(2).split("abcde")} returns an iterable containing {@code ["ab", "cd", + * "e"]}. The last piece can be smaller than {@code length} but will never be empty. + * + *