update to 30.1
This commit is contained in:
parent
bb8e73e79a
commit
bc85508c04
81 changed files with 4978 additions and 1367 deletions
|
@ -24,7 +24,9 @@ ext {
|
||||||
}
|
}
|
||||||
|
|
||||||
apply plugin: 'java-library'
|
apply plugin: 'java-library'
|
||||||
|
apply from: rootProject.file('gradle/init/banner.gradle')
|
||||||
apply from: rootProject.file('gradle/ide/idea.gradle')
|
apply from: rootProject.file('gradle/ide/idea.gradle')
|
||||||
|
apply from: rootProject.file('gradle/compile/repo.gradle')
|
||||||
apply from: rootProject.file('gradle/compile/java.gradle')
|
apply from: rootProject.file('gradle/compile/java.gradle')
|
||||||
apply from: rootProject.file('gradle/test/junit5.gradle')
|
apply from: rootProject.file('gradle/test/junit5.gradle')
|
||||||
apply from: rootProject.file('gradle/publishing/publication.gradle')
|
apply from: rootProject.file('gradle/publishing/publication.gradle')
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
group = org.xbib
|
group = org.xbib
|
||||||
name = guava
|
name = guava
|
||||||
version = 29.0
|
version = 30.1
|
||||||
|
|
||||||
gradle.wrapper.version = 6.6.1
|
gradle.wrapper.version = 6.6.1
|
||||||
|
|
|
@ -17,9 +17,7 @@ compileTestJava {
|
||||||
|
|
||||||
jar {
|
jar {
|
||||||
manifest {
|
manifest {
|
||||||
attributes('Implementation-Title': project.name)
|
|
||||||
attributes('Implementation-Version': project.version)
|
attributes('Implementation-Version': project.version)
|
||||||
attributes('Implementation-Vendor': 'Jörg Prante')
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -30,8 +28,18 @@ task sourcesJar(type: Jar, dependsOn: classes) {
|
||||||
|
|
||||||
task javadocJar(type: Jar, dependsOn: javadoc) {
|
task javadocJar(type: Jar, dependsOn: javadoc) {
|
||||||
classifier 'javadoc'
|
classifier 'javadoc'
|
||||||
|
from javadoc.destinationDir
|
||||||
}
|
}
|
||||||
|
|
||||||
artifacts {
|
artifacts {
|
||||||
archives sourcesJar, javadocJar
|
archives sourcesJar, javadocJar
|
||||||
}
|
}
|
||||||
|
|
||||||
|
tasks.withType(JavaCompile) {
|
||||||
|
options.encoding('UTF-8')
|
||||||
|
options.compilerArgs << '-Xlint:all'
|
||||||
|
}
|
||||||
|
|
||||||
|
javadoc {
|
||||||
|
options.addStringOption('Xdoclint:none', '-quiet')
|
||||||
|
}
|
||||||
|
|
4
gradle/compile/repo.gradle
Normal file
4
gradle/compile/repo.gradle
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
repositories {
|
||||||
|
mavenLocal()
|
||||||
|
mavenCentral()
|
||||||
|
}
|
|
@ -7,7 +7,6 @@ idea {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// if project was not imported via idea pluginw
|
|
||||||
clean {
|
clean {
|
||||||
delete 'out'
|
delete 'out'
|
||||||
}
|
}
|
||||||
|
|
14
gradle/init/banner.gradle
Normal file
14
gradle/init/banner.gradle
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
gradle.projectsLoaded { g ->
|
||||||
|
printf "Host: %s\nOS: %s %s %s\nJVM: %s %s %s %s\nGradle: %s Groovy: %s Java: %s\n" +
|
||||||
|
"Build: group: %s name: %s version: %s\n",
|
||||||
|
InetAddress.getLocalHost(),
|
||||||
|
System.getProperty("os.name"),
|
||||||
|
System.getProperty("os.arch"),
|
||||||
|
System.getProperty("os.version"),
|
||||||
|
System.getProperty("java.version"),
|
||||||
|
System.getProperty("java.vm.version"),
|
||||||
|
System.getProperty("java.vm.vendor"),
|
||||||
|
System.getProperty("java.vm.name"),
|
||||||
|
gradle.gradleVersion, GroovySystem.getVersion(), JavaVersion.current(),
|
||||||
|
gradle.rootProject.group, gradle.rootProject.name, gradle.rootProject.version
|
||||||
|
}
|
|
@ -53,6 +53,7 @@ if (project.hasProperty("signing.keyId")) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (project.hasProperty("ossrhUsername")) {
|
||||||
nexusPublishing {
|
nexusPublishing {
|
||||||
repositories {
|
repositories {
|
||||||
sonatype {
|
sonatype {
|
||||||
|
@ -62,3 +63,4 @@ nexusPublishing {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Binary file not shown.
2
gradle/wrapper/gradle-wrapper.properties
vendored
2
gradle/wrapper/gradle-wrapper.properties
vendored
|
@ -1,5 +1,5 @@
|
||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-6.4.1-all.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-6.6.1-all.zip
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
|
|
21
gradlew.bat
vendored
21
gradlew.bat
vendored
|
@ -40,7 +40,7 @@ if defined JAVA_HOME goto findJavaFromJavaHome
|
||||||
|
|
||||||
set JAVA_EXE=java.exe
|
set JAVA_EXE=java.exe
|
||||||
%JAVA_EXE% -version >NUL 2>&1
|
%JAVA_EXE% -version >NUL 2>&1
|
||||||
if "%ERRORLEVEL%" == "0" goto init
|
if "%ERRORLEVEL%" == "0" goto execute
|
||||||
|
|
||||||
echo.
|
echo.
|
||||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||||
|
@ -54,7 +54,7 @@ goto fail
|
||||||
set JAVA_HOME=%JAVA_HOME:"=%
|
set JAVA_HOME=%JAVA_HOME:"=%
|
||||||
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||||
|
|
||||||
if exist "%JAVA_EXE%" goto init
|
if exist "%JAVA_EXE%" goto execute
|
||||||
|
|
||||||
echo.
|
echo.
|
||||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
||||||
|
@ -64,21 +64,6 @@ echo location of your Java installation.
|
||||||
|
|
||||||
goto fail
|
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
|
:execute
|
||||||
@rem Setup the command line
|
@rem Setup the command line
|
||||||
|
|
||||||
|
@ -86,7 +71,7 @@ set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||||
|
|
||||||
|
|
||||||
@rem Execute Gradle
|
@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%
|
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
|
||||||
|
|
||||||
:end
|
:end
|
||||||
@rem End local scope for the variables with windows NT shell
|
@rem End local scope for the variables with windows NT shell
|
||||||
|
|
|
@ -79,7 +79,15 @@ public enum StandardSystemProperty {
|
||||||
/** Name of JIT compiler to use. */
|
/** Name of JIT compiler to use. */
|
||||||
JAVA_COMPILER("java.compiler"),
|
JAVA_COMPILER("java.compiler"),
|
||||||
|
|
||||||
/** Path of extension directory or directories. */
|
/**
|
||||||
|
* Path of extension directory or directories.
|
||||||
|
*
|
||||||
|
* @deprecated This property was <a
|
||||||
|
* href="https://openjdk.java.net/jeps/220#Removed:-The-extension-mechanism">deprecated</a> in
|
||||||
|
* Java 8 and removed in Java 9. We do not plan to remove this API from Guava, but if you are
|
||||||
|
* using it, it is probably not doing what you want.
|
||||||
|
*/
|
||||||
|
@Deprecated
|
||||||
JAVA_EXT_DIRS("java.ext.dirs"),
|
JAVA_EXT_DIRS("java.ext.dirs"),
|
||||||
|
|
||||||
/** Operating system name. */
|
/** Operating system name. */
|
||||||
|
|
|
@ -480,8 +480,8 @@ public final class CacheBuilder<K, V> {
|
||||||
this.maximumWeight);
|
this.maximumWeight);
|
||||||
checkState(
|
checkState(
|
||||||
this.maximumSize == UNSET_INT, "maximum size was already set to %s", this.maximumSize);
|
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");
|
checkArgument(maximumWeight >= 0, "maximum weight must not be negative");
|
||||||
|
this.maximumWeight = maximumWeight;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -35,7 +35,6 @@ import com.google.common.cache.CacheBuilder.NullListener;
|
||||||
import com.google.common.cache.CacheBuilder.OneWeigher;
|
import com.google.common.cache.CacheBuilder.OneWeigher;
|
||||||
import com.google.common.cache.CacheLoader.InvalidCacheLoadException;
|
import com.google.common.cache.CacheLoader.InvalidCacheLoadException;
|
||||||
import com.google.common.cache.CacheLoader.UnsupportedLoadingOperationException;
|
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.AbstractSequentialIterator;
|
||||||
import com.google.common.collect.ImmutableMap;
|
import com.google.common.collect.ImmutableMap;
|
||||||
import com.google.common.collect.ImmutableSet;
|
import com.google.common.collect.ImmutableSet;
|
||||||
|
@ -4284,7 +4283,7 @@ class LocalCache<K, V> extends AbstractMap<K, V> implements ConcurrentMap<K, V>
|
||||||
public Set<K> keySet() {
|
public Set<K> keySet() {
|
||||||
// does not impact recency ordering
|
// does not impact recency ordering
|
||||||
Set<K> ks = keySet;
|
Set<K> ks = keySet;
|
||||||
return (ks != null) ? ks : (keySet = new KeySet(this));
|
return (ks != null) ? ks : (keySet = new KeySet());
|
||||||
}
|
}
|
||||||
|
|
||||||
Collection<V> values;
|
Collection<V> values;
|
||||||
|
@ -4293,7 +4292,7 @@ class LocalCache<K, V> extends AbstractMap<K, V> implements ConcurrentMap<K, V>
|
||||||
public Collection<V> values() {
|
public Collection<V> values() {
|
||||||
// does not impact recency ordering
|
// does not impact recency ordering
|
||||||
Collection<V> vs = values;
|
Collection<V> vs = values;
|
||||||
return (vs != null) ? vs : (values = new Values(this));
|
return (vs != null) ? vs : (values = new Values());
|
||||||
}
|
}
|
||||||
|
|
||||||
Set<Entry<K, V>> entrySet;
|
Set<Entry<K, V>> entrySet;
|
||||||
|
@ -4303,7 +4302,7 @@ class LocalCache<K, V> extends AbstractMap<K, V> implements ConcurrentMap<K, V>
|
||||||
public Set<Entry<K, V>> entrySet() {
|
public Set<Entry<K, V>> entrySet() {
|
||||||
// does not impact recency ordering
|
// does not impact recency ordering
|
||||||
Set<Entry<K, V>> es = entrySet;
|
Set<Entry<K, V>> es = entrySet;
|
||||||
return (es != null) ? es : (entrySet = new EntrySet(this));
|
return (es != null) ? es : (entrySet = new EntrySet());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Iterator Support
|
// Iterator Support
|
||||||
|
@ -4494,25 +4493,20 @@ class LocalCache<K, V> extends AbstractMap<K, V> implements ConcurrentMap<K, V>
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract class AbstractCacheSet<T> extends AbstractSet<T> {
|
abstract class AbstractCacheSet<T> extends AbstractSet<T> {
|
||||||
final ConcurrentMap<?, ?> map;
|
|
||||||
|
|
||||||
AbstractCacheSet(ConcurrentMap<?, ?> map) {
|
|
||||||
this.map = map;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int size() {
|
public int size() {
|
||||||
return map.size();
|
return LocalCache.this.size();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isEmpty() {
|
public boolean isEmpty() {
|
||||||
return map.isEmpty();
|
return LocalCache.this.isEmpty();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void clear() {
|
public void clear() {
|
||||||
map.clear();
|
LocalCache.this.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
// super.toArray() may misbehave if size() is inaccurate, at least on old versions of Android.
|
// super.toArray() may misbehave if size() is inaccurate, at least on old versions of Android.
|
||||||
|
@ -4556,10 +4550,6 @@ class LocalCache<K, V> extends AbstractMap<K, V> implements ConcurrentMap<K, V>
|
||||||
|
|
||||||
final class KeySet extends AbstractCacheSet<K> {
|
final class KeySet extends AbstractCacheSet<K> {
|
||||||
|
|
||||||
KeySet(ConcurrentMap<?, ?> map) {
|
|
||||||
super(map);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Iterator<K> iterator() {
|
public Iterator<K> iterator() {
|
||||||
return new KeyIterator();
|
return new KeyIterator();
|
||||||
|
@ -4567,36 +4557,31 @@ class LocalCache<K, V> extends AbstractMap<K, V> implements ConcurrentMap<K, V>
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean contains(Object o) {
|
public boolean contains(Object o) {
|
||||||
return map.containsKey(o);
|
return LocalCache.this.containsKey(o);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean remove(Object o) {
|
public boolean remove(Object o) {
|
||||||
return map.remove(o) != null;
|
return LocalCache.this.remove(o) != null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
final class Values extends AbstractCollection<V> {
|
final class Values extends AbstractCollection<V> {
|
||||||
private final ConcurrentMap<?, ?> map;
|
|
||||||
|
|
||||||
Values(ConcurrentMap<?, ?> map) {
|
|
||||||
this.map = map;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int size() {
|
public int size() {
|
||||||
return map.size();
|
return LocalCache.this.size();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isEmpty() {
|
public boolean isEmpty() {
|
||||||
return map.isEmpty();
|
return LocalCache.this.isEmpty();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void clear() {
|
public void clear() {
|
||||||
map.clear();
|
LocalCache.this.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -4612,7 +4597,7 @@ class LocalCache<K, V> extends AbstractMap<K, V> implements ConcurrentMap<K, V>
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean contains(Object o) {
|
public boolean contains(Object o) {
|
||||||
return map.containsValue(o);
|
return LocalCache.this.containsValue(o);
|
||||||
}
|
}
|
||||||
|
|
||||||
// super.toArray() may misbehave if size() is inaccurate, at least on old versions of Android.
|
// super.toArray() may misbehave if size() is inaccurate, at least on old versions of Android.
|
||||||
|
@ -4632,10 +4617,6 @@ class LocalCache<K, V> extends AbstractMap<K, V> implements ConcurrentMap<K, V>
|
||||||
|
|
||||||
final class EntrySet extends AbstractCacheSet<Entry<K, V>> {
|
final class EntrySet extends AbstractCacheSet<Entry<K, V>> {
|
||||||
|
|
||||||
EntrySet(ConcurrentMap<?, ?> map) {
|
|
||||||
super(map);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Iterator<Entry<K, V>> iterator() {
|
public Iterator<Entry<K, V>> iterator() {
|
||||||
return new EntryIterator();
|
return new EntryIterator();
|
||||||
|
|
|
@ -88,6 +88,28 @@ final class CartesianList<E> extends AbstractList<List<E>> implements RandomAcce
|
||||||
return computedIndex;
|
return computedIndex;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int lastIndexOf(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).lastIndexOf(itr.next());
|
||||||
|
if (elemIndex == -1) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
computedIndex += elemIndex * axesSizeProduct[axisIndex + 1];
|
||||||
|
}
|
||||||
|
return computedIndex;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ImmutableList<E> get(final int index) {
|
public ImmutableList<E> get(final int index) {
|
||||||
checkElementIndex(index, size());
|
checkElementIndex(index, size());
|
||||||
|
@ -117,8 +139,21 @@ final class CartesianList<E> extends AbstractList<List<E>> implements RandomAcce
|
||||||
return axesSizeProduct[0];
|
return axesSizeProduct[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
public boolean contains(Object object) {
|
||||||
public boolean contains(Object o) {
|
if (!(object instanceof List)) {
|
||||||
return indexOf(o) != -1;
|
return false;
|
||||||
|
}
|
||||||
|
List<?> list = (List<?>) object;
|
||||||
|
if (list.size() != axes.size()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
int i = 0;
|
||||||
|
for (Object o : list) {
|
||||||
|
if (!axes.get(i).contains(o)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,37 +20,156 @@ import static com.google.common.base.Preconditions.checkNotNull;
|
||||||
|
|
||||||
import com.google.common.annotations.GwtCompatible;
|
import com.google.common.annotations.GwtCompatible;
|
||||||
import com.google.common.annotations.GwtIncompatible;
|
import com.google.common.annotations.GwtIncompatible;
|
||||||
|
import com.google.common.base.Preconditions;
|
||||||
|
import com.google.common.base.Supplier;
|
||||||
|
import java.util.Collection;
|
||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
|
import java.util.EnumMap;
|
||||||
|
import java.util.EnumSet;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.TreeMap;
|
||||||
|
import java.util.function.BinaryOperator;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
|
import java.util.function.ToIntFunction;
|
||||||
import java.util.stream.Collector;
|
import java.util.stream.Collector;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
/** Collectors utilities for {@code common.collect} internals. */
|
/** Collectors utilities for {@code common.collect} internals. */
|
||||||
@GwtCompatible
|
@GwtCompatible
|
||||||
final class CollectCollectors {
|
final class CollectCollectors {
|
||||||
static <T, K, V> Collector<T, ?, ImmutableBiMap<K, V>> toImmutableBiMap(
|
|
||||||
Function<? super T, ? extends K> keyFunction,
|
|
||||||
Function<? super T, ? extends V> valueFunction) {
|
|
||||||
checkNotNull(keyFunction);
|
|
||||||
checkNotNull(valueFunction);
|
|
||||||
return Collector.of(
|
|
||||||
ImmutableBiMap.Builder<K, V>::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<Object, ?, ImmutableList<Object>> TO_IMMUTABLE_LIST =
|
private static final Collector<Object, ?, ImmutableList<Object>> TO_IMMUTABLE_LIST =
|
||||||
Collector.of(
|
Collector.of(
|
||||||
ImmutableList::<Object>builder,
|
ImmutableList::builder,
|
||||||
ImmutableList.Builder::add,
|
ImmutableList.Builder::add,
|
||||||
ImmutableList.Builder::combine,
|
ImmutableList.Builder::combine,
|
||||||
ImmutableList.Builder::build);
|
ImmutableList.Builder::build);
|
||||||
|
|
||||||
|
private static final Collector<Object, ?, ImmutableSet<Object>> TO_IMMUTABLE_SET =
|
||||||
|
Collector.of(
|
||||||
|
ImmutableSet::builder,
|
||||||
|
ImmutableSet.Builder::add,
|
||||||
|
ImmutableSet.Builder::combine,
|
||||||
|
ImmutableSet.Builder::build);
|
||||||
|
|
||||||
|
@GwtIncompatible
|
||||||
|
private static final Collector<Range<Comparable<?>>, ?, ImmutableRangeSet<Comparable<?>>>
|
||||||
|
TO_IMMUTABLE_RANGE_SET =
|
||||||
|
Collector.of(
|
||||||
|
ImmutableRangeSet::builder,
|
||||||
|
ImmutableRangeSet.Builder::add,
|
||||||
|
ImmutableRangeSet.Builder::combine,
|
||||||
|
ImmutableRangeSet.Builder::build);
|
||||||
|
|
||||||
|
// Lists
|
||||||
|
|
||||||
|
@SuppressWarnings({"rawtypes", "unchecked"})
|
||||||
static <E> Collector<E, ?, ImmutableList<E>> toImmutableList() {
|
static <E> Collector<E, ?, ImmutableList<E>> toImmutableList() {
|
||||||
return (Collector) TO_IMMUTABLE_LIST;
|
return (Collector) TO_IMMUTABLE_LIST;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Sets
|
||||||
|
|
||||||
|
@SuppressWarnings({"rawtypes", "unchecked"})
|
||||||
|
static <E> Collector<E, ?, ImmutableSet<E>> toImmutableSet() {
|
||||||
|
return (Collector) TO_IMMUTABLE_SET;
|
||||||
|
}
|
||||||
|
|
||||||
|
static <E> Collector<E, ?, ImmutableSortedSet<E>> toImmutableSortedSet(
|
||||||
|
Comparator<? super E> comparator) {
|
||||||
|
checkNotNull(comparator);
|
||||||
|
return Collector.of(
|
||||||
|
() -> new ImmutableSortedSet.Builder<E>(comparator),
|
||||||
|
ImmutableSortedSet.Builder::add,
|
||||||
|
ImmutableSortedSet.Builder::combine,
|
||||||
|
ImmutableSortedSet.Builder::build);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings({"rawtypes", "unchecked"})
|
||||||
|
static <E extends Enum<E>> Collector<E, ?, ImmutableSet<E>> toImmutableEnumSet() {
|
||||||
|
return (Collector) EnumSetAccumulator.TO_IMMUTABLE_ENUM_SET;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final class EnumSetAccumulator<E extends Enum<E>> {
|
||||||
|
@SuppressWarnings({"rawtypes", "unchecked"})
|
||||||
|
static final Collector<Enum<?>, ?, ImmutableSet<? extends Enum<?>>> TO_IMMUTABLE_ENUM_SET =
|
||||||
|
(Collector)
|
||||||
|
Collector.<Enum, EnumSetAccumulator, ImmutableSet<?>>of(
|
||||||
|
EnumSetAccumulator::new,
|
||||||
|
EnumSetAccumulator::add,
|
||||||
|
EnumSetAccumulator::combine,
|
||||||
|
EnumSetAccumulator::toImmutableSet,
|
||||||
|
Collector.Characteristics.UNORDERED);
|
||||||
|
|
||||||
|
private EnumSet<E> set;
|
||||||
|
|
||||||
|
void add(E e) {
|
||||||
|
if (set == null) {
|
||||||
|
set = EnumSet.of(e);
|
||||||
|
} else {
|
||||||
|
set.add(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
EnumSetAccumulator<E> combine(EnumSetAccumulator<E> other) {
|
||||||
|
if (this.set == null) {
|
||||||
|
return other;
|
||||||
|
} else if (other.set == null) {
|
||||||
|
return this;
|
||||||
|
} else {
|
||||||
|
this.set.addAll(other.set);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ImmutableSet<E> toImmutableSet() {
|
||||||
|
return (set == null) ? ImmutableSet.<E>of() : ImmutableEnumSet.asImmutable(set);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@GwtIncompatible
|
||||||
|
@SuppressWarnings({"rawtypes", "unchecked"})
|
||||||
|
static <E extends Comparable<? super E>>
|
||||||
|
Collector<Range<E>, ?, ImmutableRangeSet<E>> toImmutableRangeSet() {
|
||||||
|
return (Collector) TO_IMMUTABLE_RANGE_SET;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Multisets
|
||||||
|
|
||||||
|
static <T, E> Collector<T, ?, ImmutableMultiset<E>> toImmutableMultiset(
|
||||||
|
Function<? super T, ? extends E> elementFunction, ToIntFunction<? super T> 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<E> multiset) -> ImmutableMultiset.copyFromEntries(multiset.entrySet()));
|
||||||
|
}
|
||||||
|
|
||||||
|
static <T, E, M extends Multiset<E>> Collector<T, ?, M> toMultiset(
|
||||||
|
java.util.function.Function<? super T, E> elementFunction,
|
||||||
|
java.util.function.ToIntFunction<? super T> countFunction,
|
||||||
|
java.util.function.Supplier<M> 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;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Maps
|
||||||
|
|
||||||
static <T, K, V> Collector<T, ?, ImmutableMap<K, V>> toImmutableMap(
|
static <T, K, V> Collector<T, ?, ImmutableMap<K, V>> toImmutableMap(
|
||||||
Function<? super T, ? extends K> keyFunction,
|
Function<? super T, ? extends K> keyFunction,
|
||||||
Function<? super T, ? extends V> valueFunction) {
|
Function<? super T, ? extends V> valueFunction) {
|
||||||
|
@ -63,15 +182,129 @@ final class CollectCollectors {
|
||||||
ImmutableMap.Builder::build);
|
ImmutableMap.Builder::build);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final Collector<Object, ?, ImmutableSet<Object>> TO_IMMUTABLE_SET =
|
public static <T, K, V> Collector<T, ?, ImmutableMap<K, V>> toImmutableMap(
|
||||||
Collector.of(
|
Function<? super T, ? extends K> keyFunction,
|
||||||
ImmutableSet::<Object>builder,
|
Function<? super T, ? extends V> valueFunction,
|
||||||
ImmutableSet.Builder::add,
|
BinaryOperator<V> mergeFunction) {
|
||||||
ImmutableSet.Builder::combine,
|
checkNotNull(keyFunction);
|
||||||
ImmutableSet.Builder::build);
|
checkNotNull(valueFunction);
|
||||||
|
checkNotNull(mergeFunction);
|
||||||
|
return Collectors.collectingAndThen(
|
||||||
|
Collectors.toMap(keyFunction, valueFunction, mergeFunction, LinkedHashMap::new),
|
||||||
|
ImmutableMap::copyOf);
|
||||||
|
}
|
||||||
|
|
||||||
static <E> Collector<E, ?, ImmutableSet<E>> toImmutableSet() {
|
static <T, K, V> Collector<T, ?, ImmutableSortedMap<K, V>> toImmutableSortedMap(
|
||||||
return (Collector) TO_IMMUTABLE_SET;
|
Comparator<? super K> comparator,
|
||||||
|
Function<? super T, ? extends K> keyFunction,
|
||||||
|
Function<? super T, ? extends V> valueFunction,
|
||||||
|
BinaryOperator<V> mergeFunction) {
|
||||||
|
checkNotNull(comparator);
|
||||||
|
checkNotNull(keyFunction);
|
||||||
|
checkNotNull(valueFunction);
|
||||||
|
checkNotNull(mergeFunction);
|
||||||
|
return Collectors.collectingAndThen(
|
||||||
|
Collectors.toMap(
|
||||||
|
keyFunction, valueFunction, mergeFunction, () -> new TreeMap<K, V>(comparator)),
|
||||||
|
ImmutableSortedMap::copyOfSorted);
|
||||||
|
}
|
||||||
|
|
||||||
|
static <T, K, V> Collector<T, ?, ImmutableBiMap<K, V>> toImmutableBiMap(
|
||||||
|
Function<? super T, ? extends K> keyFunction,
|
||||||
|
Function<? super T, ? extends V> valueFunction) {
|
||||||
|
checkNotNull(keyFunction);
|
||||||
|
checkNotNull(valueFunction);
|
||||||
|
return Collector.of(
|
||||||
|
ImmutableBiMap.Builder<K, V>::new,
|
||||||
|
(builder, input) -> builder.put(keyFunction.apply(input), valueFunction.apply(input)),
|
||||||
|
ImmutableBiMap.Builder::combine,
|
||||||
|
ImmutableBiMap.Builder::build,
|
||||||
|
new Collector.Characteristics[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
static <T, K extends Enum<K>, V> Collector<T, ?, ImmutableMap<K, V>> toImmutableEnumMap(
|
||||||
|
Function<? super T, ? extends K> keyFunction,
|
||||||
|
Function<? super T, ? extends V> valueFunction) {
|
||||||
|
checkNotNull(keyFunction);
|
||||||
|
checkNotNull(valueFunction);
|
||||||
|
return Collector.of(
|
||||||
|
() ->
|
||||||
|
new EnumMapAccumulator<K, V>(
|
||||||
|
(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);
|
||||||
|
},
|
||||||
|
EnumMapAccumulator::combine,
|
||||||
|
EnumMapAccumulator::toImmutableMap,
|
||||||
|
Collector.Characteristics.UNORDERED);
|
||||||
|
}
|
||||||
|
|
||||||
|
static <T, K extends Enum<K>, V> Collector<T, ?, ImmutableMap<K, V>> toImmutableEnumMap(
|
||||||
|
Function<? super T, ? extends K> keyFunction,
|
||||||
|
Function<? super T, ? extends V> valueFunction,
|
||||||
|
BinaryOperator<V> mergeFunction) {
|
||||||
|
checkNotNull(keyFunction);
|
||||||
|
checkNotNull(valueFunction);
|
||||||
|
checkNotNull(mergeFunction);
|
||||||
|
// not UNORDERED because we don't know if mergeFunction is commutative
|
||||||
|
return Collector.of(
|
||||||
|
() -> new EnumMapAccumulator<K, V>(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);
|
||||||
|
},
|
||||||
|
EnumMapAccumulator::combine,
|
||||||
|
EnumMapAccumulator::toImmutableMap);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class EnumMapAccumulator<K extends Enum<K>, V> {
|
||||||
|
private final BinaryOperator<V> mergeFunction;
|
||||||
|
private EnumMap<K, V> map = null;
|
||||||
|
|
||||||
|
EnumMapAccumulator(BinaryOperator<V> mergeFunction) {
|
||||||
|
this.mergeFunction = mergeFunction;
|
||||||
|
}
|
||||||
|
|
||||||
|
void put(K key, V value) {
|
||||||
|
if (map == null) {
|
||||||
|
map = new EnumMap<>(key.getDeclaringClass());
|
||||||
|
}
|
||||||
|
map.merge(key, value, mergeFunction);
|
||||||
|
}
|
||||||
|
|
||||||
|
EnumMapAccumulator<K, V> combine(EnumMapAccumulator<K, V> other) {
|
||||||
|
if (this.map == null) {
|
||||||
|
return other;
|
||||||
|
} else if (other.map == null) {
|
||||||
|
return this;
|
||||||
|
} else {
|
||||||
|
other.map.forEach(this::put);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ImmutableMap<K, V> toImmutableMap() {
|
||||||
|
return (map == null) ? ImmutableMap.<K, V>of() : ImmutableEnumMap.asImmutable(map);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@GwtIncompatible
|
||||||
|
static <T, K extends Comparable<? super K>, V>
|
||||||
|
Collector<T, ?, ImmutableRangeMap<K, V>> toImmutableRangeMap(
|
||||||
|
Function<? super T, Range<K>> keyFunction,
|
||||||
|
Function<? super T, ? extends V> valueFunction) {
|
||||||
|
checkNotNull(keyFunction);
|
||||||
|
checkNotNull(valueFunction);
|
||||||
|
return Collector.of(
|
||||||
|
ImmutableRangeMap::<K, V>builder,
|
||||||
|
(builder, input) -> builder.put(keyFunction.apply(input), valueFunction.apply(input)),
|
||||||
|
ImmutableRangeMap.Builder::combine,
|
||||||
|
ImmutableRangeMap.Builder::build);
|
||||||
}
|
}
|
||||||
|
|
||||||
static <T, K, V> Collector<T, ?, ImmutableSortedMap<K, V>> toImmutableSortedMap(
|
static <T, K, V> Collector<T, ?, ImmutableSortedMap<K, V>> toImmutableSortedMap(
|
||||||
|
@ -92,43 +325,91 @@ final class CollectCollectors {
|
||||||
ImmutableSortedMap.Builder::build,
|
ImmutableSortedMap.Builder::build,
|
||||||
Collector.Characteristics.UNORDERED);
|
Collector.Characteristics.UNORDERED);
|
||||||
}
|
}
|
||||||
|
// Multimaps
|
||||||
|
|
||||||
static <E> Collector<E, ?, ImmutableSortedSet<E>> toImmutableSortedSet(
|
static <T, K, V> Collector<T, ?, ImmutableListMultimap<K, V>> toImmutableListMultimap(
|
||||||
Comparator<? super E> comparator) {
|
Function<? super T, ? extends K> keyFunction,
|
||||||
checkNotNull(comparator);
|
|
||||||
return Collector.of(
|
|
||||||
() -> new ImmutableSortedSet.Builder<E>(comparator),
|
|
||||||
ImmutableSortedSet.Builder::add,
|
|
||||||
ImmutableSortedSet.Builder::combine,
|
|
||||||
ImmutableSortedSet.Builder::build);
|
|
||||||
}
|
|
||||||
|
|
||||||
@GwtIncompatible
|
|
||||||
private static final Collector<Range<Comparable>, ?, ImmutableRangeSet<Comparable>>
|
|
||||||
TO_IMMUTABLE_RANGE_SET =
|
|
||||||
Collector.of(
|
|
||||||
ImmutableRangeSet::<Comparable>builder,
|
|
||||||
ImmutableRangeSet.Builder::add,
|
|
||||||
ImmutableRangeSet.Builder::combine,
|
|
||||||
ImmutableRangeSet.Builder::build);
|
|
||||||
|
|
||||||
@GwtIncompatible
|
|
||||||
static <E extends Comparable<? super E>>
|
|
||||||
Collector<Range<E>, ?, ImmutableRangeSet<E>> toImmutableRangeSet() {
|
|
||||||
return (Collector) TO_IMMUTABLE_RANGE_SET;
|
|
||||||
}
|
|
||||||
|
|
||||||
@GwtIncompatible
|
|
||||||
static <T, K extends Comparable<? super K>, V>
|
|
||||||
Collector<T, ?, ImmutableRangeMap<K, V>> toImmutableRangeMap(
|
|
||||||
Function<? super T, Range<K>> keyFunction,
|
|
||||||
Function<? super T, ? extends V> valueFunction) {
|
Function<? super T, ? extends V> valueFunction) {
|
||||||
|
checkNotNull(keyFunction, "keyFunction");
|
||||||
|
checkNotNull(valueFunction, "valueFunction");
|
||||||
|
return Collector.of(
|
||||||
|
ImmutableListMultimap::<K, V>builder,
|
||||||
|
(builder, t) -> builder.put(keyFunction.apply(t), valueFunction.apply(t)),
|
||||||
|
ImmutableListMultimap.Builder::combine,
|
||||||
|
ImmutableListMultimap.Builder::build);
|
||||||
|
}
|
||||||
|
|
||||||
|
static <T, K, V> Collector<T, ?, ImmutableListMultimap<K, V>> flatteningToImmutableListMultimap(
|
||||||
|
Function<? super T, ? extends K> keyFunction,
|
||||||
|
Function<? super T, ? extends Stream<? extends V>> valuesFunction) {
|
||||||
|
checkNotNull(keyFunction);
|
||||||
|
checkNotNull(valuesFunction);
|
||||||
|
return Collectors.collectingAndThen(
|
||||||
|
flatteningToMultimap(
|
||||||
|
input -> checkNotNull(keyFunction.apply(input)),
|
||||||
|
input -> valuesFunction.apply(input).peek(Preconditions::checkNotNull),
|
||||||
|
MultimapBuilder.linkedHashKeys().arrayListValues()::<K, V>build),
|
||||||
|
ImmutableListMultimap::copyOf);
|
||||||
|
}
|
||||||
|
|
||||||
|
static <T, K, V> Collector<T, ?, ImmutableSetMultimap<K, V>> toImmutableSetMultimap(
|
||||||
|
Function<? super T, ? extends K> keyFunction,
|
||||||
|
Function<? super T, ? extends V> valueFunction) {
|
||||||
|
checkNotNull(keyFunction, "keyFunction");
|
||||||
|
checkNotNull(valueFunction, "valueFunction");
|
||||||
|
return Collector.of(
|
||||||
|
ImmutableSetMultimap::<K, V>builder,
|
||||||
|
(builder, t) -> builder.put(keyFunction.apply(t), valueFunction.apply(t)),
|
||||||
|
ImmutableSetMultimap.Builder::combine,
|
||||||
|
ImmutableSetMultimap.Builder::build);
|
||||||
|
}
|
||||||
|
|
||||||
|
static <T, K, V> Collector<T, ?, ImmutableSetMultimap<K, V>> flatteningToImmutableSetMultimap(
|
||||||
|
Function<? super T, ? extends K> keyFunction,
|
||||||
|
Function<? super T, ? extends Stream<? extends V>> valuesFunction) {
|
||||||
|
checkNotNull(keyFunction);
|
||||||
|
checkNotNull(valuesFunction);
|
||||||
|
return Collectors.collectingAndThen(
|
||||||
|
flatteningToMultimap(
|
||||||
|
input -> checkNotNull(keyFunction.apply(input)),
|
||||||
|
input -> valuesFunction.apply(input).peek(Preconditions::checkNotNull),
|
||||||
|
MultimapBuilder.linkedHashKeys().linkedHashSetValues()::<K, V>build),
|
||||||
|
ImmutableSetMultimap::copyOf);
|
||||||
|
}
|
||||||
|
|
||||||
|
static <T, K, V, M extends Multimap<K, V>> Collector<T, ?, M> toMultimap(
|
||||||
|
java.util.function.Function<? super T, ? extends K> keyFunction,
|
||||||
|
java.util.function.Function<? super T, ? extends V> valueFunction,
|
||||||
|
java.util.function.Supplier<M> multimapSupplier) {
|
||||||
checkNotNull(keyFunction);
|
checkNotNull(keyFunction);
|
||||||
checkNotNull(valueFunction);
|
checkNotNull(valueFunction);
|
||||||
|
checkNotNull(multimapSupplier);
|
||||||
return Collector.of(
|
return Collector.of(
|
||||||
ImmutableRangeMap::<K, V>builder,
|
multimapSupplier,
|
||||||
(builder, input) -> builder.put(keyFunction.apply(input), valueFunction.apply(input)),
|
(multimap, input) -> multimap.put(keyFunction.apply(input), valueFunction.apply(input)),
|
||||||
ImmutableRangeMap.Builder::combine,
|
(multimap1, multimap2) -> {
|
||||||
ImmutableRangeMap.Builder::build);
|
multimap1.putAll(multimap2);
|
||||||
|
return multimap1;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
static <T, K, V, M extends Multimap<K, V>> Collector<T, ?, M> flatteningToMultimap(
|
||||||
|
Function<? super T, ? extends K> keyFunction,
|
||||||
|
Function<? super T, ? extends Stream<? extends V>> valueFunction,
|
||||||
|
Supplier<M> multimapSupplier) {
|
||||||
|
checkNotNull(keyFunction);
|
||||||
|
checkNotNull(valueFunction);
|
||||||
|
checkNotNull(multimapSupplier);
|
||||||
|
return Collector.of(
|
||||||
|
multimapSupplier,
|
||||||
|
(multimap, input) -> {
|
||||||
|
K key = keyFunction.apply(input);
|
||||||
|
Collection<V> valuesForKey = multimap.get(key);
|
||||||
|
valueFunction.apply(input).forEachOrdered(valuesForKey::add);
|
||||||
|
},
|
||||||
|
(multimap1, multimap2) -> {
|
||||||
|
multimap1.putAll(multimap2);
|
||||||
|
return multimap1;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -352,11 +352,6 @@ public final class Collections2 {
|
||||||
return new StringBuilder((int) Math.min(size * 8L, Ints.MAX_POWER_OF_TWO));
|
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 <T> Collection<T> cast(Iterable<T> iterable) {
|
|
||||||
return (Collection<T>) iterable;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a {@link Collection} of all the permutations of the specified {@link Iterable}.
|
* Returns a {@link Collection} of all the permutations of the specified {@link Iterable}.
|
||||||
*
|
*
|
||||||
|
|
|
@ -190,4 +190,79 @@ public final class Comparators {
|
||||||
checkNotNull(valueComparator);
|
checkNotNull(valueComparator);
|
||||||
return Comparator.comparing(o -> o.orElse(null), Comparator.nullsLast(valueComparator));
|
return Comparator.comparing(o -> o.orElse(null), Comparator.nullsLast(valueComparator));
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* Returns the minimum of the two values. If the values compare as 0, the first is returned.
|
||||||
|
*
|
||||||
|
* <p>The recommended solution for finding the {@code minimum} of some values depends on the type
|
||||||
|
* of your data and the number of elements you have. Read more in the Guava User Guide article on
|
||||||
|
* <a href="https://github.com/google/guava/wiki/CollectionUtilitiesExplained#comparators">{@code
|
||||||
|
* Comparators}</a>.
|
||||||
|
*
|
||||||
|
* @param a first value to compare, returned if less than or equal to b.
|
||||||
|
* @param b second value to compare.
|
||||||
|
* @throws ClassCastException if the parameters are not <i>mutually comparable</i>.
|
||||||
|
* @since 30.0
|
||||||
|
*/
|
||||||
|
@Beta
|
||||||
|
public static <T extends Comparable<? super T>> T min(T a, T b) {
|
||||||
|
return (a.compareTo(b) <= 0) ? a : b;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the minimum of the two values, according to the given comparator. If the values compare
|
||||||
|
* as equal, the first is returned.
|
||||||
|
*
|
||||||
|
* <p>The recommended solution for finding the {@code minimum} of some values depends on the type
|
||||||
|
* of your data and the number of elements you have. Read more in the Guava User Guide article on
|
||||||
|
* <a href="https://github.com/google/guava/wiki/CollectionUtilitiesExplained#comparators">{@code
|
||||||
|
* Comparators}</a>.
|
||||||
|
*
|
||||||
|
* @param a first value to compare, returned if less than or equal to b
|
||||||
|
* @param b second value to compare.
|
||||||
|
* @throws ClassCastException if the parameters are not <i>mutually comparable</i> using the given
|
||||||
|
* comparator.
|
||||||
|
* @since 30.0
|
||||||
|
*/
|
||||||
|
@Beta
|
||||||
|
public static <T> T min(T a, T b, Comparator<T> comparator) {
|
||||||
|
return (comparator.compare(a, b) <= 0) ? a : b;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the maximum of the two values. If the values compare as 0, the first is returned.
|
||||||
|
*
|
||||||
|
* <p>The recommended solution for finding the {@code maximum} of some values depends on the type
|
||||||
|
* of your data and the number of elements you have. Read more in the Guava User Guide article on
|
||||||
|
* <a href="https://github.com/google/guava/wiki/CollectionUtilitiesExplained#comparators">{@code
|
||||||
|
* Comparators}</a>.
|
||||||
|
*
|
||||||
|
* @param a first value to compare, returned if greater than or equal to b.
|
||||||
|
* @param b second value to compare.
|
||||||
|
* @throws ClassCastException if the parameters are not <i>mutually comparable</i>.
|
||||||
|
* @since 30.0
|
||||||
|
*/
|
||||||
|
@Beta
|
||||||
|
public static <T extends Comparable<? super T>> T max(T a, T b) {
|
||||||
|
return (a.compareTo(b) >= 0) ? a : b;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the maximum of the two values, according to the given comparator. If the values compare
|
||||||
|
* as equal, the first is returned.
|
||||||
|
*
|
||||||
|
* <p>The recommended solution for finding the {@code maximum} of some values depends on the type
|
||||||
|
* of your data and the number of elements you have. Read more in the Guava User Guide article on
|
||||||
|
* <a href="https://github.com/google/guava/wiki/CollectionUtilitiesExplained#comparators">{@code
|
||||||
|
* Comparators}</a>.
|
||||||
|
*
|
||||||
|
* @param a first value to compare, returned if greater than or equal to b.
|
||||||
|
* @param b second value to compare.
|
||||||
|
* @throws ClassCastException if the parameters are not <i>mutually comparable</i> using the given
|
||||||
|
* comparator.
|
||||||
|
* @since 30.0
|
||||||
|
*/
|
||||||
|
@Beta
|
||||||
|
public static <T> T max(T a, T b, Comparator<T> comparator) {
|
||||||
|
return (comparator.compare(a, b) >= 0) ? a : b;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -191,15 +191,14 @@ public abstract class ContiguousSet<C extends Comparable> extends ImmutableSorte
|
||||||
/*
|
/*
|
||||||
* These methods perform most headSet, subSet, and tailSet logic, besides parameter validation.
|
* These methods perform most headSet, subSet, and tailSet logic, besides parameter validation.
|
||||||
*/
|
*/
|
||||||
// TODO(kevinb): we can probably make these real @Overrides now
|
@SuppressWarnings("MissingOverride") // Supermethod does not exist under GWT.
|
||||||
/* @Override */
|
|
||||||
abstract ContiguousSet<C> headSetImpl(C toElement, boolean inclusive);
|
abstract ContiguousSet<C> headSetImpl(C toElement, boolean inclusive);
|
||||||
|
|
||||||
/* @Override */
|
@SuppressWarnings("MissingOverride") // Supermethod does not exist under GWT.
|
||||||
abstract ContiguousSet<C> subSetImpl(
|
abstract ContiguousSet<C> subSetImpl(
|
||||||
C fromElement, boolean fromInclusive, C toElement, boolean toInclusive);
|
C fromElement, boolean fromInclusive, C toElement, boolean toInclusive);
|
||||||
|
|
||||||
/* @Override */
|
@SuppressWarnings("MissingOverride") // Supermethod does not exist under GWT.
|
||||||
abstract ContiguousSet<C> tailSetImpl(C fromElement, boolean inclusive);
|
abstract ContiguousSet<C> tailSetImpl(C fromElement, boolean inclusive);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -786,7 +786,7 @@ public abstract class FluentIterable<E> implements Iterable<E> {
|
||||||
checkNotNull(collection);
|
checkNotNull(collection);
|
||||||
Iterable<E> iterable = getDelegate();
|
Iterable<E> iterable = getDelegate();
|
||||||
if (iterable instanceof Collection) {
|
if (iterable instanceof Collection) {
|
||||||
collection.addAll(Collections2.cast(iterable));
|
collection.addAll((Collection<E>) iterable);
|
||||||
} else {
|
} else {
|
||||||
for (E item : iterable) {
|
for (E item : iterable) {
|
||||||
collection.add(item);
|
collection.add(item);
|
||||||
|
|
|
@ -76,8 +76,8 @@ public abstract class ForwardingMap<K, V> extends ForwardingObject implements Ma
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public V remove(Object object) {
|
public V remove(Object key) {
|
||||||
return delegate().remove(object);
|
return delegate().remove(key);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -413,15 +413,14 @@ public abstract class ImmutableBiMap<K, V> extends ImmutableBiMapFauxverideShim<
|
||||||
* <p>Since the bimap is immutable, ImmutableBiMap doesn't require special logic for keeping the
|
* <p>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.
|
* bimap and its inverse in sync during serialization, the way AbstractBiMap does.
|
||||||
*/
|
*/
|
||||||
private static class SerializedForm extends ImmutableMap.SerializedForm {
|
private static class SerializedForm<K, V> extends ImmutableMap.SerializedForm<K, V> {
|
||||||
SerializedForm(ImmutableBiMap<?, ?> bimap) {
|
SerializedForm(ImmutableBiMap<K, V> bimap) {
|
||||||
super(bimap);
|
super(bimap);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
Object readResolve() {
|
Builder<K, V> makeBuilder(int size) {
|
||||||
Builder<Object, Object> builder = new Builder<>();
|
return new Builder<>();
|
||||||
return createMap(builder);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final long serialVersionUID = 0;
|
private static final long serialVersionUID = 0;
|
||||||
|
@ -429,6 +428,6 @@ public abstract class ImmutableBiMap<K, V> extends ImmutableBiMapFauxverideShim<
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
Object writeReplace() {
|
Object writeReplace() {
|
||||||
return new SerializedForm(this);
|
return new SerializedForm<>(this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -35,7 +35,6 @@ import java.util.Map;
|
||||||
import java.util.Map.Entry;
|
import java.util.Map.Entry;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
import java.util.stream.Collector;
|
import java.util.stream.Collector;
|
||||||
import java.util.stream.Collectors;
|
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
|
||||||
|
@ -83,13 +82,7 @@ public class ImmutableListMultimap<K, V> extends ImmutableMultimap<K, V>
|
||||||
public static <T, K, V> Collector<T, ?, ImmutableListMultimap<K, V>> toImmutableListMultimap(
|
public static <T, K, V> Collector<T, ?, ImmutableListMultimap<K, V>> toImmutableListMultimap(
|
||||||
Function<? super T, ? extends K> keyFunction,
|
Function<? super T, ? extends K> keyFunction,
|
||||||
Function<? super T, ? extends V> valueFunction) {
|
Function<? super T, ? extends V> valueFunction) {
|
||||||
checkNotNull(keyFunction, "keyFunction");
|
return CollectCollectors.toImmutableListMultimap(keyFunction, valueFunction);
|
||||||
checkNotNull(valueFunction, "valueFunction");
|
|
||||||
return Collector.of(
|
|
||||||
ImmutableListMultimap::<K, V>builder,
|
|
||||||
(builder, t) -> builder.put(keyFunction.apply(t), valueFunction.apply(t)),
|
|
||||||
ImmutableListMultimap.Builder::combine,
|
|
||||||
ImmutableListMultimap.Builder::build);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -127,14 +120,7 @@ public class ImmutableListMultimap<K, V> extends ImmutableMultimap<K, V>
|
||||||
Collector<T, ?, ImmutableListMultimap<K, V>> flatteningToImmutableListMultimap(
|
Collector<T, ?, ImmutableListMultimap<K, V>> flatteningToImmutableListMultimap(
|
||||||
Function<? super T, ? extends K> keyFunction,
|
Function<? super T, ? extends K> keyFunction,
|
||||||
Function<? super T, ? extends Stream<? extends V>> valuesFunction) {
|
Function<? super T, ? extends Stream<? extends V>> valuesFunction) {
|
||||||
checkNotNull(keyFunction);
|
return CollectCollectors.flatteningToImmutableListMultimap(keyFunction, valuesFunction);
|
||||||
checkNotNull(valuesFunction);
|
|
||||||
return Collectors.collectingAndThen(
|
|
||||||
Multimaps.flatteningToMultimap(
|
|
||||||
input -> checkNotNull(keyFunction.apply(input)),
|
|
||||||
input -> valuesFunction.apply(input).peek(Preconditions::checkNotNull),
|
|
||||||
MultimapBuilder.linkedHashKeys().arrayListValues()::<K, V>build),
|
|
||||||
ImmutableListMultimap::copyOf);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Returns the empty multimap. */
|
/** Returns the empty multimap. */
|
||||||
|
|
|
@ -36,7 +36,6 @@ import java.util.Collections;
|
||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
import java.util.EnumMap;
|
import java.util.EnumMap;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.LinkedHashMap;
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.SortedMap;
|
import java.util.SortedMap;
|
||||||
import java.util.Spliterator;
|
import java.util.Spliterator;
|
||||||
|
@ -96,12 +95,7 @@ public abstract class ImmutableMap<K, V> implements Map<K, V>, Serializable {
|
||||||
Function<? super T, ? extends K> keyFunction,
|
Function<? super T, ? extends K> keyFunction,
|
||||||
Function<? super T, ? extends V> valueFunction,
|
Function<? super T, ? extends V> valueFunction,
|
||||||
BinaryOperator<V> mergeFunction) {
|
BinaryOperator<V> mergeFunction) {
|
||||||
checkNotNull(keyFunction);
|
return CollectCollectors.toImmutableMap(keyFunction, valueFunction, mergeFunction);
|
||||||
checkNotNull(valueFunction);
|
|
||||||
checkNotNull(mergeFunction);
|
|
||||||
return Collectors.collectingAndThen(
|
|
||||||
Collectors.toMap(keyFunction, valueFunction, mergeFunction, LinkedHashMap::new),
|
|
||||||
ImmutableMap::copyOf);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -889,37 +883,85 @@ public abstract class ImmutableMap<K, V> implements Map<K, V>, Serializable {
|
||||||
* reconstructed using public factory methods. This ensures that the implementation types remain
|
* reconstructed using public factory methods. This ensures that the implementation types remain
|
||||||
* as implementation details.
|
* as implementation details.
|
||||||
*/
|
*/
|
||||||
static class SerializedForm implements Serializable {
|
static class SerializedForm<K, V> implements Serializable {
|
||||||
private final Object[] keys;
|
// This object retains references to collections returned by keySet() and value(). This saves
|
||||||
private final Object[] values;
|
// bytes when the both the map and its keySet or value collection are written to the same
|
||||||
|
// instance of ObjectOutputStream.
|
||||||
|
|
||||||
SerializedForm(ImmutableMap<?, ?> map) {
|
// TODO(b/160980469): remove support for the old serialization format after some time
|
||||||
keys = new Object[map.size()];
|
private static final boolean USE_LEGACY_SERIALIZATION = true;
|
||||||
values = new Object[map.size()];
|
|
||||||
|
private final Object keys;
|
||||||
|
private final Object values;
|
||||||
|
|
||||||
|
SerializedForm(ImmutableMap<K, V> map) {
|
||||||
|
if (USE_LEGACY_SERIALIZATION) {
|
||||||
|
Object[] keys = new Object[map.size()];
|
||||||
|
Object[] values = new Object[map.size()];
|
||||||
int i = 0;
|
int i = 0;
|
||||||
for (Entry<?, ?> entry : map.entrySet()) {
|
for (Entry<?, ?> entry : map.entrySet()) {
|
||||||
keys[i] = entry.getKey();
|
keys[i] = entry.getKey();
|
||||||
values[i] = entry.getValue();
|
values[i] = entry.getValue();
|
||||||
i++;
|
i++;
|
||||||
}
|
}
|
||||||
|
this.keys = keys;
|
||||||
|
this.values = values;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.keys = map.keySet();
|
||||||
|
this.values = map.values();
|
||||||
}
|
}
|
||||||
|
|
||||||
Object readResolve() {
|
@SuppressWarnings("unchecked")
|
||||||
Builder<Object, Object> builder = new Builder<>(keys.length);
|
final Object readResolve() {
|
||||||
return createMap(builder);
|
if (!(this.keys instanceof ImmutableSet)) {
|
||||||
|
return legacyReadResolve();
|
||||||
}
|
}
|
||||||
|
|
||||||
Object createMap(Builder<Object, Object> builder) {
|
ImmutableSet<K> keySet = (ImmutableSet<K>) this.keys;
|
||||||
|
ImmutableCollection<V> values = (ImmutableCollection<V>) this.values;
|
||||||
|
|
||||||
|
Builder<K, V> builder = makeBuilder(keySet.size());
|
||||||
|
|
||||||
|
UnmodifiableIterator<K> keyIter = keySet.iterator();
|
||||||
|
UnmodifiableIterator<V> valueIter = values.iterator();
|
||||||
|
|
||||||
|
while (keyIter.hasNext()) {
|
||||||
|
builder.put(keyIter.next(), valueIter.next());
|
||||||
|
}
|
||||||
|
|
||||||
|
return builder.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
final Object legacyReadResolve() {
|
||||||
|
K[] keys = (K[]) this.keys;
|
||||||
|
V[] values = (V[]) this.values;
|
||||||
|
|
||||||
|
Builder<K, V> builder = makeBuilder(keys.length);
|
||||||
|
|
||||||
for (int i = 0; i < keys.length; i++) {
|
for (int i = 0; i < keys.length; i++) {
|
||||||
builder.put(keys[i], values[i]);
|
builder.put(keys[i], values[i]);
|
||||||
}
|
}
|
||||||
return builder.build();
|
return builder.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a builder that builds the unserialized type. Subclasses should override this method.
|
||||||
|
*/
|
||||||
|
Builder<K, V> makeBuilder(int size) {
|
||||||
|
return new Builder<>(size);
|
||||||
|
}
|
||||||
|
|
||||||
private static final long serialVersionUID = 0;
|
private static final long serialVersionUID = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a serializable form of this object. Non-public subclasses should not override this
|
||||||
|
* method. Publicly-accessible subclasses must override this method and should return a subclass
|
||||||
|
* of SerializedForm whose readResolve() method returns objects of the subclass type.
|
||||||
|
*/
|
||||||
Object writeReplace() {
|
Object writeReplace() {
|
||||||
return new SerializedForm(this);
|
return new SerializedForm<>(this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -75,12 +75,6 @@ final class ImmutableMapKeySet<K, V> extends IndexedImmutableSet<K> {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@GwtIncompatible // serialization
|
|
||||||
@Override
|
|
||||||
Object writeReplace() {
|
|
||||||
return new KeySetSerializedForm<K>(map);
|
|
||||||
}
|
|
||||||
|
|
||||||
@GwtIncompatible // serialization
|
@GwtIncompatible // serialization
|
||||||
private static class KeySetSerializedForm<K> implements Serializable {
|
private static class KeySetSerializedForm<K> implements Serializable {
|
||||||
final ImmutableMap<K, ?> map;
|
final ImmutableMap<K, ?> map;
|
||||||
|
|
|
@ -100,12 +100,6 @@ final class ImmutableMapValues<K, V> extends ImmutableCollection<V> {
|
||||||
map.forEach((k, v) -> action.accept(v));
|
map.forEach((k, v) -> action.accept(v));
|
||||||
}
|
}
|
||||||
|
|
||||||
@GwtIncompatible // serialization
|
|
||||||
@Override
|
|
||||||
Object writeReplace() {
|
|
||||||
return new SerializedForm<V>(map);
|
|
||||||
}
|
|
||||||
|
|
||||||
@GwtIncompatible // serialization
|
@GwtIncompatible // serialization
|
||||||
private static class SerializedForm<V> implements Serializable {
|
private static class SerializedForm<V> implements Serializable {
|
||||||
final ImmutableMap<?, V> map;
|
final ImmutableMap<?, V> map;
|
||||||
|
|
|
@ -64,7 +64,7 @@ public abstract class ImmutableMultiset<E> extends ImmutableMultisetGwtSerializa
|
||||||
* @since 21.0
|
* @since 21.0
|
||||||
*/
|
*/
|
||||||
public static <E> Collector<E, ?, ImmutableMultiset<E>> toImmutableMultiset() {
|
public static <E> Collector<E, ?, ImmutableMultiset<E>> toImmutableMultiset() {
|
||||||
return toImmutableMultiset(Function.identity(), e -> 1);
|
return CollectCollectors.toImmutableMultiset(Function.identity(), e -> 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -80,17 +80,7 @@ public abstract class ImmutableMultiset<E> extends ImmutableMultisetGwtSerializa
|
||||||
*/
|
*/
|
||||||
public static <T, E> Collector<T, ?, ImmutableMultiset<E>> toImmutableMultiset(
|
public static <T, E> Collector<T, ?, ImmutableMultiset<E>> toImmutableMultiset(
|
||||||
Function<? super T, ? extends E> elementFunction, ToIntFunction<? super T> countFunction) {
|
Function<? super T, ? extends E> elementFunction, ToIntFunction<? super T> countFunction) {
|
||||||
checkNotNull(elementFunction);
|
return CollectCollectors.toImmutableMultiset(elementFunction, countFunction);
|
||||||
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<E> multiset) -> copyFromEntries(multiset.entrySet()));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Returns the empty immutable multiset. */
|
/** Returns the empty immutable multiset. */
|
||||||
|
|
|
@ -514,13 +514,11 @@ public abstract class ImmutableSet<E> extends ImmutableCollection<E> implements
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
||||||
public Builder<E> add(E... elements) {
|
public Builder<E> add(E... elements) {
|
||||||
super.add(elements);
|
super.add(elements);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
/**
|
/**
|
||||||
* Adds each element of {@code elements} to the {@code ImmutableSet}, ignoring duplicate
|
* Adds each element of {@code elements} to the {@code ImmutableSet}, ignoring duplicate
|
||||||
* elements (only the first duplicate element is added).
|
* elements (only the first duplicate element is added).
|
||||||
|
@ -529,7 +527,7 @@ public abstract class ImmutableSet<E> extends ImmutableCollection<E> implements
|
||||||
* @return this {@code Builder} object
|
* @return this {@code Builder} object
|
||||||
* @throws NullPointerException if {@code elements} is null or contains a null element
|
* @throws NullPointerException if {@code elements} is null or contains a null element
|
||||||
*/
|
*/
|
||||||
|
@Override
|
||||||
public Builder<E> addAll(Iterable<? extends E> elements) {
|
public Builder<E> addAll(Iterable<? extends E> elements) {
|
||||||
super.addAll(elements);
|
super.addAll(elements);
|
||||||
return this;
|
return this;
|
||||||
|
|
|
@ -22,7 +22,6 @@ import com.google.common.annotations.Beta;
|
||||||
import com.google.common.annotations.GwtCompatible;
|
import com.google.common.annotations.GwtCompatible;
|
||||||
import com.google.common.annotations.GwtIncompatible;
|
import com.google.common.annotations.GwtIncompatible;
|
||||||
import com.google.common.base.MoreObjects;
|
import com.google.common.base.MoreObjects;
|
||||||
import com.google.common.base.Preconditions;
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -38,7 +37,6 @@ import java.util.Map;
|
||||||
import java.util.Map.Entry;
|
import java.util.Map.Entry;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
import java.util.stream.Collector;
|
import java.util.stream.Collector;
|
||||||
import java.util.stream.Collectors;
|
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
|
||||||
|
@ -87,13 +85,7 @@ public class ImmutableSetMultimap<K, V> extends ImmutableMultimap<K, V>
|
||||||
public static <T, K, V> Collector<T, ?, ImmutableSetMultimap<K, V>> toImmutableSetMultimap(
|
public static <T, K, V> Collector<T, ?, ImmutableSetMultimap<K, V>> toImmutableSetMultimap(
|
||||||
Function<? super T, ? extends K> keyFunction,
|
Function<? super T, ? extends K> keyFunction,
|
||||||
Function<? super T, ? extends V> valueFunction) {
|
Function<? super T, ? extends V> valueFunction) {
|
||||||
checkNotNull(keyFunction, "keyFunction");
|
return CollectCollectors.toImmutableSetMultimap(keyFunction, valueFunction);
|
||||||
checkNotNull(valueFunction, "valueFunction");
|
|
||||||
return Collector.of(
|
|
||||||
ImmutableSetMultimap::<K, V>builder,
|
|
||||||
(builder, t) -> builder.put(keyFunction.apply(t), valueFunction.apply(t)),
|
|
||||||
ImmutableSetMultimap.Builder::combine,
|
|
||||||
ImmutableSetMultimap.Builder::build);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -140,14 +132,7 @@ public class ImmutableSetMultimap<K, V> extends ImmutableMultimap<K, V>
|
||||||
Collector<T, ?, ImmutableSetMultimap<K, V>> flatteningToImmutableSetMultimap(
|
Collector<T, ?, ImmutableSetMultimap<K, V>> flatteningToImmutableSetMultimap(
|
||||||
Function<? super T, ? extends K> keyFunction,
|
Function<? super T, ? extends K> keyFunction,
|
||||||
Function<? super T, ? extends Stream<? extends V>> valuesFunction) {
|
Function<? super T, ? extends Stream<? extends V>> valuesFunction) {
|
||||||
checkNotNull(keyFunction);
|
return CollectCollectors.flatteningToImmutableSetMultimap(keyFunction, valuesFunction);
|
||||||
checkNotNull(valuesFunction);
|
|
||||||
return Collectors.collectingAndThen(
|
|
||||||
Multimaps.flatteningToMultimap(
|
|
||||||
input -> checkNotNull(keyFunction.apply(input)),
|
|
||||||
input -> valuesFunction.apply(input).peek(Preconditions::checkNotNull),
|
|
||||||
MultimapBuilder.linkedHashKeys().linkedHashSetValues()::<K, V>build),
|
|
||||||
ImmutableSetMultimap::copyOf);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Returns the empty multimap. */
|
/** Returns the empty multimap. */
|
||||||
|
|
|
@ -95,14 +95,7 @@ public final class ImmutableSortedMap<K, V> extends ImmutableSortedMapFauxveride
|
||||||
Function<? super T, ? extends K> keyFunction,
|
Function<? super T, ? extends K> keyFunction,
|
||||||
Function<? super T, ? extends V> valueFunction,
|
Function<? super T, ? extends V> valueFunction,
|
||||||
BinaryOperator<V> mergeFunction) {
|
BinaryOperator<V> mergeFunction) {
|
||||||
checkNotNull(comparator);
|
return CollectCollectors.toImmutableSortedMap(comparator, keyFunction, valueFunction, mergeFunction);
|
||||||
checkNotNull(keyFunction);
|
|
||||||
checkNotNull(valueFunction);
|
|
||||||
checkNotNull(mergeFunction);
|
|
||||||
return Collectors.collectingAndThen(
|
|
||||||
Collectors.toMap(
|
|
||||||
keyFunction, valueFunction, mergeFunction, () -> new TreeMap<K, V>(comparator)),
|
|
||||||
ImmutableSortedMap::copyOfSorted);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -916,19 +909,18 @@ public final class ImmutableSortedMap<K, V> extends ImmutableSortedMapFauxveride
|
||||||
* are reconstructed using public factory methods. This ensures that the implementation types
|
* are reconstructed using public factory methods. This ensures that the implementation types
|
||||||
* remain as implementation details.
|
* remain as implementation details.
|
||||||
*/
|
*/
|
||||||
private static class SerializedForm extends ImmutableMap.SerializedForm {
|
private static class SerializedForm<K, V> extends ImmutableMap.SerializedForm<K, V> {
|
||||||
private final Comparator<Object> comparator;
|
private final Comparator<? super K> comparator;
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
SerializedForm(ImmutableSortedMap<?, ?> sortedMap) {
|
SerializedForm(ImmutableSortedMap<K, V> sortedMap) {
|
||||||
super(sortedMap);
|
super(sortedMap);
|
||||||
comparator = (Comparator<Object>) sortedMap.comparator();
|
comparator = sortedMap.comparator();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
Object readResolve() {
|
Builder<K, V> makeBuilder(int size) {
|
||||||
Builder<Object, Object> builder = new Builder<>(comparator);
|
return new Builder<>(comparator);
|
||||||
return createMap(builder);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final long serialVersionUID = 0;
|
private static final long serialVersionUID = 0;
|
||||||
|
@ -936,7 +928,7 @@ public final class ImmutableSortedMap<K, V> extends ImmutableSortedMapFauxveride
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
Object writeReplace() {
|
Object writeReplace() {
|
||||||
return new SerializedForm(this);
|
return new SerializedForm<>(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
// This class is never actually serialized directly, but we have to make the
|
// This class is never actually serialized directly, but we have to make the
|
||||||
|
|
|
@ -20,10 +20,8 @@ import static com.google.common.base.Preconditions.checkNotNull;
|
||||||
|
|
||||||
import com.google.common.annotations.GwtCompatible;
|
import com.google.common.annotations.GwtCompatible;
|
||||||
import com.google.common.base.MoreObjects;
|
import com.google.common.base.MoreObjects;
|
||||||
import com.google.common.collect.Tables.AbstractCell;
|
|
||||||
|
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
@ -63,15 +61,7 @@ public abstract class ImmutableTable<R, C, V> extends AbstractTable<R, C, V>
|
||||||
Function<? super T, ? extends R> rowFunction,
|
Function<? super T, ? extends R> rowFunction,
|
||||||
Function<? super T, ? extends C> columnFunction,
|
Function<? super T, ? extends C> columnFunction,
|
||||||
Function<? super T, ? extends V> valueFunction) {
|
Function<? super T, ? extends V> valueFunction) {
|
||||||
checkNotNull(rowFunction, "rowFunction");
|
return TableCollectors.toImmutableTable(rowFunction, columnFunction, valueFunction);
|
||||||
checkNotNull(columnFunction, "columnFunction");
|
|
||||||
checkNotNull(valueFunction, "valueFunction");
|
|
||||||
return Collector.of(
|
|
||||||
() -> new ImmutableTable.Builder<R, C, V>(),
|
|
||||||
(builder, t) ->
|
|
||||||
builder.put(rowFunction.apply(t), columnFunction.apply(t), valueFunction.apply(t)),
|
|
||||||
(b1, b2) -> b1.combine(b2),
|
|
||||||
b -> b.build());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -90,88 +80,7 @@ public abstract class ImmutableTable<R, C, V> extends AbstractTable<R, C, V>
|
||||||
Function<? super T, ? extends C> columnFunction,
|
Function<? super T, ? extends C> columnFunction,
|
||||||
Function<? super T, ? extends V> valueFunction,
|
Function<? super T, ? extends V> valueFunction,
|
||||||
BinaryOperator<V> mergeFunction) {
|
BinaryOperator<V> mergeFunction) {
|
||||||
|
return TableCollectors.toImmutableTable(rowFunction, columnFunction, valueFunction, 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<R, C, V>()
|
|
||||||
/* 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<R, C, V> {
|
|
||||||
final List<MutableCell<R, C, V>> insertionOrder = new ArrayList<>();
|
|
||||||
final Table<R, C, MutableCell<R, C, V>> table = HashBasedTable.create();
|
|
||||||
|
|
||||||
void put(R row, C column, V value, BinaryOperator<V> merger) {
|
|
||||||
MutableCell<R, C, V> oldCell = table.get(row, column);
|
|
||||||
if (oldCell == null) {
|
|
||||||
MutableCell<R, C, V> cell = new MutableCell<>(row, column, value);
|
|
||||||
insertionOrder.add(cell);
|
|
||||||
table.put(row, column, cell);
|
|
||||||
} else {
|
|
||||||
oldCell.merge(value, merger);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
CollectorState<R, C, V> combine(CollectorState<R, C, V> other, BinaryOperator<V> merger) {
|
|
||||||
for (MutableCell<R, C, V> cell : other.insertionOrder) {
|
|
||||||
put(cell.getRowKey(), cell.getColumnKey(), cell.getValue(), merger);
|
|
||||||
}
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
ImmutableTable<R, C, V> toTable() {
|
|
||||||
return copyOf(insertionOrder);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static final class MutableCell<R, C, V> extends AbstractCell<R, C, V> {
|
|
||||||
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<V> mergeFunction) {
|
|
||||||
checkNotNull(value, "value");
|
|
||||||
this.value = checkNotNull(mergeFunction.apply(this.value, value), "mergeFunction.apply");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Returns an empty immutable table. */
|
/** Returns an empty immutable table. */
|
||||||
|
@ -209,7 +118,7 @@ public abstract class ImmutableTable<R, C, V> extends AbstractTable<R, C, V>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static <R, C, V> ImmutableTable<R, C, V> copyOf(
|
public static <R, C, V> ImmutableTable<R, C, V> copyOf(
|
||||||
Iterable<? extends Cell<? extends R, ? extends C, ? extends V>> cells) {
|
Iterable<? extends Cell<? extends R, ? extends C, ? extends V>> cells) {
|
||||||
ImmutableTable.Builder<R, C, V> builder = ImmutableTable.builder();
|
ImmutableTable.Builder<R, C, V> builder = ImmutableTable.builder();
|
||||||
for (Cell<? extends R, ? extends C, ? extends V> cell : cells) {
|
for (Cell<? extends R, ? extends C, ? extends V> cell : cells) {
|
||||||
|
|
|
@ -21,8 +21,12 @@ import com.google.common.annotations.GwtIncompatible;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Provides equivalent behavior to {@link String#intern} for other immutable types. Common
|
* Provides similar behavior to {@link String#intern} for any immutable type. Common implementations
|
||||||
* implementations are available from the {@link Interners} class.
|
* are available from the {@link Interners} class.
|
||||||
|
*
|
||||||
|
* <p>Note that {@code String.intern()} has some well-known performance limitations, and should
|
||||||
|
* generally be avoided. Prefer {@link Interners#newWeakInterner} or another {@code Interner}
|
||||||
|
* implementation even for {@code String} interning.
|
||||||
*
|
*
|
||||||
* @author Kevin Bourrillion
|
* @author Kevin Bourrillion
|
||||||
* @since 3.0
|
* @since 3.0
|
||||||
|
|
|
@ -314,7 +314,7 @@ public final class Iterables {
|
||||||
|
|
||||||
public static <T> boolean addAll(Collection<T> addTo, Iterable<? extends T> elementsToAdd) {
|
public static <T> boolean addAll(Collection<T> addTo, Iterable<? extends T> elementsToAdd) {
|
||||||
if (elementsToAdd instanceof Collection) {
|
if (elementsToAdd instanceof Collection) {
|
||||||
Collection<? extends T> c = Collections2.cast(elementsToAdd);
|
Collection<? extends T> c = (Collection<? extends T>) elementsToAdd;
|
||||||
return addTo.addAll(c);
|
return addTo.addAll(c);
|
||||||
}
|
}
|
||||||
return Iterators.addAll(addTo, checkNotNull(elementsToAdd).iterator());
|
return Iterators.addAll(addTo, checkNotNull(elementsToAdd).iterator());
|
||||||
|
@ -814,7 +814,7 @@ public final class Iterables {
|
||||||
*/
|
*/
|
||||||
public static <T> T getLast(Iterable<? extends T> iterable, T defaultValue) {
|
public static <T> T getLast(Iterable<? extends T> iterable, T defaultValue) {
|
||||||
if (iterable instanceof Collection) {
|
if (iterable instanceof Collection) {
|
||||||
Collection<? extends T> c = Collections2.cast(iterable);
|
Collection<? extends T> c = (Collection<? extends T>) iterable;
|
||||||
if (c.isEmpty()) {
|
if (c.isEmpty()) {
|
||||||
return defaultValue;
|
return defaultValue;
|
||||||
} else if (iterable instanceof List) {
|
} else if (iterable instanceof List) {
|
||||||
|
|
|
@ -126,7 +126,7 @@ public final class Lists {
|
||||||
checkNotNull(elements); // for GWT
|
checkNotNull(elements); // for GWT
|
||||||
// Let ArrayList's sizing logic work, if possible
|
// Let ArrayList's sizing logic work, if possible
|
||||||
return (elements instanceof Collection)
|
return (elements instanceof Collection)
|
||||||
? new ArrayList<>(Collections2.cast(elements))
|
? new ArrayList<>((Collection<? extends E>) elements)
|
||||||
: newArrayList(elements.iterator());
|
: newArrayList(elements.iterator());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -264,8 +264,9 @@ public final class Lists {
|
||||||
Iterable<? extends E> elements) {
|
Iterable<? extends E> elements) {
|
||||||
// We copy elements to an ArrayList first, rather than incurring the
|
// We copy elements to an ArrayList first, rather than incurring the
|
||||||
// quadratic cost of adding them to the COWAL directly.
|
// quadratic cost of adding them to the COWAL directly.
|
||||||
Collection<? extends E> elementsCollection =
|
Collection<? extends E> elementsCollection = (elements instanceof Collection)
|
||||||
(elements instanceof Collection) ? Collections2.cast(elements) : newArrayList(elements);
|
? (Collection<? extends E>) elements
|
||||||
|
: newArrayList(elements);
|
||||||
return new CopyOnWriteArrayList<>(elementsCollection);
|
return new CopyOnWriteArrayList<>(elementsCollection);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -64,7 +64,7 @@ import java.util.concurrent.locks.ReentrantLock;
|
||||||
* @author Charles Fry
|
* @author Charles Fry
|
||||||
* @author Doug Lea ({@code ConcurrentHashMap})
|
* @author Doug Lea ({@code ConcurrentHashMap})
|
||||||
*/
|
*/
|
||||||
// TODO(kak/cpovirk): Consider removing from this class.
|
// TODO(kak): Consider removing @CanIgnoreReturnValue from this class.
|
||||||
@GwtIncompatible
|
@GwtIncompatible
|
||||||
@SuppressWarnings("GuardedBy") // TODO(b/35466881): Fix or suppress.
|
@SuppressWarnings("GuardedBy") // TODO(b/35466881): Fix or suppress.
|
||||||
class MapMakerInternalMap<
|
class MapMakerInternalMap<
|
||||||
|
|
|
@ -172,37 +172,6 @@ public final class Maps {
|
||||||
return ImmutableEnumMap.asImmutable(enumMap);
|
return ImmutableEnumMap.asImmutable(enumMap);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class Accumulator<K extends Enum<K>, V> {
|
|
||||||
private final BinaryOperator<V> mergeFunction;
|
|
||||||
private EnumMap<K, V> map = null;
|
|
||||||
|
|
||||||
Accumulator(BinaryOperator<V> mergeFunction) {
|
|
||||||
this.mergeFunction = mergeFunction;
|
|
||||||
}
|
|
||||||
|
|
||||||
void put(K key, V value) {
|
|
||||||
if (map == null) {
|
|
||||||
map = new EnumMap<>(key.getDeclaringClass());
|
|
||||||
}
|
|
||||||
map.merge(key, value, mergeFunction);
|
|
||||||
}
|
|
||||||
|
|
||||||
Accumulator<K, V> combine(Accumulator<K, V> other) {
|
|
||||||
if (this.map == null) {
|
|
||||||
return other;
|
|
||||||
} else if (other.map == null) {
|
|
||||||
return this;
|
|
||||||
} else {
|
|
||||||
other.map.forEach(this::put);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ImmutableMap<K, V> toImmutableMap() {
|
|
||||||
return (map == null) ? ImmutableMap.<K, V>of() : ImmutableEnumMap.asImmutable(map);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a {@link Collector} that accumulates elements into an {@code ImmutableMap} whose keys
|
* 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
|
* and values are the result of applying the provided mapping functions to the input elements. The
|
||||||
|
@ -220,22 +189,7 @@ public final class Maps {
|
||||||
public static <T, K extends Enum<K>, V> Collector<T, ?, ImmutableMap<K, V>> toImmutableEnumMap(
|
public static <T, K extends Enum<K>, V> Collector<T, ?, ImmutableMap<K, V>> toImmutableEnumMap(
|
||||||
java.util.function.Function<? super T, ? extends K> keyFunction,
|
java.util.function.Function<? super T, ? extends K> keyFunction,
|
||||||
java.util.function.Function<? super T, ? extends V> valueFunction) {
|
java.util.function.Function<? super T, ? extends V> valueFunction) {
|
||||||
checkNotNull(keyFunction);
|
return CollectCollectors.toImmutableEnumMap(keyFunction, valueFunction);
|
||||||
checkNotNull(valueFunction);
|
|
||||||
return Collector.of(
|
|
||||||
() ->
|
|
||||||
new Accumulator<K, V>(
|
|
||||||
(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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -253,19 +207,7 @@ public final class Maps {
|
||||||
java.util.function.Function<? super T, ? extends K> keyFunction,
|
java.util.function.Function<? super T, ? extends K> keyFunction,
|
||||||
java.util.function.Function<? super T, ? extends V> valueFunction,
|
java.util.function.Function<? super T, ? extends V> valueFunction,
|
||||||
BinaryOperator<V> mergeFunction) {
|
BinaryOperator<V> mergeFunction) {
|
||||||
checkNotNull(keyFunction);
|
return CollectCollectors.toImmutableEnumMap(keyFunction, valueFunction, mergeFunction);
|
||||||
checkNotNull(valueFunction);
|
|
||||||
checkNotNull(mergeFunction);
|
|
||||||
// not UNORDERED because we don't know if mergeFunction is commutative
|
|
||||||
return Collector.of(
|
|
||||||
() -> new Accumulator<K, V>(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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -49,6 +49,7 @@ import java.util.NoSuchElementException;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.SortedSet;
|
import java.util.SortedSet;
|
||||||
import java.util.Spliterator;
|
import java.util.Spliterator;
|
||||||
|
import java.util.function.BiConsumer;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
import java.util.stream.Collector;
|
import java.util.stream.Collector;
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
|
@ -104,21 +105,11 @@ public final class Multimaps {
|
||||||
*
|
*
|
||||||
* @since 21.0
|
* @since 21.0
|
||||||
*/
|
*/
|
||||||
@Beta
|
|
||||||
public static <T, K, V, M extends Multimap<K, V>> Collector<T, ?, M> toMultimap(
|
public static <T, K, V, M extends Multimap<K, V>> Collector<T, ?, M> toMultimap(
|
||||||
java.util.function.Function<? super T, ? extends K> keyFunction,
|
java.util.function.Function<? super T, ? extends K> keyFunction,
|
||||||
java.util.function.Function<? super T, ? extends V> valueFunction,
|
java.util.function.Function<? super T, ? extends V> valueFunction,
|
||||||
java.util.function.Supplier<M> multimapSupplier) {
|
java.util.function.Supplier<M> multimapSupplier) {
|
||||||
checkNotNull(keyFunction);
|
return CollectCollectors.toMultimap(keyFunction, valueFunction, multimapSupplier);
|
||||||
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;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -695,6 +686,11 @@ public final class Multimaps {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void forEach(BiConsumer<? super K, ? super V> consumer) {
|
||||||
|
delegate.forEach(checkNotNull(consumer));
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Collection<V> get(K key) {
|
public Collection<V> get(K key) {
|
||||||
return unmodifiableValueCollection(delegate.get(key));
|
return unmodifiableValueCollection(delegate.get(key));
|
||||||
|
|
|
@ -77,16 +77,7 @@ public final class Multisets {
|
||||||
java.util.function.Function<? super T, E> elementFunction,
|
java.util.function.Function<? super T, E> elementFunction,
|
||||||
java.util.function.ToIntFunction<? super T> countFunction,
|
java.util.function.ToIntFunction<? super T> countFunction,
|
||||||
java.util.function.Supplier<M> multisetSupplier) {
|
java.util.function.Supplier<M> multisetSupplier) {
|
||||||
checkNotNull(elementFunction);
|
return CollectCollectors.toMultiset(elementFunction, countFunction, multisetSupplier);
|
||||||
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;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -582,8 +582,8 @@ public abstract class Ordering<T> implements Comparator<T> {
|
||||||
* <p><b>Implementation note:</b> this method is invoked by the default implementations of the
|
* <p><b>Implementation note:</b> this method is invoked by the default implementations of the
|
||||||
* other {@code min} overloads, so overriding it will affect their behavior.
|
* other {@code min} overloads, so overriding it will affect their behavior.
|
||||||
*
|
*
|
||||||
* <p><b>Java 8 users:</b> Use {@code Collections.min(Arrays.asList(a, b), thisComparator)}
|
* <p><b>Note:</b> Consider using {@code Comparators.min(a, b, thisComparator)} instead. If {@code
|
||||||
* instead (but note that it does not guarantee which tied minimum element is returned).
|
* thisComparator} is {@link Ordering#natural}, then use {@code Comparators.min(a, b)}.
|
||||||
*
|
*
|
||||||
* @param a value to compare, returned if less than or equal to b.
|
* @param a value to compare, returned if less than or equal to b.
|
||||||
* @param b value to compare.
|
* @param b value to compare.
|
||||||
|
|
|
@ -74,7 +74,7 @@ public final class Queues {
|
||||||
*/
|
*/
|
||||||
public static <E> ArrayDeque<E> newArrayDeque(Iterable<? extends E> elements) {
|
public static <E> ArrayDeque<E> newArrayDeque(Iterable<? extends E> elements) {
|
||||||
if (elements instanceof Collection) {
|
if (elements instanceof Collection) {
|
||||||
return new ArrayDeque<E>(Collections2.cast(elements));
|
return new ArrayDeque<E>((Collection<? extends E>) elements);
|
||||||
}
|
}
|
||||||
ArrayDeque<E> deque = new ArrayDeque<E>();
|
ArrayDeque<E> deque = new ArrayDeque<E>();
|
||||||
Iterables.addAll(deque, elements);
|
Iterables.addAll(deque, elements);
|
||||||
|
@ -97,7 +97,7 @@ public final class Queues {
|
||||||
public static <E> ConcurrentLinkedQueue<E> newConcurrentLinkedQueue(
|
public static <E> ConcurrentLinkedQueue<E> newConcurrentLinkedQueue(
|
||||||
Iterable<? extends E> elements) {
|
Iterable<? extends E> elements) {
|
||||||
if (elements instanceof Collection) {
|
if (elements instanceof Collection) {
|
||||||
return new ConcurrentLinkedQueue<E>(Collections2.cast(elements));
|
return new ConcurrentLinkedQueue<E>((Collection<? extends E>) elements);
|
||||||
}
|
}
|
||||||
ConcurrentLinkedQueue<E> queue = new ConcurrentLinkedQueue<E>();
|
ConcurrentLinkedQueue<E> queue = new ConcurrentLinkedQueue<E>();
|
||||||
Iterables.addAll(queue, elements);
|
Iterables.addAll(queue, elements);
|
||||||
|
@ -137,7 +137,7 @@ public final class Queues {
|
||||||
@GwtIncompatible // LinkedBlockingDeque
|
@GwtIncompatible // LinkedBlockingDeque
|
||||||
public static <E> LinkedBlockingDeque<E> newLinkedBlockingDeque(Iterable<? extends E> elements) {
|
public static <E> LinkedBlockingDeque<E> newLinkedBlockingDeque(Iterable<? extends E> elements) {
|
||||||
if (elements instanceof Collection) {
|
if (elements instanceof Collection) {
|
||||||
return new LinkedBlockingDeque<E>(Collections2.cast(elements));
|
return new LinkedBlockingDeque<E>((Collection<? extends E>) elements);
|
||||||
}
|
}
|
||||||
LinkedBlockingDeque<E> deque = new LinkedBlockingDeque<E>();
|
LinkedBlockingDeque<E> deque = new LinkedBlockingDeque<E>();
|
||||||
Iterables.addAll(deque, elements);
|
Iterables.addAll(deque, elements);
|
||||||
|
@ -173,7 +173,7 @@ public final class Queues {
|
||||||
@GwtIncompatible // LinkedBlockingQueue
|
@GwtIncompatible // LinkedBlockingQueue
|
||||||
public static <E> LinkedBlockingQueue<E> newLinkedBlockingQueue(Iterable<? extends E> elements) {
|
public static <E> LinkedBlockingQueue<E> newLinkedBlockingQueue(Iterable<? extends E> elements) {
|
||||||
if (elements instanceof Collection) {
|
if (elements instanceof Collection) {
|
||||||
return new LinkedBlockingQueue<E>(Collections2.cast(elements));
|
return new LinkedBlockingQueue<E>((Collection<? extends E>) elements);
|
||||||
}
|
}
|
||||||
LinkedBlockingQueue<E> queue = new LinkedBlockingQueue<E>();
|
LinkedBlockingQueue<E> queue = new LinkedBlockingQueue<E>();
|
||||||
Iterables.addAll(queue, elements);
|
Iterables.addAll(queue, elements);
|
||||||
|
@ -207,7 +207,7 @@ public final class Queues {
|
||||||
public static <E extends Comparable> PriorityBlockingQueue<E> newPriorityBlockingQueue(
|
public static <E extends Comparable> PriorityBlockingQueue<E> newPriorityBlockingQueue(
|
||||||
Iterable<? extends E> elements) {
|
Iterable<? extends E> elements) {
|
||||||
if (elements instanceof Collection) {
|
if (elements instanceof Collection) {
|
||||||
return new PriorityBlockingQueue<E>(Collections2.cast(elements));
|
return new PriorityBlockingQueue<E>((Collection<? extends E>) elements);
|
||||||
}
|
}
|
||||||
PriorityBlockingQueue<E> queue = new PriorityBlockingQueue<E>();
|
PriorityBlockingQueue<E> queue = new PriorityBlockingQueue<E>();
|
||||||
Iterables.addAll(queue, elements);
|
Iterables.addAll(queue, elements);
|
||||||
|
@ -237,7 +237,7 @@ public final class Queues {
|
||||||
public static <E extends Comparable> PriorityQueue<E> newPriorityQueue(
|
public static <E extends Comparable> PriorityQueue<E> newPriorityQueue(
|
||||||
Iterable<? extends E> elements) {
|
Iterable<? extends E> elements) {
|
||||||
if (elements instanceof Collection) {
|
if (elements instanceof Collection) {
|
||||||
return new PriorityQueue<E>(Collections2.cast(elements));
|
return new PriorityQueue<E>((Collection<? extends E>) elements);
|
||||||
}
|
}
|
||||||
PriorityQueue<E> queue = new PriorityQueue<E>();
|
PriorityQueue<E> queue = new PriorityQueue<E>();
|
||||||
Iterables.addAll(queue, elements);
|
Iterables.addAll(queue, elements);
|
||||||
|
@ -322,7 +322,7 @@ public final class Queues {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Drains the queue as {@linkplain #drain(BlockingQueue, Collection, int, Duration)}, but with a
|
* 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
|
* 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
|
* continue as usual, and in the end the thread's interruption status will be set (no {@code
|
||||||
* InterruptedException} is thrown).
|
* InterruptedException} is thrown).
|
||||||
|
|
|
@ -576,6 +576,21 @@ public final class Range<C extends Comparable> extends RangeGwtSerializationDepe
|
||||||
* @since 27.0
|
* @since 27.0
|
||||||
*/
|
*/
|
||||||
public Range<C> gap(Range<C> otherRange) {
|
public Range<C> gap(Range<C> otherRange) {
|
||||||
|
/*
|
||||||
|
* For an explanation of the basic principle behind this check, see
|
||||||
|
* https://stackoverflow.com/a/35754308/28465
|
||||||
|
*
|
||||||
|
* In that explanation's notation, our `overlap` check would be `x1 < y2 && y1 < x2`. We've
|
||||||
|
* flipped one part of the check so that we're using "less than" in both cases (rather than a
|
||||||
|
* mix of "less than" and "greater than"). We've also switched to "strictly less than" rather
|
||||||
|
* than "less than or equal to" because of *handwave* the difference between "endpoints of
|
||||||
|
* inclusive ranges" and "Cuts."
|
||||||
|
*/
|
||||||
|
if (lowerBound.compareTo(otherRange.upperBound) < 0
|
||||||
|
&& otherRange.lowerBound.compareTo(upperBound) < 0) {
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
"Ranges have a nonempty intersection: " + this + ", " + otherRange);
|
||||||
|
}
|
||||||
boolean isThisFirst = this.lowerBound.compareTo(otherRange.lowerBound) < 0;
|
boolean isThisFirst = this.lowerBound.compareTo(otherRange.lowerBound) < 0;
|
||||||
Range<C> firstRange = isThisFirst ? this : otherRange;
|
Range<C> firstRange = isThisFirst ? this : otherRange;
|
||||||
Range<C> secondRange = isThisFirst ? otherRange : this;
|
Range<C> secondRange = isThisFirst ? otherRange : this;
|
||||||
|
|
|
@ -207,10 +207,10 @@ final class RegularImmutableMap<K, V> extends ImmutableMap<K, V> {
|
||||||
}
|
}
|
||||||
|
|
||||||
@GwtCompatible(emulated = true)
|
@GwtCompatible(emulated = true)
|
||||||
private static final class KeySet<K, V> extends IndexedImmutableSet<K> {
|
private static final class KeySet<K> extends IndexedImmutableSet<K> {
|
||||||
private final RegularImmutableMap<K, V> map;
|
private final RegularImmutableMap<K, ?> map;
|
||||||
|
|
||||||
KeySet(RegularImmutableMap<K, V> map) {
|
KeySet(RegularImmutableMap<K, ?> map) {
|
||||||
this.map = map;
|
this.map = map;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -234,12 +234,6 @@ final class RegularImmutableMap<K, V> extends ImmutableMap<K, V> {
|
||||||
return map.size();
|
return map.size();
|
||||||
}
|
}
|
||||||
|
|
||||||
@GwtIncompatible // serialization
|
|
||||||
@Override
|
|
||||||
Object writeReplace() {
|
|
||||||
return new SerializedForm<K>(map);
|
|
||||||
}
|
|
||||||
|
|
||||||
@GwtIncompatible // serialization
|
@GwtIncompatible // serialization
|
||||||
private static class SerializedForm<K> implements Serializable {
|
private static class SerializedForm<K> implements Serializable {
|
||||||
final ImmutableMap<K, ?> map;
|
final ImmutableMap<K, ?> map;
|
||||||
|
@ -284,12 +278,6 @@ final class RegularImmutableMap<K, V> extends ImmutableMap<K, V> {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@GwtIncompatible // serialization
|
|
||||||
@Override
|
|
||||||
Object writeReplace() {
|
|
||||||
return new SerializedForm<V>(map);
|
|
||||||
}
|
|
||||||
|
|
||||||
@GwtIncompatible // serialization
|
@GwtIncompatible // serialization
|
||||||
private static class SerializedForm<V> implements Serializable {
|
private static class SerializedForm<V> implements Serializable {
|
||||||
final ImmutableMap<?, V> map;
|
final ImmutableMap<?, V> map;
|
||||||
|
|
|
@ -138,42 +138,6 @@ public final class Sets {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final class Accumulator<E extends Enum<E>> {
|
|
||||||
static final Collector<Enum<?>, ?, ImmutableSet<? extends Enum<?>>> TO_IMMUTABLE_ENUM_SET =
|
|
||||||
(Collector)
|
|
||||||
Collector.<Enum, Accumulator, ImmutableSet<?>>of(
|
|
||||||
Accumulator::new,
|
|
||||||
Accumulator::add,
|
|
||||||
Accumulator::combine,
|
|
||||||
Accumulator::toImmutableSet,
|
|
||||||
Collector.Characteristics.UNORDERED);
|
|
||||||
|
|
||||||
private EnumSet<E> set;
|
|
||||||
|
|
||||||
void add(E e) {
|
|
||||||
if (set == null) {
|
|
||||||
set = EnumSet.of(e);
|
|
||||||
} else {
|
|
||||||
set.add(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Accumulator<E> combine(Accumulator<E> other) {
|
|
||||||
if (this.set == null) {
|
|
||||||
return other;
|
|
||||||
} else if (other.set == null) {
|
|
||||||
return this;
|
|
||||||
} else {
|
|
||||||
this.set.addAll(other.set);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ImmutableSet<E> toImmutableSet() {
|
|
||||||
return (set == null) ? ImmutableSet.<E>of() : ImmutableEnumSet.asImmutable(set);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a {@code Collector} that accumulates the input elements into a new {@code ImmutableSet}
|
* 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
|
* with an implementation specialized for enums. Unlike {@link ImmutableSet#toImmutableSet}, the
|
||||||
|
@ -182,7 +146,7 @@ public final class Sets {
|
||||||
* @since 21.0
|
* @since 21.0
|
||||||
*/
|
*/
|
||||||
public static <E extends Enum<E>> Collector<E, ?, ImmutableSet<E>> toImmutableEnumSet() {
|
public static <E extends Enum<E>> Collector<E, ?, ImmutableSet<E>> toImmutableEnumSet() {
|
||||||
return (Collector) Accumulator.TO_IMMUTABLE_ENUM_SET;
|
return CollectCollectors.toImmutableEnumSet();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -254,7 +218,7 @@ public final class Sets {
|
||||||
*/
|
*/
|
||||||
public static <E> HashSet<E> newHashSet(Iterable<? extends E> elements) {
|
public static <E> HashSet<E> newHashSet(Iterable<? extends E> elements) {
|
||||||
return (elements instanceof Collection)
|
return (elements instanceof Collection)
|
||||||
? new HashSet<E>(Collections2.cast(elements))
|
? new HashSet<E>((Collection<? extends E>) elements)
|
||||||
: newHashSet(elements.iterator());
|
: newHashSet(elements.iterator());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -359,7 +323,7 @@ public final class Sets {
|
||||||
*/
|
*/
|
||||||
public static <E> LinkedHashSet<E> newLinkedHashSet(Iterable<? extends E> elements) {
|
public static <E> LinkedHashSet<E> newLinkedHashSet(Iterable<? extends E> elements) {
|
||||||
if (elements instanceof Collection) {
|
if (elements instanceof Collection) {
|
||||||
return new LinkedHashSet<E>(Collections2.cast(elements));
|
return new LinkedHashSet<E>((Collection<? extends E>) elements);
|
||||||
}
|
}
|
||||||
LinkedHashSet<E> set = newLinkedHashSet();
|
LinkedHashSet<E> set = newLinkedHashSet();
|
||||||
Iterables.addAll(set, elements);
|
Iterables.addAll(set, elements);
|
||||||
|
@ -487,7 +451,7 @@ public final class Sets {
|
||||||
// quadratic cost of adding them to the COWAS directly.
|
// quadratic cost of adding them to the COWAS directly.
|
||||||
Collection<? extends E> elementsCollection =
|
Collection<? extends E> elementsCollection =
|
||||||
(elements instanceof Collection)
|
(elements instanceof Collection)
|
||||||
? Collections2.cast(elements)
|
? (Collection<? extends E>) elements
|
||||||
: Lists.newArrayList(elements);
|
: Lists.newArrayList(elements);
|
||||||
return new CopyOnWriteArraySet<E>(elementsCollection);
|
return new CopyOnWriteArraySet<E>(elementsCollection);
|
||||||
}
|
}
|
||||||
|
@ -1427,6 +1391,25 @@ public final class Sets {
|
||||||
return delegate;
|
return delegate;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean contains(Object object) {
|
||||||
|
if (!(object instanceof List)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
List<?> list = (List<?>) object;
|
||||||
|
if (list.size() != axes.size()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
int i = 0;
|
||||||
|
for (Object o : list) {
|
||||||
|
if (!axes.get(i).contains(o)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean equals(Object object) {
|
public boolean equals(Object object) {
|
||||||
// Warning: this is broken if size() == 0, so it is critical that we
|
// Warning: this is broken if size() == 0, so it is critical that we
|
||||||
|
@ -1576,7 +1559,7 @@ public final class Sets {
|
||||||
public boolean equals(Object obj) {
|
public boolean equals(Object obj) {
|
||||||
if (obj instanceof PowerSet) {
|
if (obj instanceof PowerSet) {
|
||||||
PowerSet<?> that = (PowerSet<?>) obj;
|
PowerSet<?> that = (PowerSet<?>) obj;
|
||||||
return inputSet.equals(that.inputSet);
|
return inputSet.keySet().equals(that.inputSet.keySet());
|
||||||
}
|
}
|
||||||
return super.equals(obj);
|
return super.equals(obj);
|
||||||
}
|
}
|
||||||
|
|
|
@ -42,6 +42,7 @@ final class SingletonImmutableBiMap<K, V> extends ImmutableBiMap<K, V> {
|
||||||
checkEntryNotNull(singleKey, singleValue);
|
checkEntryNotNull(singleKey, singleValue);
|
||||||
this.singleKey = singleKey;
|
this.singleKey = singleKey;
|
||||||
this.singleValue = singleValue;
|
this.singleValue = singleValue;
|
||||||
|
this.inverse = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private SingletonImmutableBiMap(K singleKey, V singleValue, ImmutableBiMap<V, K> inverse) {
|
private SingletonImmutableBiMap(K singleKey, V singleValue, ImmutableBiMap<V, K> inverse) {
|
||||||
|
@ -92,14 +93,21 @@ final class SingletonImmutableBiMap<K, V> extends ImmutableBiMap<K, V> {
|
||||||
|
|
||||||
transient ImmutableBiMap<V, K> inverse;
|
transient ImmutableBiMap<V, K> inverse;
|
||||||
|
|
||||||
|
private transient ImmutableBiMap<V, K> lazyInverse;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ImmutableBiMap<V, K> inverse() {
|
public ImmutableBiMap<V, K> inverse() {
|
||||||
// racy single-check idiom
|
// racy single-check idiom
|
||||||
ImmutableBiMap<V, K> result = inverse;
|
if (inverse != null) {
|
||||||
|
return inverse;
|
||||||
|
} else {
|
||||||
|
// racy single-check idiom
|
||||||
|
ImmutableBiMap<V, K> result = lazyInverse;
|
||||||
if (result == null) {
|
if (result == null) {
|
||||||
return inverse = new SingletonImmutableBiMap<>(singleValue, singleKey, this);
|
return lazyInverse = new SingletonImmutableBiMap<>(singleValue, singleKey, this);
|
||||||
} else {
|
} else {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
200
src/main/java/com/google/common/collect/TableCollectors.java
Normal file
200
src/main/java/com/google/common/collect/TableCollectors.java
Normal file
|
@ -0,0 +1,200 @@
|
||||||
|
/*
|
||||||
|
* 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.collect.Tables.AbstractCell;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.function.BinaryOperator;
|
||||||
|
import java.util.function.Function;
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
import java.util.stream.Collector;
|
||||||
|
|
||||||
|
/** Collectors utilities for {@code common.collect.Table} internals. */
|
||||||
|
@GwtCompatible
|
||||||
|
final class TableCollectors {
|
||||||
|
|
||||||
|
static <T, R, C, V> Collector<T, ?, ImmutableTable<R, C, V>> toImmutableTable(
|
||||||
|
Function<? super T, ? extends R> rowFunction,
|
||||||
|
Function<? super T, ? extends C> columnFunction,
|
||||||
|
Function<? super T, ? extends V> valueFunction) {
|
||||||
|
checkNotNull(rowFunction, "rowFunction");
|
||||||
|
checkNotNull(columnFunction, "columnFunction");
|
||||||
|
checkNotNull(valueFunction, "valueFunction");
|
||||||
|
return Collector.of(
|
||||||
|
(Supplier<ImmutableTable.Builder<R, C, V>>) ImmutableTable.Builder::new,
|
||||||
|
(builder, t) ->
|
||||||
|
builder.put(rowFunction.apply(t), columnFunction.apply(t), valueFunction.apply(t)),
|
||||||
|
ImmutableTable.Builder::combine,
|
||||||
|
ImmutableTable.Builder::build);
|
||||||
|
}
|
||||||
|
|
||||||
|
static <T, R, C, V> Collector<T, ?, ImmutableTable<R, C, V>> toImmutableTable(
|
||||||
|
Function<? super T, ? extends R> rowFunction,
|
||||||
|
Function<? super T, ? extends C> columnFunction,
|
||||||
|
Function<? super T, ? extends V> valueFunction,
|
||||||
|
BinaryOperator<V> 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 ImmutableTableCollectorState<R, C, V>()
|
||||||
|
/* 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());
|
||||||
|
}
|
||||||
|
|
||||||
|
static <T, R, C, V, I extends Table<R, C, V>> Collector<T, ?, I> toTable(
|
||||||
|
Function<? super T, ? extends R> rowFunction,
|
||||||
|
Function<? super T, ? extends C> columnFunction,
|
||||||
|
Function<? super T, ? extends V> valueFunction,
|
||||||
|
Supplier<I> tableSupplier) {
|
||||||
|
return toTable(
|
||||||
|
rowFunction,
|
||||||
|
columnFunction,
|
||||||
|
valueFunction,
|
||||||
|
(v1, v2) -> {
|
||||||
|
throw new IllegalStateException("Conflicting values " + v1 + " and " + v2);
|
||||||
|
},
|
||||||
|
tableSupplier);
|
||||||
|
}
|
||||||
|
|
||||||
|
static <T, R, C, V, I extends Table<R, C, V>> Collector<T, ?, I> toTable(
|
||||||
|
Function<? super T, ? extends R> rowFunction,
|
||||||
|
Function<? super T, ? extends C> columnFunction,
|
||||||
|
Function<? super T, ? extends V> valueFunction,
|
||||||
|
BinaryOperator<V> mergeFunction,
|
||||||
|
Supplier<I> tableSupplier) {
|
||||||
|
checkNotNull(rowFunction);
|
||||||
|
checkNotNull(columnFunction);
|
||||||
|
checkNotNull(valueFunction);
|
||||||
|
checkNotNull(mergeFunction);
|
||||||
|
checkNotNull(tableSupplier);
|
||||||
|
return Collector.of(
|
||||||
|
tableSupplier,
|
||||||
|
(table, input) ->
|
||||||
|
mergeTables(
|
||||||
|
table,
|
||||||
|
rowFunction.apply(input),
|
||||||
|
columnFunction.apply(input),
|
||||||
|
valueFunction.apply(input),
|
||||||
|
mergeFunction),
|
||||||
|
(table1, table2) -> {
|
||||||
|
for (Table.Cell<R, C, V> cell2 : table2.cellSet()) {
|
||||||
|
mergeTables(
|
||||||
|
table1, cell2.getRowKey(), cell2.getColumnKey(), cell2.getValue(), mergeFunction);
|
||||||
|
}
|
||||||
|
return table1;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final class ImmutableTableCollectorState<R, C, V> {
|
||||||
|
final List<MutableCell<R, C, V>> insertionOrder = new ArrayList<>();
|
||||||
|
final Table<R, C, MutableCell<R, C, V>> table = HashBasedTable.create();
|
||||||
|
|
||||||
|
void put(R row, C column, V value, BinaryOperator<V> merger) {
|
||||||
|
MutableCell<R, C, V> oldCell = table.get(row, column);
|
||||||
|
if (oldCell == null) {
|
||||||
|
MutableCell<R, C, V> cell = new MutableCell<>(row, column, value);
|
||||||
|
insertionOrder.add(cell);
|
||||||
|
table.put(row, column, cell);
|
||||||
|
} else {
|
||||||
|
oldCell.merge(value, merger);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ImmutableTableCollectorState<R, C, V> combine(
|
||||||
|
ImmutableTableCollectorState<R, C, V> other, BinaryOperator<V> merger) {
|
||||||
|
for (MutableCell<R, C, V> cell : other.insertionOrder) {
|
||||||
|
put(cell.getRowKey(), cell.getColumnKey(), cell.getValue(), merger);
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
ImmutableTable<R, C, V> toTable() {
|
||||||
|
return ImmutableTable.copyOf(insertionOrder);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final class MutableCell<R, C, V> extends AbstractCell<R, C, V> {
|
||||||
|
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<V> mergeFunction) {
|
||||||
|
checkNotNull(value, "value");
|
||||||
|
this.value = checkNotNull(mergeFunction.apply(this.value, value), "mergeFunction.apply");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static <R, C, V> void mergeTables(
|
||||||
|
Table<R, C, V> table, R row, C column, V value, BinaryOperator<V> 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private TableCollectors() {}
|
||||||
|
}
|
|
@ -68,14 +68,7 @@ public final class Tables {
|
||||||
java.util.function.Function<? super T, ? extends C> columnFunction,
|
java.util.function.Function<? super T, ? extends C> columnFunction,
|
||||||
java.util.function.Function<? super T, ? extends V> valueFunction,
|
java.util.function.Function<? super T, ? extends V> valueFunction,
|
||||||
java.util.function.Supplier<I> tableSupplier) {
|
java.util.function.Supplier<I> tableSupplier) {
|
||||||
return toTable(
|
return TableCollectors.toTable(rowFunction, columnFunction, valueFunction, tableSupplier);
|
||||||
rowFunction,
|
|
||||||
columnFunction,
|
|
||||||
valueFunction,
|
|
||||||
(v1, v2) -> {
|
|
||||||
throw new IllegalStateException("Conflicting values " + v1 + " and " + v2);
|
|
||||||
},
|
|
||||||
tableSupplier);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -98,26 +91,7 @@ public final class Tables {
|
||||||
java.util.function.Function<? super T, ? extends V> valueFunction,
|
java.util.function.Function<? super T, ? extends V> valueFunction,
|
||||||
BinaryOperator<V> mergeFunction,
|
BinaryOperator<V> mergeFunction,
|
||||||
java.util.function.Supplier<I> tableSupplier) {
|
java.util.function.Supplier<I> tableSupplier) {
|
||||||
checkNotNull(rowFunction);
|
return TableCollectors.toTable(rowFunction, columnFunction, valueFunction, mergeFunction, tableSupplier);
|
||||||
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<R, C, V> cell2 : table2.cellSet()) {
|
|
||||||
merge(table1, cell2.getRowKey(), cell2.getColumnKey(), cell2.getValue(), mergeFunction);
|
|
||||||
}
|
|
||||||
return table1;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static <R, C, V> void merge(
|
private static <R, C, V> void merge(
|
||||||
|
|
|
@ -119,7 +119,6 @@ public final class TreeRangeMap<K extends Comparable, V> implements RangeMap<K,
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void put(Range<K> range, V value) {
|
public void put(Range<K> range, V value) {
|
||||||
// don't short-circuit if the range is empty - it may be between two ranges we can coalesce.
|
|
||||||
if (!range.isEmpty()) {
|
if (!range.isEmpty()) {
|
||||||
checkNotNull(value);
|
checkNotNull(value);
|
||||||
remove(range);
|
remove(range);
|
||||||
|
@ -129,6 +128,7 @@ public final class TreeRangeMap<K extends Comparable, V> implements RangeMap<K,
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void putCoalescing(Range<K> range, V value) {
|
public void putCoalescing(Range<K> range, V value) {
|
||||||
|
// don't short-circuit if the range is empty - it may be between two ranges we can coalesce.
|
||||||
if (entriesByLowerBound.isEmpty()) {
|
if (entriesByLowerBound.isEmpty()) {
|
||||||
put(range, value);
|
put(range, value);
|
||||||
return;
|
return;
|
||||||
|
@ -508,7 +508,7 @@ public final class TreeRangeMap<K extends Comparable, V> implements RangeMap<K,
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void putCoalescing(Range<K> range, V value) {
|
public void putCoalescing(Range<K> range, V value) {
|
||||||
if (entriesByLowerBound.isEmpty() || range.isEmpty() || !subRange.encloses(range)) {
|
if (entriesByLowerBound.isEmpty() || !subRange.encloses(range)) {
|
||||||
put(range, value);
|
put(range, value);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
@ -892,7 +892,7 @@ public class TreeRangeSet<C extends Comparable<?>> extends AbstractRangeSet<C>
|
||||||
"Cannot add range %s to subRangeSet(%s)",
|
"Cannot add range %s to subRangeSet(%s)",
|
||||||
rangeToAdd,
|
rangeToAdd,
|
||||||
restriction);
|
restriction);
|
||||||
super.add(rangeToAdd);
|
TreeRangeSet.this.add(rangeToAdd);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -23,9 +23,10 @@ import java.lang.annotation.Target;
|
||||||
/**
|
/**
|
||||||
* Marks a method as an event subscriber.
|
* Marks a method as an event subscriber.
|
||||||
*
|
*
|
||||||
* <p>The type of event will be indicated by the method's first (and only) parameter. If this
|
* <p>The type of event will be indicated by the method's first (and only) parameter, which cannot
|
||||||
* annotation is applied to methods with zero parameters, or more than one parameter, the object
|
* be primitive. If this annotation is applied to methods with zero parameters, or more than one
|
||||||
* containing the method will not be able to register for event delivery from the {@link EventBus}.
|
* parameter, the object containing the method will not be able to register for event delivery from
|
||||||
|
* the {@link EventBus}.
|
||||||
*
|
*
|
||||||
* <p>Unless also annotated with @{@link AllowConcurrentEvents}, event subscriber methods will be
|
* <p>Unless also annotated with @{@link AllowConcurrentEvents}, event subscriber methods will be
|
||||||
* invoked serially by each event bus that they are registered with.
|
* invoked serially by each event bus that they are registered with.
|
||||||
|
|
|
@ -16,6 +16,7 @@ package com.google.common.eventbus;
|
||||||
|
|
||||||
import static com.google.common.base.Preconditions.checkArgument;
|
import static com.google.common.base.Preconditions.checkArgument;
|
||||||
import static com.google.common.base.Preconditions.checkNotNull;
|
import static com.google.common.base.Preconditions.checkNotNull;
|
||||||
|
import static com.google.common.base.Throwables.throwIfUnchecked;
|
||||||
|
|
||||||
import com.google.common.annotations.VisibleForTesting;
|
import com.google.common.annotations.VisibleForTesting;
|
||||||
import com.google.common.base.MoreObjects;
|
import com.google.common.base.MoreObjects;
|
||||||
|
@ -31,6 +32,7 @@ import com.google.common.collect.Iterators;
|
||||||
import com.google.common.collect.Lists;
|
import com.google.common.collect.Lists;
|
||||||
import com.google.common.collect.Maps;
|
import com.google.common.collect.Maps;
|
||||||
import com.google.common.collect.Multimap;
|
import com.google.common.collect.Multimap;
|
||||||
|
import com.google.common.primitives.Primitives;
|
||||||
import com.google.common.reflect.TypeToken;
|
import com.google.common.reflect.TypeToken;
|
||||||
import com.google.common.util.concurrent.UncheckedExecutionException;
|
import com.google.common.util.concurrent.UncheckedExecutionException;
|
||||||
|
|
||||||
|
@ -170,7 +172,12 @@ final class SubscriberRegistry {
|
||||||
}
|
}
|
||||||
|
|
||||||
private static ImmutableList<Method> getAnnotatedMethods(Class<?> clazz) {
|
private static ImmutableList<Method> getAnnotatedMethods(Class<?> clazz) {
|
||||||
|
try {
|
||||||
return subscriberMethodsCache.getUnchecked(clazz);
|
return subscriberMethodsCache.getUnchecked(clazz);
|
||||||
|
} catch (UncheckedExecutionException e) {
|
||||||
|
throwIfUnchecked(e.getCause());
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static ImmutableList<Method> getAnnotatedMethodsNotCached(Class<?> clazz) {
|
private static ImmutableList<Method> getAnnotatedMethodsNotCached(Class<?> clazz) {
|
||||||
|
@ -187,7 +194,14 @@ final class SubscriberRegistry {
|
||||||
+ "Subscriber methods must have exactly 1 parameter.",
|
+ "Subscriber methods must have exactly 1 parameter.",
|
||||||
method,
|
method,
|
||||||
parameterTypes.length);
|
parameterTypes.length);
|
||||||
|
checkArgument(
|
||||||
|
!parameterTypes[0].isPrimitive(),
|
||||||
|
"@Subscribe method %s's parameter is %s. "
|
||||||
|
+ "Subscriber methods cannot accept primitives. "
|
||||||
|
+ "Consider changing the parameter to %s.",
|
||||||
|
method,
|
||||||
|
parameterTypes[0].getName(),
|
||||||
|
Primitives.wrap(parameterTypes[0]).getSimpleName());
|
||||||
MethodIdentifier ident = new MethodIdentifier(method);
|
MethodIdentifier ident = new MethodIdentifier(method);
|
||||||
if (!identifiers.containsKey(ident)) {
|
if (!identifiers.containsKey(ident)) {
|
||||||
identifiers.put(ident, method);
|
identifiers.put(ident, method);
|
||||||
|
|
|
@ -22,13 +22,10 @@ import static com.google.common.base.Preconditions.checkNotNull;
|
||||||
import com.google.common.annotations.Beta;
|
import com.google.common.annotations.Beta;
|
||||||
import com.google.common.collect.AbstractIterator;
|
import com.google.common.collect.AbstractIterator;
|
||||||
import com.google.common.collect.ImmutableSet;
|
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.ArrayDeque;
|
||||||
import java.util.Deque;
|
import java.util.Deque;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.Queue;
|
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
|
|
||||||
|
@ -63,6 +60,11 @@ import java.util.Set;
|
||||||
*/
|
*/
|
||||||
@Beta
|
@Beta
|
||||||
public abstract class Traverser<N> {
|
public abstract class Traverser<N> {
|
||||||
|
private final SuccessorsFunction<N> successorFunction;
|
||||||
|
|
||||||
|
private Traverser(SuccessorsFunction<N> successorFunction) {
|
||||||
|
this.successorFunction = checkNotNull(successorFunction);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new traverser for the given general {@code graph}.
|
* Creates a new traverser for the given general {@code graph}.
|
||||||
|
@ -88,9 +90,13 @@ public abstract class Traverser<N> {
|
||||||
*
|
*
|
||||||
* @param graph {@link SuccessorsFunction} representing a general graph that may have cycles.
|
* @param graph {@link SuccessorsFunction} representing a general graph that may have cycles.
|
||||||
*/
|
*/
|
||||||
public static <N> Traverser<N> forGraph(SuccessorsFunction<N> graph) {
|
public static <N> Traverser<N> forGraph(final SuccessorsFunction<N> graph) {
|
||||||
checkNotNull(graph);
|
return new Traverser<N>(graph) {
|
||||||
return new GraphTraverser<>(graph);
|
@Override
|
||||||
|
Traversal<N> newTraversal() {
|
||||||
|
return Traversal.inGraph(graph);
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -174,7 +180,12 @@ public abstract class Traverser<N> {
|
||||||
if (tree instanceof Network) {
|
if (tree instanceof Network) {
|
||||||
checkArgument(((Network<?, ?>) tree).isDirected(), "Undirected networks can never be trees.");
|
checkArgument(((Network<?, ?>) tree).isDirected(), "Undirected networks can never be trees.");
|
||||||
}
|
}
|
||||||
return new TreeTraverser<>(tree);
|
return new Traverser<N>(tree) {
|
||||||
|
@Override
|
||||||
|
Traversal<N> newTraversal() {
|
||||||
|
return Traversal.inTree(tree);
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -208,7 +219,9 @@ public abstract class Traverser<N> {
|
||||||
*
|
*
|
||||||
* @throws IllegalArgumentException if {@code startNode} is not an element of the graph
|
* @throws IllegalArgumentException if {@code startNode} is not an element of the graph
|
||||||
*/
|
*/
|
||||||
public abstract Iterable<N> breadthFirst(N startNode);
|
public final Iterable<N> breadthFirst(N startNode) {
|
||||||
|
return breadthFirst(ImmutableSet.of(startNode));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns an unmodifiable {@code Iterable} over the nodes reachable from any of the {@code
|
* Returns an unmodifiable {@code Iterable} over the nodes reachable from any of the {@code
|
||||||
|
@ -220,7 +233,15 @@ public abstract class Traverser<N> {
|
||||||
* @see #breadthFirst(Object)
|
* @see #breadthFirst(Object)
|
||||||
* @since 24.1
|
* @since 24.1
|
||||||
*/
|
*/
|
||||||
public abstract Iterable<N> breadthFirst(Iterable<? extends N> startNodes);
|
public final Iterable<N> breadthFirst(Iterable<? extends N> startNodes) {
|
||||||
|
final ImmutableSet<N> validated = validate(startNodes);
|
||||||
|
return new Iterable<N>() {
|
||||||
|
@Override
|
||||||
|
public Iterator<N> iterator() {
|
||||||
|
return newTraversal().breadthFirst(validated.iterator());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns an unmodifiable {@code Iterable} over the nodes reachable from {@code startNode}, in
|
* Returns an unmodifiable {@code Iterable} over the nodes reachable from {@code startNode}, in
|
||||||
|
@ -253,7 +274,9 @@ public abstract class Traverser<N> {
|
||||||
*
|
*
|
||||||
* @throws IllegalArgumentException if {@code startNode} is not an element of the graph
|
* @throws IllegalArgumentException if {@code startNode} is not an element of the graph
|
||||||
*/
|
*/
|
||||||
public abstract Iterable<N> depthFirstPreOrder(N startNode);
|
public final Iterable<N> depthFirstPreOrder(N startNode) {
|
||||||
|
return depthFirstPreOrder(ImmutableSet.of(startNode));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns an unmodifiable {@code Iterable} over the nodes reachable from any of the {@code
|
* Returns an unmodifiable {@code Iterable} over the nodes reachable from any of the {@code
|
||||||
|
@ -265,7 +288,15 @@ public abstract class Traverser<N> {
|
||||||
* @see #depthFirstPreOrder(Object)
|
* @see #depthFirstPreOrder(Object)
|
||||||
* @since 24.1
|
* @since 24.1
|
||||||
*/
|
*/
|
||||||
public abstract Iterable<N> depthFirstPreOrder(Iterable<? extends N> startNodes);
|
public final Iterable<N> depthFirstPreOrder(Iterable<? extends N> startNodes) {
|
||||||
|
final ImmutableSet<N> validated = validate(startNodes);
|
||||||
|
return new Iterable<N>() {
|
||||||
|
@Override
|
||||||
|
public Iterator<N> iterator() {
|
||||||
|
return newTraversal().preOrder(validated.iterator());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns an unmodifiable {@code Iterable} over the nodes reachable from {@code startNode}, in
|
* Returns an unmodifiable {@code Iterable} over the nodes reachable from {@code startNode}, in
|
||||||
|
@ -298,7 +329,9 @@ public abstract class Traverser<N> {
|
||||||
*
|
*
|
||||||
* @throws IllegalArgumentException if {@code startNode} is not an element of the graph
|
* @throws IllegalArgumentException if {@code startNode} is not an element of the graph
|
||||||
*/
|
*/
|
||||||
public abstract Iterable<N> depthFirstPostOrder(N startNode);
|
public final Iterable<N> depthFirstPostOrder(N startNode) {
|
||||||
|
return depthFirstPostOrder(ImmutableSet.of(startNode));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns an unmodifiable {@code Iterable} over the nodes reachable from any of the {@code
|
* Returns an unmodifiable {@code Iterable} over the nodes reachable from any of the {@code
|
||||||
|
@ -310,352 +343,156 @@ public abstract class Traverser<N> {
|
||||||
* @see #depthFirstPostOrder(Object)
|
* @see #depthFirstPostOrder(Object)
|
||||||
* @since 24.1
|
* @since 24.1
|
||||||
*/
|
*/
|
||||||
public abstract Iterable<N> depthFirstPostOrder(Iterable<? extends N> startNodes);
|
public final Iterable<N> depthFirstPostOrder(Iterable<? extends N> startNodes) {
|
||||||
|
final ImmutableSet<N> validated = validate(startNodes);
|
||||||
// Avoid subclasses outside of this class
|
|
||||||
private Traverser() {}
|
|
||||||
|
|
||||||
private static final class GraphTraverser<N> extends Traverser<N> {
|
|
||||||
private final SuccessorsFunction<N> graph;
|
|
||||||
|
|
||||||
GraphTraverser(SuccessorsFunction<N> graph) {
|
|
||||||
this.graph = checkNotNull(graph);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Iterable<N> breadthFirst(final N startNode) {
|
|
||||||
checkNotNull(startNode);
|
|
||||||
return breadthFirst(ImmutableSet.of(startNode));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Iterable<N> breadthFirst(final Iterable<? extends N> startNodes) {
|
|
||||||
checkNotNull(startNodes);
|
|
||||||
if (Iterables.isEmpty(startNodes)) {
|
|
||||||
return ImmutableSet.of();
|
|
||||||
}
|
|
||||||
for (N startNode : startNodes) {
|
|
||||||
checkThatNodeIsInGraph(startNode);
|
|
||||||
}
|
|
||||||
return new Iterable<N>() {
|
return new Iterable<N>() {
|
||||||
@Override
|
@Override
|
||||||
public Iterator<N> iterator() {
|
public Iterator<N> iterator() {
|
||||||
return new BreadthFirstIterator(startNodes);
|
return newTraversal().postOrder(validated.iterator());
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
abstract Traversal<N> newTraversal();
|
||||||
public Iterable<N> depthFirstPreOrder(final N startNode) {
|
|
||||||
checkNotNull(startNode);
|
|
||||||
return depthFirstPreOrder(ImmutableSet.of(startNode));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Iterable<N> depthFirstPreOrder(final Iterable<? extends N> startNodes) {
|
|
||||||
checkNotNull(startNodes);
|
|
||||||
if (Iterables.isEmpty(startNodes)) {
|
|
||||||
return ImmutableSet.of();
|
|
||||||
}
|
|
||||||
for (N startNode : startNodes) {
|
|
||||||
checkThatNodeIsInGraph(startNode);
|
|
||||||
}
|
|
||||||
return new Iterable<N>() {
|
|
||||||
@Override
|
|
||||||
public Iterator<N> iterator() {
|
|
||||||
return new DepthFirstIterator(startNodes, Order.PREORDER);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Iterable<N> depthFirstPostOrder(final N startNode) {
|
|
||||||
checkNotNull(startNode);
|
|
||||||
return depthFirstPostOrder(ImmutableSet.of(startNode));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Iterable<N> depthFirstPostOrder(final Iterable<? extends N> startNodes) {
|
|
||||||
checkNotNull(startNodes);
|
|
||||||
if (Iterables.isEmpty(startNodes)) {
|
|
||||||
return ImmutableSet.of();
|
|
||||||
}
|
|
||||||
for (N startNode : startNodes) {
|
|
||||||
checkThatNodeIsInGraph(startNode);
|
|
||||||
}
|
|
||||||
return new Iterable<N>() {
|
|
||||||
@Override
|
|
||||||
public Iterator<N> iterator() {
|
|
||||||
return new DepthFirstIterator(startNodes, Order.POSTORDER);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("CheckReturnValue")
|
@SuppressWarnings("CheckReturnValue")
|
||||||
private void checkThatNodeIsInGraph(N startNode) {
|
private ImmutableSet<N> validate(Iterable<? extends N> startNodes) {
|
||||||
// successors() throws an IllegalArgumentException for nodes that are not an element of the
|
ImmutableSet<N> copy = ImmutableSet.copyOf(startNodes);
|
||||||
// graph.
|
for (N node : copy) {
|
||||||
graph.successors(startNode);
|
successorFunction.successors(node); // Will throw if node doesn't exist
|
||||||
|
}
|
||||||
|
return copy;
|
||||||
}
|
}
|
||||||
|
|
||||||
private final class BreadthFirstIterator extends UnmodifiableIterator<N> {
|
/**
|
||||||
private final Queue<N> queue = new ArrayDeque<>();
|
* Abstracts away the difference between traversing a graph vs. a tree. For a tree, we just take
|
||||||
private final Set<N> visited = new HashSet<>();
|
* the next element from the next non-empty iterator; for graph, we need to loop through the next
|
||||||
|
* non-empty iterator to find first unvisited node.
|
||||||
|
*/
|
||||||
|
private abstract static class Traversal<N> {
|
||||||
|
final SuccessorsFunction<N> successorFunction;
|
||||||
|
|
||||||
BreadthFirstIterator(Iterable<? extends N> roots) {
|
Traversal(SuccessorsFunction<N> successorFunction) {
|
||||||
for (N root : roots) {
|
this.successorFunction = successorFunction;
|
||||||
// add all roots to the queue, skipping duplicates
|
|
||||||
if (visited.add(root)) {
|
|
||||||
queue.add(root);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static <N> Traversal<N> inGraph(SuccessorsFunction<N> graph) {
|
||||||
|
final Set<N> visited = new HashSet<>();
|
||||||
|
return new Traversal<N>(graph) {
|
||||||
@Override
|
@Override
|
||||||
public boolean hasNext() {
|
N visitNext(Deque<Iterator<? extends N>> horizon) {
|
||||||
return !queue.isEmpty();
|
Iterator<? extends N> top = horizon.getFirst();
|
||||||
|
while (top.hasNext()) {
|
||||||
|
N element = checkNotNull(top.next());
|
||||||
|
if (visited.add(element)) {
|
||||||
|
return element;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
horizon.removeFirst();
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static <N> Traversal<N> inTree(SuccessorsFunction<N> tree) {
|
||||||
|
return new Traversal<N>(tree) {
|
||||||
@Override
|
@Override
|
||||||
public N next() {
|
N visitNext(Deque<Iterator<? extends N>> horizon) {
|
||||||
N current = queue.remove();
|
Iterator<? extends N> top = horizon.getFirst();
|
||||||
for (N neighbor : graph.successors(current)) {
|
if (top.hasNext()) {
|
||||||
if (visited.add(neighbor)) {
|
return checkNotNull(top.next());
|
||||||
queue.add(neighbor);
|
|
||||||
}
|
}
|
||||||
|
horizon.removeFirst();
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
return current;
|
};
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private final class DepthFirstIterator extends AbstractIterator<N> {
|
final Iterator<N> breadthFirst(Iterator<? extends N> startNodes) {
|
||||||
private final Deque<NodeAndSuccessors> stack = new ArrayDeque<>();
|
return topDown(startNodes, InsertionOrder.BACK);
|
||||||
private final Set<N> visited = new HashSet<>();
|
|
||||||
private final Order order;
|
|
||||||
|
|
||||||
DepthFirstIterator(Iterable<? extends N> roots, Order order) {
|
|
||||||
stack.push(new NodeAndSuccessors(null, roots));
|
|
||||||
this.order = order;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final Iterator<N> preOrder(Iterator<? extends N> startNodes) {
|
||||||
|
return topDown(startNodes, InsertionOrder.FRONT);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* In top-down traversal, an ancestor node is always traversed before any of its descendant
|
||||||
|
* nodes. The traversal order among descendant nodes (particularly aunts and nieces) are
|
||||||
|
* determined by the {@code InsertionOrder} parameter: nieces are placed at the FRONT before
|
||||||
|
* aunts for pre-order; while in BFS they are placed at the BACK after aunts.
|
||||||
|
*/
|
||||||
|
private Iterator<N> topDown(Iterator<? extends N> startNodes, final InsertionOrder order) {
|
||||||
|
final Deque<Iterator<? extends N>> horizon = new ArrayDeque<>();
|
||||||
|
horizon.add(startNodes);
|
||||||
|
return new AbstractIterator<N>() {
|
||||||
@Override
|
@Override
|
||||||
protected N computeNext() {
|
protected N computeNext() {
|
||||||
while (true) {
|
do {
|
||||||
if (stack.isEmpty()) {
|
N next = visitNext(horizon);
|
||||||
|
if (next != null) {
|
||||||
|
Iterator<? extends N> successors = successorFunction.successors(next).iterator();
|
||||||
|
if (successors.hasNext()) {
|
||||||
|
// BFS: horizon.addLast(successors)
|
||||||
|
// Pre-order: horizon.addFirst(successors)
|
||||||
|
order.insertInto(horizon, successors);
|
||||||
|
}
|
||||||
|
return next;
|
||||||
|
}
|
||||||
|
} while (!horizon.isEmpty());
|
||||||
return endOfData();
|
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<? extends N> successorIterator;
|
|
||||||
|
|
||||||
NodeAndSuccessors(N node, Iterable<? extends N> successors) {
|
|
||||||
this.node = node;
|
|
||||||
this.successorIterator = successors.iterator();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static final class TreeTraverser<N> extends Traverser<N> {
|
|
||||||
private final SuccessorsFunction<N> tree;
|
|
||||||
|
|
||||||
TreeTraverser(SuccessorsFunction<N> tree) {
|
|
||||||
this.tree = checkNotNull(tree);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Iterable<N> breadthFirst(final N startNode) {
|
|
||||||
checkNotNull(startNode);
|
|
||||||
return breadthFirst(ImmutableSet.of(startNode));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Iterable<N> breadthFirst(final Iterable<? extends N> startNodes) {
|
|
||||||
checkNotNull(startNodes);
|
|
||||||
if (Iterables.isEmpty(startNodes)) {
|
|
||||||
return ImmutableSet.of();
|
|
||||||
}
|
|
||||||
for (N startNode : startNodes) {
|
|
||||||
checkThatNodeIsInTree(startNode);
|
|
||||||
}
|
|
||||||
return new Iterable<N>() {
|
|
||||||
@Override
|
|
||||||
public Iterator<N> iterator() {
|
|
||||||
return new BreadthFirstIterator(startNodes);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
final Iterator<N> postOrder(Iterator<? extends N> startNodes) {
|
||||||
public Iterable<N> depthFirstPreOrder(final N startNode) {
|
final Deque<N> ancestorStack = new ArrayDeque<>();
|
||||||
checkNotNull(startNode);
|
final Deque<Iterator<? extends N>> horizon = new ArrayDeque<>();
|
||||||
return depthFirstPreOrder(ImmutableSet.of(startNode));
|
horizon.add(startNodes);
|
||||||
}
|
return new AbstractIterator<N>() {
|
||||||
|
|
||||||
@Override
|
|
||||||
public Iterable<N> depthFirstPreOrder(final Iterable<? extends N> startNodes) {
|
|
||||||
checkNotNull(startNodes);
|
|
||||||
if (Iterables.isEmpty(startNodes)) {
|
|
||||||
return ImmutableSet.of();
|
|
||||||
}
|
|
||||||
for (N node : startNodes) {
|
|
||||||
checkThatNodeIsInTree(node);
|
|
||||||
}
|
|
||||||
return new Iterable<N>() {
|
|
||||||
@Override
|
|
||||||
public Iterator<N> iterator() {
|
|
||||||
return new DepthFirstPreOrderIterator(startNodes);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Iterable<N> depthFirstPostOrder(final N startNode) {
|
|
||||||
checkNotNull(startNode);
|
|
||||||
return depthFirstPostOrder(ImmutableSet.of(startNode));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Iterable<N> depthFirstPostOrder(final Iterable<? extends N> startNodes) {
|
|
||||||
checkNotNull(startNodes);
|
|
||||||
if (Iterables.isEmpty(startNodes)) {
|
|
||||||
return ImmutableSet.of();
|
|
||||||
}
|
|
||||||
for (N startNode : startNodes) {
|
|
||||||
checkThatNodeIsInTree(startNode);
|
|
||||||
}
|
|
||||||
return new Iterable<N>() {
|
|
||||||
@Override
|
|
||||||
public Iterator<N> 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<N> {
|
|
||||||
private final Queue<N> queue = new ArrayDeque<>();
|
|
||||||
|
|
||||||
BreadthFirstIterator(Iterable<? extends N> 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<N> {
|
|
||||||
private final Deque<Iterator<? extends N>> stack = new ArrayDeque<>();
|
|
||||||
|
|
||||||
DepthFirstPreOrderIterator(Iterable<? extends N> roots) {
|
|
||||||
stack.addLast(roots.iterator());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean hasNext() {
|
|
||||||
return !stack.isEmpty();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public N next() {
|
|
||||||
Iterator<? extends N> iterator = stack.getLast(); // throws NoSuchElementException if empty
|
|
||||||
N result = checkNotNull(iterator.next());
|
|
||||||
if (!iterator.hasNext()) {
|
|
||||||
stack.removeLast();
|
|
||||||
}
|
|
||||||
Iterator<? extends N> childIterator = tree.successors(result).iterator();
|
|
||||||
if (childIterator.hasNext()) {
|
|
||||||
stack.addLast(childIterator);
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private final class DepthFirstPostOrderIterator extends AbstractIterator<N> {
|
|
||||||
private final ArrayDeque<NodeAndChildren> stack = new ArrayDeque<>();
|
|
||||||
|
|
||||||
DepthFirstPostOrderIterator(Iterable<? extends N> roots) {
|
|
||||||
stack.addLast(new NodeAndChildren(null, roots));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected N computeNext() {
|
protected N computeNext() {
|
||||||
while (!stack.isEmpty()) {
|
for (N next = visitNext(horizon); next != null; next = visitNext(horizon)) {
|
||||||
NodeAndChildren top = stack.getLast();
|
Iterator<? extends N> successors = successorFunction.successors(next).iterator();
|
||||||
if (top.childIterator.hasNext()) {
|
if (!successors.hasNext()) {
|
||||||
N child = top.childIterator.next();
|
return next;
|
||||||
stack.addLast(withChildren(child));
|
|
||||||
} else {
|
|
||||||
stack.removeLast();
|
|
||||||
if (top.node != null) {
|
|
||||||
return top.node;
|
|
||||||
}
|
}
|
||||||
|
horizon.addFirst(successors);
|
||||||
|
ancestorStack.push(next);
|
||||||
}
|
}
|
||||||
|
return ancestorStack.isEmpty() ? endOfData() : ancestorStack.pop();
|
||||||
}
|
}
|
||||||
return endOfData();
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
NodeAndChildren withChildren(N node) {
|
/**
|
||||||
return new NodeAndChildren(node, tree.successors(node));
|
* Visits the next node from the top iterator of {@code horizon} and returns the visited node.
|
||||||
|
* Null is returned to indicate reaching the end of the top iterator.
|
||||||
|
*
|
||||||
|
* <p>For example, if horizon is {@code [[a, b], [c, d], [e]]}, {@code visitNext()} will return
|
||||||
|
* {@code [a, b, null, c, d, null, e, null]} sequentially, encoding the topological structure.
|
||||||
|
* (Note, however, that the callers of {@code visitNext()} often insert additional iterators
|
||||||
|
* into {@code horizon} between calls to {@code visitNext()}. This causes them to receive
|
||||||
|
* additional values interleaved with those shown above.)
|
||||||
|
*/
|
||||||
|
abstract N visitNext(Deque<Iterator<? extends N>> horizon);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** A simple tuple of a node and a partially iterated {@link Iterator} of its children. */
|
/** Poor man's method reference for {@code Deque::addFirst} and {@code Deque::addLast}. */
|
||||||
private final class NodeAndChildren {
|
private enum InsertionOrder {
|
||||||
final N node;
|
FRONT {
|
||||||
final Iterator<? extends N> childIterator;
|
@Override
|
||||||
|
<T> void insertInto(Deque<T> deque, T value) {
|
||||||
|
deque.addFirst(value);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
BACK {
|
||||||
|
@Override
|
||||||
|
<T> void insertInto(Deque<T> deque, T value) {
|
||||||
|
deque.addLast(value);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
NodeAndChildren(N node, Iterable<? extends N> children) {
|
abstract <T> void insertInto(Deque<T> deque, T value);
|
||||||
this.node = node;
|
|
||||||
this.childIterator = children.iterator();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private enum Order {
|
|
||||||
PREORDER,
|
|
||||||
POSTORDER
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -110,18 +110,18 @@ final class Crc32cHashFunction extends AbstractHashFunction {
|
||||||
0xbe2da0a5, 0x4c4623a6, 0x5f16d052, 0xad7d5351
|
0xbe2da0a5, 0x4c4623a6, 0x5f16d052, 0xad7d5351
|
||||||
};
|
};
|
||||||
|
|
||||||
private int crc = 0;
|
private int crc = ~0;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void update(byte b) {
|
public void update(byte b) {
|
||||||
crc ^= 0xFFFFFFFF;
|
crc ^= 0xFFFFFFFF;
|
||||||
// See Hacker's Delight 2nd Edition, Figure 14-7.
|
// See Hacker's Delight 2nd Edition, Figure 14-7.
|
||||||
crc = ~((crc >>> 8) ^ CRC_TABLE[(crc ^ b) & 0xFF]);
|
crc = (crc >>> 8) ^ CRC_TABLE[(crc ^ b) & 0xFF];
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public HashCode hash() {
|
public HashCode hash() {
|
||||||
return HashCode.fromInt(crc);
|
return HashCode.fromInt(~crc);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -39,7 +39,7 @@ import java.nio.channels.ReadableByteChannel;
|
||||||
import java.nio.channels.WritableByteChannel;
|
import java.nio.channels.WritableByteChannel;
|
||||||
import java.util.ArrayDeque;
|
import java.util.ArrayDeque;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Deque;
|
import java.util.Queue;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Provides utility methods for working with byte arrays and I/O streams.
|
* Provides utility methods for working with byte arrays and I/O streams.
|
||||||
|
@ -164,7 +164,7 @@ public final class ByteStreams {
|
||||||
* a total combined length of {@code totalLen} bytes) followed by all bytes remaining in the given
|
* a total combined length of {@code totalLen} bytes) followed by all bytes remaining in the given
|
||||||
* input stream.
|
* input stream.
|
||||||
*/
|
*/
|
||||||
private static byte[] toByteArrayInternal(InputStream in, Deque<byte[]> bufs, int totalLen)
|
private static byte[] toByteArrayInternal(InputStream in, Queue<byte[]> bufs, int totalLen)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
// Starting with an 8k buffer, double the size of each sucessive buffer. Buffers are retained
|
// 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 a deque so that there's no copying between buffers while reading and so all of the bytes
|
||||||
|
@ -195,11 +195,11 @@ public final class ByteStreams {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static byte[] combineBuffers(Deque<byte[]> bufs, int totalLen) {
|
private static byte[] combineBuffers(Queue<byte[]> bufs, int totalLen) {
|
||||||
byte[] result = new byte[totalLen];
|
byte[] result = new byte[totalLen];
|
||||||
int remaining = totalLen;
|
int remaining = totalLen;
|
||||||
while (remaining > 0) {
|
while (remaining > 0) {
|
||||||
byte[] buf = bufs.removeFirst();
|
byte[] buf = bufs.remove();
|
||||||
int bytesToCopy = Math.min(remaining, buf.length);
|
int bytesToCopy = Math.min(remaining, buf.length);
|
||||||
int resultOffset = totalLen - remaining;
|
int resultOffset = totalLen - remaining;
|
||||||
System.arraycopy(buf, 0, result, resultOffset, bytesToCopy);
|
System.arraycopy(buf, 0, result, resultOffset, bytesToCopy);
|
||||||
|
@ -252,7 +252,7 @@ public final class ByteStreams {
|
||||||
}
|
}
|
||||||
|
|
||||||
// the stream was longer, so read the rest normally
|
// the stream was longer, so read the rest normally
|
||||||
Deque<byte[]> bufs = new ArrayDeque<byte[]>(TO_BYTE_ARRAY_DEQUE_SIZE + 2);
|
Queue<byte[]> bufs = new ArrayDeque<byte[]>(TO_BYTE_ARRAY_DEQUE_SIZE + 2);
|
||||||
bufs.add(bytes);
|
bufs.add(bytes);
|
||||||
bufs.add(new byte[] {(byte) b});
|
bufs.add(new byte[] {(byte) b});
|
||||||
return toByteArrayInternal(in, bufs, bytes.length + 1);
|
return toByteArrayInternal(in, bufs, bytes.length + 1);
|
||||||
|
|
|
@ -24,6 +24,7 @@ import com.google.common.base.Joiner;
|
||||||
import com.google.common.base.Optional;
|
import com.google.common.base.Optional;
|
||||||
import com.google.common.base.Predicate;
|
import com.google.common.base.Predicate;
|
||||||
import com.google.common.base.Splitter;
|
import com.google.common.base.Splitter;
|
||||||
|
import com.google.common.collect.ImmutableList;
|
||||||
import com.google.common.collect.ImmutableSet;
|
import com.google.common.collect.ImmutableSet;
|
||||||
import com.google.common.collect.Lists;
|
import com.google.common.collect.Lists;
|
||||||
import com.google.common.collect.TreeTraverser;
|
import com.google.common.collect.TreeTraverser;
|
||||||
|
@ -398,6 +399,11 @@ public final class Files {
|
||||||
* be exploited to create security vulnerabilities, especially when executable files are to be
|
* be exploited to create security vulnerabilities, especially when executable files are to be
|
||||||
* written into the directory.
|
* written into the directory.
|
||||||
*
|
*
|
||||||
|
* <p>Depending on the environmment that this code is run in, the system temporary directory (and
|
||||||
|
* thus the directory this method creates) may be more visible that a program would like - files
|
||||||
|
* written to this directory may be read or overwritten by hostile programs running on the same
|
||||||
|
* machine.
|
||||||
|
*
|
||||||
* <p>This method assumes that the temporary volume is writable, has free inodes and free blocks,
|
* <p>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.
|
* and that it will not be called thousands of times per second.
|
||||||
*
|
*
|
||||||
|
@ -812,36 +818,6 @@ public final class Files {
|
||||||
return (dotIndex == -1) ? fileName : fileName.substring(0, dotIndex);
|
return (dotIndex == -1) ? fileName : fileName.substring(0, dotIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a {@link TreeTraverser} instance for {@link File} trees.
|
|
||||||
*
|
|
||||||
* <p><b>Warning:</b> {@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<File> fileTreeTraverser() {
|
|
||||||
return FILE_TREE_TRAVERSER;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static final TreeTraverser<File> FILE_TREE_TRAVERSER =
|
|
||||||
new TreeTraverser<File>() {
|
|
||||||
@Override
|
|
||||||
public Iterable<File> 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
|
* 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.
|
* starts from a {@link File} and will return all files and directories it encounters.
|
||||||
|
@ -870,12 +846,16 @@ public final class Files {
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final SuccessorsFunction<File> FILE_TREE =
|
private static final SuccessorsFunction<File> FILE_TREE =
|
||||||
new SuccessorsFunction<File>() {
|
file -> {
|
||||||
@Override
|
// check isDirectory() just because it may be faster than listFiles() on a non-directory
|
||||||
public Iterable<File> successors(File file) {
|
if (file.isDirectory()) {
|
||||||
return fileTreeChildren(file);
|
File[] files = file.listFiles();
|
||||||
|
if (files != null) {
|
||||||
|
return Collections.unmodifiableList(Arrays.asList(files));
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
|
return ImmutableList.of(); };
|
||||||
|
|
||||||
private static Iterable<File> fileTreeChildren(File file) {
|
private static Iterable<File> fileTreeChildren(File file) {
|
||||||
// check isDirectory() just because it may be faster than listFiles() on a non-directory
|
// check isDirectory() just because it may be faster than listFiles() on a non-directory
|
||||||
|
|
81
src/main/java/com/google/common/math/BigDecimalMath.java
Normal file
81
src/main/java/com/google/common/math/BigDecimalMath.java
Normal file
|
@ -0,0 +1,81 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2020 The Guava Authors
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
|
||||||
|
* in compliance with the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software distributed under the License
|
||||||
|
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF 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.GwtIncompatible;
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.math.RoundingMode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A class for arithmetic on {@link BigDecimal} that is not covered by its built-in methods.
|
||||||
|
*
|
||||||
|
* @author Louis Wasserman
|
||||||
|
* @since 30.0
|
||||||
|
*/
|
||||||
|
@GwtIncompatible
|
||||||
|
public class BigDecimalMath {
|
||||||
|
private BigDecimalMath() {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns {@code x}, rounded to a {@code double} with the specified rounding mode. If {@code x}
|
||||||
|
* is precisely representable as a {@code double}, its {@code double} value will be returned;
|
||||||
|
* otherwise, the rounding will choose between the two nearest representable values with {@code
|
||||||
|
* mode}.
|
||||||
|
*
|
||||||
|
* <p>For the case of {@link RoundingMode#HALF_DOWN}, {@code HALF_UP}, and {@code HALF_EVEN},
|
||||||
|
* infinite {@code double} values are considered infinitely far away. For example, 2^2000 is not
|
||||||
|
* representable as a double, but {@code roundToDouble(BigDecimal.valueOf(2).pow(2000), HALF_UP)}
|
||||||
|
* will return {@code Double.MAX_VALUE}, not {@code Double.POSITIVE_INFINITY}.
|
||||||
|
*
|
||||||
|
* <p>For the case of {@link RoundingMode#HALF_EVEN}, this implementation uses the IEEE 754
|
||||||
|
* default rounding mode: if the two nearest representable values are equally near, the one with
|
||||||
|
* the least significant bit zero is chosen. (In such cases, both of the nearest representable
|
||||||
|
* values are even integers; this method returns the one that is a multiple of a greater power of
|
||||||
|
* two.)
|
||||||
|
*
|
||||||
|
* @throws ArithmeticException if {@code mode} is {@link RoundingMode#UNNECESSARY} and {@code x}
|
||||||
|
* is not precisely representable as a {@code double}
|
||||||
|
* @since 30.0
|
||||||
|
*/
|
||||||
|
public static double roundToDouble(BigDecimal x, RoundingMode mode) {
|
||||||
|
return BigDecimalToDoubleRounder.INSTANCE.roundToDouble(x, mode);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class BigDecimalToDoubleRounder extends ToDoubleRounder<BigDecimal> {
|
||||||
|
static final BigDecimalToDoubleRounder INSTANCE = new BigDecimalToDoubleRounder();
|
||||||
|
|
||||||
|
private BigDecimalToDoubleRounder() {}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
double roundToDoubleArbitrarily(BigDecimal bigDecimal) {
|
||||||
|
return bigDecimal.doubleValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
int sign(BigDecimal bigDecimal) {
|
||||||
|
return bigDecimal.signum();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
BigDecimal toX(double d, RoundingMode mode) {
|
||||||
|
return new BigDecimal(d);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
BigDecimal minus(BigDecimal a, BigDecimal b) {
|
||||||
|
return a.subtract(b);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -21,6 +21,7 @@ import static com.google.common.math.MathPreconditions.checkPositive;
|
||||||
import static com.google.common.math.MathPreconditions.checkRoundingUnnecessary;
|
import static com.google.common.math.MathPreconditions.checkRoundingUnnecessary;
|
||||||
import static java.math.RoundingMode.CEILING;
|
import static java.math.RoundingMode.CEILING;
|
||||||
import static java.math.RoundingMode.FLOOR;
|
import static java.math.RoundingMode.FLOOR;
|
||||||
|
import static java.math.RoundingMode.HALF_DOWN;
|
||||||
import static java.math.RoundingMode.HALF_EVEN;
|
import static java.math.RoundingMode.HALF_EVEN;
|
||||||
|
|
||||||
import com.google.common.annotations.Beta;
|
import com.google.common.annotations.Beta;
|
||||||
|
@ -56,7 +57,7 @@ public final class BigIntegerMath {
|
||||||
*/
|
*/
|
||||||
@Beta
|
@Beta
|
||||||
public static BigInteger ceilingPowerOfTwo(BigInteger x) {
|
public static BigInteger ceilingPowerOfTwo(BigInteger x) {
|
||||||
return BigInteger.ZERO.setBit(log2(x, RoundingMode.CEILING));
|
return BigInteger.ZERO.setBit(log2(x, CEILING));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -68,7 +69,7 @@ public final class BigIntegerMath {
|
||||||
*/
|
*/
|
||||||
@Beta
|
@Beta
|
||||||
public static BigInteger floorPowerOfTwo(BigInteger x) {
|
public static BigInteger floorPowerOfTwo(BigInteger x) {
|
||||||
return BigInteger.ZERO.setBit(log2(x, RoundingMode.FLOOR));
|
return BigInteger.ZERO.setBit(log2(x, FLOOR));
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Returns {@code true} if {@code x} represents a power of two. */
|
/** Returns {@code true} if {@code x} represents a power of two. */
|
||||||
|
@ -306,6 +307,59 @@ public final class BigIntegerMath {
|
||||||
return DoubleMath.roundToBigInteger(Math.sqrt(DoubleUtils.bigToDouble(x)), HALF_EVEN);
|
return DoubleMath.roundToBigInteger(Math.sqrt(DoubleUtils.bigToDouble(x)), HALF_EVEN);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns {@code x}, rounded to a {@code double} with the specified rounding mode. If {@code x}
|
||||||
|
* is precisely representable as a {@code double}, its {@code double} value will be returned;
|
||||||
|
* otherwise, the rounding will choose between the two nearest representable values with {@code
|
||||||
|
* mode}.
|
||||||
|
*
|
||||||
|
* <p>For the case of {@link RoundingMode#HALF_DOWN}, {@code HALF_UP}, and {@code HALF_EVEN},
|
||||||
|
* infinite {@code double} values are considered infinitely far away. For example, 2^2000 is not
|
||||||
|
* representable as a double, but {@code roundToDouble(BigInteger.valueOf(2).pow(2000), HALF_UP)}
|
||||||
|
* will return {@code Double.MAX_VALUE}, not {@code Double.POSITIVE_INFINITY}.
|
||||||
|
*
|
||||||
|
* <p>For the case of {@link RoundingMode#HALF_EVEN}, this implementation uses the IEEE 754
|
||||||
|
* default rounding mode: if the two nearest representable values are equally near, the one with
|
||||||
|
* the least significant bit zero is chosen. (In such cases, both of the nearest representable
|
||||||
|
* values are even integers; this method returns the one that is a multiple of a greater power of
|
||||||
|
* two.)
|
||||||
|
*
|
||||||
|
* @throws ArithmeticException if {@code mode} is {@link RoundingMode#UNNECESSARY} and {@code x}
|
||||||
|
* is not precisely representable as a {@code double}
|
||||||
|
* @since 30.0
|
||||||
|
*/
|
||||||
|
@GwtIncompatible
|
||||||
|
public static double roundToDouble(BigInteger x, RoundingMode mode) {
|
||||||
|
return BigIntegerToDoubleRounder.INSTANCE.roundToDouble(x, mode);
|
||||||
|
}
|
||||||
|
|
||||||
|
@GwtIncompatible
|
||||||
|
private static class BigIntegerToDoubleRounder extends ToDoubleRounder<BigInteger> {
|
||||||
|
static final BigIntegerToDoubleRounder INSTANCE = new BigIntegerToDoubleRounder();
|
||||||
|
|
||||||
|
private BigIntegerToDoubleRounder() {}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
double roundToDoubleArbitrarily(BigInteger bigInteger) {
|
||||||
|
return DoubleUtils.bigToDouble(bigInteger);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
int sign(BigInteger bigInteger) {
|
||||||
|
return bigInteger.signum();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
BigInteger toX(double d, RoundingMode mode) {
|
||||||
|
return DoubleMath.roundToBigInteger(d, mode);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
BigInteger minus(BigInteger a, BigInteger b) {
|
||||||
|
return a.subtract(b);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the result of dividing {@code p} by {@code q}, rounding using the specified {@code
|
* Returns the result of dividing {@code p} by {@code q}, rounding using the specified {@code
|
||||||
* RoundingMode}.
|
* RoundingMode}.
|
||||||
|
@ -432,7 +486,7 @@ public final class BigIntegerMath {
|
||||||
long numeratorAccum = n;
|
long numeratorAccum = n;
|
||||||
long denominatorAccum = 1;
|
long denominatorAccum = 1;
|
||||||
|
|
||||||
int bits = LongMath.log2(n, RoundingMode.CEILING);
|
int bits = LongMath.log2(n, CEILING);
|
||||||
|
|
||||||
int numeratorBits = bits;
|
int numeratorBits = bits;
|
||||||
|
|
||||||
|
|
|
@ -29,6 +29,7 @@ import com.google.common.annotations.Beta;
|
||||||
import com.google.common.annotations.GwtCompatible;
|
import com.google.common.annotations.GwtCompatible;
|
||||||
import com.google.common.annotations.GwtIncompatible;
|
import com.google.common.annotations.GwtIncompatible;
|
||||||
import com.google.common.annotations.VisibleForTesting;
|
import com.google.common.annotations.VisibleForTesting;
|
||||||
|
import com.google.common.primitives.Longs;
|
||||||
import com.google.common.primitives.UnsignedLongs;
|
import com.google.common.primitives.UnsignedLongs;
|
||||||
import java.math.BigInteger;
|
import java.math.BigInteger;
|
||||||
import java.math.RoundingMode;
|
import java.math.RoundingMode;
|
||||||
|
@ -1004,10 +1005,30 @@ public final class LongMath {
|
||||||
checkNonNegative("n", n);
|
checkNonNegative("n", n);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (n == 2 || n == 3 || n == 5 || n == 7 || n == 11 || n == 13) {
|
if (n < 66) {
|
||||||
return true;
|
// Encode all primes less than 66 into mask without 0 and 1.
|
||||||
|
long mask =
|
||||||
|
(1L << (2 - 2))
|
||||||
|
| (1L << (3 - 2))
|
||||||
|
| (1L << (5 - 2))
|
||||||
|
| (1L << (7 - 2))
|
||||||
|
| (1L << (11 - 2))
|
||||||
|
| (1L << (13 - 2))
|
||||||
|
| (1L << (17 - 2))
|
||||||
|
| (1L << (19 - 2))
|
||||||
|
| (1L << (23 - 2))
|
||||||
|
| (1L << (29 - 2))
|
||||||
|
| (1L << (31 - 2))
|
||||||
|
| (1L << (37 - 2))
|
||||||
|
| (1L << (41 - 2))
|
||||||
|
| (1L << (43 - 2))
|
||||||
|
| (1L << (47 - 2))
|
||||||
|
| (1L << (53 - 2))
|
||||||
|
| (1L << (59 - 2))
|
||||||
|
| (1L << (61 - 2));
|
||||||
|
// Look up n within the mask.
|
||||||
|
return ((mask >> ((int) n - 2)) & 1) != 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((SIEVE_30 & (1 << (n % 30))) != 0) {
|
if ((SIEVE_30 & (1 << (n % 30))) != 0) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -1069,10 +1090,10 @@ public final class LongMath {
|
||||||
@Override
|
@Override
|
||||||
long mulMod(long a, long b, long m) {
|
long mulMod(long a, long b, long m) {
|
||||||
/*
|
/*
|
||||||
* NOTE(lowasser, 2015-Feb-12): Benchmarks suggest that changing this to
|
* lowasser, 2015-Feb-12: Benchmarks suggest that changing this to UnsignedLongs.remainder
|
||||||
* UnsignedLongs.remainder and increasing the threshold to 2^32 doesn't pay for itself, and
|
* and increasing the threshold to 2^32 doesn't pay for itself, and adding another enum
|
||||||
* adding another enum constant hurts performance further -- I suspect because bimorphic
|
* constant hurts performance further -- I suspect because bimorphic implementation is a
|
||||||
* implementation is a sweet spot for the JVM.
|
* sweet spot for the JVM.
|
||||||
*/
|
*/
|
||||||
return (a * b) % m;
|
return (a * b) % m;
|
||||||
}
|
}
|
||||||
|
@ -1203,5 +1224,125 @@ public final class LongMath {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns {@code x}, rounded to a {@code double} with the specified rounding mode. If {@code x}
|
||||||
|
* is precisely representable as a {@code double}, its {@code double} value will be returned;
|
||||||
|
* otherwise, the rounding will choose between the two nearest representable values with {@code
|
||||||
|
* mode}.
|
||||||
|
*
|
||||||
|
* <p>For the case of {@link RoundingMode#HALF_EVEN}, this implementation uses the IEEE 754
|
||||||
|
* default rounding mode: if the two nearest representable values are equally near, the one with
|
||||||
|
* the least significant bit zero is chosen. (In such cases, both of the nearest representable
|
||||||
|
* values are even integers; this method returns the one that is a multiple of a greater power of
|
||||||
|
* two.)
|
||||||
|
*
|
||||||
|
* @throws ArithmeticException if {@code mode} is {@link RoundingMode#UNNECESSARY} and {@code x}
|
||||||
|
* is not precisely representable as a {@code double}
|
||||||
|
* @since 30.0
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("deprecation")
|
||||||
|
@GwtIncompatible
|
||||||
|
public static double roundToDouble(long x, RoundingMode mode) {
|
||||||
|
// Logic adapted from ToDoubleRounder.
|
||||||
|
double roundArbitrarily = (double) x;
|
||||||
|
long roundArbitrarilyAsLong = (long) roundArbitrarily;
|
||||||
|
int cmpXToRoundArbitrarily;
|
||||||
|
|
||||||
|
if (roundArbitrarilyAsLong == Long.MAX_VALUE) {
|
||||||
|
/*
|
||||||
|
* For most values, the conversion from roundArbitrarily to roundArbitrarilyAsLong is
|
||||||
|
* lossless. In that case we can compare x to roundArbitrarily using Longs.compare(x,
|
||||||
|
* roundArbitrarilyAsLong). The exception is for values where the conversion to double rounds
|
||||||
|
* up to give roundArbitrarily equal to 2^63, so the conversion back to long overflows and
|
||||||
|
* roundArbitrarilyAsLong is Long.MAX_VALUE. (This is the only way this condition can occur as
|
||||||
|
* otherwise the conversion back to long pads with zero bits.) In this case we know that
|
||||||
|
* roundArbitrarily > x. (This is important when x == Long.MAX_VALUE ==
|
||||||
|
* roundArbitrarilyAsLong.)
|
||||||
|
*/
|
||||||
|
cmpXToRoundArbitrarily = -1;
|
||||||
|
} else {
|
||||||
|
cmpXToRoundArbitrarily = Longs.compare(x, roundArbitrarilyAsLong);
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (mode) {
|
||||||
|
case UNNECESSARY:
|
||||||
|
checkRoundingUnnecessary(cmpXToRoundArbitrarily == 0);
|
||||||
|
return roundArbitrarily;
|
||||||
|
case FLOOR:
|
||||||
|
return (cmpXToRoundArbitrarily >= 0)
|
||||||
|
? roundArbitrarily
|
||||||
|
: DoubleUtils.nextDown(roundArbitrarily);
|
||||||
|
case CEILING:
|
||||||
|
return (cmpXToRoundArbitrarily <= 0) ? roundArbitrarily : Math.nextUp(roundArbitrarily);
|
||||||
|
case DOWN:
|
||||||
|
if (x >= 0) {
|
||||||
|
return (cmpXToRoundArbitrarily >= 0)
|
||||||
|
? roundArbitrarily
|
||||||
|
: DoubleUtils.nextDown(roundArbitrarily);
|
||||||
|
} else {
|
||||||
|
return (cmpXToRoundArbitrarily <= 0) ? roundArbitrarily : Math.nextUp(roundArbitrarily);
|
||||||
|
}
|
||||||
|
case UP:
|
||||||
|
if (x >= 0) {
|
||||||
|
return (cmpXToRoundArbitrarily <= 0) ? roundArbitrarily : Math.nextUp(roundArbitrarily);
|
||||||
|
} else {
|
||||||
|
return (cmpXToRoundArbitrarily >= 0)
|
||||||
|
? roundArbitrarily
|
||||||
|
: DoubleUtils.nextDown(roundArbitrarily);
|
||||||
|
}
|
||||||
|
case HALF_DOWN:
|
||||||
|
case HALF_UP:
|
||||||
|
case HALF_EVEN:
|
||||||
|
{
|
||||||
|
long roundFloor;
|
||||||
|
double roundFloorAsDouble;
|
||||||
|
long roundCeiling;
|
||||||
|
double roundCeilingAsDouble;
|
||||||
|
|
||||||
|
if (cmpXToRoundArbitrarily >= 0) {
|
||||||
|
roundFloorAsDouble = roundArbitrarily;
|
||||||
|
roundFloor = roundArbitrarilyAsLong;
|
||||||
|
roundCeilingAsDouble = Math.nextUp(roundArbitrarily);
|
||||||
|
roundCeiling = (long) Math.ceil(roundCeilingAsDouble);
|
||||||
|
} else {
|
||||||
|
roundCeilingAsDouble = roundArbitrarily;
|
||||||
|
roundCeiling = roundArbitrarilyAsLong;
|
||||||
|
roundFloorAsDouble = DoubleUtils.nextDown(roundArbitrarily);
|
||||||
|
roundFloor = (long) Math.floor(roundFloorAsDouble);
|
||||||
|
}
|
||||||
|
|
||||||
|
long deltaToFloor = x - roundFloor;
|
||||||
|
long deltaToCeiling = roundCeiling - x;
|
||||||
|
|
||||||
|
if (roundCeiling == Long.MAX_VALUE) {
|
||||||
|
// correct for Long.MAX_VALUE as discussed above: roundCeilingAsDouble must be 2^63, but
|
||||||
|
// roundCeiling is 2^63-1.
|
||||||
|
deltaToCeiling++;
|
||||||
|
}
|
||||||
|
|
||||||
|
int diff = Longs.compare(deltaToFloor, deltaToCeiling);
|
||||||
|
if (diff < 0) { // closer to floor
|
||||||
|
return roundFloorAsDouble;
|
||||||
|
} else if (diff > 0) { // closer to ceiling
|
||||||
|
return roundCeilingAsDouble;
|
||||||
|
}
|
||||||
|
// halfway between the representable values; do the half-whatever logic
|
||||||
|
switch (mode) {
|
||||||
|
case HALF_EVEN:
|
||||||
|
return ((DoubleUtils.getSignificand(roundFloorAsDouble) & 1L) == 0)
|
||||||
|
? roundFloorAsDouble
|
||||||
|
: roundCeilingAsDouble;
|
||||||
|
case HALF_DOWN:
|
||||||
|
return (x >= 0) ? roundFloorAsDouble : roundCeilingAsDouble;
|
||||||
|
case HALF_UP:
|
||||||
|
return (x >= 0) ? roundCeilingAsDouble : roundFloorAsDouble;
|
||||||
|
default:
|
||||||
|
throw new AssertionError("impossible");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new AssertionError("impossible");
|
||||||
|
}
|
||||||
|
|
||||||
private LongMath() {}
|
private LongMath() {}
|
||||||
}
|
}
|
||||||
|
|
|
@ -424,11 +424,11 @@ public final class Stats implements Serializable {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
Stats other = (Stats) obj;
|
Stats other = (Stats) obj;
|
||||||
return (count == other.count)
|
return count == other.count
|
||||||
&& (doubleToLongBits(mean) == doubleToLongBits(other.mean))
|
&& doubleToLongBits(mean) == doubleToLongBits(other.mean)
|
||||||
&& (doubleToLongBits(sumOfSquaresOfDeltas) == doubleToLongBits(other.sumOfSquaresOfDeltas))
|
&& doubleToLongBits(sumOfSquaresOfDeltas) == doubleToLongBits(other.sumOfSquaresOfDeltas)
|
||||||
&& (doubleToLongBits(min) == doubleToLongBits(other.min))
|
&& doubleToLongBits(min) == doubleToLongBits(other.min)
|
||||||
&& (doubleToLongBits(max) == doubleToLongBits(other.max));
|
&& doubleToLongBits(max) == doubleToLongBits(other.max);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
152
src/main/java/com/google/common/math/ToDoubleRounder.java
Normal file
152
src/main/java/com/google/common/math/ToDoubleRounder.java
Normal file
|
@ -0,0 +1,152 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2020 The Guava Authors
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
|
||||||
|
* in compliance with the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software distributed under the License
|
||||||
|
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF 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.checkNotNull;
|
||||||
|
import static com.google.common.math.MathPreconditions.checkRoundingUnnecessary;
|
||||||
|
|
||||||
|
import com.google.common.annotations.GwtIncompatible;
|
||||||
|
import java.math.RoundingMode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper type to implement rounding {@code X} to a representable {@code double} value according to
|
||||||
|
* a {@link RoundingMode}.
|
||||||
|
*/
|
||||||
|
@GwtIncompatible
|
||||||
|
abstract class ToDoubleRounder<X extends Number & Comparable<X>> {
|
||||||
|
/**
|
||||||
|
* Returns x rounded to either the greatest double less than or equal to the precise value of x,
|
||||||
|
* or the least double greater than or equal to the precise value of x.
|
||||||
|
*/
|
||||||
|
abstract double roundToDoubleArbitrarily(X x);
|
||||||
|
|
||||||
|
/** Returns the sign of x: either -1, 0, or 1. */
|
||||||
|
abstract int sign(X x);
|
||||||
|
|
||||||
|
/** Returns d's value as an X, rounded with the specified mode. */
|
||||||
|
abstract X toX(double d, RoundingMode mode);
|
||||||
|
|
||||||
|
/** Returns a - b, guaranteed that both arguments are nonnegative. */
|
||||||
|
abstract X minus(X a, X b);
|
||||||
|
|
||||||
|
/** Rounds {@code x} to a {@code double}. */
|
||||||
|
final double roundToDouble(X x, RoundingMode mode) {
|
||||||
|
checkNotNull(x, "x");
|
||||||
|
checkNotNull(mode, "mode");
|
||||||
|
double roundArbitrarily = roundToDoubleArbitrarily(x);
|
||||||
|
if (Double.isInfinite(roundArbitrarily)) {
|
||||||
|
switch (mode) {
|
||||||
|
case DOWN:
|
||||||
|
case HALF_EVEN:
|
||||||
|
case HALF_DOWN:
|
||||||
|
case HALF_UP:
|
||||||
|
return Double.MAX_VALUE * sign(x);
|
||||||
|
case FLOOR:
|
||||||
|
return (roundArbitrarily == Double.POSITIVE_INFINITY)
|
||||||
|
? Double.MAX_VALUE
|
||||||
|
: Double.NEGATIVE_INFINITY;
|
||||||
|
case CEILING:
|
||||||
|
return (roundArbitrarily == Double.POSITIVE_INFINITY)
|
||||||
|
? Double.POSITIVE_INFINITY
|
||||||
|
: -Double.MAX_VALUE;
|
||||||
|
case UP:
|
||||||
|
return roundArbitrarily;
|
||||||
|
case UNNECESSARY:
|
||||||
|
throw new ArithmeticException(x + " cannot be represented precisely as a double");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
X roundArbitrarilyAsX = toX(roundArbitrarily, RoundingMode.UNNECESSARY);
|
||||||
|
int cmpXToRoundArbitrarily = x.compareTo(roundArbitrarilyAsX);
|
||||||
|
switch (mode) {
|
||||||
|
case UNNECESSARY:
|
||||||
|
checkRoundingUnnecessary(cmpXToRoundArbitrarily == 0);
|
||||||
|
return roundArbitrarily;
|
||||||
|
case FLOOR:
|
||||||
|
return (cmpXToRoundArbitrarily >= 0)
|
||||||
|
? roundArbitrarily
|
||||||
|
: DoubleUtils.nextDown(roundArbitrarily);
|
||||||
|
case CEILING:
|
||||||
|
return (cmpXToRoundArbitrarily <= 0) ? roundArbitrarily : Math.nextUp(roundArbitrarily);
|
||||||
|
case DOWN:
|
||||||
|
if (sign(x) >= 0) {
|
||||||
|
return (cmpXToRoundArbitrarily >= 0)
|
||||||
|
? roundArbitrarily
|
||||||
|
: DoubleUtils.nextDown(roundArbitrarily);
|
||||||
|
} else {
|
||||||
|
return (cmpXToRoundArbitrarily <= 0) ? roundArbitrarily : Math.nextUp(roundArbitrarily);
|
||||||
|
}
|
||||||
|
case UP:
|
||||||
|
if (sign(x) >= 0) {
|
||||||
|
return (cmpXToRoundArbitrarily <= 0) ? roundArbitrarily : Math.nextUp(roundArbitrarily);
|
||||||
|
} else {
|
||||||
|
return (cmpXToRoundArbitrarily >= 0)
|
||||||
|
? roundArbitrarily
|
||||||
|
: DoubleUtils.nextDown(roundArbitrarily);
|
||||||
|
}
|
||||||
|
case HALF_DOWN:
|
||||||
|
case HALF_UP:
|
||||||
|
case HALF_EVEN:
|
||||||
|
{
|
||||||
|
X roundFloor;
|
||||||
|
double roundFloorAsDouble;
|
||||||
|
X roundCeiling;
|
||||||
|
double roundCeilingAsDouble;
|
||||||
|
|
||||||
|
if (cmpXToRoundArbitrarily >= 0) {
|
||||||
|
roundFloorAsDouble = roundArbitrarily;
|
||||||
|
roundFloor = roundArbitrarilyAsX;
|
||||||
|
roundCeilingAsDouble = Math.nextUp(roundArbitrarily);
|
||||||
|
if (roundCeilingAsDouble == Double.POSITIVE_INFINITY) {
|
||||||
|
return roundFloorAsDouble;
|
||||||
|
}
|
||||||
|
roundCeiling = toX(roundCeilingAsDouble, RoundingMode.CEILING);
|
||||||
|
} else {
|
||||||
|
roundCeilingAsDouble = roundArbitrarily;
|
||||||
|
roundCeiling = roundArbitrarilyAsX;
|
||||||
|
roundFloorAsDouble = DoubleUtils.nextDown(roundArbitrarily);
|
||||||
|
if (roundFloorAsDouble == Double.NEGATIVE_INFINITY) {
|
||||||
|
return roundCeilingAsDouble;
|
||||||
|
}
|
||||||
|
roundFloor = toX(roundFloorAsDouble, RoundingMode.FLOOR);
|
||||||
|
}
|
||||||
|
|
||||||
|
X deltaToFloor = minus(x, roundFloor);
|
||||||
|
X deltaToCeiling = minus(roundCeiling, x);
|
||||||
|
int diff = deltaToFloor.compareTo(deltaToCeiling);
|
||||||
|
if (diff < 0) { // closer to floor
|
||||||
|
return roundFloorAsDouble;
|
||||||
|
} else if (diff > 0) { // closer to ceiling
|
||||||
|
return roundCeilingAsDouble;
|
||||||
|
}
|
||||||
|
// halfway between the representable values; do the half-whatever logic
|
||||||
|
switch (mode) {
|
||||||
|
case HALF_EVEN:
|
||||||
|
// roundFloorAsDouble and roundCeilingAsDouble are neighbors, so precisely
|
||||||
|
// one of them should have an even long representation
|
||||||
|
return ((Double.doubleToRawLongBits(roundFloorAsDouble) & 1L) == 0)
|
||||||
|
? roundFloorAsDouble
|
||||||
|
: roundCeilingAsDouble;
|
||||||
|
case HALF_DOWN:
|
||||||
|
return (sign(x) >= 0) ? roundFloorAsDouble : roundCeilingAsDouble;
|
||||||
|
case HALF_UP:
|
||||||
|
return (sign(x) >= 0) ? roundCeilingAsDouble : roundFloorAsDouble;
|
||||||
|
default:
|
||||||
|
throw new AssertionError("impossible");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new AssertionError("impossible");
|
||||||
|
}
|
||||||
|
}
|
|
@ -129,6 +129,13 @@ public final class HttpHeaders {
|
||||||
public static final String MAX_FORWARDS = "Max-Forwards";
|
public static final String MAX_FORWARDS = "Max-Forwards";
|
||||||
/** The HTTP {@code Origin} header field name. */
|
/** The HTTP {@code Origin} header field name. */
|
||||||
public static final String ORIGIN = "Origin";
|
public static final String ORIGIN = "Origin";
|
||||||
|
/**
|
||||||
|
* The HTTP <a href="https://github.com/WICG/origin-isolation">{@code Origin-Isolation}</a> header
|
||||||
|
* field name.
|
||||||
|
*
|
||||||
|
* @since 30.1
|
||||||
|
*/
|
||||||
|
public static final String ORIGIN_ISOLATION = "Origin-Isolation";
|
||||||
/** The HTTP {@code Proxy-Authorization} header field name. */
|
/** The HTTP {@code Proxy-Authorization} header field name. */
|
||||||
public static final String PROXY_AUTHORIZATION = "Proxy-Authorization";
|
public static final String PROXY_AUTHORIZATION = "Proxy-Authorization";
|
||||||
/** The HTTP {@code Range} header field name. */
|
/** The HTTP {@code Range} header field name. */
|
||||||
|
@ -266,6 +273,21 @@ public final class HttpHeaders {
|
||||||
* @since 20.0
|
* @since 20.0
|
||||||
*/
|
*/
|
||||||
public static final String X_WEBKIT_CSP_REPORT_ONLY = "X-WebKit-CSP-Report-Only";
|
public static final String X_WEBKIT_CSP_REPORT_ONLY = "X-WebKit-CSP-Report-Only";
|
||||||
|
/**
|
||||||
|
* The HTTP <a href="https://wicg.github.io/cross-origin-embedder-policy/#COEP">{@code
|
||||||
|
* Cross-Origin-Embedder-Policy}</a> header field name.
|
||||||
|
*
|
||||||
|
* @since 30.0
|
||||||
|
*/
|
||||||
|
public static final String CROSS_ORIGIN_EMBEDDER_POLICY = "Cross-Origin-Embedder-Policy";
|
||||||
|
/**
|
||||||
|
* The HTTP <a href="https://wicg.github.io/cross-origin-embedder-policy/#COEP-RO">{@code
|
||||||
|
* Cross-Origin-Embedder-Policy-Report-Only}</a> header field name.
|
||||||
|
*
|
||||||
|
* @since 30.0
|
||||||
|
*/
|
||||||
|
public static final String CROSS_ORIGIN_EMBEDDER_POLICY_REPORT_ONLY =
|
||||||
|
"Cross-Origin-Embedder-Policy-Report-Only";
|
||||||
/**
|
/**
|
||||||
* The HTTP Cross-Origin-Opener-Policy header field name.
|
* The HTTP Cross-Origin-Opener-Policy header field name.
|
||||||
*
|
*
|
||||||
|
@ -389,6 +411,12 @@ public final class HttpHeaders {
|
||||||
* @since 15.0
|
* @since 15.0
|
||||||
*/
|
*/
|
||||||
@Beta public static final String PUBLIC_KEY_PINS = "Public-Key-Pins";
|
@Beta public static final String PUBLIC_KEY_PINS = "Public-Key-Pins";
|
||||||
|
/**
|
||||||
|
* The HTTP {@code X-Request-ID} header field name.
|
||||||
|
*
|
||||||
|
* @since 30.1
|
||||||
|
*/
|
||||||
|
public static final String X_REQUEST_ID = "X-Request-ID";
|
||||||
/**
|
/**
|
||||||
* The HTTP <a href="http://tools.ietf.org/html/draft-evans-palmer-key-pinning">{@code
|
* The HTTP <a href="http://tools.ietf.org/html/draft-evans-palmer-key-pinning">{@code
|
||||||
* Public-Key-Pins-Report-Only}</a> header field name.
|
* Public-Key-Pins-Report-Only}</a> header field name.
|
||||||
|
@ -459,6 +487,56 @@ public final class HttpHeaders {
|
||||||
*/
|
*/
|
||||||
public static final String X_MOZ = "X-Moz";
|
public static final String X_MOZ = "X-Moz";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The HTTP <a href="https://wicg.github.io/ua-client-hints/#sec-ch-ua">{@code Sec-CH-UA}</a>
|
||||||
|
* header field name.
|
||||||
|
*
|
||||||
|
* @since 30.0
|
||||||
|
*/
|
||||||
|
public static final String SEC_CH_UA = "Sec-CH-UA";
|
||||||
|
/**
|
||||||
|
* The HTTP <a href="https://wicg.github.io/ua-client-hints/#sec-ch-arch">{@code
|
||||||
|
* Sec-CH-UA-Arch}</a> header field name.
|
||||||
|
*
|
||||||
|
* @since 30.0
|
||||||
|
*/
|
||||||
|
public static final String SEC_CH_UA_ARCH = "Sec-CH-UA-Arch";
|
||||||
|
/**
|
||||||
|
* The HTTP <a href="https://wicg.github.io/ua-client-hints/#sec-ch-model">{@code
|
||||||
|
* Sec-CH-UA-Model}</a> header field name.
|
||||||
|
*
|
||||||
|
* @since 30.0
|
||||||
|
*/
|
||||||
|
public static final String SEC_CH_UA_MODEL = "Sec-CH-UA-Model";
|
||||||
|
/**
|
||||||
|
* The HTTP <a href="https://wicg.github.io/ua-client-hints/#sec-ch-platform">{@code
|
||||||
|
* Sec-CH-UA-Platform}</a> header field name.
|
||||||
|
*
|
||||||
|
* @since 30.0
|
||||||
|
*/
|
||||||
|
public static final String SEC_CH_UA_PLATFORM = "Sec-CH-UA-Platform";
|
||||||
|
/**
|
||||||
|
* The HTTP <a href="https://wicg.github.io/ua-client-hints/#sec-ch-platform-version">{@code
|
||||||
|
* Sec-CH-UA-Platform-Version}</a> header field name.
|
||||||
|
*
|
||||||
|
* @since 30.0
|
||||||
|
*/
|
||||||
|
public static final String SEC_CH_UA_PLATFORM_VERSION = "Sec-CH-UA-Platform-Version";
|
||||||
|
/**
|
||||||
|
* The HTTP <a href="https://wicg.github.io/ua-client-hints/#sec-ch-full-version">{@code
|
||||||
|
* Sec-CH-UA-Full-Version}</a> header field name.
|
||||||
|
*
|
||||||
|
* @since 30.0
|
||||||
|
*/
|
||||||
|
public static final String SEC_CH_UA_FULL_VERSION = "Sec-CH-UA-Full-Version";
|
||||||
|
/**
|
||||||
|
* The HTTP <a href="https://wicg.github.io/ua-client-hints/#sec-ch-mobile">{@code
|
||||||
|
* Sec-CH-UA-Mobile}</a> header field name.
|
||||||
|
*
|
||||||
|
* @since 30.0
|
||||||
|
*/
|
||||||
|
public static final String SEC_CH_UA_MOBILE = "Sec-CH-UA-Mobile";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The HTTP <a href="https://w3c.github.io/webappsec-fetch-metadata/">{@code Sec-Fetch-Dest}</a>
|
* The HTTP <a href="https://w3c.github.io/webappsec-fetch-metadata/">{@code Sec-Fetch-Dest}</a>
|
||||||
* header field name.
|
* header field name.
|
||||||
|
|
|
@ -19,9 +19,9 @@ import static com.google.common.base.Preconditions.checkNotNull;
|
||||||
|
|
||||||
import com.google.common.annotations.Beta;
|
import com.google.common.annotations.Beta;
|
||||||
import com.google.common.annotations.GwtIncompatible;
|
import com.google.common.annotations.GwtIncompatible;
|
||||||
|
import com.google.common.base.CharMatcher;
|
||||||
import com.google.common.base.MoreObjects;
|
import com.google.common.base.MoreObjects;
|
||||||
import com.google.common.base.Splitter;
|
import com.google.common.base.Splitter;
|
||||||
import com.google.common.collect.Iterables;
|
|
||||||
import com.google.common.hash.Hashing;
|
import com.google.common.hash.Hashing;
|
||||||
import com.google.common.io.ByteStreams;
|
import com.google.common.io.ByteStreams;
|
||||||
import com.google.common.primitives.Ints;
|
import com.google.common.primitives.Ints;
|
||||||
|
@ -32,7 +32,6 @@ import java.net.InetAddress;
|
||||||
import java.net.UnknownHostException;
|
import java.net.UnknownHostException;
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
|
|
||||||
|
|
||||||
|
@ -104,6 +103,10 @@ public final class InetAddresses {
|
||||||
private static final int IPV6_PART_COUNT = 8;
|
private static final int IPV6_PART_COUNT = 8;
|
||||||
private static final Splitter IPV4_SPLITTER = Splitter.on('.').limit(IPV4_PART_COUNT);
|
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 Splitter IPV6_SPLITTER = Splitter.on(':').limit(IPV6_PART_COUNT + 2);
|
||||||
|
private static final char IPV4_DELIMITER = '.';
|
||||||
|
private static final char IPV6_DELIMITER = ':';
|
||||||
|
private static final CharMatcher IPV4_DELIMITER_MATCHER = CharMatcher.is(IPV4_DELIMITER);
|
||||||
|
private static final CharMatcher IPV6_DELIMITER_MATCHER = CharMatcher.is(IPV6_DELIMITER);
|
||||||
private static final Inet4Address LOOPBACK4 = (Inet4Address) forString("127.0.0.1");
|
private static final Inet4Address LOOPBACK4 = (Inet4Address) forString("127.0.0.1");
|
||||||
private static final Inet4Address ANY4 = (Inet4Address) forString("0.0.0.0");
|
private static final Inet4Address ANY4 = (Inet4Address) forString("0.0.0.0");
|
||||||
|
|
||||||
|
@ -196,81 +199,100 @@ public final class InetAddresses {
|
||||||
}
|
}
|
||||||
return textToNumericFormatV6(ipString);
|
return textToNumericFormatV6(ipString);
|
||||||
} else if (hasDot) {
|
} else if (hasDot) {
|
||||||
|
if (percentIndex != -1) {
|
||||||
|
return null; // Scope IDs are not supported for IPV4
|
||||||
|
}
|
||||||
return textToNumericFormatV4(ipString);
|
return textToNumericFormatV4(ipString);
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static byte [] textToNumericFormatV4(String ipString) {
|
private static byte [] textToNumericFormatV4(String ipString) {
|
||||||
byte[] bytes = new byte[IPV4_PART_COUNT];
|
if (IPV4_DELIMITER_MATCHER.countIn(ipString) + 1 != IPV4_PART_COUNT) {
|
||||||
int i = 0;
|
return null; // Wrong number of parts
|
||||||
try {
|
|
||||||
for (String octet : IPV4_SPLITTER.split(ipString)) {
|
|
||||||
bytes[i++] = parseOctet(octet);
|
|
||||||
}
|
}
|
||||||
|
byte[] bytes = new byte[IPV4_PART_COUNT];
|
||||||
|
int start = 0;
|
||||||
|
// Iterate through the parts of the ip string.
|
||||||
|
// Invariant: start is always the beginning of an octet.
|
||||||
|
for (int i = 0; i < IPV4_PART_COUNT; i++) {
|
||||||
|
int end = ipString.indexOf(IPV4_DELIMITER, start);
|
||||||
|
if (end == -1) {
|
||||||
|
end = ipString.length();
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
bytes[i] = parseOctet(ipString, start, end);
|
||||||
} catch (NumberFormatException ex) {
|
} catch (NumberFormatException ex) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
start = end + 1;
|
||||||
return i == IPV4_PART_COUNT ? bytes : null;
|
}
|
||||||
|
return bytes;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static byte[] textToNumericFormatV6(String ipString) {
|
private static byte[] textToNumericFormatV6(String ipString) {
|
||||||
// An address can have [2..8] colons, and N colons make N+1 parts.
|
// An address can have [2..8] colons.
|
||||||
List<String> parts = IPV6_SPLITTER.splitToList(ipString);
|
int delimiterCount = IPV6_DELIMITER_MATCHER.countIn(ipString);
|
||||||
if (parts.size() < 3 || parts.size() > IPV6_PART_COUNT + 1) {
|
if (delimiterCount < 2 || delimiterCount > IPV6_PART_COUNT) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
int partsSkipped = IPV6_PART_COUNT - (delimiterCount + 1); // estimate; may be modified later
|
||||||
// Disregarding the endpoints, find "::" with nothing in between.
|
boolean hasSkip = false;
|
||||||
// This indicates that a run of zeroes has been skipped.
|
// Scan for the appearance of ::, to mark a skip-format IPV6 string and adjust the partsSkipped
|
||||||
int skipIndex = -1;
|
// estimate.
|
||||||
for (int i = 1; i < parts.size() - 1; i++) {
|
for (int i = 0; i < ipString.length() - 1; i++) {
|
||||||
if (parts.get(i).length() == 0) {
|
if (ipString.charAt(i) == IPV6_DELIMITER && ipString.charAt(i + 1) == IPV6_DELIMITER) {
|
||||||
if (skipIndex >= 0) {
|
if (hasSkip) {
|
||||||
return null; // Can't have more than one ::
|
return null; // Can't have more than one ::
|
||||||
}
|
}
|
||||||
skipIndex = i;
|
hasSkip = true;
|
||||||
|
partsSkipped++; // :: means we skipped an extra part in between the two delimiters.
|
||||||
|
if (i == 0) {
|
||||||
|
partsSkipped++; // Begins with ::, so we skipped the part preceding the first :
|
||||||
|
}
|
||||||
|
if (i == ipString.length() - 2) {
|
||||||
|
partsSkipped++; // Ends with ::, so we skipped the part after the last :
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
int partsHi; // Number of parts to copy from above/before the "::"
|
if (ipString.charAt(0) == IPV6_DELIMITER && ipString.charAt(1) != IPV6_DELIMITER) {
|
||||||
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 ^::
|
return null; // ^: requires ^::
|
||||||
}
|
}
|
||||||
if (Iterables.getLast(parts).length() == 0 && --partsLo != 0) {
|
if (ipString.charAt(ipString.length() - 1) == IPV6_DELIMITER
|
||||||
|
&& ipString.charAt(ipString.length() - 2) != IPV6_DELIMITER) {
|
||||||
return null; // :$ requires ::$
|
return null; // :$ requires ::$
|
||||||
}
|
}
|
||||||
} else {
|
if (hasSkip && partsSkipped <= 0) {
|
||||||
// Otherwise, allocate the entire address to partsHi. The endpoints
|
return null; // :: must expand to at least one '0'
|
||||||
// could still be empty, but parseHextet() will check for that.
|
}
|
||||||
partsHi = parts.size();
|
if (!hasSkip && delimiterCount + 1 != IPV6_PART_COUNT) {
|
||||||
partsLo = 0;
|
return null; // Incorrect number of parts
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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);
|
ByteBuffer rawBytes = ByteBuffer.allocate(2 * IPV6_PART_COUNT);
|
||||||
try {
|
try {
|
||||||
for (int i = 0; i < partsHi; i++) {
|
// Iterate through the parts of the ip string.
|
||||||
rawBytes.putShort(parseHextet(parts.get(i)));
|
// Invariant: start is always the beginning of a hextet, or the second ':' of the skip
|
||||||
|
// sequence "::"
|
||||||
|
int start = 0;
|
||||||
|
if (ipString.charAt(0) == IPV6_DELIMITER) {
|
||||||
|
start = 1;
|
||||||
}
|
}
|
||||||
|
while (start < ipString.length()) {
|
||||||
|
int end = ipString.indexOf(IPV6_DELIMITER, start);
|
||||||
|
if (end == -1) {
|
||||||
|
end = ipString.length();
|
||||||
|
}
|
||||||
|
if (ipString.charAt(start) == IPV6_DELIMITER) {
|
||||||
|
// expand zeroes
|
||||||
for (int i = 0; i < partsSkipped; i++) {
|
for (int i = 0; i < partsSkipped; i++) {
|
||||||
rawBytes.putShort((short) 0);
|
rawBytes.putShort((short) 0);
|
||||||
}
|
}
|
||||||
for (int i = partsLo; i > 0; i--) {
|
|
||||||
rawBytes.putShort(parseHextet(parts.get(parts.size() - i)));
|
} else {
|
||||||
|
rawBytes.putShort(parseHextet(ipString, start, end));
|
||||||
|
}
|
||||||
|
start = end + 1;
|
||||||
}
|
}
|
||||||
} catch (NumberFormatException ex) {
|
} catch (NumberFormatException ex) {
|
||||||
return null;
|
return null;
|
||||||
|
@ -291,23 +313,45 @@ public final class InetAddresses {
|
||||||
return initialPart + penultimate + ":" + ultimate;
|
return initialPart + penultimate + ":" + ultimate;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static byte parseOctet(String ipPart) {
|
private static byte parseOctet(String ipString, int start, int end) {
|
||||||
// Note: we already verified that this string contains only hex digits.
|
// Note: we already verified that this string contains only hex digits, but the string may still
|
||||||
int octet = Integer.parseInt(ipPart);
|
// contain non-decimal characters.
|
||||||
|
int length = end - start;
|
||||||
|
if (length <= 0 || length > 3) {
|
||||||
|
throw new NumberFormatException();
|
||||||
|
}
|
||||||
// Disallow leading zeroes, because no clear standard exists on
|
// Disallow leading zeroes, because no clear standard exists on
|
||||||
// whether these should be interpreted as decimal or octal.
|
// whether these should be interpreted as decimal or octal.
|
||||||
if (octet > 255 || (ipPart.startsWith("0") && ipPart.length() > 1)) {
|
if (length > 1 && ipString.charAt(start) == '0') {
|
||||||
|
throw new NumberFormatException();
|
||||||
|
}
|
||||||
|
int octet = 0;
|
||||||
|
for (int i = start; i < end; i++) {
|
||||||
|
octet *= 10;
|
||||||
|
int digit = Character.digit(ipString.charAt(i), 10);
|
||||||
|
if (digit < 0) {
|
||||||
|
throw new NumberFormatException();
|
||||||
|
}
|
||||||
|
octet += digit;
|
||||||
|
}
|
||||||
|
if (octet > 255) {
|
||||||
throw new NumberFormatException();
|
throw new NumberFormatException();
|
||||||
}
|
}
|
||||||
return (byte) octet;
|
return (byte) octet;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static short parseHextet(String ipPart) {
|
// Parse a hextet out of the ipString from start (inclusive) to end (exclusive)
|
||||||
|
private static short parseHextet(String ipString, int start, int end) {
|
||||||
// Note: we already verified that this string contains only hex digits.
|
// Note: we already verified that this string contains only hex digits.
|
||||||
int hextet = Integer.parseInt(ipPart, 16);
|
int length = end - start;
|
||||||
if (hextet > 0xffff) {
|
if (length <= 0 || length > 4) {
|
||||||
throw new NumberFormatException();
|
throw new NumberFormatException();
|
||||||
}
|
}
|
||||||
|
int hextet = 0;
|
||||||
|
for (int i = start; i < end; i++) {
|
||||||
|
hextet = hextet << 4;
|
||||||
|
hextet |= Character.digit(ipString.charAt(i), 16);
|
||||||
|
}
|
||||||
return (short) hextet;
|
return (short) hextet;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -101,6 +101,7 @@ public final class MediaType {
|
||||||
private static final String IMAGE_TYPE = "image";
|
private static final String IMAGE_TYPE = "image";
|
||||||
private static final String TEXT_TYPE = "text";
|
private static final String TEXT_TYPE = "text";
|
||||||
private static final String VIDEO_TYPE = "video";
|
private static final String VIDEO_TYPE = "video";
|
||||||
|
private static final String FONT_TYPE = "font";
|
||||||
|
|
||||||
private static final String WILDCARD = "*";
|
private static final String WILDCARD = "*";
|
||||||
|
|
||||||
|
@ -140,6 +141,7 @@ public final class MediaType {
|
||||||
public static final MediaType ANY_AUDIO_TYPE = createConstant(AUDIO_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_VIDEO_TYPE = createConstant(VIDEO_TYPE, WILDCARD);
|
||||||
public static final MediaType ANY_APPLICATION_TYPE = createConstant(APPLICATION_TYPE, WILDCARD);
|
public static final MediaType ANY_APPLICATION_TYPE = createConstant(APPLICATION_TYPE, WILDCARD);
|
||||||
|
public static final MediaType ANY_FONT_TYPE = createConstant(FONT_TYPE, WILDCARD);
|
||||||
|
|
||||||
/* text types */
|
/* text types */
|
||||||
public static final MediaType CACHE_MANIFEST_UTF_8 =
|
public static final MediaType CACHE_MANIFEST_UTF_8 =
|
||||||
|
@ -695,6 +697,60 @@ public final class MediaType {
|
||||||
|
|
||||||
public static final MediaType ZIP = createConstant(APPLICATION_TYPE, "zip");
|
public static final MediaType ZIP = createConstant(APPLICATION_TYPE, "zip");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A collection of font outlines as defined by <a href="https://tools.ietf.org/html/rfc8081">RFC
|
||||||
|
* 8081</a>.
|
||||||
|
*
|
||||||
|
* @since 30.0
|
||||||
|
*/
|
||||||
|
public static final MediaType FONT_COLLECTION = createConstant(FONT_TYPE, "collection");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <a href="https://en.wikipedia.org/wiki/OpenType">Open Type Font Format</a> (OTF) as defined by
|
||||||
|
* <a href="https://tools.ietf.org/html/rfc8081">RFC 8081</a>.
|
||||||
|
*
|
||||||
|
* @since 30.0
|
||||||
|
*/
|
||||||
|
public static final MediaType FONT_OTF = createConstant(FONT_TYPE, "otf");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <a href="https://en.wikipedia.org/wiki/SFNT">Spline or Scalable Font Format</a> (SFNT). <a
|
||||||
|
* href="https://tools.ietf.org/html/rfc8081">RFC 8081</a> declares this to be the correct media
|
||||||
|
* type for SFNT, but {@link #SFNT application/font-sfnt} may be necessary in certain situations
|
||||||
|
* for compatibility.
|
||||||
|
*
|
||||||
|
* @since 30.0
|
||||||
|
*/
|
||||||
|
public static final MediaType FONT_SFNT = createConstant(FONT_TYPE, "sfnt");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <a href="https://en.wikipedia.org/wiki/TrueType">True Type Font Format</a> (TTF) as defined by
|
||||||
|
* <a href="https://tools.ietf.org/html/rfc8081">RFC 8081</a>.
|
||||||
|
*
|
||||||
|
* @since 30.0
|
||||||
|
*/
|
||||||
|
public static final MediaType FONT_TTF = createConstant(FONT_TYPE, "ttf");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <a href="http://en.wikipedia.org/wiki/Web_Open_Font_Format">Web Open Font Format</a> (WOFF). <a
|
||||||
|
* href="https://tools.ietf.org/html/rfc8081">RFC 8081</a> declares this to be the correct media
|
||||||
|
* type for SFNT, but {@link #WOFF application/font-woff} may be necessary in certain situations
|
||||||
|
* for compatibility.
|
||||||
|
*
|
||||||
|
* @since 30.0
|
||||||
|
*/
|
||||||
|
public static final MediaType FONT_WOFF = createConstant(FONT_TYPE, "woff");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <a href="http://en.wikipedia.org/wiki/Web_Open_Font_Format">Web Open Font Format</a> (WOFF2).
|
||||||
|
* <a href="https://tools.ietf.org/html/rfc8081">RFC 8081</a> declares this to be the correct
|
||||||
|
* media type for SFNT, but {@link #WOFF2 application/font-woff2} may be necessary in certain
|
||||||
|
* situations for compatibility.
|
||||||
|
*
|
||||||
|
* @since 30.0
|
||||||
|
*/
|
||||||
|
public static final MediaType FONT_WOFF2 = createConstant(FONT_TYPE, "woff2");
|
||||||
|
|
||||||
private final String type;
|
private final String type;
|
||||||
private final String subtype;
|
private final String subtype;
|
||||||
private final ImmutableListMultimap<String, String> parameters;
|
private final ImmutableListMultimap<String, String> parameters;
|
||||||
|
@ -931,6 +987,15 @@ public final class MediaType {
|
||||||
return create(AUDIO_TYPE, subtype);
|
return create(AUDIO_TYPE, subtype);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a media type with the "font" type and the given subtype.
|
||||||
|
*
|
||||||
|
* @throws IllegalArgumentException if subtype is invalid
|
||||||
|
*/
|
||||||
|
static MediaType createFontType(String subtype) {
|
||||||
|
return create(FONT_TYPE, subtype);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a media type with the "image" type and the given subtype.
|
* Creates a media type with the "image" type and the given subtype.
|
||||||
*
|
*
|
||||||
|
|
|
@ -18,6 +18,7 @@ import static com.google.common.base.Preconditions.checkArgument;
|
||||||
import static com.google.common.base.Preconditions.checkElementIndex;
|
import static com.google.common.base.Preconditions.checkElementIndex;
|
||||||
import static com.google.common.base.Preconditions.checkNotNull;
|
import static com.google.common.base.Preconditions.checkNotNull;
|
||||||
import static com.google.common.base.Preconditions.checkPositionIndexes;
|
import static com.google.common.base.Preconditions.checkPositionIndexes;
|
||||||
|
import static com.google.common.base.Strings.lenientFormat;
|
||||||
import static java.lang.Double.NEGATIVE_INFINITY;
|
import static java.lang.Double.NEGATIVE_INFINITY;
|
||||||
import static java.lang.Double.POSITIVE_INFINITY;
|
import static java.lang.Double.POSITIVE_INFINITY;
|
||||||
|
|
||||||
|
@ -251,9 +252,14 @@ public final class Doubles {
|
||||||
*/
|
*/
|
||||||
@Beta
|
@Beta
|
||||||
public static double constrainToRange(double value, double min, double max) {
|
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);
|
// avoid auto-boxing by not using Preconditions.checkArgument(); see Guava issue 3984
|
||||||
|
// Reject NaN by testing for the good case (min <= max) instead of the bad (min > max).
|
||||||
|
if (min <= max) {
|
||||||
return Math.min(Math.max(value, min), max);
|
return Math.min(Math.max(value, min), max);
|
||||||
}
|
}
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
lenientFormat("min (%s) must be less than or equal to max (%s)", min, max));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the values from each provided array combined into a single array. For example, {@code
|
* Returns the values from each provided array combined into a single array. For example, {@code
|
||||||
|
|
|
@ -18,6 +18,7 @@ import static com.google.common.base.Preconditions.checkArgument;
|
||||||
import static com.google.common.base.Preconditions.checkElementIndex;
|
import static com.google.common.base.Preconditions.checkElementIndex;
|
||||||
import static com.google.common.base.Preconditions.checkNotNull;
|
import static com.google.common.base.Preconditions.checkNotNull;
|
||||||
import static com.google.common.base.Preconditions.checkPositionIndexes;
|
import static com.google.common.base.Preconditions.checkPositionIndexes;
|
||||||
|
import static com.google.common.base.Strings.lenientFormat;
|
||||||
import static java.lang.Float.NEGATIVE_INFINITY;
|
import static java.lang.Float.NEGATIVE_INFINITY;
|
||||||
import static java.lang.Float.POSITIVE_INFINITY;
|
import static java.lang.Float.POSITIVE_INFINITY;
|
||||||
|
|
||||||
|
@ -246,9 +247,14 @@ public final class Floats {
|
||||||
*/
|
*/
|
||||||
@Beta
|
@Beta
|
||||||
public static float constrainToRange(float value, float min, float max) {
|
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);
|
// avoid auto-boxing by not using Preconditions.checkArgument(); see Guava issue 3984
|
||||||
|
// Reject NaN by testing for the good case (min <= max) instead of the bad (min > max).
|
||||||
|
if (min <= max) {
|
||||||
return Math.min(Math.max(value, min), max);
|
return Math.min(Math.max(value, min), max);
|
||||||
}
|
}
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
lenientFormat("min (%s) must be less than or equal to max (%s)", min, max));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the values from each provided array combined into a single array. For example, {@code
|
* Returns the values from each provided array combined into a single array. For example, {@code
|
||||||
|
|
|
@ -30,9 +30,6 @@ import com.google.common.collect.ImmutableList;
|
||||||
import com.google.common.collect.ImmutableMap;
|
import com.google.common.collect.ImmutableMap;
|
||||||
import com.google.common.collect.ImmutableSet;
|
import com.google.common.collect.ImmutableSet;
|
||||||
import com.google.common.collect.Maps;
|
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.ByteSource;
|
||||||
import com.google.common.io.CharSource;
|
import com.google.common.io.CharSource;
|
||||||
import com.google.common.io.Resources;
|
import com.google.common.io.Resources;
|
||||||
|
@ -46,7 +43,7 @@ import java.nio.charset.Charset;
|
||||||
import java.util.Enumeration;
|
import java.util.Enumeration;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.LinkedHashMap;
|
import java.util.LinkedHashMap;
|
||||||
import java.util.Map.Entry;
|
import java.util.Map;
|
||||||
import java.util.NoSuchElementException;
|
import java.util.NoSuchElementException;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.jar.Attributes;
|
import java.util.jar.Attributes;
|
||||||
|
@ -78,14 +75,6 @@ import java.util.logging.Logger;
|
||||||
public final class ClassPath {
|
public final class ClassPath {
|
||||||
private static final Logger logger = Logger.getLogger(ClassPath.class.getName());
|
private static final Logger logger = Logger.getLogger(ClassPath.class.getName());
|
||||||
|
|
||||||
private static final Predicate<ClassInfo> IS_TOP_LEVEL =
|
|
||||||
new Predicate<ClassInfo>() {
|
|
||||||
@Override
|
|
||||||
public boolean apply(ClassInfo info) {
|
|
||||||
return info.className.indexOf('$') == -1;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/** Separator for the Class-Path manifest attribute value in jar files. */
|
/** Separator for the Class-Path manifest attribute value in jar files. */
|
||||||
private static final Splitter CLASS_PATH_ATTRIBUTE_SEPARATOR =
|
private static final Splitter CLASS_PATH_ATTRIBUTE_SEPARATOR =
|
||||||
Splitter.on(" ").omitEmptyStrings();
|
Splitter.on(" ").omitEmptyStrings();
|
||||||
|
@ -115,9 +104,21 @@ public final class ClassPath {
|
||||||
* failed.
|
* failed.
|
||||||
*/
|
*/
|
||||||
public static ClassPath from(ClassLoader classloader) throws IOException {
|
public static ClassPath from(ClassLoader classloader) throws IOException {
|
||||||
DefaultScanner scanner = new DefaultScanner();
|
ImmutableSet<LocationInfo> locations = locationsFrom(classloader);
|
||||||
scanner.scan(classloader);
|
|
||||||
return new ClassPath(scanner.getResources());
|
// Add all locations to the scanned set so that in a classpath [jar1, jar2], where jar1 has a
|
||||||
|
// manifest with Class-Path pointing to jar2, we won't scan jar2 twice.
|
||||||
|
Set<File> scanned = new HashSet<>();
|
||||||
|
for (LocationInfo location : locations) {
|
||||||
|
scanned.add(location.file());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Scan all locations
|
||||||
|
ImmutableSet.Builder<ResourceInfo> builder = ImmutableSet.builder();
|
||||||
|
for (LocationInfo location : locations) {
|
||||||
|
builder.addAll(location.scanResources(scanned));
|
||||||
|
}
|
||||||
|
return new ClassPath(builder.build());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -137,9 +138,20 @@ public final class ClassPath {
|
||||||
return FluentIterable.from(resources).filter(ClassInfo.class).toSet();
|
return FluentIterable.from(resources).filter(ClassInfo.class).toSet();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Returns all top level classes loadable from the current class path. */
|
/**
|
||||||
public ImmutableSet<ClassInfo> getTopLevelClasses() {
|
* Returns all top level classes loadable from the current class path. Note that "top-level-ness"
|
||||||
return FluentIterable.from(resources).filter(ClassInfo.class).filter(IS_TOP_LEVEL).toSet();
|
* is determined heuristically by class name (see {@link ClassInfo#isTopLevel}).
|
||||||
|
*/ public ImmutableSet<ClassInfo> getTopLevelClasses() {
|
||||||
|
return FluentIterable.from(resources)
|
||||||
|
.filter(ClassInfo.class)
|
||||||
|
.filter(
|
||||||
|
new Predicate<ClassInfo>() {
|
||||||
|
@Override
|
||||||
|
public boolean apply(ClassInfo info) {
|
||||||
|
return info.isTopLevel();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.toSet();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Returns all top level classes whose package name is {@code packageName}. */
|
/** Returns all top level classes whose package name is {@code packageName}. */
|
||||||
|
@ -178,19 +190,21 @@ public final class ClassPath {
|
||||||
*/
|
*/
|
||||||
@Beta
|
@Beta
|
||||||
public static class ResourceInfo {
|
public static class ResourceInfo {
|
||||||
|
private final File file;
|
||||||
private final String resourceName;
|
private final String resourceName;
|
||||||
|
|
||||||
final ClassLoader loader;
|
final ClassLoader loader;
|
||||||
|
|
||||||
static ResourceInfo of(String resourceName, ClassLoader loader) {
|
static ResourceInfo of(File file, String resourceName, ClassLoader loader) {
|
||||||
if (resourceName.endsWith(CLASS_FILE_NAME_EXTENSION)) {
|
if (resourceName.endsWith(CLASS_FILE_NAME_EXTENSION)) {
|
||||||
return new ClassInfo(resourceName, loader);
|
return new ClassInfo(file, resourceName, loader);
|
||||||
} else {
|
} else {
|
||||||
return new ResourceInfo(resourceName, loader);
|
return new ResourceInfo(file, resourceName, loader);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ResourceInfo(String resourceName, ClassLoader loader) {
|
ResourceInfo(File file, String resourceName, ClassLoader loader) {
|
||||||
|
this.file = checkNotNull(file);
|
||||||
this.resourceName = checkNotNull(resourceName);
|
this.resourceName = checkNotNull(resourceName);
|
||||||
this.loader = checkNotNull(loader);
|
this.loader = checkNotNull(loader);
|
||||||
}
|
}
|
||||||
|
@ -239,6 +253,11 @@ public final class ClassPath {
|
||||||
return resourceName;
|
return resourceName;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Returns the file that includes this resource. */
|
||||||
|
final File getFile() {
|
||||||
|
return file;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int hashCode() {
|
public int hashCode() {
|
||||||
return resourceName.hashCode();
|
return resourceName.hashCode();
|
||||||
|
@ -269,8 +288,8 @@ public final class ClassPath {
|
||||||
public static final class ClassInfo extends ResourceInfo {
|
public static final class ClassInfo extends ResourceInfo {
|
||||||
private final String className;
|
private final String className;
|
||||||
|
|
||||||
ClassInfo(String resourceName, ClassLoader loader) {
|
ClassInfo(File file, String resourceName, ClassLoader loader) {
|
||||||
super(resourceName, loader);
|
super(file, resourceName, loader);
|
||||||
this.className = getClassName(resourceName);
|
this.className = getClassName(resourceName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -317,6 +336,18 @@ public final class ClassPath {
|
||||||
return className;
|
return className;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if the class name "looks to be" top level (not nested), that is, it includes no
|
||||||
|
* '$' in the name. This method may return false for a top-level class that's intentionally
|
||||||
|
* named with the '$' character. If this is a concern, you could use {@link #load} and then
|
||||||
|
* check on the loaded {@link Class} object instead.
|
||||||
|
*
|
||||||
|
* @since 30.1
|
||||||
|
*/
|
||||||
|
public boolean isTopLevel() {
|
||||||
|
return className.indexOf('$') == -1;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Loads (but doesn't link or initialize) the class.
|
* Loads (but doesn't link or initialize) the class.
|
||||||
*
|
*
|
||||||
|
@ -338,37 +369,66 @@ public final class ClassPath {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 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
|
* Returns all locations that {@code classloader} and parent loaders load classes and resources
|
||||||
* respectively.
|
* from. Callers can {@linkplain LocationInfo#scanResources scan} individual locations selectively
|
||||||
|
* or even in parallel.
|
||||||
*/
|
*/
|
||||||
abstract static class Scanner {
|
static ImmutableSet<LocationInfo> locationsFrom(ClassLoader classloader) {
|
||||||
|
ImmutableSet.Builder<LocationInfo> builder = ImmutableSet.builder();
|
||||||
// We only scan each file once independent of the classloader that resource might be associated
|
for (Map.Entry<File, ClassLoader> entry : getClassPathEntries(classloader).entrySet()) {
|
||||||
// with.
|
builder.add(new LocationInfo(entry.getKey(), entry.getValue()));
|
||||||
private final Set<File> scannedUris = Sets.newHashSet();
|
|
||||||
|
|
||||||
public final void scan(ClassLoader classloader) throws IOException {
|
|
||||||
for (Entry<File, ClassLoader> entry : getClassPathEntries(classloader).entrySet()) {
|
|
||||||
scan(entry.getKey(), entry.getValue());
|
|
||||||
}
|
}
|
||||||
|
return builder.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
@VisibleForTesting
|
/**
|
||||||
final void scan(File file, ClassLoader classloader) throws IOException {
|
* Represents a single location (a directory or a jar file) in the class path and is responsible
|
||||||
if (scannedUris.add(file.getCanonicalFile())) {
|
* for scanning resources from this location.
|
||||||
scanFrom(file, classloader);
|
*/
|
||||||
}
|
static final class LocationInfo {
|
||||||
|
final File home;
|
||||||
|
private final ClassLoader classloader;
|
||||||
|
|
||||||
|
LocationInfo(File home, ClassLoader classloader) {
|
||||||
|
this.home = checkNotNull(home);
|
||||||
|
this.classloader = checkNotNull(classloader);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Called when a directory is scanned for resource files. */
|
/** Returns the file this location is from. */
|
||||||
protected abstract void scanDirectory(ClassLoader loader, File directory) throws IOException;
|
public final File file() {
|
||||||
|
return home;
|
||||||
|
}
|
||||||
|
|
||||||
/** Called when a jar file is scanned for resource entries. */
|
/** Scans this location and returns all scanned resources. */
|
||||||
protected abstract void scanJarFile(ClassLoader loader, JarFile file) throws IOException;
|
public ImmutableSet<ResourceInfo> scanResources() throws IOException {
|
||||||
|
return scanResources(new HashSet<File>());
|
||||||
|
}
|
||||||
|
|
||||||
private void scanFrom(File file, ClassLoader classloader) throws IOException {
|
/**
|
||||||
|
* Scans this location and returns all scanned resources.
|
||||||
|
*
|
||||||
|
* <p>This file and jar files from "Class-Path" entry in the scanned manifest files will be
|
||||||
|
* added to {@code scannedFiles}.
|
||||||
|
*
|
||||||
|
* <p>A file will be scanned at most once even if specified multiple times by one or multiple
|
||||||
|
* jar files' "Class-Path" manifest entries. Particularly, if a jar file from the "Class-Path"
|
||||||
|
* manifest entry is already in {@code scannedFiles}, either because it was scanned earlier, or
|
||||||
|
* it was intentionally added to the set by the caller, it will not be scanned again.
|
||||||
|
*
|
||||||
|
* <p>Note that when you call {@code location.scanResources(scannedFiles)}, the location will
|
||||||
|
* always be scanned even if {@code scannedFiles} already contains it.
|
||||||
|
*/
|
||||||
|
public ImmutableSet<ResourceInfo> scanResources(Set<File> scannedFiles) throws IOException {
|
||||||
|
ImmutableSet.Builder<ResourceInfo> builder = ImmutableSet.builder();
|
||||||
|
scannedFiles.add(home);
|
||||||
|
scan(home, scannedFiles, builder);
|
||||||
|
return builder.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void scan(File file, Set<File> scannedUris, ImmutableSet.Builder<ResourceInfo> builder)
|
||||||
|
throws IOException {
|
||||||
try {
|
try {
|
||||||
if (!file.exists()) {
|
if (!file.exists()) {
|
||||||
return;
|
return;
|
||||||
|
@ -379,13 +439,15 @@ public final class ClassPath {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (file.isDirectory()) {
|
if (file.isDirectory()) {
|
||||||
scanDirectory(classloader, file);
|
scanDirectory(file, builder);
|
||||||
} else {
|
} else {
|
||||||
scanJar(file, classloader);
|
scanJar(file, scannedUris, builder);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void scanJar(File file, ClassLoader classloader) throws IOException {
|
private void scanJar(
|
||||||
|
File file, Set<File> scannedUris, ImmutableSet.Builder<ResourceInfo> builder)
|
||||||
|
throws IOException {
|
||||||
JarFile jarFile;
|
JarFile jarFile;
|
||||||
try {
|
try {
|
||||||
jarFile = new JarFile(file);
|
jarFile = new JarFile(file);
|
||||||
|
@ -395,23 +457,106 @@ public final class ClassPath {
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
for (File path : getClassPathFromManifest(file, jarFile.getManifest())) {
|
for (File path : getClassPathFromManifest(file, jarFile.getManifest())) {
|
||||||
scan(path, classloader);
|
// We only scan each file once independent of the classloader that file might be
|
||||||
|
// associated with.
|
||||||
|
if (scannedUris.add(path.getCanonicalFile())) {
|
||||||
|
scan(path, scannedUris, builder);
|
||||||
}
|
}
|
||||||
scanJarFile(classloader, jarFile);
|
}
|
||||||
|
scanJarFile(jarFile, builder);
|
||||||
} finally {
|
} finally {
|
||||||
try {
|
try {
|
||||||
jarFile.close();
|
jarFile.close();
|
||||||
} catch (IOException ignored) {
|
} catch (IOException ignored) { // similar to try-with-resources, but don't fail scanning
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void scanJarFile(JarFile file, ImmutableSet.Builder<ResourceInfo> builder) {
|
||||||
|
Enumeration<JarEntry> entries = file.entries();
|
||||||
|
while (entries.hasMoreElements()) {
|
||||||
|
JarEntry entry = entries.nextElement();
|
||||||
|
if (entry.isDirectory() || entry.getName().equals(JarFile.MANIFEST_NAME)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
builder.add(ResourceInfo.of(new File(file.getName()), entry.getName(), classloader));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void scanDirectory(File directory, ImmutableSet.Builder<ResourceInfo> builder)
|
||||||
|
throws IOException {
|
||||||
|
Set<File> currentPath = new HashSet<>();
|
||||||
|
currentPath.add(directory.getCanonicalFile());
|
||||||
|
scanDirectory(directory, "", currentPath, builder);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 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,
|
||||||
|
String packagePrefix,
|
||||||
|
Set<File> currentPath,
|
||||||
|
ImmutableSet.Builder<ResourceInfo> builder)
|
||||||
|
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, packagePrefix + name + "/", currentPath, builder);
|
||||||
|
currentPath.remove(deref);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
String resourceName = packagePrefix + name;
|
||||||
|
if (!resourceName.equals(JarFile.MANIFEST_NAME)) {
|
||||||
|
builder.add(ResourceInfo.of(f, resourceName, classloader));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object obj) {
|
||||||
|
if (obj instanceof LocationInfo) {
|
||||||
|
LocationInfo that = (LocationInfo) obj;
|
||||||
|
return home.equals(that.home) && classloader.equals(that.classloader);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return home.hashCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return home.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the class path URIs specified by the {@code Class-Path} manifest attribute, according
|
* Returns the class path URIs specified by the {@code Class-Path} manifest attribute, according
|
||||||
* to <a
|
* to <a
|
||||||
* href="http://docs.oracle.com/javase/8/docs/technotes/guides/jar/jar.html#Main_Attributes">JAR
|
* href="http://docs.oracle.com/javase/8/docs/technotes/guides/jar/jar.html#Main_Attributes">JAR
|
||||||
* File Specification</a>. If {@code manifest} is null, it means the jar file has no manifest,
|
* File Specification</a>. If {@code manifest} is null, it means the jar file has no manifest, and
|
||||||
* and an empty set will be returned.
|
* an empty set will be returned.
|
||||||
*/
|
*/
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
static ImmutableSet<File> getClassPathFromManifest(File jarFile, Manifest manifest) {
|
static ImmutableSet<File> getClassPathFromManifest(File jarFile, Manifest manifest) {
|
||||||
|
@ -468,6 +613,7 @@ public final class ClassPath {
|
||||||
return ImmutableList.of();
|
return ImmutableList.of();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the URLs in the class path specified by the {@code java.class.path} {@linkplain
|
* Returns the URLs in the class path specified by the {@code java.class.path} {@linkplain
|
||||||
* System#getProperty system property}.
|
* System#getProperty system property}.
|
||||||
|
@ -492,85 +638,13 @@ public final class ClassPath {
|
||||||
/**
|
/**
|
||||||
* Returns the absolute uri of the Class-Path entry value as specified in <a
|
* Returns the absolute uri of the Class-Path entry value as specified in <a
|
||||||
* href="http://docs.oracle.com/javase/8/docs/technotes/guides/jar/jar.html#Main_Attributes">JAR
|
* href="http://docs.oracle.com/javase/8/docs/technotes/guides/jar/jar.html#Main_Attributes">JAR
|
||||||
* File Specification</a>. Even though the specification only talks about relative urls,
|
* File Specification</a>. Even though the specification only talks about relative urls, absolute
|
||||||
* absolute urls are actually supported too (for example, in Maven surefire plugin).
|
* urls are actually supported too (for example, in Maven surefire plugin).
|
||||||
*/
|
*/
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
static URL getClassPathEntry(File jarFile, String path) throws MalformedURLException {
|
static URL getClassPathEntry(File jarFile, String path) throws MalformedURLException {
|
||||||
return new URL(jarFile.toURI().toURL(), path);
|
return new URL(jarFile.toURI().toURL(), path);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
@VisibleForTesting
|
|
||||||
static final class DefaultScanner extends Scanner {
|
|
||||||
private final SetMultimap<ClassLoader, String> resources =
|
|
||||||
MultimapBuilder.hashKeys().linkedHashSetValues().build();
|
|
||||||
|
|
||||||
ImmutableSet<ResourceInfo> getResources() {
|
|
||||||
ImmutableSet.Builder<ResourceInfo> builder = ImmutableSet.builder();
|
|
||||||
for (Entry<ClassLoader, String> entry : resources.entries()) {
|
|
||||||
builder.add(ResourceInfo.of(entry.getValue(), entry.getKey()));
|
|
||||||
}
|
|
||||||
return builder.build();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void scanJarFile(ClassLoader classloader, JarFile file) {
|
|
||||||
Enumeration<JarEntry> 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<File> 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<File> 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
|
@VisibleForTesting
|
||||||
static String getClassName(String filename) {
|
static String getClassName(String filename) {
|
||||||
|
|
|
@ -33,6 +33,8 @@ import java.util.logging.Level;
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
import static com.google.common.base.Preconditions.checkNotNull;
|
import static com.google.common.base.Preconditions.checkNotNull;
|
||||||
|
import static java.lang.Integer.toHexString;
|
||||||
|
import static java.lang.System.identityHashCode;
|
||||||
import static java.util.concurrent.atomic.AtomicReferenceFieldUpdater.newUpdater;
|
import static java.util.concurrent.atomic.AtomicReferenceFieldUpdater.newUpdater;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -396,7 +398,7 @@ public abstract class AbstractFuture<V> extends InternalFutureFailureAccess
|
||||||
node.setNext(oldHead);
|
node.setNext(oldHead);
|
||||||
if (ATOMIC_HELPER.casWaiters(this, oldHead, node)) {
|
if (ATOMIC_HELPER.casWaiters(this, oldHead, node)) {
|
||||||
while (true) {
|
while (true) {
|
||||||
LockSupport.parkNanos(this, remainingNanos);
|
OverflowAvoidingLockSupport.parkNanos(this, remainingNanos);
|
||||||
// Check interruption first, if we woke up due to interruption we need to honor that.
|
// Check interruption first, if we woke up due to interruption we need to honor that.
|
||||||
if (Thread.interrupted()) {
|
if (Thread.interrupted()) {
|
||||||
removeWaiter(node);
|
removeWaiter(node);
|
||||||
|
@ -561,6 +563,8 @@ public abstract class AbstractFuture<V> extends InternalFutureFailureAccess
|
||||||
* #wasInterrupted} as necessary. This ensures that the work is done even if the future is
|
* #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
|
* cancelled without a call to {@code cancel}, such as by calling {@code
|
||||||
* setFuture(cancelledFuture)}.
|
* setFuture(cancelledFuture)}.
|
||||||
|
* <p>Beware of completing a future while holding a lock. Its listeners may do slow work or
|
||||||
|
* acquire other locks, risking deadlocks.
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public boolean cancel(boolean mayInterruptIfRunning) {
|
public boolean cancel(boolean mayInterruptIfRunning) {
|
||||||
|
@ -580,7 +584,7 @@ public abstract class AbstractFuture<V> extends InternalFutureFailureAccess
|
||||||
while (true) {
|
while (true) {
|
||||||
if (ATOMIC_HELPER.casValue(abstractFuture, localValue, valueToSet)) {
|
if (ATOMIC_HELPER.casValue(abstractFuture, localValue, valueToSet)) {
|
||||||
rValue = true;
|
rValue = true;
|
||||||
// We call interuptTask before calling complete(), which is consistent with
|
// We call interruptTask before calling complete(), which is consistent with
|
||||||
// FutureTask
|
// FutureTask
|
||||||
if (mayInterruptIfRunning) {
|
if (mayInterruptIfRunning) {
|
||||||
abstractFuture.interruptTask();
|
abstractFuture.interruptTask();
|
||||||
|
@ -714,6 +718,9 @@ public abstract class AbstractFuture<V> extends InternalFutureFailureAccess
|
||||||
* known yet. That result, though not yet known, cannot be overridden by a call to a {@code set*}
|
* 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}.
|
* method, only by a call to {@link #cancel}.
|
||||||
*
|
*
|
||||||
|
* <p>Beware of completing a future while holding a lock. Its listeners may do slow work or
|
||||||
|
* acquire other locks, risking deadlocks.
|
||||||
|
*
|
||||||
* @param throwable the exception to be used as the failed result
|
* @param throwable the exception to be used as the failed result
|
||||||
* @return true if the attempt was accepted, completing the {@code Future}
|
* @return true if the attempt was accepted, completing the {@code Future}
|
||||||
*/
|
*/
|
||||||
|
@ -747,6 +754,9 @@ public abstract class AbstractFuture<V> extends InternalFutureFailureAccess
|
||||||
* invoke the {@link #interruptTask} method, and the {@link #wasInterrupted} method will not
|
* invoke the {@link #interruptTask} method, and the {@link #wasInterrupted} method will not
|
||||||
* return {@code true}.
|
* return {@code true}.
|
||||||
*
|
*
|
||||||
|
* <p>Beware of completing a future while holding a lock. Its listeners may do slow work or
|
||||||
|
* acquire other locks, risking deadlocks.
|
||||||
|
*
|
||||||
* @param future the future to delegate to
|
* @param future the future to delegate to
|
||||||
* @return true if the attempt was accepted, indicating that the {@code Future} was not previously
|
* @return true if the attempt was accepted, indicating that the {@code Future} was not previously
|
||||||
* cancelled or set.
|
* cancelled or set.
|
||||||
|
@ -1031,7 +1041,14 @@ public abstract class AbstractFuture<V> extends InternalFutureFailureAccess
|
||||||
// TODO(user): move parts into a default method on ListenableFuture?
|
// TODO(user): move parts into a default method on ListenableFuture?
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
StringBuilder builder = new StringBuilder().append(super.toString()).append("[status=");
|
// TODO(cpovirk): Presize to something plausible?
|
||||||
|
StringBuilder builder = new StringBuilder();
|
||||||
|
if (getClass().getName().startsWith("com.google.common.util.concurrent.")) {
|
||||||
|
builder.append(getClass().getSimpleName());
|
||||||
|
} else {
|
||||||
|
builder.append(getClass().getName());
|
||||||
|
}
|
||||||
|
builder.append('@').append(toHexString(identityHashCode(this))).append("[status=");
|
||||||
if (isCancelled()) {
|
if (isCancelled()) {
|
||||||
builder.append("CANCELLED");
|
builder.append("CANCELLED");
|
||||||
} else if (isDone()) {
|
} else if (isDone()) {
|
||||||
|
@ -1079,7 +1096,8 @@ public abstract class AbstractFuture<V> extends InternalFutureFailureAccess
|
||||||
private void addDoneString(StringBuilder builder) {
|
private void addDoneString(StringBuilder builder) {
|
||||||
try {
|
try {
|
||||||
V value = getUninterruptibly(this);
|
V value = getUninterruptibly(this);
|
||||||
builder.append("SUCCESS, result=[").append(userObjectToString(value)).append("]");
|
builder.append("SUCCESS, result=[");
|
||||||
|
appendResultObject(builder, value);
|
||||||
} catch (ExecutionException e) {
|
} catch (ExecutionException e) {
|
||||||
builder.append("FAILURE, cause=[").append(e.getCause()).append("]");
|
builder.append("FAILURE, cause=[").append(e.getCause()).append("]");
|
||||||
} catch (CancellationException e) {
|
} catch (CancellationException e) {
|
||||||
|
@ -1089,6 +1107,24 @@ public abstract class AbstractFuture<V> extends InternalFutureFailureAccess
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Any object can be the result of a Future, and not every object has a reasonable toString()
|
||||||
|
* implementation. Using a reconstruction of the default Object.toString() prevents OOMs and stack
|
||||||
|
* overflows, and helps avoid sensitive data inadvertently ending up in exception messages.
|
||||||
|
*/
|
||||||
|
private void appendResultObject(StringBuilder builder, Object o) {
|
||||||
|
if (o == null) {
|
||||||
|
builder.append("null");
|
||||||
|
} else if (o == this) {
|
||||||
|
builder.append("this future");
|
||||||
|
} else {
|
||||||
|
builder
|
||||||
|
.append(o.getClass().getName())
|
||||||
|
.append("@")
|
||||||
|
.append(Integer.toHexString(System.identityHashCode(o)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/** Helper for printing user supplied objects into our toString method. */
|
/** Helper for printing user supplied objects into our toString method. */
|
||||||
private String userObjectToString(Object o) {
|
private String userObjectToString(Object o) {
|
||||||
// This is some basic recursion detection for when people create cycles via set/setFuture
|
// This is some basic recursion detection for when people create cycles via set/setFuture
|
||||||
|
|
2245
src/main/java/com/google/common/util/concurrent/ClosingFuture.java
Normal file
2245
src/main/java/com/google/common/util/concurrent/ClosingFuture.java
Normal file
File diff suppressed because it is too large
Load diff
|
@ -89,7 +89,6 @@ final class CombinedFuture<V> extends AggregateFuture<Object, V> {
|
||||||
|
|
||||||
private abstract class CombinedFutureInterruptibleTask<T> extends InterruptibleTask<T> {
|
private abstract class CombinedFutureInterruptibleTask<T> extends InterruptibleTask<T> {
|
||||||
private final Executor listenerExecutor;
|
private final Executor listenerExecutor;
|
||||||
boolean thrownByExecute = true;
|
|
||||||
|
|
||||||
CombinedFutureInterruptibleTask(Executor listenerExecutor) {
|
CombinedFutureInterruptibleTask(Executor listenerExecutor) {
|
||||||
this.listenerExecutor = checkNotNull(listenerExecutor);
|
this.listenerExecutor = checkNotNull(listenerExecutor);
|
||||||
|
@ -104,11 +103,9 @@ final class CombinedFuture<V> extends AggregateFuture<Object, V> {
|
||||||
try {
|
try {
|
||||||
listenerExecutor.execute(this);
|
listenerExecutor.execute(this);
|
||||||
} catch (RejectedExecutionException e) {
|
} catch (RejectedExecutionException e) {
|
||||||
if (thrownByExecute) {
|
|
||||||
CombinedFuture.this.setException(e);
|
CombinedFuture.this.setException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
final void afterRanInterruptibly(T result, Throwable error) {
|
final void afterRanInterruptibly(T result, Throwable error) {
|
||||||
|
@ -153,7 +150,6 @@ final class CombinedFuture<V> extends AggregateFuture<Object, V> {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
ListenableFuture<V> runInterruptibly() throws Exception {
|
ListenableFuture<V> runInterruptibly() throws Exception {
|
||||||
thrownByExecute = false;
|
|
||||||
ListenableFuture<V> result = callable.call();
|
ListenableFuture<V> result = callable.call();
|
||||||
return checkNotNull(
|
return checkNotNull(
|
||||||
result,
|
result,
|
||||||
|
@ -184,7 +180,6 @@ final class CombinedFuture<V> extends AggregateFuture<Object, V> {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
V runInterruptibly() throws Exception {
|
V runInterruptibly() throws Exception {
|
||||||
thrownByExecute = false;
|
|
||||||
return callable.call();
|
return callable.call();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -15,11 +15,13 @@
|
||||||
package com.google.common.util.concurrent;
|
package com.google.common.util.concurrent;
|
||||||
|
|
||||||
import static com.google.common.base.Preconditions.checkNotNull;
|
import static com.google.common.base.Preconditions.checkNotNull;
|
||||||
|
import static com.google.common.base.Preconditions.checkState;
|
||||||
import static com.google.common.util.concurrent.ExecutionSequencer.RunningState.CANCELLED;
|
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.NOT_RUN;
|
||||||
import static com.google.common.util.concurrent.ExecutionSequencer.RunningState.STARTED;
|
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.immediateCancelledFuture;
|
||||||
import static com.google.common.util.concurrent.Futures.immediateFuture;
|
import static com.google.common.util.concurrent.Futures.immediateFuture;
|
||||||
|
import static com.google.common.util.concurrent.Futures.immediateVoidFuture;
|
||||||
import static com.google.common.util.concurrent.MoreExecutors.directExecutor;
|
import static com.google.common.util.concurrent.MoreExecutors.directExecutor;
|
||||||
|
|
||||||
import com.google.common.annotations.Beta;
|
import com.google.common.annotations.Beta;
|
||||||
|
@ -28,13 +30,50 @@ import java.util.concurrent.Executor;
|
||||||
import java.util.concurrent.atomic.AtomicReference;
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Serializes execution of a set of operations. This class guarantees that a submitted callable will
|
* Serializes execution of tasks, somewhat like an "asynchronous {@code synchronized} block." Each
|
||||||
* not be called before previously submitted callables (and any {@code Future}s returned from them)
|
* {@linkplain #submit enqueued} callable will not be submitted to its associated executor until the
|
||||||
* have completed.
|
* previous callable has returned -- and, if the previous callable was an {@link AsyncCallable}, not
|
||||||
|
* until the {@code Future} it returned is {@linkplain java.util.concurrent.Future#isDone done} (successful, failed, or
|
||||||
|
* cancelled).
|
||||||
*
|
*
|
||||||
* <p>This class implements a superset of the behavior of {@link
|
* <p>This class has limited support for cancellation and other "early completion":
|
||||||
* 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.
|
* <ul>
|
||||||
|
* <li>While calls to {@code submit} and {@code submitAsync} return a {@code Future} that can be
|
||||||
|
* cancelled, cancellation never propagates to a task that has started to run -- neither to
|
||||||
|
* the callable itself nor to any {@code Future} returned by an {@code AsyncCallable}.
|
||||||
|
* (However, cancellation can prevent an <i>unstarted</i> task from running.) Therefore, the
|
||||||
|
* next task will wait for any running callable (or pending {@code Future} returned by an
|
||||||
|
* {@code AsyncCallable}) to complete, without interrupting it (and without calling {@code
|
||||||
|
* cancel} on the {@code Future}). So beware: <i>Even if you cancel every precededing {@code
|
||||||
|
* Future} returned by this class, the next task may still have to wait.</i>.
|
||||||
|
* <li>Once an {@code AsyncCallable} returns a {@code Future}, this class considers that task to
|
||||||
|
* be "done" as soon as <i>that</i> {@code Future} completes in any way. Notably, a {@code
|
||||||
|
* Future} is "completed" even if it is cancelled while its underlying work continues on a
|
||||||
|
* thread, an RPC, etc. The {@code Future} is also "completed" if it fails "early" -- for
|
||||||
|
* example, if the deadline expires on a {@code Future} returned from {@link
|
||||||
|
* Futures#withTimeout} while the {@code Future} it wraps continues its underlying work. So
|
||||||
|
* beware: <i>Your {@code AsyncCallable} should not complete its {@code Future} until it is
|
||||||
|
* safe for the next task to start.</i>
|
||||||
|
* </ul>
|
||||||
|
*
|
||||||
|
* <p>An additional limitation: this class serializes execution of <i>tasks</i> but not any
|
||||||
|
* <i>listeners</i> of those tasks.
|
||||||
|
*
|
||||||
|
* <p>This class is similar to {@link MoreExecutors#newSequentialExecutor}. This class is different
|
||||||
|
* in a few ways:
|
||||||
|
*
|
||||||
|
* <ul>
|
||||||
|
* <li>Each task may be associated with a different executor.
|
||||||
|
* <li>Tasks may be of type {@code AsyncCallable}.
|
||||||
|
* <li>Running tasks <i>cannot</i> be interrupted. (Note that {@code newSequentialExecutor} does
|
||||||
|
* not return {@code Future} objects, so it doesn't support interruption directly, either.
|
||||||
|
* However, utilities that <i>use</i> that executor have the ability to interrupt tasks
|
||||||
|
* running on it. This class, by contrast, does not expose an {@code Executor} API.)
|
||||||
|
* </ul>
|
||||||
|
*
|
||||||
|
* <p>If you don't need the features of this class, you may prefer {@code newSequentialExecutor} for
|
||||||
|
* its simplicity and ability to accommodate interruption.
|
||||||
*
|
*
|
||||||
* @since 26.0
|
* @since 26.0
|
||||||
*/
|
*/
|
||||||
|
@ -48,15 +87,47 @@ public final class ExecutionSequencer {
|
||||||
return new ExecutionSequencer();
|
return new ExecutionSequencer();
|
||||||
}
|
}
|
||||||
|
|
||||||
enum RunningState {
|
|
||||||
NOT_RUN,
|
|
||||||
CANCELLED,
|
|
||||||
STARTED,
|
|
||||||
}
|
|
||||||
|
|
||||||
/** This reference acts as a pointer tracking the head of a linked list of ListenableFutures. */
|
/** This reference acts as a pointer tracking the head of a linked list of ListenableFutures. */
|
||||||
private final AtomicReference<ListenableFuture<Object>> ref =
|
private final AtomicReference<ListenableFuture<Void>> ref =
|
||||||
new AtomicReference<>(immediateFuture(null));
|
new AtomicReference<>(immediateVoidFuture());
|
||||||
|
|
||||||
|
private ThreadConfinedTaskQueue latestTaskQueue = new ThreadConfinedTaskQueue();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This object is unsafely published, but avoids problematic races by relying exclusively on the
|
||||||
|
* identity equality of its Thread field so that the task field is only accessed by a single
|
||||||
|
* thread.
|
||||||
|
*/
|
||||||
|
private static final class ThreadConfinedTaskQueue {
|
||||||
|
/**
|
||||||
|
* This field is only used for identity comparisons with the current thread. Field assignments
|
||||||
|
* are atomic, but do not provide happens-before ordering; however:
|
||||||
|
*
|
||||||
|
* <ul>
|
||||||
|
* <li>If this field's value == currentThread, we know that it's up to date, because write
|
||||||
|
* operations in a thread always happen-before subsequent read operations in the same
|
||||||
|
* thread
|
||||||
|
* <li>If this field's value == null because of unsafe publication, we know that it isn't the
|
||||||
|
* object associated with our thread, because if it was the publication wouldn't have been
|
||||||
|
* unsafe and we'd have seen our thread as the value. This state is also why a new
|
||||||
|
* ThreadConfinedTaskQueue object must be created for each inline execution, because
|
||||||
|
* observing a null thread does not mean the object is safe to reuse.
|
||||||
|
* <li>If this field's value is some other thread object, we know that it's not our thread.
|
||||||
|
* <li>If this field's value == null because it originally belonged to another thread and that
|
||||||
|
* thread cleared it, we still know that it's not associated with our thread
|
||||||
|
* <li>If this field's value == null because it was associated with our thread and was
|
||||||
|
* cleared, we know that we're not executing inline any more
|
||||||
|
* </ul>
|
||||||
|
*
|
||||||
|
* All the states where thread != currentThread are identical for our purposes, and so even
|
||||||
|
* though it's racy, we don't care which of those values we get, so no need to synchronize.
|
||||||
|
*/
|
||||||
|
Thread thread;
|
||||||
|
/** Only used by the thread associated with this object */
|
||||||
|
Runnable nextTask;
|
||||||
|
/** Only used by the thread associated with this object */
|
||||||
|
Executor nextExecutor;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Enqueues a task to run when the previous task (if any) completes.
|
* Enqueues a task to run when the previous task (if any) completes.
|
||||||
|
@ -67,6 +138,7 @@ public final class ExecutionSequencer {
|
||||||
*/
|
*/
|
||||||
public <T> ListenableFuture<T> submit(final Callable<T> callable, Executor executor) {
|
public <T> ListenableFuture<T> submit(final Callable<T> callable, Executor executor) {
|
||||||
checkNotNull(callable);
|
checkNotNull(callable);
|
||||||
|
checkNotNull(executor);
|
||||||
return submitAsync(
|
return submitAsync(
|
||||||
new AsyncCallable<T>() {
|
new AsyncCallable<T>() {
|
||||||
@Override
|
@Override
|
||||||
|
@ -92,12 +164,13 @@ public final class ExecutionSequencer {
|
||||||
public <T> ListenableFuture<T> submitAsync(
|
public <T> ListenableFuture<T> submitAsync(
|
||||||
final AsyncCallable<T> callable, final Executor executor) {
|
final AsyncCallable<T> callable, final Executor executor) {
|
||||||
checkNotNull(callable);
|
checkNotNull(callable);
|
||||||
final AtomicReference<RunningState> runningState = new AtomicReference<>(NOT_RUN);
|
checkNotNull(executor);
|
||||||
|
final TaskNonReentrantExecutor taskExecutor = new TaskNonReentrantExecutor(executor, this);
|
||||||
final AsyncCallable<T> task =
|
final AsyncCallable<T> task =
|
||||||
new AsyncCallable<T>() {
|
new AsyncCallable<T>() {
|
||||||
@Override
|
@Override
|
||||||
public ListenableFuture<T> call() throws Exception {
|
public ListenableFuture<T> call() throws Exception {
|
||||||
if (!runningState.compareAndSet(NOT_RUN, STARTED)) {
|
if (!taskExecutor.trySetStarted()) {
|
||||||
return immediateCancelledFuture();
|
return immediateCancelledFuture();
|
||||||
}
|
}
|
||||||
return callable.call();
|
return callable.call();
|
||||||
|
@ -119,20 +192,13 @@ public final class ExecutionSequencer {
|
||||||
* have completed - namely after oldFuture is done, and taskFuture has either completed or been
|
* have completed - namely after oldFuture is done, and taskFuture has either completed or been
|
||||||
* cancelled before the callable started execution.
|
* cancelled before the callable started execution.
|
||||||
*/
|
*/
|
||||||
final SettableFuture<Object> newFuture = SettableFuture.create();
|
final SettableFuture<Void> newFuture = SettableFuture.create();
|
||||||
|
|
||||||
final ListenableFuture<?> oldFuture = ref.getAndSet(newFuture);
|
final ListenableFuture<Void> oldFuture = ref.getAndSet(newFuture);
|
||||||
|
|
||||||
// Invoke our task once the previous future completes.
|
// Invoke our task once the previous future completes.
|
||||||
final ListenableFuture<T> taskFuture =
|
final TrustedListenableFutureTask<T> taskFuture = TrustedListenableFutureTask.create(task);
|
||||||
Futures.submitAsync(
|
oldFuture.addListener(taskFuture, taskExecutor);
|
||||||
task,
|
|
||||||
new Executor() {
|
|
||||||
@Override
|
|
||||||
public void execute(Runnable runnable) {
|
|
||||||
oldFuture.addListener(runnable, executor);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
final ListenableFuture<T> outputFuture = Futures.nonCancellationPropagating(taskFuture);
|
final ListenableFuture<T> outputFuture = Futures.nonCancellationPropagating(taskFuture);
|
||||||
|
|
||||||
|
@ -144,15 +210,39 @@ public final class ExecutionSequencer {
|
||||||
new Runnable() {
|
new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
if (taskFuture.isDone()
|
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
|
// 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
|
// a future that eventually came from immediateFuture(null), this doesn't leak
|
||||||
// throwables or completion values.
|
// throwables or completion values.
|
||||||
newFuture.setFuture(oldFuture);
|
newFuture.setFuture(oldFuture);
|
||||||
|
} else if (outputFuture.isCancelled() && taskExecutor.trySetCancelled()) {
|
||||||
|
// 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. Doing this immediately here lets the next task run without waiting for
|
||||||
|
// the cancelled task's executor to run the noop AsyncCallable.
|
||||||
|
//
|
||||||
|
// ---
|
||||||
|
//
|
||||||
|
// If the CAS fails, the provided callable already started running (or it is about
|
||||||
|
// to). Our contract promises:
|
||||||
|
//
|
||||||
|
// 1. not to execute a new callable until the old one has returned
|
||||||
|
//
|
||||||
|
// If we were to cancel taskFuture, that would let the next task start while the old
|
||||||
|
// one is still running.
|
||||||
|
//
|
||||||
|
// Now, maybe we could tweak our implementation to not start the next task until the
|
||||||
|
// callable actually completes. (We could detect completion in our wrapper
|
||||||
|
// `AsyncCallable task`.) However, our contract also promises:
|
||||||
|
//
|
||||||
|
// 2. not to cancel any Future the user returned from an AsyncCallable
|
||||||
|
//
|
||||||
|
// We promise this because, once we cancel that Future, we would no longer be able to
|
||||||
|
// tell when any underlying work it is doing is done. Thus, we might start a new task
|
||||||
|
// while that underlying work is still running.
|
||||||
|
//
|
||||||
|
// So that is why we cancel only in the case of CAS success.
|
||||||
|
taskFuture.cancel(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -164,4 +254,163 @@ public final class ExecutionSequencer {
|
||||||
|
|
||||||
return outputFuture;
|
return outputFuture;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum RunningState {
|
||||||
|
NOT_RUN,
|
||||||
|
CANCELLED,
|
||||||
|
STARTED,
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class helps avoid a StackOverflowError when large numbers of tasks are submitted with
|
||||||
|
* {@link MoreExecutors#directExecutor}. Normally, when the first future completes, all the other
|
||||||
|
* tasks would be called recursively. Here, we detect that the delegate executor is executing
|
||||||
|
* inline, and maintain a queue to dispatch tasks iteratively. There is one instance of this class
|
||||||
|
* per call to submit() or submitAsync(), and each instance supports only one call to execute().
|
||||||
|
*
|
||||||
|
* <p>This class would certainly be simpler and easier to reason about if it were built with
|
||||||
|
* ThreadLocal; however, ThreadLocal is not well optimized for the case where the ThreadLocal is
|
||||||
|
* non-static, and is initialized/removed frequently - this causes churn in the Thread specific
|
||||||
|
* hashmaps. Using a static ThreadLocal to avoid that overhead would mean that different
|
||||||
|
* ExecutionSequencer objects interfere with each other, which would be undesirable, in addition
|
||||||
|
* to increasing the memory footprint of every thread that interacted with it. In order to release
|
||||||
|
* entries in thread-specific maps when the ThreadLocal object itself is no longer referenced,
|
||||||
|
* ThreadLocal is usually implemented with a WeakReference, which can have negative performance
|
||||||
|
* properties; for example, calling WeakReference.get() on Android will block during an
|
||||||
|
* otherwise-concurrent GC cycle.
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("ShouldNotSubclass") // Saving an allocation here is worth it
|
||||||
|
private static final class TaskNonReentrantExecutor extends AtomicReference<RunningState>
|
||||||
|
implements Executor, Runnable {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used to update and read the latestTaskQueue field. Set to null once the runnable has been run
|
||||||
|
* or queued.
|
||||||
|
*/
|
||||||
|
ExecutionSequencer sequencer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Executor the task was set to run on. Set to null when the task has been queued, run, or
|
||||||
|
* cancelled.
|
||||||
|
*/
|
||||||
|
Executor delegate;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set before calling delegate.execute(); set to null once run, so that it can be GCed; this
|
||||||
|
* object may live on after, if submitAsync returns an incomplete future.
|
||||||
|
*/
|
||||||
|
Runnable task;
|
||||||
|
|
||||||
|
/** Thread that called execute(). Set in execute, cleared when delegate.execute() returns. */
|
||||||
|
Thread submitting;
|
||||||
|
|
||||||
|
private TaskNonReentrantExecutor(Executor delegate, ExecutionSequencer sequencer) {
|
||||||
|
super(NOT_RUN);
|
||||||
|
this.delegate = delegate;
|
||||||
|
this.sequencer = sequencer;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void execute(Runnable task) {
|
||||||
|
// If this operation was successfully cancelled already, calling the runnable will be a noop.
|
||||||
|
// This also avoids a race where if outputFuture is cancelled, it will call taskFuture.cancel,
|
||||||
|
// which will call newFuture.setFuture(oldFuture), to allow the next task in the queue to run
|
||||||
|
// without waiting for the user's executor to run our submitted Runnable. However, this can
|
||||||
|
// interact poorly with the reentrancy-avoiding behavior of this executor - when the operation
|
||||||
|
// before the cancelled future completes, it will synchronously complete both the newFuture
|
||||||
|
// from the cancelled operation and its own. This can cause one runnable to queue two tasks,
|
||||||
|
// breaking the invariant this method relies on to iteratively run the next task after the
|
||||||
|
// previous one completes.
|
||||||
|
if (get() == RunningState.CANCELLED) {
|
||||||
|
delegate = null;
|
||||||
|
sequencer = null;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
submitting = Thread.currentThread();
|
||||||
|
try {
|
||||||
|
ThreadConfinedTaskQueue submittingTaskQueue = sequencer.latestTaskQueue;
|
||||||
|
if (submittingTaskQueue.thread == submitting) {
|
||||||
|
sequencer = null;
|
||||||
|
// Submit from inside a reentrant submit. We don't know if this one will be reentrant (and
|
||||||
|
// can't know without submitting something to the executor) so queue to run iteratively.
|
||||||
|
// Task must be null, since each execution on this executor can only produce one more
|
||||||
|
// execution.
|
||||||
|
checkState(submittingTaskQueue.nextTask == null);
|
||||||
|
submittingTaskQueue.nextTask = task;
|
||||||
|
submittingTaskQueue.nextExecutor = delegate;
|
||||||
|
delegate = null;
|
||||||
|
} else {
|
||||||
|
Executor localDelegate = delegate;
|
||||||
|
delegate = null;
|
||||||
|
this.task = task;
|
||||||
|
localDelegate.execute(this);
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
// Important to null this out here - if we did *not* execute inline, we might still
|
||||||
|
// run() on the same thread that called execute() - such as in a thread pool, and think
|
||||||
|
// that it was happening inline. As a side benefit, avoids holding on to the Thread object
|
||||||
|
// longer than necessary.
|
||||||
|
submitting = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("ShortCircuitBoolean")
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
Thread currentThread = Thread.currentThread();
|
||||||
|
if (currentThread != submitting) {
|
||||||
|
Runnable localTask = task;
|
||||||
|
task = null;
|
||||||
|
localTask.run();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Executor called reentrantly! Make sure that further calls don't overflow stack. Further
|
||||||
|
// reentrant calls will see that their current thread is the same as the one set in
|
||||||
|
// latestTaskQueue, and queue rather than calling execute() directly.
|
||||||
|
ThreadConfinedTaskQueue executingTaskQueue = new ThreadConfinedTaskQueue();
|
||||||
|
executingTaskQueue.thread = currentThread;
|
||||||
|
// Unconditionally set; there is no risk of throwing away a queued task from another thread,
|
||||||
|
// because in order for the current task to run on this executor the previous task must have
|
||||||
|
// already started execution. Because each task on a TaskNonReentrantExecutor can only produce
|
||||||
|
// one execute() call to another instance from the same ExecutionSequencer, we know by
|
||||||
|
// induction that the task that launched this one must not have added any other runnables to
|
||||||
|
// that thread's queue, and thus we cannot be replacing a TaskAndThread object that would
|
||||||
|
// otherwise have another task queued on to it. Note the exception to this, cancellation, is
|
||||||
|
// specially handled in execute() - execute() calls triggered by cancellation are no-ops, and
|
||||||
|
// thus don't count.
|
||||||
|
sequencer.latestTaskQueue = executingTaskQueue;
|
||||||
|
sequencer = null;
|
||||||
|
try {
|
||||||
|
Runnable localTask = task;
|
||||||
|
task = null;
|
||||||
|
localTask.run();
|
||||||
|
// Now check if our task attempted to reentrantly execute the next task.
|
||||||
|
Runnable queuedTask;
|
||||||
|
Executor queuedExecutor;
|
||||||
|
// Intentionally using non-short-circuit operator
|
||||||
|
while ((queuedTask = executingTaskQueue.nextTask) != null
|
||||||
|
& (queuedExecutor = executingTaskQueue.nextExecutor) != null) {
|
||||||
|
executingTaskQueue.nextTask = null;
|
||||||
|
executingTaskQueue.nextExecutor = null;
|
||||||
|
queuedExecutor.execute(queuedTask);
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
// Null out the thread field, so that we don't leak a reference to Thread, and so that
|
||||||
|
// future `thread == currentThread()` calls from this thread don't incorrectly queue instead
|
||||||
|
// of executing. Don't null out the latestTaskQueue field, because the work done here
|
||||||
|
// may have scheduled more operations on another thread, and if those operations then
|
||||||
|
// trigger reentrant calls that thread will have updated the latestTaskQueue field, and
|
||||||
|
// we'd be interfering with their operation.
|
||||||
|
executingTaskQueue.thread = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean trySetStarted() {
|
||||||
|
return compareAndSet(NOT_RUN, STARTED);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean trySetCancelled() {
|
||||||
|
return compareAndSet(NOT_RUN, CANCELLED);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -49,7 +49,7 @@ import java.util.concurrent.TimeoutException;
|
||||||
* debugging, and cancellation. Examples of frameworks include:
|
* debugging, and cancellation. Examples of frameworks include:
|
||||||
*
|
*
|
||||||
* <ul>
|
* <ul>
|
||||||
* <li><a href="http://dagger.dev/producers.html">Dagger Producers</a>
|
* <li><a href="https://dagger.dev/producers.html">Dagger Producers</a>
|
||||||
* </ul>
|
* </ul>
|
||||||
*
|
*
|
||||||
* <h4>{@link java.util.concurrent.CompletableFuture} / {@link java.util.concurrent.CompletionStage}
|
* <h4>{@link java.util.concurrent.CompletableFuture} / {@link java.util.concurrent.CompletionStage}
|
||||||
|
|
|
@ -30,7 +30,8 @@ import com.google.common.collect.ImmutableList;
|
||||||
import com.google.common.util.concurrent.CollectionFuture.ListFuture;
|
import com.google.common.util.concurrent.CollectionFuture.ListFuture;
|
||||||
import com.google.common.util.concurrent.ImmediateFuture.ImmediateCancelledFuture;
|
import com.google.common.util.concurrent.ImmediateFuture.ImmediateCancelledFuture;
|
||||||
import com.google.common.util.concurrent.ImmediateFuture.ImmediateFailedFuture;
|
import com.google.common.util.concurrent.ImmediateFuture.ImmediateFailedFuture;
|
||||||
|
import com.google.common.util.concurrent.internal.InternalFutureFailureAccess;
|
||||||
|
import com.google.common.util.concurrent.internal.InternalFutures;
|
||||||
import java.time.Duration;
|
import java.time.Duration;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
@ -45,7 +46,6 @@ import java.util.concurrent.TimeUnit;
|
||||||
import java.util.concurrent.TimeoutException;
|
import java.util.concurrent.TimeoutException;
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Static utility methods pertaining to the {@link Future} interface.
|
* Static utility methods pertaining to the {@link Future} interface.
|
||||||
*
|
*
|
||||||
|
@ -60,7 +60,7 @@ import java.util.concurrent.atomic.AtomicInteger;
|
||||||
* monitoring, debugging, and cancellation. Examples of frameworks include:
|
* monitoring, debugging, and cancellation. Examples of frameworks include:
|
||||||
*
|
*
|
||||||
* <ul>
|
* <ul>
|
||||||
* <li><a href="http://dagger.dev/producers.html">Dagger Producers</a>
|
* <li><a href="https://dagger.dev/producers.html">Dagger Producers</a>
|
||||||
* </ul>
|
* </ul>
|
||||||
*
|
*
|
||||||
* <p>If you do chain your operations manually, you may want to use {@link FluentFuture}.
|
* <p>If you do chain your operations manually, you may want to use {@link FluentFuture}.
|
||||||
|
@ -135,6 +135,17 @@ public final class Futures extends GwtFuturesCatchingSpecialization {
|
||||||
return new ImmediateFuture<>(value);
|
return new ImmediateFuture<>(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a successful {@code ListenableFuture<Void>}. This method is equivalent to {@code
|
||||||
|
* immediateFuture(null)} except that it is restricted to produce futures of type {@code Void}.
|
||||||
|
*
|
||||||
|
* @since 29.0
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public static ListenableFuture<Void> immediateVoidFuture() {
|
||||||
|
return (ListenableFuture<Void>) ImmediateFuture.NULL;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a {@code ListenableFuture} which has an exception set immediately upon construction.
|
* Returns a {@code ListenableFuture} which has an exception set immediately upon construction.
|
||||||
*
|
*
|
||||||
|
@ -161,7 +172,7 @@ public final class Futures extends GwtFuturesCatchingSpecialization {
|
||||||
* Executes {@code callable} on the specified {@code executor}, returning a {@code Future}.
|
* Executes {@code callable} on the specified {@code executor}, returning a {@code Future}.
|
||||||
*
|
*
|
||||||
* @throws RejectedExecutionException if the task cannot be scheduled for execution
|
* @throws RejectedExecutionException if the task cannot be scheduled for execution
|
||||||
* @since NEXT
|
* @since 28.2
|
||||||
*/
|
*/
|
||||||
@Beta
|
@Beta
|
||||||
public static <O> ListenableFuture<O> submit(Callable<O> callable, Executor executor) {
|
public static <O> ListenableFuture<O> submit(Callable<O> callable, Executor executor) {
|
||||||
|
@ -175,7 +186,7 @@ public final class Futures extends GwtFuturesCatchingSpecialization {
|
||||||
* will complete after execution.
|
* will complete after execution.
|
||||||
*
|
*
|
||||||
* @throws RejectedExecutionException if the task cannot be scheduled for execution
|
* @throws RejectedExecutionException if the task cannot be scheduled for execution
|
||||||
* @since NEXT
|
* @since 28.2
|
||||||
*/
|
*/
|
||||||
@Beta
|
@Beta
|
||||||
public static ListenableFuture<Void> submit(Runnable runnable, Executor executor) {
|
public static ListenableFuture<Void> submit(Runnable runnable, Executor executor) {
|
||||||
|
@ -258,9 +269,7 @@ public final class Futures extends GwtFuturesCatchingSpecialization {
|
||||||
* }</pre>
|
* }</pre>
|
||||||
*
|
*
|
||||||
* <p>When selecting an executor, note that {@code directExecutor} is dangerous in some cases. See
|
* <p>When selecting an executor, note that {@code directExecutor} is dangerous in some cases. See
|
||||||
* the discussion in the {@link ListenableFuture#addListener ListenableFuture.addListener}
|
* the warnings the {@link MoreExecutors#directExecutor} documentation.
|
||||||
* 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 input the primary input {@code Future}
|
||||||
* @param exceptionType the exception type that triggers use of {@code fallback}. The exception
|
* @param exceptionType the exception type that triggers use of {@code fallback}. The exception
|
||||||
|
@ -325,11 +334,7 @@ public final class Futures extends GwtFuturesCatchingSpecialization {
|
||||||
* }</pre>
|
* }</pre>
|
||||||
*
|
*
|
||||||
* <p>When selecting an executor, note that {@code directExecutor} is dangerous in some cases. See
|
* <p>When selecting an executor, note that {@code directExecutor} is dangerous in some cases. See
|
||||||
* the discussion in the {@link ListenableFuture#addListener ListenableFuture.addListener}
|
* the warnings the {@link MoreExecutors#directExecutor} documentation.
|
||||||
* 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 input the primary input {@code Future}
|
||||||
* @param exceptionType the exception type that triggers use of {@code fallback}. The exception
|
* @param exceptionType the exception type that triggers use of {@code fallback}. The exception
|
||||||
|
@ -415,11 +420,7 @@ public final class Futures extends GwtFuturesCatchingSpecialization {
|
||||||
* }</pre>
|
* }</pre>
|
||||||
*
|
*
|
||||||
* <p>When selecting an executor, note that {@code directExecutor} is dangerous in some cases. See
|
* <p>When selecting an executor, note that {@code directExecutor} is dangerous in some cases. See
|
||||||
* the discussion in the {@link ListenableFuture#addListener ListenableFuture.addListener}
|
* the warnings the {@link MoreExecutors#directExecutor} documentation.
|
||||||
* 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}.)
|
|
||||||
*
|
*
|
||||||
* <p>The returned {@code Future} attempts to keep its cancellation state in sync with that of the
|
* <p>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
|
* input future and that of the future returned by the chain function. That is, if the returned
|
||||||
|
@ -455,9 +456,7 @@ public final class Futures extends GwtFuturesCatchingSpecialization {
|
||||||
* }</pre>
|
* }</pre>
|
||||||
*
|
*
|
||||||
* <p>When selecting an executor, note that {@code directExecutor} is dangerous in some cases. See
|
* <p>When selecting an executor, note that {@code directExecutor} is dangerous in some cases. See
|
||||||
* the discussion in the {@link ListenableFuture#addListener ListenableFuture.addListener}
|
* the warnings the {@link MoreExecutors#directExecutor} documentation.
|
||||||
* documentation. All its warnings about heavyweight listeners are also applicable to heavyweight
|
|
||||||
* functions passed to this method.
|
|
||||||
*
|
*
|
||||||
* <p>The returned {@code Future} attempts to keep its cancellation state in sync with that of the
|
* <p>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
|
* input future. That is, if the returned {@code Future} is cancelled, it will attempt to cancel
|
||||||
|
@ -669,7 +668,6 @@ public final class Futures extends GwtFuturesCatchingSpecialization {
|
||||||
* @since 20.0
|
* @since 20.0
|
||||||
*/
|
*/
|
||||||
@Beta
|
@Beta
|
||||||
// TODO(cpovirk): Consider removing, especially if we provide run(Runnable)
|
|
||||||
@GwtCompatible
|
@GwtCompatible
|
||||||
public static final class FutureCombiner<V> {
|
public static final class FutureCombiner<V> {
|
||||||
private final boolean allMustSucceed;
|
private final boolean allMustSucceed;
|
||||||
|
@ -713,7 +711,6 @@ public final class Futures extends GwtFuturesCatchingSpecialization {
|
||||||
*
|
*
|
||||||
* <p>Canceling this future will attempt to cancel all the component futures.
|
* <p>Canceling this future will attempt to cancel all the component futures.
|
||||||
*/
|
*/
|
||||||
// TODO(cpovirk): Remove this
|
|
||||||
public <C> ListenableFuture<C> call(Callable<C> combiner, Executor executor) {
|
public <C> ListenableFuture<C> call(Callable<C> combiner, Executor executor) {
|
||||||
return new CombinedFuture<C>(futures, allMustSucceed, executor, combiner);
|
return new CombinedFuture<C>(futures, allMustSucceed, executor, combiner);
|
||||||
}
|
}
|
||||||
|
@ -999,13 +996,18 @@ public final class Futures extends GwtFuturesCatchingSpecialization {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Registers separate success and failure callbacks to be run when the {@code Future}'s
|
* 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 {@linkplain Future#isDone() complete} or, if the
|
||||||
* computation is already complete, immediately.
|
* computation is already complete, immediately.
|
||||||
*
|
*
|
||||||
* <p>The callback is run on {@code executor}. There is no guaranteed ordering of execution of
|
* <p>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
|
* callbacks, but any callback added through this method is guaranteed to be called once the
|
||||||
* computation is complete.
|
* computation is complete.
|
||||||
*
|
*
|
||||||
|
* <p>Exceptions thrown by a {@code callback} 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.
|
||||||
|
*
|
||||||
* <p>Example:
|
* <p>Example:
|
||||||
*
|
*
|
||||||
* <pre>{@code
|
* <pre>{@code
|
||||||
|
@ -1023,9 +1025,7 @@ public final class Futures extends GwtFuturesCatchingSpecialization {
|
||||||
* }</pre>
|
* }</pre>
|
||||||
*
|
*
|
||||||
* <p>When selecting an executor, note that {@code directExecutor} is dangerous in some cases. See
|
* <p>When selecting an executor, note that {@code directExecutor} is dangerous in some cases. See
|
||||||
* the discussion in the {@link ListenableFuture#addListener ListenableFuture.addListener}
|
* the warnings the {@link MoreExecutors#directExecutor} documentation.
|
||||||
* documentation. All its warnings about heavyweight listeners are also applicable to heavyweight
|
|
||||||
* callbacks passed to this method.
|
|
||||||
*
|
*
|
||||||
* <p>For a more general interface to attach a completion listener to a {@code Future}, see {@link
|
* <p>For a more general interface to attach a completion listener to a {@code Future}, see {@link
|
||||||
* ListenableFuture#addListener addListener}.
|
* ListenableFuture#addListener addListener}.
|
||||||
|
@ -1055,6 +1055,14 @@ public final class Futures extends GwtFuturesCatchingSpecialization {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
|
if (future instanceof InternalFutureFailureAccess) {
|
||||||
|
Throwable failure =
|
||||||
|
InternalFutures.tryInternalFastPathGetFailure((InternalFutureFailureAccess) future);
|
||||||
|
if (failure != null) {
|
||||||
|
callback.onFailure(failure);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
final V value;
|
final V value;
|
||||||
try {
|
try {
|
||||||
value = getDone(future);
|
value = getDone(future);
|
||||||
|
@ -1093,7 +1101,6 @@ public final class Futures extends GwtFuturesCatchingSpecialization {
|
||||||
* @throws IllegalStateException if the {@code Future} is not done
|
* @throws IllegalStateException if the {@code Future} is not done
|
||||||
* @since 20.0
|
* @since 20.0
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// TODO(cpovirk): Consider calling getDone() in our own code.
|
// TODO(cpovirk): Consider calling getDone() in our own code.
|
||||||
public static <V> V getDone(Future<V> future) throws ExecutionException {
|
public static <V> V getDone(Future<V> future) throws ExecutionException {
|
||||||
/*
|
/*
|
||||||
|
@ -1103,7 +1110,6 @@ public final class Futures extends GwtFuturesCatchingSpecialization {
|
||||||
* IllegalArgumentException here, in part to keep its recommendation simple: Static methods
|
* IllegalArgumentException here, in part to keep its recommendation simple: Static methods
|
||||||
* should throw IllegalStateException only when they use static state.
|
* 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
|
* Why do we deviate here? The answer: We want for fluentFuture.getDone() to throw the same
|
||||||
* exception as Futures.getDone(fluentFuture).
|
* exception as Futures.getDone(fluentFuture).
|
||||||
*/
|
*/
|
||||||
|
@ -1154,7 +1160,6 @@ public final class Futures extends GwtFuturesCatchingSpecialization {
|
||||||
* @since 19.0 (in 10.0 as {@code get})
|
* @since 19.0 (in 10.0 as {@code get})
|
||||||
*/
|
*/
|
||||||
@Beta
|
@Beta
|
||||||
|
|
||||||
@GwtIncompatible // reflection
|
@GwtIncompatible // reflection
|
||||||
public static <V, X extends Exception> V getChecked(Future<V> future, Class<X> exceptionClass)
|
public static <V, X extends Exception> V getChecked(Future<V> future, Class<X> exceptionClass)
|
||||||
throws X {
|
throws X {
|
||||||
|
@ -1205,7 +1210,6 @@ public final class Futures extends GwtFuturesCatchingSpecialization {
|
||||||
* @since 28.0
|
* @since 28.0
|
||||||
*/
|
*/
|
||||||
@Beta
|
@Beta
|
||||||
|
|
||||||
@GwtIncompatible // reflection
|
@GwtIncompatible // reflection
|
||||||
public static <V, X extends Exception> V getChecked(
|
public static <V, X extends Exception> V getChecked(
|
||||||
Future<V> future, Class<X> exceptionClass, Duration timeout) throws X {
|
Future<V> future, Class<X> exceptionClass, Duration timeout) throws X {
|
||||||
|
@ -1256,7 +1260,6 @@ public final class Futures extends GwtFuturesCatchingSpecialization {
|
||||||
* @since 19.0 (in 10.0 as {@code get} and with different parameter order)
|
* @since 19.0 (in 10.0 as {@code get} and with different parameter order)
|
||||||
*/
|
*/
|
||||||
@Beta
|
@Beta
|
||||||
|
|
||||||
@GwtIncompatible // reflection
|
@GwtIncompatible // reflection
|
||||||
@SuppressWarnings("GoodTime") // should accept a java.time.Duration
|
@SuppressWarnings("GoodTime") // should accept a java.time.Duration
|
||||||
public static <V, X extends Exception> V getChecked(
|
public static <V, X extends Exception> V getChecked(
|
||||||
|
@ -1298,7 +1301,6 @@ public final class Futures extends GwtFuturesCatchingSpecialization {
|
||||||
* @throws CancellationException if {@code get} throws a {@code CancellationException}
|
* @throws CancellationException if {@code get} throws a {@code CancellationException}
|
||||||
* @since 10.0
|
* @since 10.0
|
||||||
*/
|
*/
|
||||||
|
|
||||||
public static <V> V getUnchecked(Future<V> future) {
|
public static <V> V getUnchecked(Future<V> future) {
|
||||||
checkNotNull(future);
|
checkNotNull(future);
|
||||||
try {
|
try {
|
||||||
|
|
|
@ -40,7 +40,7 @@ import java.util.concurrent.RejectedExecutionException;
|
||||||
* frameworks include:
|
* frameworks include:
|
||||||
*
|
*
|
||||||
* <ul>
|
* <ul>
|
||||||
* <li><a href="http://dagger.dev/producers.html">Dagger Producers</a>
|
* <li><a href="https://dagger.dev/producers.html">Dagger Producers</a>
|
||||||
* </ul>
|
* </ul>
|
||||||
*
|
*
|
||||||
* <p>The main purpose of {@link #addListener addListener} is to support this chaining. You will
|
* <p>The main purpose of {@link #addListener addListener} is to support this chaining. You will
|
||||||
|
@ -112,20 +112,10 @@ public interface ListenableFuture<V> extends Future<V> {
|
||||||
* thrown by {@linkplain MoreExecutors#directExecutor direct execution}) will be caught and
|
* thrown by {@linkplain MoreExecutors#directExecutor direct execution}) will be caught and
|
||||||
* logged.
|
* logged.
|
||||||
*
|
*
|
||||||
* <p>Note: For fast, lightweight listeners that would be safe to execute in any thread, consider
|
* <p>Note: If your listener is lightweight -- and will not cause stack overflow by completing
|
||||||
* {@link MoreExecutors#directExecutor}. Otherwise, avoid it. Heavyweight {@code directExecutor}
|
* more futures or adding more {@code directExecutor()} listeners inline -- consider {@link
|
||||||
* listeners can cause problems, and these problems can be difficult to reproduce because they
|
* MoreExecutors#directExecutor}. Otherwise, avoid it: See the warnings on the docs for {@code
|
||||||
* depend on timing. For example:
|
* directExecutor}.
|
||||||
*
|
|
||||||
* <ul>
|
|
||||||
* <li>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.
|
|
||||||
* <li>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.
|
|
||||||
* <li>The listener may delay other listeners, even listeners that are not themselves {@code
|
|
||||||
* directExecutor} listeners.
|
|
||||||
* </ul>
|
|
||||||
*
|
*
|
||||||
* <p>This is the most general listener interface. For common operations performed using
|
* <p>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
|
* listeners, see {@link Futures}. For a simplified but general listener interface, see {@link
|
||||||
|
|
|
@ -14,11 +14,16 @@
|
||||||
|
|
||||||
package com.google.common.util.concurrent;
|
package com.google.common.util.concurrent;
|
||||||
|
|
||||||
|
import static java.lang.Math.min;
|
||||||
|
import static java.util.concurrent.TimeUnit.NANOSECONDS;
|
||||||
|
|
||||||
import com.google.common.annotations.GwtIncompatible;
|
import com.google.common.annotations.GwtIncompatible;
|
||||||
import java.util.concurrent.Callable;
|
import java.util.concurrent.Callable;
|
||||||
|
import java.util.concurrent.ExecutionException;
|
||||||
import java.util.concurrent.Executor;
|
import java.util.concurrent.Executor;
|
||||||
import java.util.concurrent.FutureTask;
|
import java.util.concurrent.FutureTask;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.concurrent.TimeoutException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A {@link FutureTask} that also implements the {@link ListenableFuture} interface. Unlike {@code
|
* A {@link FutureTask} that also implements the {@link ListenableFuture} interface. Unlike {@code
|
||||||
|
@ -80,6 +85,19 @@ public class ListenableFutureTask<V> extends FutureTask<V> implements Listenable
|
||||||
executionList.add(listener, exec);
|
executionList.add(listener, exec);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public V get(long timeout, TimeUnit unit)
|
||||||
|
throws TimeoutException, InterruptedException, ExecutionException {
|
||||||
|
|
||||||
|
long timeoutNanos = unit.toNanos(timeout);
|
||||||
|
if (timeoutNanos <= OverflowAvoidingLockSupport.MAX_NANOSECONDS_THRESHOLD) {
|
||||||
|
return super.get(timeout, unit);
|
||||||
|
}
|
||||||
|
// Waiting 68 years should be enough for any program.
|
||||||
|
return super.get(
|
||||||
|
min(timeoutNanos, OverflowAvoidingLockSupport.MAX_NANOSECONDS_THRESHOLD), NANOSECONDS);
|
||||||
|
}
|
||||||
|
|
||||||
/** Internal implementation detail used to invoke the listeners. */
|
/** Internal implementation detail used to invoke the listeners. */
|
||||||
@Override
|
@Override
|
||||||
protected void done() {
|
protected void done() {
|
||||||
|
|
|
@ -27,8 +27,6 @@ import com.google.common.base.Throwables;
|
||||||
import com.google.common.collect.Lists;
|
import com.google.common.collect.Lists;
|
||||||
import com.google.common.collect.Queues;
|
import com.google.common.collect.Queues;
|
||||||
import com.google.common.util.concurrent.ForwardingListenableFuture.SimpleForwardingListenableFuture;
|
import com.google.common.util.concurrent.ForwardingListenableFuture.SimpleForwardingListenableFuture;
|
||||||
|
|
||||||
|
|
||||||
import java.lang.reflect.InvocationTargetException;
|
import java.lang.reflect.InvocationTargetException;
|
||||||
import java.time.Duration;
|
import java.time.Duration;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
|
@ -49,12 +47,11 @@ import java.util.concurrent.ScheduledFuture;
|
||||||
import java.util.concurrent.ScheduledThreadPoolExecutor;
|
import java.util.concurrent.ScheduledThreadPoolExecutor;
|
||||||
import java.util.concurrent.ThreadFactory;
|
import java.util.concurrent.ThreadFactory;
|
||||||
import java.util.concurrent.ThreadPoolExecutor;
|
import java.util.concurrent.ThreadPoolExecutor;
|
||||||
import java.util.concurrent.ThreadPoolExecutor.CallerRunsPolicy;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.concurrent.TimeoutException;
|
import java.util.concurrent.TimeoutException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Factory and utility methods for {@link java.util.concurrent.Executor}, {@link ExecutorService},
|
* Factory and utility methods for {@link Executor}, {@link ExecutorService},
|
||||||
* and {@link ThreadFactory}.
|
* and {@link ThreadFactory}.
|
||||||
*
|
*
|
||||||
* @author Eric Fellheimer
|
* @author Eric Fellheimer
|
||||||
|
@ -309,10 +306,8 @@ public final class MoreExecutors {
|
||||||
* - Shutdown: runningTasks > 0 and shutdown == true
|
* - Shutdown: runningTasks > 0 and shutdown == true
|
||||||
* - Terminated: runningTasks == 0 and shutdown == true
|
* - Terminated: runningTasks == 0 and shutdown == true
|
||||||
*/
|
*/
|
||||||
|
|
||||||
private int runningTasks = 0;
|
private int runningTasks = 0;
|
||||||
|
|
||||||
|
|
||||||
private boolean shutdown = false;
|
private boolean shutdown = false;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -401,10 +396,11 @@ public final class MoreExecutors {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates an executor service that runs each task in the thread that invokes {@code
|
* 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
|
* execute/submit}, as in {@code ThreadPoolExecutor.CallerRunsPolicy}. This applies both to
|
||||||
* tasks and to collections of tasks submitted via {@code invokeAll} or {@code invokeAny}. In the
|
* individually submitted tasks and to collections of tasks submitted via {@code invokeAll} or
|
||||||
* latter case, tasks will run serially on the calling thread. Tasks are run to completion before
|
* {@code invokeAny}. In the latter case, tasks will run serially on the calling thread. Tasks are
|
||||||
* a {@code Future} is returned to the caller (unless the executor has been shutdown).
|
* run to completion before a {@code Future} is returned to the caller (unless the executor has
|
||||||
|
* been shutdown).
|
||||||
*
|
*
|
||||||
* <p>Although all tasks are immediately executed in the thread that submitted the task, this
|
* <p>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
|
* {@code ExecutorService} imposes a small locking overhead on each task submission in order to
|
||||||
|
@ -431,7 +427,33 @@ public final class MoreExecutors {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns an {@link Executor} that runs each task in the thread that invokes {@link
|
* Returns an {@link Executor} that runs each task in the thread that invokes {@link
|
||||||
* Executor#execute execute}, as in {@link CallerRunsPolicy}.
|
* Executor#execute execute}, as in {@code ThreadPoolExecutor.CallerRunsPolicy}.
|
||||||
|
*
|
||||||
|
* <p>This executor is appropriate for tasks that are lightweight and not deeply chained.
|
||||||
|
* Inappropriate {@code directExecutor} usage can cause problems, and these problems can be
|
||||||
|
* difficult to reproduce because they depend on timing. For example:
|
||||||
|
*
|
||||||
|
* <ul>
|
||||||
|
* <li>A call like {@code future.transform(function, directExecutor())} may execute the function
|
||||||
|
* immediately in the thread that is calling {@code transform}. (This specific case happens
|
||||||
|
* if the future is already completed.) If {@code transform} call was made from a UI thread
|
||||||
|
* or other latency-sensitive thread, a heavyweight function can harm responsiveness.
|
||||||
|
* <li>If the task will be executed later, consider which thread will trigger the execution --
|
||||||
|
* since that thread will execute the task inline. If the thread is a shared system thread
|
||||||
|
* like an RPC network thread, a heavyweight task can stall progress of the whole system or
|
||||||
|
* even deadlock it.
|
||||||
|
* <li>If many tasks will be triggered by the same event, one heavyweight task may delay other
|
||||||
|
* tasks -- even tasks that are not themselves {@code directExecutor} tasks.
|
||||||
|
* <li>If many such tasks are chained together (such as with {@code
|
||||||
|
* future.transform(...).transform(...).transform(...)....}), they may overflow the stack.
|
||||||
|
* (In simple cases, callers can avoid this by registering all tasks with the same {@link
|
||||||
|
* MoreExecutors#newSequentialExecutor} wrapper around {@code directExecutor()}. More
|
||||||
|
* complex cases may require using thread pools or making deeper changes.)
|
||||||
|
* </ul>
|
||||||
|
*
|
||||||
|
* Additionally, beware of executing tasks with {@code directExecutor} while holding a lock. Since
|
||||||
|
* the task you submit to the executor (or any other arbitrary work the executor does) may do slow
|
||||||
|
* work or acquire other locks, you risk deadlocks.
|
||||||
*
|
*
|
||||||
* <p>This instance is equivalent to:
|
* <p>This instance is equivalent to:
|
||||||
*
|
*
|
||||||
|
@ -446,7 +468,6 @@ public final class MoreExecutors {
|
||||||
* <p>This should be preferred to {@link #newDirectExecutorService()} because implementing the
|
* <p>This should be preferred to {@link #newDirectExecutorService()} because implementing the
|
||||||
* {@link ExecutorService} subinterface necessitates significant performance overhead.
|
* {@link ExecutorService} subinterface necessitates significant performance overhead.
|
||||||
*
|
*
|
||||||
*
|
|
||||||
* @since 18.0
|
* @since 18.0
|
||||||
*/
|
*/
|
||||||
public static Executor directExecutor() {
|
public static Executor directExecutor() {
|
||||||
|
@ -700,7 +721,8 @@ public final class MoreExecutors {
|
||||||
* An implementation of {@link ExecutorService#invokeAny} for {@link ListeningExecutorService}
|
* An implementation of {@link ExecutorService#invokeAny} for {@link ListeningExecutorService}
|
||||||
* implementations.
|
* implementations.
|
||||||
*/
|
*/
|
||||||
@GwtIncompatible static <T> T invokeAnyImpl(
|
@GwtIncompatible
|
||||||
|
static <T> T invokeAnyImpl(
|
||||||
ListeningExecutorService executorService,
|
ListeningExecutorService executorService,
|
||||||
Collection<? extends Callable<T>> tasks,
|
Collection<? extends Callable<T>> tasks,
|
||||||
boolean timed,
|
boolean timed,
|
||||||
|
@ -715,7 +737,8 @@ public final class MoreExecutors {
|
||||||
* implementations.
|
* implementations.
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("GoodTime") // should accept a java.time.Duration
|
@SuppressWarnings("GoodTime") // should accept a java.time.Duration
|
||||||
@GwtIncompatible static <T> T invokeAnyImpl(
|
@GwtIncompatible
|
||||||
|
static <T> T invokeAnyImpl(
|
||||||
ListeningExecutorService executorService,
|
ListeningExecutorService executorService,
|
||||||
Collection<? extends Callable<T>> tasks,
|
Collection<? extends Callable<T>> tasks,
|
||||||
boolean timed,
|
boolean timed,
|
||||||
|
@ -907,7 +930,6 @@ public final class MoreExecutors {
|
||||||
* right before each task is run. The renaming is best effort, if a {@link SecurityManager}
|
* 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.
|
* prevents the renaming then it will be skipped but the tasks will still execute.
|
||||||
*
|
*
|
||||||
*
|
|
||||||
* @param executor The executor to decorate
|
* @param executor The executor to decorate
|
||||||
* @param nameSupplier The source of names for each task
|
* @param nameSupplier The source of names for each task
|
||||||
*/
|
*/
|
||||||
|
@ -931,7 +953,6 @@ public final class MoreExecutors {
|
||||||
* right before each task is run. The renaming is best effort, if a {@link SecurityManager}
|
* 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.
|
* prevents the renaming then it will be skipped but the tasks will still execute.
|
||||||
*
|
*
|
||||||
*
|
|
||||||
* @param service The executor to decorate
|
* @param service The executor to decorate
|
||||||
* @param nameSupplier The source of names for each task
|
* @param nameSupplier The source of names for each task
|
||||||
*/
|
*/
|
||||||
|
@ -961,7 +982,6 @@ public final class MoreExecutors {
|
||||||
* right before each task is run. The renaming is best effort, if a {@link SecurityManager}
|
* 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.
|
* prevents the renaming then it will be skipped but the tasks will still execute.
|
||||||
*
|
*
|
||||||
*
|
|
||||||
* @param service The executor to decorate
|
* @param service The executor to decorate
|
||||||
* @param nameSupplier The source of names for each task
|
* @param nameSupplier The source of names for each task
|
||||||
*/
|
*/
|
||||||
|
@ -1007,7 +1027,6 @@ public final class MoreExecutors {
|
||||||
* @since 28.0
|
* @since 28.0
|
||||||
*/
|
*/
|
||||||
@Beta
|
@Beta
|
||||||
|
|
||||||
@GwtIncompatible // java.time.Duration
|
@GwtIncompatible // java.time.Duration
|
||||||
public static boolean shutdownAndAwaitTermination(ExecutorService service, Duration timeout) {
|
public static boolean shutdownAndAwaitTermination(ExecutorService service, Duration timeout) {
|
||||||
return shutdownAndAwaitTermination(service, toNanosSaturated(timeout), TimeUnit.NANOSECONDS);
|
return shutdownAndAwaitTermination(service, toNanosSaturated(timeout), TimeUnit.NANOSECONDS);
|
||||||
|
@ -1038,7 +1057,6 @@ public final class MoreExecutors {
|
||||||
* @since 17.0
|
* @since 17.0
|
||||||
*/
|
*/
|
||||||
@Beta
|
@Beta
|
||||||
|
|
||||||
@GwtIncompatible // concurrency
|
@GwtIncompatible // concurrency
|
||||||
@SuppressWarnings("GoodTime") // should accept a java.time.Duration
|
@SuppressWarnings("GoodTime") // should accept a java.time.Duration
|
||||||
public static boolean shutdownAndAwaitTermination(
|
public static boolean shutdownAndAwaitTermination(
|
||||||
|
@ -1078,27 +1096,13 @@ public final class MoreExecutors {
|
||||||
return delegate;
|
return delegate;
|
||||||
}
|
}
|
||||||
return new Executor() {
|
return new Executor() {
|
||||||
boolean thrownFromDelegate = true;
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void execute(final Runnable command) {
|
public void execute(Runnable command) {
|
||||||
try {
|
try {
|
||||||
delegate.execute(
|
delegate.execute(command);
|
||||||
new Runnable() {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
thrownFromDelegate = false;
|
|
||||||
command.run();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} catch (RejectedExecutionException e) {
|
} catch (RejectedExecutionException e) {
|
||||||
if (thrownFromDelegate) {
|
|
||||||
// wrap exception?
|
|
||||||
future.setException(e);
|
future.setException(e);
|
||||||
}
|
}
|
||||||
// otherwise it must have been thrown from a transitive call and the delegate runnable
|
|
||||||
// should have handled it.
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,36 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2020 The Guava Authors
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
|
||||||
|
* in compliance with the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software distributed under the License
|
||||||
|
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF 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 java.util.concurrent.locks.LockSupport;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Works around an android bug, where parking for more than INT_MAX seconds can produce an abort
|
||||||
|
* signal on 32 bit devices running Android Q.
|
||||||
|
*/
|
||||||
|
final class OverflowAvoidingLockSupport {
|
||||||
|
// Represents the max nanoseconds representable on a linux timespec with a 32 bit tv_sec
|
||||||
|
static final long MAX_NANOSECONDS_THRESHOLD = (1L + Integer.MAX_VALUE) * 1_000_000_000L - 1L;
|
||||||
|
|
||||||
|
private OverflowAvoidingLockSupport() {}
|
||||||
|
|
||||||
|
static void parkNanos(Object blocker, long nanos) {
|
||||||
|
// Even in the extremely unlikely event that a thread unblocks itself early after only 68 years,
|
||||||
|
// this is indistinguishable from a spurious wakeup, which LockSupport allows.
|
||||||
|
LockSupport.parkNanos(blocker, min(nanos, MAX_NANOSECONDS_THRESHOLD));
|
||||||
|
}
|
||||||
|
}
|
|
@ -19,11 +19,10 @@ import static com.google.common.util.concurrent.SequentialExecutor.WorkerRunning
|
||||||
import static com.google.common.util.concurrent.SequentialExecutor.WorkerRunningState.QUEUED;
|
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.QUEUING;
|
||||||
import static com.google.common.util.concurrent.SequentialExecutor.WorkerRunningState.RUNNING;
|
import static com.google.common.util.concurrent.SequentialExecutor.WorkerRunningState.RUNNING;
|
||||||
|
import static java.lang.System.identityHashCode;
|
||||||
|
|
||||||
import com.google.common.annotations.GwtIncompatible;
|
import com.google.common.annotations.GwtIncompatible;
|
||||||
import com.google.common.base.Preconditions;
|
import com.google.common.base.Preconditions;
|
||||||
|
|
||||||
|
|
||||||
import java.util.ArrayDeque;
|
import java.util.ArrayDeque;
|
||||||
import java.util.Deque;
|
import java.util.Deque;
|
||||||
import java.util.concurrent.Executor;
|
import java.util.concurrent.Executor;
|
||||||
|
@ -87,7 +86,7 @@ final class SequentialExecutor implements Executor {
|
||||||
* Adds a task to the queue and makes sure a worker thread is running.
|
* Adds a task to the queue and makes sure a worker thread is running.
|
||||||
*
|
*
|
||||||
* <p>If this method throws, e.g. a {@code RejectedExecutionException} from the delegate executor,
|
* <p>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.
|
* execution of tasks will stop until a call to this method is made.
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void execute(final Runnable task) {
|
public void execute(final Runnable task) {
|
||||||
|
@ -116,6 +115,11 @@ final class SequentialExecutor implements Executor {
|
||||||
public void run() {
|
public void run() {
|
||||||
task.run();
|
task.run();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return task.toString();
|
||||||
|
}
|
||||||
};
|
};
|
||||||
queue.add(submittedTask);
|
queue.add(submittedTask);
|
||||||
workerRunningState = QUEUING;
|
workerRunningState = QUEUING;
|
||||||
|
@ -160,8 +164,9 @@ final class SequentialExecutor implements Executor {
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Worker that runs tasks from {@link #queue} until it is empty. */
|
/** Worker that runs tasks from {@link #queue} until it is empty. */
|
||||||
|
|
||||||
private final class QueueWorker implements Runnable {
|
private final class QueueWorker implements Runnable {
|
||||||
|
Runnable task;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
try {
|
try {
|
||||||
|
@ -193,7 +198,6 @@ final class SequentialExecutor implements Executor {
|
||||||
boolean hasSetRunning = false;
|
boolean hasSetRunning = false;
|
||||||
try {
|
try {
|
||||||
while (true) {
|
while (true) {
|
||||||
Runnable task;
|
|
||||||
synchronized (queue) {
|
synchronized (queue) {
|
||||||
// Choose whether this thread will run or not after acquiring the lock on the first
|
// Choose whether this thread will run or not after acquiring the lock on the first
|
||||||
// iteration
|
// iteration
|
||||||
|
@ -224,6 +228,8 @@ final class SequentialExecutor implements Executor {
|
||||||
task.run();
|
task.run();
|
||||||
} catch (RuntimeException e) {
|
} catch (RuntimeException e) {
|
||||||
log.log(Level.SEVERE, "Exception while executing runnable " + task, e);
|
log.log(Level.SEVERE, "Exception while executing runnable " + task, e);
|
||||||
|
} finally {
|
||||||
|
task = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
|
@ -235,5 +241,20 @@ final class SequentialExecutor implements Executor {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("GuardedBy")
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
Runnable currentlyRunning = task;
|
||||||
|
if (currentlyRunning != null) {
|
||||||
|
return "SequentialExecutorWorker{running=" + currentlyRunning + "}";
|
||||||
|
}
|
||||||
|
return "SequentialExecutorWorker{state=" + workerRunningState + "}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "SequentialExecutor@" + identityHashCode(this) + "{" + executor + "}";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,7 +31,6 @@ import static com.google.common.util.concurrent.Service.State.STOPPING;
|
||||||
import static com.google.common.util.concurrent.Service.State.TERMINATED;
|
import static com.google.common.util.concurrent.Service.State.TERMINATED;
|
||||||
import static java.util.concurrent.TimeUnit.MILLISECONDS;
|
import static java.util.concurrent.TimeUnit.MILLISECONDS;
|
||||||
|
|
||||||
import com.google.common.annotations.Beta;
|
|
||||||
import com.google.common.annotations.GwtIncompatible;
|
import com.google.common.annotations.GwtIncompatible;
|
||||||
import com.google.common.base.Function;
|
import com.google.common.base.Function;
|
||||||
import com.google.common.base.MoreObjects;
|
import com.google.common.base.MoreObjects;
|
||||||
|
@ -40,7 +39,6 @@ import com.google.common.collect.Collections2;
|
||||||
import com.google.common.collect.ImmutableCollection;
|
import com.google.common.collect.ImmutableCollection;
|
||||||
import com.google.common.collect.ImmutableList;
|
import com.google.common.collect.ImmutableList;
|
||||||
import com.google.common.collect.ImmutableMap;
|
import com.google.common.collect.ImmutableMap;
|
||||||
import com.google.common.collect.ImmutableMultimap;
|
|
||||||
import com.google.common.collect.ImmutableSet;
|
import com.google.common.collect.ImmutableSet;
|
||||||
import com.google.common.collect.ImmutableSetMultimap;
|
import com.google.common.collect.ImmutableSetMultimap;
|
||||||
import com.google.common.collect.Lists;
|
import com.google.common.collect.Lists;
|
||||||
|
@ -51,9 +49,6 @@ import com.google.common.collect.Multiset;
|
||||||
import com.google.common.collect.Ordering;
|
import com.google.common.collect.Ordering;
|
||||||
import com.google.common.collect.SetMultimap;
|
import com.google.common.collect.SetMultimap;
|
||||||
import com.google.common.util.concurrent.Service.State;
|
import com.google.common.util.concurrent.Service.State;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
import java.lang.ref.WeakReference;
|
import java.lang.ref.WeakReference;
|
||||||
import java.time.Duration;
|
import java.time.Duration;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
@ -121,9 +116,8 @@ import java.util.logging.Logger;
|
||||||
* @author Luke Sandberg
|
* @author Luke Sandberg
|
||||||
* @since 14.0
|
* @since 14.0
|
||||||
*/
|
*/
|
||||||
@Beta
|
|
||||||
@GwtIncompatible
|
@GwtIncompatible
|
||||||
public final class ServiceManager {
|
public final class ServiceManager implements ServiceManagerBridge {
|
||||||
private static final Logger logger = Logger.getLogger(ServiceManager.class.getName());
|
private static final Logger logger = Logger.getLogger(ServiceManager.class.getName());
|
||||||
private static final ListenerCallQueue.Event<Listener> HEALTHY_EVENT =
|
private static final ListenerCallQueue.Event<Listener> HEALTHY_EVENT =
|
||||||
new ListenerCallQueue.Event<Listener>() {
|
new ListenerCallQueue.Event<Listener>() {
|
||||||
|
@ -159,7 +153,6 @@ public final class ServiceManager {
|
||||||
* @author Luke Sandberg
|
* @author Luke Sandberg
|
||||||
* @since 15.0 (present as an interface in 14.0)
|
* @since 15.0 (present as an interface in 14.0)
|
||||||
*/
|
*/
|
||||||
@Beta // Should come out of Beta when ServiceManager does
|
|
||||||
public abstract static class Listener {
|
public abstract static class Listener {
|
||||||
/**
|
/**
|
||||||
* Called when the service initially becomes healthy.
|
* Called when the service initially becomes healthy.
|
||||||
|
@ -245,8 +238,9 @@ public final class ServiceManager {
|
||||||
* during {@code Executor.execute} (e.g., a {@code RejectedExecutionException}) will be caught and
|
* during {@code Executor.execute} (e.g., a {@code RejectedExecutionException}) will be caught and
|
||||||
* logged.
|
* logged.
|
||||||
*
|
*
|
||||||
* <p>For fast, lightweight listeners that would be safe to execute in any thread, consider
|
* <p>When selecting an executor, note that {@code directExecutor} is dangerous in some cases. See
|
||||||
* calling {@link #addListener(Listener)}.
|
* the discussion in the {@link ListenableFuture#addListener ListenableFuture.addListener}
|
||||||
|
* documentation.
|
||||||
*
|
*
|
||||||
* @param listener the listener to run when the manager changes state
|
* @param listener the listener to run when the manager changes state
|
||||||
* @param executor the executor in which the listeners callback methods will be run.
|
* @param executor the executor in which the listeners callback methods will be run.
|
||||||
|
@ -255,26 +249,6 @@ public final class ServiceManager {
|
||||||
state.addListener(listener, 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}.
|
|
||||||
*
|
|
||||||
* <p>{@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.
|
|
||||||
*
|
|
||||||
* <p>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
|
* 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}.
|
* is only valid to call this method if all of the services are {@linkplain State#NEW new}.
|
||||||
|
@ -283,7 +257,6 @@ public final class ServiceManager {
|
||||||
* @throws IllegalStateException if any of the Services are not {@link State#NEW new} when the
|
* @throws IllegalStateException if any of the Services are not {@link State#NEW new} when the
|
||||||
* method is called.
|
* method is called.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
public ServiceManager startAsync() {
|
public ServiceManager startAsync() {
|
||||||
for (Service service : services) {
|
for (Service service : services) {
|
||||||
State state = service.state();
|
State state = service.state();
|
||||||
|
@ -353,7 +326,6 @@ public final class ServiceManager {
|
||||||
*
|
*
|
||||||
* @return this
|
* @return this
|
||||||
*/
|
*/
|
||||||
|
|
||||||
public ServiceManager stopAsync() {
|
public ServiceManager stopAsync() {
|
||||||
for (Service service : services) {
|
for (Service service : services) {
|
||||||
service.stopAsync();
|
service.stopAsync();
|
||||||
|
@ -363,8 +335,8 @@ public final class ServiceManager {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Waits for the all the services to reach a terminal state. After this method returns all
|
* 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
|
* services will either be {@linkplain State#TERMINATED terminated} or {@linkplain
|
||||||
* Service.State#FAILED failed}.
|
* State#FAILED failed}.
|
||||||
*/
|
*/
|
||||||
public void awaitStopped() {
|
public void awaitStopped() {
|
||||||
state.awaitStopped();
|
state.awaitStopped();
|
||||||
|
@ -372,8 +344,8 @@ public final class ServiceManager {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Waits for the all the services to reach a terminal state for no more than the given time. After
|
* 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
|
* this method returns all services will either be {@linkplain State#TERMINATED
|
||||||
* terminated} or {@linkplain Service.State#FAILED failed}.
|
* terminated} or {@linkplain State#FAILED failed}.
|
||||||
*
|
*
|
||||||
* @param timeout the maximum time to wait
|
* @param timeout the maximum time to wait
|
||||||
* @throws TimeoutException if not all of the services have stopped within the deadline
|
* @throws TimeoutException if not all of the services have stopped within the deadline
|
||||||
|
@ -385,8 +357,8 @@ public final class ServiceManager {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Waits for the all the services to reach a terminal state for no more than the given time. After
|
* 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
|
* this method returns all services will either be {@linkplain State#TERMINATED
|
||||||
* terminated} or {@linkplain Service.State#FAILED failed}.
|
* terminated} or {@linkplain State#FAILED failed}.
|
||||||
*
|
*
|
||||||
* @param timeout the maximum time to wait
|
* @param timeout the maximum time to wait
|
||||||
* @param unit the time unit of the timeout argument
|
* @param unit the time unit of the timeout argument
|
||||||
|
@ -417,8 +389,11 @@ public final class ServiceManager {
|
||||||
*
|
*
|
||||||
* <p>N.B. This snapshot is guaranteed to be consistent, i.e. the set of states returned will
|
* <p>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.
|
* correspond to a point in time view of the services.
|
||||||
|
*
|
||||||
|
* @since 29.0 (present with return type {@code ImmutableMultimap} since 14.0)
|
||||||
*/
|
*/
|
||||||
public ImmutableMultimap<State, Service> servicesByState() {
|
@Override
|
||||||
|
public ImmutableSetMultimap<State, Service> servicesByState() {
|
||||||
return state.servicesByState();
|
return state.servicesByState();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -479,7 +454,6 @@ public final class ServiceManager {
|
||||||
*/
|
*/
|
||||||
final Monitor.Guard awaitHealthGuard = new AwaitHealthGuard();
|
final Monitor.Guard awaitHealthGuard = new AwaitHealthGuard();
|
||||||
|
|
||||||
|
|
||||||
final class AwaitHealthGuard extends Monitor.Guard {
|
final class AwaitHealthGuard extends Monitor.Guard {
|
||||||
AwaitHealthGuard() {
|
AwaitHealthGuard() {
|
||||||
super(ServiceManagerState.this.monitor);
|
super(ServiceManagerState.this.monitor);
|
||||||
|
@ -498,7 +472,6 @@ public final class ServiceManager {
|
||||||
/** Controls how long to wait for all services to reach a terminal state. */
|
/** Controls how long to wait for all services to reach a terminal state. */
|
||||||
final Monitor.Guard stoppedGuard = new StoppedGuard();
|
final Monitor.Guard stoppedGuard = new StoppedGuard();
|
||||||
|
|
||||||
|
|
||||||
final class StoppedGuard extends Monitor.Guard {
|
final class StoppedGuard extends Monitor.Guard {
|
||||||
StoppedGuard() {
|
StoppedGuard() {
|
||||||
super(ServiceManagerState.this.monitor);
|
super(ServiceManagerState.this.monitor);
|
||||||
|
@ -615,7 +588,7 @@ public final class ServiceManager {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ImmutableMultimap<State, Service> servicesByState() {
|
ImmutableSetMultimap<State, Service> servicesByState() {
|
||||||
ImmutableSetMultimap.Builder<State, Service> builder = ImmutableSetMultimap.builder();
|
ImmutableSetMultimap.Builder<State, Service> builder = ImmutableSetMultimap.builder();
|
||||||
monitor.enter();
|
monitor.enter();
|
||||||
try {
|
try {
|
||||||
|
|
|
@ -0,0 +1,31 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2020 The Guava Authors
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF 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.ImmutableMultimap;
|
||||||
|
import com.google.common.util.concurrent.Service.State;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Superinterface of {@link ServiceManager} to introduce a bridge method for {@code
|
||||||
|
* servicesByState()}, to ensure binary compatibility with older Guava versions that specified
|
||||||
|
* {@code servicesByState()} to return {@code ImmutableMultimap}.
|
||||||
|
*/
|
||||||
|
@GwtIncompatible
|
||||||
|
interface ServiceManagerBridge {
|
||||||
|
ImmutableMultimap<State, Service> servicesByState();
|
||||||
|
}
|
|
@ -31,6 +31,11 @@ abstract class SmoothRateLimiter extends RateLimiter {
|
||||||
* compute, for an incoming request, the appropriate throttle time, and make the calling thread
|
* compute, for an incoming request, the appropriate throttle time, and make the calling thread
|
||||||
* wait as much.
|
* wait as much.
|
||||||
*
|
*
|
||||||
|
* The primary feature of a RateLimiter is its "stable rate", the maximum rate that it should
|
||||||
|
* allow in normal conditions. This is enforced by "throttling" incoming requests as needed. For
|
||||||
|
* example, we could compute the appropriate throttle time for an incoming request, and make the
|
||||||
|
* calling thread wait for that time.
|
||||||
|
*
|
||||||
* The simplest way to maintain a rate of QPS is to keep the timestamp of the last granted
|
* 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
|
* 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
|
* QPS=5 (5 tokens per second), if we ensure that a request isn't granted earlier than 200ms after
|
||||||
|
|
|
@ -65,7 +65,6 @@ public final class UncaughtExceptionHandlers {
|
||||||
@Override
|
@Override
|
||||||
public void uncaughtException(Thread t, Throwable e) {
|
public void uncaughtException(Thread t, Throwable e) {
|
||||||
try {
|
try {
|
||||||
// cannot use FormattingLogger due to a dependency loop
|
|
||||||
logger.log(
|
logger.log(
|
||||||
SEVERE, String.format(Locale.ROOT, "Caught an exception in %s. Shutting down.", t), e);
|
SEVERE, String.format(Locale.ROOT, "Caught an exception in %s. Shutting down.", t), e);
|
||||||
} catch (Throwable errorInLogging) {
|
} catch (Throwable errorInLogging) {
|
||||||
|
|
|
@ -14,6 +14,7 @@
|
||||||
|
|
||||||
package com.google.common.util.concurrent;
|
package com.google.common.util.concurrent;
|
||||||
|
|
||||||
|
import static com.google.common.base.Verify.verify;
|
||||||
import static com.google.common.util.concurrent.Internal.toNanosSaturated;
|
import static com.google.common.util.concurrent.Internal.toNanosSaturated;
|
||||||
import static java.util.concurrent.TimeUnit.NANOSECONDS;
|
import static java.util.concurrent.TimeUnit.NANOSECONDS;
|
||||||
|
|
||||||
|
@ -21,17 +22,18 @@ import com.google.common.annotations.Beta;
|
||||||
import com.google.common.annotations.GwtCompatible;
|
import com.google.common.annotations.GwtCompatible;
|
||||||
import com.google.common.annotations.GwtIncompatible;
|
import com.google.common.annotations.GwtIncompatible;
|
||||||
import com.google.common.base.Preconditions;
|
import com.google.common.base.Preconditions;
|
||||||
|
|
||||||
import java.time.Duration;
|
import java.time.Duration;
|
||||||
import java.util.concurrent.BlockingQueue;
|
import java.util.concurrent.BlockingQueue;
|
||||||
import java.util.concurrent.CancellationException;
|
import java.util.concurrent.CancellationException;
|
||||||
import java.util.concurrent.CountDownLatch;
|
import java.util.concurrent.CountDownLatch;
|
||||||
import java.util.concurrent.ExecutionException;
|
import java.util.concurrent.ExecutionException;
|
||||||
|
import java.util.concurrent.ExecutorService;
|
||||||
import java.util.concurrent.Future;
|
import java.util.concurrent.Future;
|
||||||
import java.util.concurrent.Semaphore;
|
import java.util.concurrent.Semaphore;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.concurrent.TimeoutException;
|
import java.util.concurrent.TimeoutException;
|
||||||
import java.util.concurrent.locks.Condition;
|
import java.util.concurrent.locks.Condition;
|
||||||
|
import java.util.concurrent.locks.Lock;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Utilities for treating interruptible operations as uninterruptible. In all cases, if a thread is
|
* Utilities for treating interruptible operations as uninterruptible. In all cases, if a thread is
|
||||||
|
@ -73,7 +75,6 @@ public final class Uninterruptibles {
|
||||||
*
|
*
|
||||||
* @since 28.0
|
* @since 28.0
|
||||||
*/
|
*/
|
||||||
// TODO(cpovirk): Consider being more strict.
|
|
||||||
@GwtIncompatible // concurrency
|
@GwtIncompatible // concurrency
|
||||||
@Beta
|
@Beta
|
||||||
public static boolean awaitUninterruptibly(CountDownLatch latch, Duration timeout) {
|
public static boolean awaitUninterruptibly(CountDownLatch latch, Duration timeout) {
|
||||||
|
@ -84,7 +85,6 @@ public final class Uninterruptibles {
|
||||||
* Invokes {@code latch.}{@link CountDownLatch#await(long, TimeUnit) await(timeout, unit)}
|
* Invokes {@code latch.}{@link CountDownLatch#await(long, TimeUnit) await(timeout, unit)}
|
||||||
* uninterruptibly.
|
* uninterruptibly.
|
||||||
*/
|
*/
|
||||||
// TODO(cpovirk): Consider being more strict.
|
|
||||||
@GwtIncompatible // concurrency
|
@GwtIncompatible // concurrency
|
||||||
@SuppressWarnings("GoodTime") // should accept a java.time.Duration
|
@SuppressWarnings("GoodTime") // should accept a java.time.Duration
|
||||||
public static boolean awaitUninterruptibly(CountDownLatch latch, long timeout, TimeUnit unit) {
|
public static boolean awaitUninterruptibly(CountDownLatch latch, long timeout, TimeUnit unit) {
|
||||||
|
@ -228,7 +228,6 @@ public final class Uninterruptibles {
|
||||||
* @throws ExecutionException if the computation threw an exception
|
* @throws ExecutionException if the computation threw an exception
|
||||||
* @throws CancellationException if the computation was cancelled
|
* @throws CancellationException if the computation was cancelled
|
||||||
*/
|
*/
|
||||||
|
|
||||||
public static <V> V getUninterruptibly(Future<V> future) throws ExecutionException {
|
public static <V> V getUninterruptibly(Future<V> future) throws ExecutionException {
|
||||||
boolean interrupted = false;
|
boolean interrupted = false;
|
||||||
try {
|
try {
|
||||||
|
@ -265,7 +264,6 @@ public final class Uninterruptibles {
|
||||||
* @throws TimeoutException if the wait timed out
|
* @throws TimeoutException if the wait timed out
|
||||||
* @since 28.0
|
* @since 28.0
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@GwtIncompatible // java.time.Duration
|
@GwtIncompatible // java.time.Duration
|
||||||
@Beta
|
@Beta
|
||||||
public static <V> V getUninterruptibly(Future<V> future, Duration timeout)
|
public static <V> V getUninterruptibly(Future<V> future, Duration timeout)
|
||||||
|
@ -291,7 +289,6 @@ public final class Uninterruptibles {
|
||||||
* @throws CancellationException if the computation was cancelled
|
* @throws CancellationException if the computation was cancelled
|
||||||
* @throws TimeoutException if the wait timed out
|
* @throws TimeoutException if the wait timed out
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@GwtIncompatible // TODO
|
@GwtIncompatible // TODO
|
||||||
@SuppressWarnings("GoodTime") // should accept a java.time.Duration
|
@SuppressWarnings("GoodTime") // should accept a java.time.Duration
|
||||||
public static <V> V getUninterruptibly(Future<V> future, long timeout, TimeUnit unit)
|
public static <V> V getUninterruptibly(Future<V> future, long timeout, TimeUnit unit)
|
||||||
|
@ -471,6 +468,104 @@ public final class Uninterruptibles {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invokes {@code lock.}{@link Lock#tryLock(long, TimeUnit) tryLock(timeout, unit)}
|
||||||
|
* uninterruptibly.
|
||||||
|
*
|
||||||
|
* @since 30.0
|
||||||
|
*/
|
||||||
|
@GwtIncompatible // concurrency
|
||||||
|
@Beta
|
||||||
|
public static boolean tryLockUninterruptibly(Lock lock, Duration timeout) {
|
||||||
|
return tryLockUninterruptibly(lock, toNanosSaturated(timeout), TimeUnit.NANOSECONDS);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invokes {@code lock.}{@link Lock#tryLock(long, TimeUnit) tryLock(timeout, unit)}
|
||||||
|
* uninterruptibly.
|
||||||
|
*
|
||||||
|
* @since 30.0
|
||||||
|
*/
|
||||||
|
@GwtIncompatible // concurrency
|
||||||
|
@SuppressWarnings("GoodTime") // should accept a java.time.Duration
|
||||||
|
public static boolean tryLockUninterruptibly(Lock lock, long timeout, TimeUnit unit) {
|
||||||
|
boolean interrupted = false;
|
||||||
|
try {
|
||||||
|
long remainingNanos = unit.toNanos(timeout);
|
||||||
|
long end = System.nanoTime() + remainingNanos;
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
try {
|
||||||
|
return lock.tryLock(remainingNanos, NANOSECONDS);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
interrupted = true;
|
||||||
|
remainingNanos = end - System.nanoTime();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
if (interrupted) {
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invokes {@code executor.}{@link ExecutorService#awaitTermination(long, TimeUnit)
|
||||||
|
* awaitTermination(long, TimeUnit)} uninterruptibly with no timeout.
|
||||||
|
*
|
||||||
|
* @since 30.0
|
||||||
|
*/
|
||||||
|
@Beta
|
||||||
|
@GwtIncompatible // concurrency
|
||||||
|
public static void awaitTerminationUninterruptibly(ExecutorService executor) {
|
||||||
|
// TODO(cpovirk): We could optimize this to avoid calling nanoTime() at all.
|
||||||
|
verify(awaitTerminationUninterruptibly(executor, Long.MAX_VALUE, NANOSECONDS));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invokes {@code executor.}{@link ExecutorService#awaitTermination(long, TimeUnit)
|
||||||
|
* awaitTermination(long, TimeUnit)} uninterruptibly.
|
||||||
|
*
|
||||||
|
* @since 30.0
|
||||||
|
*/
|
||||||
|
@Beta
|
||||||
|
@GwtIncompatible // concurrency
|
||||||
|
public static boolean awaitTerminationUninterruptibly(
|
||||||
|
ExecutorService executor, Duration timeout) {
|
||||||
|
return awaitTerminationUninterruptibly(executor, toNanosSaturated(timeout), NANOSECONDS);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invokes {@code executor.}{@link ExecutorService#awaitTermination(long, TimeUnit)
|
||||||
|
* awaitTermination(long, TimeUnit)} uninterruptibly.
|
||||||
|
*
|
||||||
|
* @since 30.0
|
||||||
|
*/
|
||||||
|
@Beta
|
||||||
|
@GwtIncompatible // concurrency
|
||||||
|
@SuppressWarnings("GoodTime")
|
||||||
|
public static boolean awaitTerminationUninterruptibly(
|
||||||
|
ExecutorService executor, long timeout, TimeUnit unit) {
|
||||||
|
boolean interrupted = false;
|
||||||
|
try {
|
||||||
|
long remainingNanos = unit.toNanos(timeout);
|
||||||
|
long end = System.nanoTime() + remainingNanos;
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
try {
|
||||||
|
return executor.awaitTermination(remainingNanos, NANOSECONDS);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
interrupted = true;
|
||||||
|
remainingNanos = end - System.nanoTime();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
if (interrupted) {
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// TODO(user): Add support for waitUninterruptibly.
|
// TODO(user): Add support for waitUninterruptibly.
|
||||||
|
|
||||||
private Uninterruptibles() {}
|
private Uninterruptibles() {}
|
||||||
|
|
Loading…
Reference in a new issue