Compare commits
7 commits
Author | SHA1 | Date | |
---|---|---|---|
ecd1e41452 | |||
0992e12c50 | |||
c50bb3f8b4 | |||
41a0c248a6 | |||
e05ba8ce33 | |||
e07804a8a8 | |||
bc85508c04 |
108 changed files with 16050 additions and 1807 deletions
|
@ -1,3 +0,0 @@
|
|||
language: java
|
||||
jdk:
|
||||
- openjdk11
|
|
@ -1,15 +1,15 @@
|
|||
# xbib Guava
|
||||
# Guava - simplified and reduced
|
||||
|
||||
This is xbib Guava, a build of Google Guava with the following differences to the
|
||||
original [Google Guava Library](https://github.com/google/guava):
|
||||
|
||||
- forked master branch on November 21, 2019 ("28.1+")
|
||||
- forked master branch on November 21, 2019 (28.1+)
|
||||
- removed all external annotations, so this library does not have any dependencies
|
||||
- removed duplicate JDK classes (LongAdder, Striped64)
|
||||
- compiled under Java 11 and with a module info for JPMS (module org.xbib.guava)
|
||||
- removed duplicate JDK11 classes (LongAdder, Striped64)
|
||||
- replaced sun.misc.Unsafe dependent classes with safe versions (LongAdders, UnsignedBytes, LittleEndianByteArray, AbstractFuture)
|
||||
- the guava failureaccess dependency is included
|
||||
- removed listenablefuture empty dependency hack
|
||||
- compiled under and for Java 11 and with a module info for JPMS (module org.xbib.guava)
|
||||
- Gradle as build system
|
||||
|
||||
All credits belong to the original authors
|
17
build.gradle
17
build.gradle
|
@ -1,15 +1,16 @@
|
|||
plugins {
|
||||
id "de.marcphilipp.nexus-publish" version "0.4.0"
|
||||
id "io.codearte.nexus-staging" version "0.21.1"
|
||||
id 'maven-publish'
|
||||
id 'signing'
|
||||
id "io.github.gradle-nexus.publish-plugin" version "2.0.0-rc-1"
|
||||
}
|
||||
|
||||
wrapper {
|
||||
gradleVersion = "${project.property('gradle.wrapper.version')}"
|
||||
gradleVersion = libs.versions.gradle.get()
|
||||
distributionType = Wrapper.DistributionType.ALL
|
||||
}
|
||||
|
||||
ext {
|
||||
user = 'jprante'
|
||||
user = 'joerg'
|
||||
name = 'guava'
|
||||
description = 'Guava implementation with named modules for Java 11+'
|
||||
inceptionYear = '2019'
|
||||
|
@ -24,8 +25,10 @@ ext {
|
|||
}
|
||||
|
||||
apply plugin: 'java-library'
|
||||
apply from: rootProject.file('gradle/ide/idea.gradle')
|
||||
|
||||
apply from: rootProject.file('gradle/repositories/maven.gradle')
|
||||
apply from: rootProject.file('gradle/compile/java.gradle')
|
||||
apply from: rootProject.file('gradle/test/junit5.gradle')
|
||||
apply from: rootProject.file('gradle/publishing/publication.gradle')
|
||||
apply from: rootProject.file('gradle/publishing/sonatype.gradle')
|
||||
apply from: rootProject.file('gradle/publish/maven.gradle')
|
||||
apply from: rootProject.file('gradle/publish/sonatype.gradle')
|
||||
apply from: rootProject.file('gradle/publish/forgejo.gradle')
|
||||
|
|
|
@ -1,177 +0,0 @@
|
|||
<?xml version="1.0"?>
|
||||
<!DOCTYPE module PUBLIC "-//Puppy Crawl//DTD Check Configuration 1.3//EN"
|
||||
"https://checkstyle.org/dtds/configuration_1_3.dtd">
|
||||
<module name="Checker">
|
||||
<module name="SuppressionFilter">
|
||||
<property name="file" value="${config_loc}/suppressions.xml"/>
|
||||
</module>
|
||||
|
||||
<module name="SuppressWarningsFilter"/>
|
||||
|
||||
<module name="SeverityMatchFilter">
|
||||
<property name="severity" value="info"/>
|
||||
<property name="acceptOnMatch" value="false"/>
|
||||
</module>
|
||||
|
||||
<module name="FileTabCharacter">
|
||||
<property name="eachLine" value="true"/>
|
||||
</module>
|
||||
|
||||
<module name="LineLength">
|
||||
<property name="max" value="120"/>
|
||||
<property name="ignorePattern" value="^[ \t]*\*.*@.*$"/>
|
||||
</module>
|
||||
|
||||
<module name="TreeWalker">
|
||||
<property name="tabWidth" value="4"/>
|
||||
|
||||
<module name="SuppressWarningsHolder"/>
|
||||
|
||||
<module name="Indentation">
|
||||
<property name="forceStrictCondition" value="true"/>
|
||||
</module>
|
||||
|
||||
<module name="ConstantName"/>
|
||||
|
||||
<module name="FinalParameters">
|
||||
<property name="tokens" value="METHOD_DEF, CTOR_DEF, LITERAL_CATCH, FOR_EACH_CLAUSE"/>
|
||||
</module>
|
||||
|
||||
<module name="FinalLocalVariable">
|
||||
<property name="validateEnhancedForLoopVariable" value="true"/>
|
||||
</module>
|
||||
|
||||
<module name="LocalFinalVariableName"/>
|
||||
|
||||
<module name="LocalVariableName"/>
|
||||
|
||||
<module name="MemberName">
|
||||
<property name="format" value="^[a-z][a-zA-Z0-9_]*$"/>
|
||||
</module>
|
||||
|
||||
<module name="MethodName"/>
|
||||
|
||||
<module name="PackageName"/>
|
||||
|
||||
<module name="ParameterName"/>
|
||||
|
||||
<module name="StaticVariableName"/>
|
||||
|
||||
<module name="TypeName"/>
|
||||
|
||||
<module name="RedundantImport"/>
|
||||
|
||||
<module name="UnusedImports"/>
|
||||
|
||||
<module name="MethodLength">
|
||||
<property name="tokens" value="METHOD_DEF"/>
|
||||
<property name="max" value="100"/>
|
||||
</module>
|
||||
|
||||
<module name="EmptyForInitializerPad"/>
|
||||
|
||||
<module name="MethodParamPad"/>
|
||||
|
||||
<module name="NoWhitespaceBefore"/>
|
||||
|
||||
<module name="WhitespaceAfter">
|
||||
<property name="tokens" value="COMMA, SEMI, LITERAL_IF, LITERAL_ELSE, LITERAL_WHILE, LITERAL_DO, LITERAL_FOR, DO_WHILE"/>
|
||||
</module>
|
||||
|
||||
<module name="NoWhitespaceAfter">
|
||||
<property name="tokens" value="INC, DEC, UNARY_MINUS, UNARY_PLUS, BNOT, LNOT, DOT, TYPECAST, ARRAY_DECLARATOR, INDEX_OP, METHOD_REF"/>
|
||||
<property name="allowLineBreaks" value="false"/>
|
||||
</module>
|
||||
|
||||
<module name="WhitespaceAround">
|
||||
<property name="allowEmptyLambdas" value="true"/>
|
||||
</module>
|
||||
|
||||
<module name="SingleSpaceSeparator"/>
|
||||
|
||||
<module name="OperatorWrap">
|
||||
<property name="option" value="eol"/>
|
||||
</module>
|
||||
|
||||
<module name="NeedBraces"/>
|
||||
|
||||
<module name="ParenPad"/>
|
||||
|
||||
<module name="TypecastParenPad"/>
|
||||
|
||||
<module name="ModifierOrder"/>
|
||||
|
||||
<module name="RedundantModifier"/>
|
||||
|
||||
<module name="NestedTryDepth">
|
||||
<property name="max" value="2"/>
|
||||
</module>
|
||||
|
||||
<module name="CovariantEquals"/>
|
||||
|
||||
<module name="LeftCurly">
|
||||
<property name="option" value="nl"/>
|
||||
</module>
|
||||
|
||||
<module name="RightCurly">
|
||||
<property name="option" value="alone"/>
|
||||
<property name="tokens" value="LITERAL_TRY, LITERAL_CATCH, LITERAL_FINALLY, LITERAL_IF, LITERAL_ELSE, CLASS_DEF, METHOD_DEF, CTOR_DEF, LITERAL_FOR, LITERAL_WHILE, LITERAL_DO, STATIC_INIT, INSTANCE_INIT"/>
|
||||
</module>
|
||||
|
||||
<module name="EmptyStatement"/>
|
||||
|
||||
<module name="EqualsHashCode"/>
|
||||
|
||||
<module name="DefaultComesLast"/>
|
||||
|
||||
<module name="SimplifyBooleanExpression"/>
|
||||
|
||||
<module name="SimplifyBooleanReturn"/>
|
||||
|
||||
<module name="StringLiteralEquality"/>
|
||||
|
||||
<module name="PackageDeclaration"/>
|
||||
|
||||
<module name="FallThrough"/>
|
||||
|
||||
<module name="FinalClass"/>
|
||||
|
||||
<module name="MutableException"/>
|
||||
|
||||
<module name="EmptyLineSeparator">
|
||||
<property name="allowNoEmptyLineBetweenFields" value="true"/>
|
||||
<property name="tokens" value="IMPORT, CLASS_DEF, INTERFACE_DEF, ENUM_DEF, STATIC_INIT, INSTANCE_INIT, METHOD_DEF, CTOR_DEF"/>
|
||||
</module>
|
||||
|
||||
<module name="TodoComment">
|
||||
<property name="severity" value="info"/>
|
||||
<property name="format" value="TODO"/>
|
||||
</module>
|
||||
|
||||
<module name="UpperEll"/>
|
||||
|
||||
<module name="IllegalType">
|
||||
<property name="legalAbstractClassNames"
|
||||
value="AbstractBeanDefinition, AbstractEntry"/>
|
||||
<property name="illegalClassNames"
|
||||
value="java.util.GregorianCalendar, java.util.Vector"/>
|
||||
</module>
|
||||
|
||||
<module name="DescendantToken">
|
||||
<property name="tokens" value="LITERAL_ASSERT"/>
|
||||
<property name="limitedTokens"
|
||||
value="ASSIGN,DEC,INC,POST_DEC,POST_INC,PLUS_ASSIGN,MINUS_ASSIGN,STAR_ASSIGN,DIV_ASSIGN,MOD_ASSIGN,BSR_ASSIGN,SR_ASSIGN,SL_ASSIGN,BAND_ASSIGN,BXOR_ASSIGN,BOR_ASSIGN,METHOD_CALL"/>
|
||||
<property name="maximumNumber" value="2"/>
|
||||
</module>
|
||||
|
||||
<module name="Regexp">
|
||||
<property name="format" value="[ \t]+$"/>
|
||||
<property name="illegalPattern" value="true"/>
|
||||
<property name="message" value="Trailing whitespace"/>
|
||||
</module>
|
||||
|
||||
<module name="JavadocMethod">
|
||||
<property name="allowUndeclaredRTE" value="true"/>
|
||||
</module>
|
||||
</module>
|
||||
</module>
|
|
@ -1,7 +0,0 @@
|
|||
<?xml version="1.0"?>
|
||||
<!DOCTYPE suppressions PUBLIC
|
||||
"-//Puppy Crawl//DTD Suppressions 1.0//EN"
|
||||
"https://checkstyle.org/dtds/suppressions_1_0.dtd">
|
||||
<suppressions>
|
||||
<suppress files=".*generated-src.*" checks="."/>
|
||||
</suppressions>
|
|
@ -1,5 +1,3 @@
|
|||
group = org.xbib
|
||||
name = guava
|
||||
version = 29.0
|
||||
|
||||
gradle.wrapper.version = 6.6.1
|
||||
version = 30.2
|
||||
|
|
|
@ -2,36 +2,29 @@
|
|||
apply plugin: 'java-library'
|
||||
|
||||
java {
|
||||
toolchain {
|
||||
languageVersion = JavaLanguageVersion.of(21)
|
||||
}
|
||||
modularity.inferModulePath.set(true)
|
||||
}
|
||||
|
||||
compileJava {
|
||||
sourceCompatibility = JavaVersion.VERSION_11
|
||||
targetCompatibility = JavaVersion.VERSION_11
|
||||
}
|
||||
|
||||
compileTestJava {
|
||||
sourceCompatibility = JavaVersion.VERSION_11
|
||||
targetCompatibility = JavaVersion.VERSION_11
|
||||
withSourcesJar()
|
||||
withJavadocJar()
|
||||
}
|
||||
|
||||
jar {
|
||||
manifest {
|
||||
attributes('Implementation-Title': project.name)
|
||||
attributes('Implementation-Version': project.version)
|
||||
attributes('Implementation-Vendor': 'Jörg Prante')
|
||||
}
|
||||
}
|
||||
|
||||
task sourcesJar(type: Jar, dependsOn: classes) {
|
||||
classifier 'sources'
|
||||
from sourceSets.main.allSource
|
||||
tasks.withType(JavaCompile) {
|
||||
options.fork = true
|
||||
options.forkOptions.jvmArgs += ['-Duser.language=en','-Duser.country=US']
|
||||
options.compilerArgs.add('-Xlint:-requires-transitive-automatic')
|
||||
options.compilerArgs.add('-Xlint:deprecation')
|
||||
options.encoding = 'UTF-8'
|
||||
}
|
||||
|
||||
task javadocJar(type: Jar, dependsOn: javadoc) {
|
||||
classifier 'javadoc'
|
||||
tasks.withType(Javadoc) {
|
||||
options.addStringOption('Xdoclint:none', '-quiet')
|
||||
options.encoding = 'UTF-8'
|
||||
}
|
||||
|
||||
artifacts {
|
||||
archives sourcesJar, javadocJar
|
||||
}
|
|
@ -7,7 +7,6 @@ idea {
|
|||
}
|
||||
}
|
||||
|
||||
// if project was not imported via idea pluginw
|
||||
clean {
|
||||
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
|
||||
}
|
16
gradle/publish/forgejo.gradle
Normal file
16
gradle/publish/forgejo.gradle
Normal file
|
@ -0,0 +1,16 @@
|
|||
if (project.hasProperty('forgeJoToken')) {
|
||||
publishing {
|
||||
repositories {
|
||||
maven {
|
||||
url 'https://xbib.org/api/packages/joerg/maven'
|
||||
credentials(HttpHeaderCredentials) {
|
||||
name = "Authorization"
|
||||
value = "token ${project.property('forgeJoToken')}"
|
||||
}
|
||||
authentication {
|
||||
header(HttpHeaderAuthentication)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
27
gradle/publish/ivy.gradle
Normal file
27
gradle/publish/ivy.gradle
Normal file
|
@ -0,0 +1,27 @@
|
|||
apply plugin: 'ivy-publish'
|
||||
|
||||
publishing {
|
||||
repositories {
|
||||
ivy {
|
||||
url = "https://xbib.org/repo"
|
||||
}
|
||||
}
|
||||
publications {
|
||||
ivy(IvyPublication) {
|
||||
from components.java
|
||||
descriptor {
|
||||
license {
|
||||
name = 'The Apache License, Version 2.0'
|
||||
url = 'http://www.apache.org/licenses/LICENSE-2.0.txt'
|
||||
}
|
||||
author {
|
||||
name = 'Jörg Prante'
|
||||
url = 'https://xbib.org/joerg'
|
||||
}
|
||||
descriptor.description {
|
||||
text = rootProject.ext.description
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,13 +1,10 @@
|
|||
|
||||
apply plugin: "de.marcphilipp.nexus-publish"
|
||||
|
||||
publishing {
|
||||
publications {
|
||||
mavenJava(MavenPublication) {
|
||||
"${project.name}"(MavenPublication) {
|
||||
from components.java
|
||||
artifact sourcesJar
|
||||
artifact javadocJar
|
||||
pom {
|
||||
artifactId = project.name
|
||||
name = project.name
|
||||
description = rootProject.ext.description
|
||||
url = rootProject.ext.url
|
||||
|
@ -19,10 +16,10 @@ publishing {
|
|||
}
|
||||
developers {
|
||||
developer {
|
||||
id = 'jprante'
|
||||
id = 'joerg'
|
||||
name = 'Jörg Prante'
|
||||
email = 'joergprante@gmail.com'
|
||||
url = 'https://github.com/jprante'
|
||||
url = 'https://xbib.org/joerg'
|
||||
}
|
||||
}
|
||||
scm {
|
||||
|
@ -49,16 +46,6 @@ publishing {
|
|||
if (project.hasProperty("signing.keyId")) {
|
||||
apply plugin: 'signing'
|
||||
signing {
|
||||
sign publishing.publications.mavenJava
|
||||
}
|
||||
}
|
||||
|
||||
nexusPublishing {
|
||||
repositories {
|
||||
sonatype {
|
||||
username = project.property('ossrhUsername')
|
||||
password = project.property('ossrhPassword')
|
||||
packageGroup = "org.xbib"
|
||||
}
|
||||
sign publishing.publications."${project.name}"
|
||||
}
|
||||
}
|
11
gradle/publish/sonatype.gradle
Normal file
11
gradle/publish/sonatype.gradle
Normal file
|
@ -0,0 +1,11 @@
|
|||
if (project.hasProperty('ossrhUsername') && project.hasProperty('ossrhPassword')) {
|
||||
nexusPublishing {
|
||||
repositories {
|
||||
sonatype {
|
||||
username = project.property('ossrhUsername')
|
||||
password = project.property('ossrhPassword')
|
||||
packageGroup = "org.xbib"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,11 +0,0 @@
|
|||
|
||||
if (project.hasProperty('ossrhUsername') && project.hasProperty('ossrhPassword')) {
|
||||
|
||||
apply plugin: 'io.codearte.nexus-staging'
|
||||
|
||||
nexusStaging {
|
||||
username = project.property('ossrhUsername')
|
||||
password = project.property('ossrhPassword')
|
||||
packageGroup = "org.xbib"
|
||||
}
|
||||
}
|
18
gradle/quality/checkstyle.gradle
Normal file
18
gradle/quality/checkstyle.gradle
Normal file
|
@ -0,0 +1,18 @@
|
|||
|
||||
tasks.withType(Checkstyle) {
|
||||
ignoreFailures = true
|
||||
reports {
|
||||
xml.getRequired().set(true)
|
||||
html.getRequired().set(true)
|
||||
}
|
||||
}
|
||||
|
||||
checkstyle {
|
||||
toolVersion = '10.4'
|
||||
configFile = rootProject.file('gradle/quality/checkstyle.xml')
|
||||
ignoreFailures = true
|
||||
showViolations = false
|
||||
checkstyleMain {
|
||||
source = sourceSets.main.allSource
|
||||
}
|
||||
}
|
333
gradle/quality/checkstyle.xml
Normal file
333
gradle/quality/checkstyle.xml
Normal file
|
@ -0,0 +1,333 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE module PUBLIC
|
||||
"-//Puppy Crawl//DTD Check Configuration 1.3//EN"
|
||||
"http://www.puppycrawl.com/dtds/configuration_1_3.dtd">
|
||||
|
||||
<!-- This is a checkstyle configuration file. For descriptions of
|
||||
what the following rules do, please see the checkstyle configuration
|
||||
page at http://checkstyle.sourceforge.net/config.html -->
|
||||
|
||||
<module name="Checker">
|
||||
|
||||
<module name="BeforeExecutionExclusionFileFilter">
|
||||
<property name="fileNamePattern" value=".*(Example|Test|module-info)(\$.*)?"/>
|
||||
</module>
|
||||
|
||||
<module name="FileTabCharacter">
|
||||
<!-- Checks that there are no tab characters in the file.
|
||||
-->
|
||||
</module>
|
||||
|
||||
<module name="NewlineAtEndOfFile">
|
||||
<property name="lineSeparator" value="lf"/>
|
||||
</module>
|
||||
|
||||
<module name="RegexpSingleline">
|
||||
<!-- Checks that FIXME is not used in comments. TODO is preferred.
|
||||
-->
|
||||
<property name="format" value="((//.*)|(\*.*))FIXME" />
|
||||
<property name="message" value='TODO is preferred to FIXME. e.g. "TODO(johndoe): Refactor when v2 is released."' />
|
||||
</module>
|
||||
|
||||
<module name="RegexpSingleline">
|
||||
<!-- Checks that TODOs are named. (Actually, just that they are followed
|
||||
by an open paren.)
|
||||
-->
|
||||
<property name="format" value="((//.*)|(\*.*))TODO[^(]" />
|
||||
<property name="message" value='All TODOs should be named. e.g. "TODO(johndoe): Refactor when v2 is released."' />
|
||||
</module>
|
||||
|
||||
<module name="JavadocPackage">
|
||||
<!-- Checks that each Java package has a Javadoc file used for commenting.
|
||||
Only allows a package-info.java, not package.html. -->
|
||||
</module>
|
||||
|
||||
<!-- All Java AST specific tests live under TreeWalker module. -->
|
||||
<module name="TreeWalker">
|
||||
|
||||
<!--
|
||||
|
||||
IMPORT CHECKS
|
||||
|
||||
-->
|
||||
|
||||
<module name="RedundantImport">
|
||||
<!-- Checks for redundant import statements. -->
|
||||
<property name="severity" value="error"/>
|
||||
</module>
|
||||
|
||||
<module name="ImportOrder">
|
||||
<!-- Checks for out of order import statements. -->
|
||||
|
||||
<property name="severity" value="warning"/>
|
||||
<!-- <property name="tokens" value="IMPORT, STATIC_IMPORT"/> -->
|
||||
<property name="separated" value="false"/>
|
||||
<property name="groups" value="*"/>
|
||||
<!-- <property name="option" value="above"/> -->
|
||||
<property name="sortStaticImportsAlphabetically" value="true"/>
|
||||
</module>
|
||||
|
||||
<module name="CustomImportOrder">
|
||||
<!-- <property name="customImportOrderRules" value="THIRD_PARTY_PACKAGE###SPECIAL_IMPORTS###STANDARD_JAVA_PACKAGE###STATIC"/> -->
|
||||
<!-- <property name="specialImportsRegExp" value="^javax\."/> -->
|
||||
<!-- <property name="standardPackageRegExp" value="^java\."/> -->
|
||||
<property name="sortImportsInGroupAlphabetically" value="true"/>
|
||||
<property name="separateLineBetweenGroups" value="false"/>
|
||||
</module>
|
||||
|
||||
<!--
|
||||
|
||||
JAVADOC CHECKS
|
||||
|
||||
-->
|
||||
|
||||
<!-- Checks for Javadoc comments. -->
|
||||
<!-- See http://checkstyle.sf.net/config_javadoc.html -->
|
||||
<module name="JavadocMethod">
|
||||
<property name="accessModifiers" value="protected"/>
|
||||
<property name="severity" value="warning"/>
|
||||
<property name="allowMissingParamTags" value="true"/>
|
||||
<property name="allowMissingReturnTag" value="true"/>
|
||||
</module>
|
||||
|
||||
<module name="JavadocType">
|
||||
<property name="scope" value="protected"/>
|
||||
<property name="severity" value="error"/>
|
||||
</module>
|
||||
|
||||
<module name="JavadocStyle">
|
||||
<property name="severity" value="warning"/>
|
||||
</module>
|
||||
|
||||
<!--
|
||||
|
||||
NAMING CHECKS
|
||||
|
||||
-->
|
||||
|
||||
<!-- Item 38 - Adhere to generally accepted naming conventions -->
|
||||
|
||||
<module name="PackageName">
|
||||
<!-- Validates identifiers for package names against the
|
||||
supplied expression. -->
|
||||
<!-- Here the default checkstyle rule restricts package name parts to
|
||||
seven characters, this is not in line with common practice at Google.
|
||||
-->
|
||||
<property name="format" value="^[a-z]+(\.[a-z][a-z0-9]{1,})*$"/>
|
||||
<property name="severity" value="warning"/>
|
||||
</module>
|
||||
|
||||
<module name="TypeNameCheck">
|
||||
<!-- Validates static, final fields against the
|
||||
expression "^[A-Z][a-zA-Z0-9]*$". -->
|
||||
<metadata name="altname" value="TypeName"/>
|
||||
<property name="severity" value="warning"/>
|
||||
</module>
|
||||
|
||||
<module name="ConstantNameCheck">
|
||||
<!-- Validates non-private, static, final fields against the supplied
|
||||
public/package final fields "^[A-Z][A-Z0-9]*(_[A-Z0-9]+)*$". -->
|
||||
<metadata name="altname" value="ConstantName"/>
|
||||
<property name="applyToPublic" value="true"/>
|
||||
<property name="applyToProtected" value="true"/>
|
||||
<property name="applyToPackage" value="true"/>
|
||||
<property name="applyToPrivate" value="false"/>
|
||||
<property name="format" value="^([A-Z][A-Z0-9]*(_[A-Z0-9]+)*|FLAG_.*)$"/>
|
||||
<message key="name.invalidPattern"
|
||||
value="Variable ''{0}'' should be in ALL_CAPS (if it is a constant) or be private (otherwise)."/>
|
||||
<property name="severity" value="warning"/>
|
||||
</module>
|
||||
|
||||
<module name="StaticVariableNameCheck">
|
||||
<!-- Validates static, non-final fields against the supplied
|
||||
expression "^[a-z][a-zA-Z0-9]*_?$". -->
|
||||
<metadata name="altname" value="StaticVariableName"/>
|
||||
<property name="applyToPublic" value="true"/>
|
||||
<property name="applyToProtected" value="true"/>
|
||||
<property name="applyToPackage" value="true"/>
|
||||
<property name="applyToPrivate" value="true"/>
|
||||
<property name="format" value="^[a-z][a-zA-Z0-9]*_?$"/>
|
||||
<property name="severity" value="warning"/>
|
||||
</module>
|
||||
|
||||
<module name="MemberNameCheck">
|
||||
<!-- Validates non-static members against the supplied expression. -->
|
||||
<metadata name="altname" value="MemberName"/>
|
||||
<property name="applyToPublic" value="true"/>
|
||||
<property name="applyToProtected" value="true"/>
|
||||
<property name="applyToPackage" value="true"/>
|
||||
<property name="applyToPrivate" value="true"/>
|
||||
<property name="format" value="^[a-z][a-zA-Z0-9]*$"/>
|
||||
<property name="severity" value="warning"/>
|
||||
</module>
|
||||
|
||||
<module name="MethodNameCheck">
|
||||
<!-- Validates identifiers for method names. -->
|
||||
<metadata name="altname" value="MethodName"/>
|
||||
<property name="format" value="^[a-z][a-zA-Z0-9]*(_[a-zA-Z0-9]+)*$"/>
|
||||
<property name="severity" value="warning"/>
|
||||
</module>
|
||||
|
||||
<module name="ParameterName">
|
||||
<!-- Validates identifiers for method parameters against the
|
||||
expression "^[a-z][a-zA-Z0-9]*$". -->
|
||||
<property name="severity" value="warning"/>
|
||||
</module>
|
||||
|
||||
<module name="LocalFinalVariableName">
|
||||
<!-- Validates identifiers for local final variables against the
|
||||
expression "^[a-z][a-zA-Z0-9]*$". -->
|
||||
<property name="severity" value="warning"/>
|
||||
</module>
|
||||
|
||||
<module name="LocalVariableName">
|
||||
<!-- Validates identifiers for local variables against the
|
||||
expression "^[a-z][a-zA-Z0-9]*$". -->
|
||||
<property name="severity" value="warning"/>
|
||||
</module>
|
||||
|
||||
|
||||
<!--
|
||||
|
||||
LENGTH and CODING CHECKS
|
||||
|
||||
-->
|
||||
|
||||
|
||||
<module name="LeftCurly">
|
||||
<!-- Checks for placement of the left curly brace ('{'). -->
|
||||
<property name="severity" value="warning"/>
|
||||
</module>
|
||||
|
||||
<module name="RightCurly">
|
||||
<!-- Checks right curlies on CATCH, ELSE, and TRY blocks are on
|
||||
the same line. e.g., the following example is fine:
|
||||
<pre>
|
||||
if {
|
||||
...
|
||||
} else
|
||||
</pre>
|
||||
-->
|
||||
<!-- This next example is not fine:
|
||||
<pre>
|
||||
if {
|
||||
...
|
||||
}
|
||||
else
|
||||
</pre>
|
||||
-->
|
||||
<property name="option" value="same"/>
|
||||
<property name="severity" value="warning"/>
|
||||
</module>
|
||||
|
||||
<!-- Checks for braces around if and else blocks -->
|
||||
<module name="NeedBraces">
|
||||
<property name="severity" value="warning"/>
|
||||
<property name="tokens" value="LITERAL_IF, LITERAL_ELSE, LITERAL_FOR, LITERAL_WHILE, LITERAL_DO"/>
|
||||
</module>
|
||||
|
||||
<module name="UpperEll">
|
||||
<!-- Checks that long constants are defined with an upper ell.-->
|
||||
<property name="severity" value="error"/>
|
||||
</module>
|
||||
|
||||
<module name="FallThrough">
|
||||
<!-- Warn about falling through to the next case statement. Similar to
|
||||
javac -Xlint:fallthrough, but the check is suppressed if a single-line comment
|
||||
on the last non-blank line preceding the fallen-into case contains 'fall through' (or
|
||||
some other variants which we don't publicized to promote consistency).
|
||||
-->
|
||||
<property name="reliefPattern"
|
||||
value="fall through|Fall through|fallthru|Fallthru|falls through|Falls through|fallthrough|Fallthrough|No break|NO break|no break|continue on"/>
|
||||
<property name="severity" value="error"/>
|
||||
</module>
|
||||
|
||||
|
||||
<!--
|
||||
|
||||
MODIFIERS CHECKS
|
||||
|
||||
-->
|
||||
|
||||
<module name="ModifierOrder">
|
||||
<!-- Warn if modifier order is inconsistent with JLS3 8.1.1, 8.3.1, and
|
||||
8.4.3. The prescribed order is:
|
||||
public, protected, private, abstract, static, final, transient, volatile,
|
||||
synchronized, native, strictfp
|
||||
-->
|
||||
</module>
|
||||
|
||||
|
||||
<!--
|
||||
|
||||
WHITESPACE CHECKS
|
||||
|
||||
-->
|
||||
|
||||
<module name="WhitespaceAround">
|
||||
<!-- Checks that various tokens are surrounded by whitespace.
|
||||
This includes most binary operators and keywords followed
|
||||
by regular or curly braces.
|
||||
-->
|
||||
<property name="tokens" value="ASSIGN, BAND, BAND_ASSIGN, BOR,
|
||||
BOR_ASSIGN, BSR, BSR_ASSIGN, BXOR, BXOR_ASSIGN, COLON, DIV, DIV_ASSIGN,
|
||||
EQUAL, GE, GT, LAND, LE, LITERAL_CATCH, LITERAL_DO, LITERAL_ELSE,
|
||||
LITERAL_FINALLY, LITERAL_FOR, LITERAL_IF, LITERAL_RETURN,
|
||||
LITERAL_SYNCHRONIZED, LITERAL_TRY, LITERAL_WHILE, LOR, LT, MINUS,
|
||||
MINUS_ASSIGN, MOD, MOD_ASSIGN, NOT_EQUAL, PLUS, PLUS_ASSIGN, QUESTION,
|
||||
SL, SL_ASSIGN, SR_ASSIGN, STAR, STAR_ASSIGN"/>
|
||||
<property name="severity" value="error"/>
|
||||
</module>
|
||||
|
||||
<module name="WhitespaceAfter">
|
||||
<!-- Checks that commas, semicolons and typecasts are followed by
|
||||
whitespace.
|
||||
-->
|
||||
<property name="tokens" value="COMMA, SEMI, TYPECAST"/>
|
||||
</module>
|
||||
|
||||
<module name="NoWhitespaceAfter">
|
||||
<!-- Checks that there is no whitespace after various unary operators.
|
||||
Linebreaks are allowed.
|
||||
-->
|
||||
<property name="tokens" value="BNOT, DEC, DOT, INC, LNOT, UNARY_MINUS,
|
||||
UNARY_PLUS"/>
|
||||
<property name="allowLineBreaks" value="true"/>
|
||||
<property name="severity" value="error"/>
|
||||
</module>
|
||||
|
||||
<module name="NoWhitespaceBefore">
|
||||
<!-- Checks that there is no whitespace before various unary operators.
|
||||
Linebreaks are allowed.
|
||||
-->
|
||||
<property name="tokens" value="SEMI, DOT, POST_DEC, POST_INC"/>
|
||||
<property name="allowLineBreaks" value="true"/>
|
||||
<property name="severity" value="error"/>
|
||||
</module>
|
||||
|
||||
<module name="ParenPad">
|
||||
<!-- Checks that there is no whitespace before close parens or after
|
||||
open parens.
|
||||
-->
|
||||
<property name="severity" value="warning"/>
|
||||
</module>
|
||||
|
||||
</module>
|
||||
|
||||
<module name="LineLength">
|
||||
<!-- Checks if a line is too long. -->
|
||||
<property name="max" value="${com.puppycrawl.tools.checkstyle.checks.sizes.LineLength.max}" default="128"/>
|
||||
<property name="severity" value="error"/>
|
||||
|
||||
<!--
|
||||
The default ignore pattern exempts the following elements:
|
||||
- import statements
|
||||
- long URLs inside comments
|
||||
-->
|
||||
|
||||
<property name="ignorePattern"
|
||||
value="${com.puppycrawl.tools.checkstyle.checks.sizes.LineLength.ignorePattern}"
|
||||
default="^(package .*;\s*)|(import .*;\s*)|( *(\*|//).*https?://.*)$"/>
|
||||
</module>
|
||||
</module>
|
||||
|
11
gradle/quality/cyclonedx.gradle
Normal file
11
gradle/quality/cyclonedx.gradle
Normal file
|
@ -0,0 +1,11 @@
|
|||
cyclonedxBom {
|
||||
includeConfigs = [ 'runtimeClasspath' ]
|
||||
skipConfigs = [ 'compileClasspath', 'testCompileClasspath' ]
|
||||
projectType = "library"
|
||||
schemaVersion = "1.4"
|
||||
destination = file("build/reports")
|
||||
outputName = "bom"
|
||||
outputFormat = "json"
|
||||
includeBomSerialNumber = true
|
||||
componentVersion = "2.0.0"
|
||||
}
|
15
gradle/quality/pmd.gradle
Normal file
15
gradle/quality/pmd.gradle
Normal file
|
@ -0,0 +1,15 @@
|
|||
|
||||
tasks.withType(Pmd) {
|
||||
ignoreFailures = true
|
||||
reports {
|
||||
xml.getRequired().set(true)
|
||||
html.getRequired().set(true)
|
||||
}
|
||||
}
|
||||
|
||||
pmd {
|
||||
ignoreFailures = true
|
||||
consoleOutput = false
|
||||
toolVersion = "6.51.0"
|
||||
ruleSetFiles = rootProject.files('gradle/quality/pmd/category/java/bestpractices.xml')
|
||||
}
|
1650
gradle/quality/pmd/category/java/bestpractices.xml
Normal file
1650
gradle/quality/pmd/category/java/bestpractices.xml
Normal file
File diff suppressed because it is too large
Load diff
10
gradle/quality/pmd/category/java/categories.properties
Normal file
10
gradle/quality/pmd/category/java/categories.properties
Normal file
|
@ -0,0 +1,10 @@
|
|||
|
||||
rulesets.filenames=\
|
||||
category/java/bestpractices.xml,\
|
||||
category/java/codestyle.xml,\
|
||||
category/java/design.xml,\
|
||||
category/java/documentation.xml,\
|
||||
category/java/errorprone.xml,\
|
||||
category/java/multithreading.xml,\
|
||||
category/java/performance.xml,\
|
||||
category/java/security.xml
|
2176
gradle/quality/pmd/category/java/codestyle.xml
Normal file
2176
gradle/quality/pmd/category/java/codestyle.xml
Normal file
File diff suppressed because it is too large
Load diff
1657
gradle/quality/pmd/category/java/design.xml
Normal file
1657
gradle/quality/pmd/category/java/design.xml
Normal file
File diff suppressed because it is too large
Load diff
144
gradle/quality/pmd/category/java/documentation.xml
Normal file
144
gradle/quality/pmd/category/java/documentation.xml
Normal file
|
@ -0,0 +1,144 @@
|
|||
<?xml version="1.0"?>
|
||||
|
||||
<ruleset name="Documentation"
|
||||
xmlns="http://pmd.sourceforge.net/ruleset/2.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 https://pmd.sourceforge.io/ruleset_2_0_0.xsd">
|
||||
|
||||
<description>
|
||||
Rules that are related to code documentation.
|
||||
</description>
|
||||
|
||||
<rule name="CommentContent"
|
||||
since="5.0"
|
||||
message="Invalid words or phrases found"
|
||||
class="net.sourceforge.pmd.lang.java.rule.documentation.CommentContentRule"
|
||||
externalInfoUrl="${pmd.website.baseurl}/pmd_rules_java_documentation.html#commentcontent">
|
||||
<description>
|
||||
A rule for the politically correct... we don't want to offend anyone.
|
||||
</description>
|
||||
<priority>3</priority>
|
||||
<example>
|
||||
<![CDATA[
|
||||
//OMG, this is horrible, Bob is an idiot !!!
|
||||
]]>
|
||||
</example>
|
||||
</rule>
|
||||
|
||||
<rule name="CommentRequired"
|
||||
since="5.1"
|
||||
message="Comment is required"
|
||||
class="net.sourceforge.pmd.lang.java.rule.documentation.CommentRequiredRule"
|
||||
externalInfoUrl="${pmd.website.baseurl}/pmd_rules_java_documentation.html#commentrequired">
|
||||
<description>
|
||||
Denotes whether comments are required (or unwanted) for specific language elements.
|
||||
</description>
|
||||
<priority>3</priority>
|
||||
<example>
|
||||
<![CDATA[
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @author Jon Doe
|
||||
*/
|
||||
]]>
|
||||
</example>
|
||||
</rule>
|
||||
|
||||
<rule name="CommentSize"
|
||||
since="5.0"
|
||||
message="Comment is too large"
|
||||
class="net.sourceforge.pmd.lang.java.rule.documentation.CommentSizeRule"
|
||||
externalInfoUrl="${pmd.website.baseurl}/pmd_rules_java_documentation.html#commentsize">
|
||||
<description>
|
||||
Determines whether the dimensions of non-header comments found are within the specified limits.
|
||||
</description>
|
||||
<priority>3</priority>
|
||||
<example>
|
||||
<![CDATA[
|
||||
/**
|
||||
*
|
||||
* too many lines!
|
||||
*
|
||||
*
|
||||
*
|
||||
*
|
||||
*
|
||||
*
|
||||
*
|
||||
*
|
||||
*
|
||||
*
|
||||
*
|
||||
*
|
||||
*/
|
||||
]]>
|
||||
</example>
|
||||
</rule>
|
||||
|
||||
<rule name="UncommentedEmptyConstructor"
|
||||
language="java"
|
||||
since="3.4"
|
||||
message="Document empty constructor"
|
||||
class="net.sourceforge.pmd.lang.rule.XPathRule"
|
||||
typeResolution="true"
|
||||
externalInfoUrl="${pmd.website.baseurl}/pmd_rules_java_documentation.html#uncommentedemptyconstructor">
|
||||
<description>
|
||||
Uncommented Empty Constructor finds instances where a constructor does not
|
||||
contain statements, but there is no comment. By explicitly commenting empty
|
||||
constructors it is easier to distinguish between intentional (commented)
|
||||
and unintentional empty constructors.
|
||||
</description>
|
||||
<priority>3</priority>
|
||||
<properties>
|
||||
<property name="xpath">
|
||||
<value>
|
||||
<![CDATA[
|
||||
//ConstructorDeclaration[@Private='false']
|
||||
[count(BlockStatement) = 0 and ($ignoreExplicitConstructorInvocation = 'true' or not(ExplicitConstructorInvocation)) and @containsComment = 'false']
|
||||
[not(../Annotation/MarkerAnnotation/Name[pmd-java:typeIs('javax.inject.Inject')])]
|
||||
]]>
|
||||
</value>
|
||||
</property>
|
||||
<property name="ignoreExplicitConstructorInvocation" type="Boolean" description="Ignore explicit constructor invocation when deciding whether constructor is empty or not" value="false"/>
|
||||
</properties>
|
||||
<example>
|
||||
<![CDATA[
|
||||
public Foo() {
|
||||
// This constructor is intentionally empty. Nothing special is needed here.
|
||||
}
|
||||
]]>
|
||||
</example>
|
||||
</rule>
|
||||
|
||||
<rule name="UncommentedEmptyMethodBody"
|
||||
language="java"
|
||||
since="3.4"
|
||||
message="Document empty method body"
|
||||
class="net.sourceforge.pmd.lang.rule.XPathRule"
|
||||
externalInfoUrl="${pmd.website.baseurl}/pmd_rules_java_documentation.html#uncommentedemptymethodbody">
|
||||
<description>
|
||||
Uncommented Empty Method Body finds instances where a method body does not contain
|
||||
statements, but there is no comment. By explicitly commenting empty method bodies
|
||||
it is easier to distinguish between intentional (commented) and unintentional
|
||||
empty methods.
|
||||
</description>
|
||||
<priority>3</priority>
|
||||
<properties>
|
||||
<property name="xpath">
|
||||
<value>
|
||||
<![CDATA[
|
||||
//MethodDeclaration/Block[count(BlockStatement) = 0 and @containsComment = 'false']
|
||||
]]>
|
||||
</value>
|
||||
</property>
|
||||
</properties>
|
||||
<example>
|
||||
<![CDATA[
|
||||
public void doSomething() {
|
||||
}
|
||||
]]>
|
||||
</example>
|
||||
</rule>
|
||||
|
||||
</ruleset>
|
3383
gradle/quality/pmd/category/java/errorprone.xml
Normal file
3383
gradle/quality/pmd/category/java/errorprone.xml
Normal file
File diff suppressed because it is too large
Load diff
393
gradle/quality/pmd/category/java/multithreading.xml
Normal file
393
gradle/quality/pmd/category/java/multithreading.xml
Normal file
|
@ -0,0 +1,393 @@
|
|||
<?xml version="1.0"?>
|
||||
|
||||
<ruleset name="Multithreading"
|
||||
xmlns="http://pmd.sourceforge.net/ruleset/2.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 https://pmd.sourceforge.io/ruleset_2_0_0.xsd">
|
||||
|
||||
<description>
|
||||
Rules that flag issues when dealing with multiple threads of execution.
|
||||
</description>
|
||||
|
||||
<rule name="AvoidSynchronizedAtMethodLevel"
|
||||
language="java"
|
||||
since="3.0"
|
||||
message="Use block level rather than method level synchronization"
|
||||
class="net.sourceforge.pmd.lang.rule.XPathRule"
|
||||
externalInfoUrl="${pmd.website.baseurl}/pmd_rules_java_multithreading.html#avoidsynchronizedatmethodlevel">
|
||||
<description>
|
||||
Method-level synchronization can cause problems when new code is added to the method.
|
||||
Block-level synchronization helps to ensure that only the code that needs synchronization
|
||||
gets it.
|
||||
</description>
|
||||
<priority>3</priority>
|
||||
<properties>
|
||||
<property name="xpath">
|
||||
<value>//MethodDeclaration[@Synchronized='true']</value>
|
||||
</property>
|
||||
</properties>
|
||||
<example>
|
||||
<![CDATA[
|
||||
public class Foo {
|
||||
// Try to avoid this:
|
||||
synchronized void foo() {
|
||||
}
|
||||
// Prefer this:
|
||||
void bar() {
|
||||
synchronized(this) {
|
||||
}
|
||||
}
|
||||
|
||||
// Try to avoid this for static methods:
|
||||
static synchronized void fooStatic() {
|
||||
}
|
||||
|
||||
// Prefer this:
|
||||
static void barStatic() {
|
||||
synchronized(Foo.class) {
|
||||
}
|
||||
}
|
||||
}
|
||||
]]>
|
||||
</example>
|
||||
</rule>
|
||||
|
||||
<rule name="AvoidThreadGroup"
|
||||
language="java"
|
||||
since="3.6"
|
||||
message="Avoid using java.lang.ThreadGroup; it is not thread safe"
|
||||
class="net.sourceforge.pmd.lang.rule.XPathRule"
|
||||
typeResolution="true"
|
||||
externalInfoUrl="${pmd.website.baseurl}/pmd_rules_java_multithreading.html#avoidthreadgroup">
|
||||
<description>
|
||||
Avoid using java.lang.ThreadGroup; although it is intended to be used in a threaded environment
|
||||
it contains methods that are not thread-safe.
|
||||
</description>
|
||||
<priority>3</priority>
|
||||
<properties>
|
||||
<property name="xpath">
|
||||
<value>
|
||||
<![CDATA[
|
||||
//AllocationExpression/ClassOrInterfaceType[pmd-java:typeIs('java.lang.ThreadGroup')]|
|
||||
//PrimarySuffix[contains(@Image, 'getThreadGroup')]
|
||||
]]>
|
||||
</value>
|
||||
</property>
|
||||
</properties>
|
||||
<example>
|
||||
<![CDATA[
|
||||
public class Bar {
|
||||
void buz() {
|
||||
ThreadGroup tg = new ThreadGroup("My threadgroup");
|
||||
tg = new ThreadGroup(tg, "my thread group");
|
||||
tg = Thread.currentThread().getThreadGroup();
|
||||
tg = System.getSecurityManager().getThreadGroup();
|
||||
}
|
||||
}
|
||||
]]>
|
||||
</example>
|
||||
</rule>
|
||||
|
||||
<rule name="AvoidUsingVolatile"
|
||||
language="java"
|
||||
since="4.1"
|
||||
class="net.sourceforge.pmd.lang.rule.XPathRule"
|
||||
message="Use of modifier volatile is not recommended."
|
||||
externalInfoUrl="${pmd.website.baseurl}/pmd_rules_java_multithreading.html#avoidusingvolatile">
|
||||
<description>
|
||||
Use of the keyword 'volatile' is generally used to fine tune a Java application, and therefore, requires
|
||||
a good expertise of the Java Memory Model. Moreover, its range of action is somewhat misknown. Therefore,
|
||||
the volatile keyword should not be used for maintenance purpose and portability.
|
||||
</description>
|
||||
<priority>2</priority>
|
||||
<properties>
|
||||
<property name="xpath">
|
||||
<value>//FieldDeclaration[contains(@Volatile,'true')]</value>
|
||||
</property>
|
||||
</properties>
|
||||
<example>
|
||||
<![CDATA[
|
||||
public class ThrDeux {
|
||||
private volatile String var1; // not suggested
|
||||
private String var2; // preferred
|
||||
}
|
||||
]]>
|
||||
</example>
|
||||
</rule>
|
||||
|
||||
<rule name="DoNotUseThreads"
|
||||
language="java"
|
||||
since="4.1"
|
||||
class="net.sourceforge.pmd.lang.rule.XPathRule"
|
||||
message="To be compliant to J2EE, a webapp should not use any thread."
|
||||
externalInfoUrl="${pmd.website.baseurl}/pmd_rules_java_multithreading.html#donotusethreads">
|
||||
<description>
|
||||
The J2EE specification explicitly forbids the use of threads.
|
||||
</description>
|
||||
<priority>3</priority>
|
||||
<properties>
|
||||
<property name="xpath">
|
||||
<value>//ClassOrInterfaceType[@Image = 'Thread' or @Image = 'Runnable']</value>
|
||||
</property>
|
||||
</properties>
|
||||
<example>
|
||||
<![CDATA[
|
||||
// This is not allowed
|
||||
public class UsingThread extends Thread {
|
||||
|
||||
}
|
||||
|
||||
// Neither this,
|
||||
public class OtherThread implements Runnable {
|
||||
// Nor this ...
|
||||
public void methode() {
|
||||
Runnable thread = new Thread(); thread.run();
|
||||
}
|
||||
}
|
||||
]]>
|
||||
</example>
|
||||
</rule>
|
||||
|
||||
<rule name="DontCallThreadRun"
|
||||
language="java"
|
||||
since="4.3"
|
||||
message="Don't call Thread.run() explicitly, use Thread.start()"
|
||||
class="net.sourceforge.pmd.lang.rule.XPathRule"
|
||||
typeResolution="true"
|
||||
externalInfoUrl="${pmd.website.baseurl}/pmd_rules_java_multithreading.html#dontcallthreadrun">
|
||||
<description>
|
||||
Explicitly calling Thread.run() method will execute in the caller's thread of control. Instead, call Thread.start() for the intended behavior.
|
||||
</description>
|
||||
<priority>4</priority>
|
||||
<properties>
|
||||
<property name="xpath">
|
||||
<value>
|
||||
<![CDATA[
|
||||
//StatementExpression/PrimaryExpression
|
||||
[
|
||||
PrimaryPrefix
|
||||
[
|
||||
./Name[ends-with(@Image, '.run') or @Image = 'run']
|
||||
and substring-before(Name/@Image, '.') =//VariableDeclarator/VariableDeclaratorId/@Image
|
||||
[../../../Type/ReferenceType/ClassOrInterfaceType[pmd-java:typeIs('java.lang.Thread')]]
|
||||
or (./AllocationExpression/ClassOrInterfaceType[pmd-java:typeIs('java.lang.Thread')]
|
||||
and ../PrimarySuffix[@Image = 'run'])
|
||||
]
|
||||
]
|
||||
]]>
|
||||
</value>
|
||||
</property>
|
||||
</properties>
|
||||
<example>
|
||||
<![CDATA[
|
||||
Thread t = new Thread();
|
||||
t.run(); // use t.start() instead
|
||||
new Thread().run(); // same violation
|
||||
]]>
|
||||
</example>
|
||||
</rule>
|
||||
|
||||
<rule name="DoubleCheckedLocking"
|
||||
language="java"
|
||||
since="1.04"
|
||||
message="Double checked locking is not thread safe in Java."
|
||||
class="net.sourceforge.pmd.lang.java.rule.multithreading.DoubleCheckedLockingRule"
|
||||
externalInfoUrl="${pmd.website.baseurl}/pmd_rules_java_multithreading.html#doublecheckedlocking">
|
||||
<description>
|
||||
Partially created objects can be returned by the Double Checked Locking pattern when used in Java.
|
||||
An optimizing JRE may assign a reference to the baz variable before it calls the constructor of the object the
|
||||
reference points to.
|
||||
|
||||
Note: With Java 5, you can make Double checked locking work, if you declare the variable to be `volatile`.
|
||||
|
||||
For more details refer to: <http://www.javaworld.com/javaworld/jw-02-2001/jw-0209-double.html>
|
||||
or <http://www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html>
|
||||
</description>
|
||||
<priority>1</priority>
|
||||
<example>
|
||||
<![CDATA[
|
||||
public class Foo {
|
||||
/*volatile */ Object baz = null; // fix for Java5 and later: volatile
|
||||
Object bar() {
|
||||
if (baz == null) { // baz may be non-null yet not fully created
|
||||
synchronized(this) {
|
||||
if (baz == null) {
|
||||
baz = new Object();
|
||||
}
|
||||
}
|
||||
}
|
||||
return baz;
|
||||
}
|
||||
}
|
||||
]]>
|
||||
</example>
|
||||
</rule>
|
||||
|
||||
<rule name="NonThreadSafeSingleton"
|
||||
since="3.4"
|
||||
message="Singleton is not thread safe"
|
||||
class="net.sourceforge.pmd.lang.java.rule.multithreading.NonThreadSafeSingletonRule"
|
||||
externalInfoUrl="${pmd.website.baseurl}/pmd_rules_java_multithreading.html#nonthreadsafesingleton">
|
||||
<description>
|
||||
Non-thread safe singletons can result in bad state changes. Eliminate
|
||||
static singletons if possible by instantiating the object directly. Static
|
||||
singletons are usually not needed as only a single instance exists anyway.
|
||||
Other possible fixes are to synchronize the entire method or to use an
|
||||
[initialize-on-demand holder class](https://en.wikipedia.org/wiki/Initialization-on-demand_holder_idiom).
|
||||
|
||||
Refrain from using the double-checked locking pattern. The Java Memory Model doesn't
|
||||
guarantee it to work unless the variable is declared as `volatile`, adding an uneeded
|
||||
performance penalty. [Reference](http://www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html)
|
||||
|
||||
See Effective Java, item 48.
|
||||
</description>
|
||||
<priority>3</priority>
|
||||
<example>
|
||||
<![CDATA[
|
||||
private static Foo foo = null;
|
||||
|
||||
//multiple simultaneous callers may see partially initialized objects
|
||||
public static Foo getFoo() {
|
||||
if (foo==null) {
|
||||
foo = new Foo();
|
||||
}
|
||||
return foo;
|
||||
}
|
||||
]]>
|
||||
</example>
|
||||
</rule>
|
||||
|
||||
<rule name="UnsynchronizedStaticDateFormatter"
|
||||
since="3.6"
|
||||
deprecated="true"
|
||||
message="Static DateFormatter objects should be accessed in a synchronized manner"
|
||||
class="net.sourceforge.pmd.lang.java.rule.multithreading.UnsynchronizedStaticDateFormatterRule"
|
||||
externalInfoUrl="${pmd.website.baseurl}/pmd_rules_java_multithreading.html#unsynchronizedstaticdateformatter">
|
||||
<description>
|
||||
SimpleDateFormat instances are not synchronized. Sun recommends using separate format instances
|
||||
for each thread. If multiple threads must access a static formatter, the formatter must be
|
||||
synchronized either on method or block level.
|
||||
|
||||
This rule has been deprecated in favor of the rule {% rule UnsynchronizedStaticFormatter %}.
|
||||
</description>
|
||||
<priority>3</priority>
|
||||
<example>
|
||||
<![CDATA[
|
||||
public class Foo {
|
||||
private static final SimpleDateFormat sdf = new SimpleDateFormat();
|
||||
void bar() {
|
||||
sdf.format(); // poor, no thread-safety
|
||||
}
|
||||
synchronized void foo() {
|
||||
sdf.format(); // preferred
|
||||
}
|
||||
}
|
||||
]]>
|
||||
</example>
|
||||
</rule>
|
||||
|
||||
<rule name="UnsynchronizedStaticFormatter"
|
||||
since="6.11.0"
|
||||
message="Static Formatter objects should be accessed in a synchronized manner"
|
||||
class="net.sourceforge.pmd.lang.java.rule.multithreading.UnsynchronizedStaticFormatterRule"
|
||||
externalInfoUrl="${pmd.website.baseurl}/pmd_rules_java_multithreading.html#unsynchronizedstaticformatter">
|
||||
<description>
|
||||
Instances of `java.text.Format` are generally not synchronized.
|
||||
Sun recommends using separate format instances for each thread.
|
||||
If multiple threads must access a static formatter, the formatter must be
|
||||
synchronized either on method or block level.
|
||||
</description>
|
||||
<priority>3</priority>
|
||||
<example>
|
||||
<![CDATA[
|
||||
public class Foo {
|
||||
private static final SimpleDateFormat sdf = new SimpleDateFormat();
|
||||
void bar() {
|
||||
sdf.format(); // poor, no thread-safety
|
||||
}
|
||||
synchronized void foo() {
|
||||
sdf.format(); // preferred
|
||||
}
|
||||
}
|
||||
]]>
|
||||
</example>
|
||||
</rule>
|
||||
|
||||
<rule name="UseConcurrentHashMap"
|
||||
language="java"
|
||||
minimumLanguageVersion="1.5"
|
||||
since="4.2.6"
|
||||
message="If you run in Java5 or newer and have concurrent access, you should use the ConcurrentHashMap implementation"
|
||||
class="net.sourceforge.pmd.lang.rule.XPathRule"
|
||||
externalInfoUrl="${pmd.website.baseurl}/pmd_rules_java_multithreading.html#useconcurrenthashmap">
|
||||
<description>
|
||||
Since Java5 brought a new implementation of the Map designed for multi-threaded access, you can
|
||||
perform efficient map reads without blocking other threads.
|
||||
</description>
|
||||
<priority>3</priority>
|
||||
<properties>
|
||||
<property name="xpath">
|
||||
<value>
|
||||
<![CDATA[
|
||||
//Type[../VariableDeclarator/VariableInitializer//AllocationExpression/ClassOrInterfaceType[@Image != 'ConcurrentHashMap']]
|
||||
/ReferenceType/ClassOrInterfaceType[@Image = 'Map']
|
||||
]]>
|
||||
</value>
|
||||
</property>
|
||||
</properties>
|
||||
<example>
|
||||
<![CDATA[
|
||||
public class ConcurrentApp {
|
||||
public void getMyInstance() {
|
||||
Map map1 = new HashMap(); // fine for single-threaded access
|
||||
Map map2 = new ConcurrentHashMap(); // preferred for use with multiple threads
|
||||
|
||||
// the following case will be ignored by this rule
|
||||
Map map3 = someModule.methodThatReturnMap(); // might be OK, if the returned map is already thread-safe
|
||||
}
|
||||
}
|
||||
]]>
|
||||
</example>
|
||||
</rule>
|
||||
|
||||
<rule name="UseNotifyAllInsteadOfNotify"
|
||||
language="java"
|
||||
since="3.0"
|
||||
message="Call Thread.notifyAll() rather than Thread.notify()"
|
||||
class="net.sourceforge.pmd.lang.rule.XPathRule"
|
||||
externalInfoUrl="${pmd.website.baseurl}/pmd_rules_java_multithreading.html#usenotifyallinsteadofnotify">
|
||||
<description>
|
||||
Thread.notify() awakens a thread monitoring the object. If more than one thread is monitoring, then only
|
||||
one is chosen. The thread chosen is arbitrary; thus its usually safer to call notifyAll() instead.
|
||||
</description>
|
||||
<priority>3</priority>
|
||||
<properties>
|
||||
<property name="xpath">
|
||||
<value>
|
||||
<![CDATA[
|
||||
//StatementExpression/PrimaryExpression
|
||||
[PrimarySuffix/Arguments[@ArgumentCount = '0']]
|
||||
[
|
||||
PrimaryPrefix[
|
||||
./Name[@Image='notify' or ends-with(@Image,'.notify')]
|
||||
or ../PrimarySuffix/@Image='notify'
|
||||
or (./AllocationExpression and ../PrimarySuffix[@Image='notify'])
|
||||
]
|
||||
]
|
||||
]]>
|
||||
</value>
|
||||
</property>
|
||||
</properties>
|
||||
<example>
|
||||
<![CDATA[
|
||||
void bar() {
|
||||
x.notify();
|
||||
// If many threads are monitoring x, only one (and you won't know which) will be notified.
|
||||
// use instead:
|
||||
x.notifyAll();
|
||||
}
|
||||
]]>
|
||||
</example>
|
||||
</rule>
|
||||
|
||||
</ruleset>
|
1006
gradle/quality/pmd/category/java/performance.xml
Normal file
1006
gradle/quality/pmd/category/java/performance.xml
Normal file
File diff suppressed because it is too large
Load diff
65
gradle/quality/pmd/category/java/security.xml
Normal file
65
gradle/quality/pmd/category/java/security.xml
Normal file
|
@ -0,0 +1,65 @@
|
|||
<?xml version="1.0"?>
|
||||
|
||||
<ruleset name="Security" xmlns="http://pmd.sourceforge.net/ruleset/2.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 https://pmd.sourceforge.io/ruleset_2_0_0.xsd">
|
||||
|
||||
<description>
|
||||
Rules that flag potential security flaws.
|
||||
</description>
|
||||
|
||||
<rule name="HardCodedCryptoKey"
|
||||
since="6.4.0"
|
||||
message="Do not use hard coded encryption keys"
|
||||
class="net.sourceforge.pmd.lang.java.rule.security.HardCodedCryptoKeyRule"
|
||||
externalInfoUrl="${pmd.website.baseurl}/pmd_rules_java_security.html#hardcodedcryptokey">
|
||||
<description>
|
||||
Do not use hard coded values for cryptographic operations. Please store keys outside of source code.
|
||||
</description>
|
||||
<priority>3</priority>
|
||||
<example>
|
||||
<![CDATA[
|
||||
public class Foo {
|
||||
void good() {
|
||||
SecretKeySpec secretKeySpec = new SecretKeySpec(Properties.getKey(), "AES");
|
||||
}
|
||||
|
||||
void bad() {
|
||||
SecretKeySpec secretKeySpec = new SecretKeySpec("my secret here".getBytes(), "AES");
|
||||
}
|
||||
}
|
||||
]]>
|
||||
</example>
|
||||
</rule>
|
||||
|
||||
<rule name="InsecureCryptoIv"
|
||||
since="6.3.0"
|
||||
message="Do not use hard coded initialization vector in crypto operations"
|
||||
class="net.sourceforge.pmd.lang.java.rule.security.InsecureCryptoIvRule"
|
||||
externalInfoUrl="${pmd.website.baseurl}/pmd_rules_java_security.html#insecurecryptoiv">
|
||||
<description>
|
||||
Do not use hard coded initialization vector in cryptographic operations. Please use a randomly generated IV.
|
||||
</description>
|
||||
<priority>3</priority>
|
||||
<example>
|
||||
<![CDATA[
|
||||
public class Foo {
|
||||
void good() {
|
||||
SecureRandom random = new SecureRandom();
|
||||
byte iv[] = new byte[16];
|
||||
random.nextBytes(bytes);
|
||||
}
|
||||
|
||||
void bad() {
|
||||
byte[] iv = new byte[] { 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, };
|
||||
}
|
||||
|
||||
void alsoBad() {
|
||||
byte[] iv = "secret iv in here".getBytes();
|
||||
}
|
||||
}
|
||||
]]>
|
||||
</example>
|
||||
</rule>
|
||||
|
||||
</ruleset>
|
10
gradle/quality/sonarqube.gradle
Normal file
10
gradle/quality/sonarqube.gradle
Normal file
|
@ -0,0 +1,10 @@
|
|||
/*
|
||||
sonarqube {
|
||||
properties {
|
||||
property "sonar.projectName", "${project.group} ${project.name}"
|
||||
property "sonar.sourceEncoding", "UTF-8"
|
||||
property "sonar.tests", "src/test/java"
|
||||
property "sonar.scm.provider", "git"
|
||||
}
|
||||
}
|
||||
*/
|
|
@ -1,53 +1,13 @@
|
|||
/*
|
||||
|
||||
spotbugs {
|
||||
effort = "max"
|
||||
reportLevel = "low"
|
||||
ignoreFailures = true
|
||||
}
|
||||
|
||||
tasks.withType(com.github.spotbugs.SpotBugsTask) {
|
||||
ignoreFailures = true
|
||||
spotbugsMain {
|
||||
reports {
|
||||
xml.enabled = false
|
||||
html.enabled = true
|
||||
xml.getRequired().set(false)
|
||||
html.getRequired().set(true)
|
||||
}
|
||||
}
|
||||
|
||||
pmd {
|
||||
toolVersion = '6.11.0'
|
||||
ruleSets = ['category/java/bestpractices.xml']
|
||||
}
|
||||
|
||||
tasks.withType(Pmd) {
|
||||
ignoreFailures = true
|
||||
reports {
|
||||
xml.enabled = true
|
||||
html.enabled = true
|
||||
}
|
||||
}
|
||||
|
||||
checkstyle {
|
||||
toolVersion = '8.26'
|
||||
configFile = rootProject.file('config/checkstyle/checkstyle.xml')
|
||||
ignoreFailures = true
|
||||
checkstyleMain {
|
||||
source = sourceSets.main.allSource
|
||||
}
|
||||
}
|
||||
|
||||
tasks.withType(Checkstyle) {
|
||||
ignoreFailures = true
|
||||
reports {
|
||||
xml.enabled = true
|
||||
html.enabled = true
|
||||
}
|
||||
}
|
||||
|
||||
sonarqube {
|
||||
properties {
|
||||
property "sonar.projectName", "${project.group} ${project.name}"
|
||||
property "sonar.sourceEncoding", "UTF-8"
|
||||
property "sonar.tests", "src/test/java"
|
||||
property "sonar.scm.provider", "git"
|
||||
}
|
||||
}
|
||||
*/
|
4
gradle/repositories/maven.gradle
Normal file
4
gradle/repositories/maven.gradle
Normal file
|
@ -0,0 +1,4 @@
|
|||
repositories {
|
||||
mavenLocal()
|
||||
mavenCentral()
|
||||
}
|
|
@ -1,12 +1,7 @@
|
|||
|
||||
def junitVersion = project.hasProperty('junit.version')?project.property('junit.version'):'5.6.2'
|
||||
def hamcrestVersion = project.hasProperty('hamcrest.version')?project.property('hamcrest.version'):'2.2'
|
||||
|
||||
dependencies {
|
||||
testImplementation "org.junit.jupiter:junit-jupiter-api:${junitVersion}"
|
||||
testImplementation "org.junit.jupiter:junit-jupiter-params:${junitVersion}"
|
||||
testImplementation "org.hamcrest:hamcrest-library:${hamcrestVersion}"
|
||||
testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:${junitVersion}"
|
||||
testImplementation testLibs.junit.jupiter.api
|
||||
testImplementation testLibs.hamcrest
|
||||
testRuntimeOnly testLibs.junit.jupiter.engine
|
||||
}
|
||||
|
||||
test {
|
||||
|
@ -18,9 +13,6 @@ test {
|
|||
'--add-opens=java.base/java.nio=ALL-UNNAMED'
|
||||
]
|
||||
}
|
||||
systemProperty 'java.util.logging.manager', 'org.apache.logging.log4j.jul.LogManager'
|
||||
systemProperty 'path.home', "${project.buildDir}/"
|
||||
systemProperty 'jna.debug_load', 'true'
|
||||
failFast = true
|
||||
testLogging {
|
||||
events 'STARTED', 'PASSED', 'FAILED', 'SKIPPED'
|
||||
|
|
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Binary file not shown.
4
gradle/wrapper/gradle-wrapper.properties
vendored
4
gradle/wrapper/gradle-wrapper.properties
vendored
|
@ -1,5 +1,7 @@
|
|||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-6.4.1-all.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-all.zip
|
||||
networkTimeout=10000
|
||||
validateDistributionUrl=true
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
|
|
294
gradlew
vendored
294
gradlew
vendored
|
@ -1,7 +1,7 @@
|
|||
#!/usr/bin/env sh
|
||||
#!/bin/sh
|
||||
|
||||
#
|
||||
# Copyright 2015 the original author or authors.
|
||||
# Copyright © 2015-2021 the original authors.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
|
@ -17,67 +17,99 @@
|
|||
#
|
||||
|
||||
##############################################################################
|
||||
##
|
||||
## Gradle start up script for UN*X
|
||||
##
|
||||
#
|
||||
# Gradle start up script for POSIX generated by Gradle.
|
||||
#
|
||||
# Important for running:
|
||||
#
|
||||
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
|
||||
# noncompliant, but you have some other compliant shell such as ksh or
|
||||
# bash, then to run this script, type that shell name before the whole
|
||||
# command line, like:
|
||||
#
|
||||
# ksh Gradle
|
||||
#
|
||||
# Busybox and similar reduced shells will NOT work, because this script
|
||||
# requires all of these POSIX shell features:
|
||||
# * functions;
|
||||
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
|
||||
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
|
||||
# * compound commands having a testable exit status, especially «case»;
|
||||
# * various built-in commands including «command», «set», and «ulimit».
|
||||
#
|
||||
# Important for patching:
|
||||
#
|
||||
# (2) This script targets any POSIX shell, so it avoids extensions provided
|
||||
# by Bash, Ksh, etc; in particular arrays are avoided.
|
||||
#
|
||||
# The "traditional" practice of packing multiple parameters into a
|
||||
# space-separated string is a well documented source of bugs and security
|
||||
# problems, so this is (mostly) avoided, by progressively accumulating
|
||||
# options in "$@", and eventually passing that to Java.
|
||||
#
|
||||
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
|
||||
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
|
||||
# see the in-line comments for details.
|
||||
#
|
||||
# There are tweaks for specific operating systems such as AIX, CygWin,
|
||||
# Darwin, MinGW, and NonStop.
|
||||
#
|
||||
# (3) This script is generated from the Groovy template
|
||||
# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
||||
# within the Gradle project.
|
||||
#
|
||||
# You can find Gradle at https://github.com/gradle/gradle/.
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
# Attempt to set APP_HOME
|
||||
|
||||
# Resolve links: $0 may be a link
|
||||
PRG="$0"
|
||||
# Need this for relative symlinks.
|
||||
while [ -h "$PRG" ] ; do
|
||||
ls=`ls -ld "$PRG"`
|
||||
link=`expr "$ls" : '.*-> \(.*\)$'`
|
||||
if expr "$link" : '/.*' > /dev/null; then
|
||||
PRG="$link"
|
||||
else
|
||||
PRG=`dirname "$PRG"`"/$link"
|
||||
fi
|
||||
app_path=$0
|
||||
|
||||
# Need this for daisy-chained symlinks.
|
||||
while
|
||||
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
|
||||
[ -h "$app_path" ]
|
||||
do
|
||||
ls=$( ls -ld "$app_path" )
|
||||
link=${ls#*' -> '}
|
||||
case $link in #(
|
||||
/*) app_path=$link ;; #(
|
||||
*) app_path=$APP_HOME$link ;;
|
||||
esac
|
||||
done
|
||||
SAVED="`pwd`"
|
||||
cd "`dirname \"$PRG\"`/" >/dev/null
|
||||
APP_HOME="`pwd -P`"
|
||||
cd "$SAVED" >/dev/null
|
||||
|
||||
APP_NAME="Gradle"
|
||||
APP_BASE_NAME=`basename "$0"`
|
||||
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||
# This is normally unused
|
||||
# shellcheck disable=SC2034
|
||||
APP_BASE_NAME=${0##*/}
|
||||
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
|
||||
APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit
|
||||
|
||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||
MAX_FD="maximum"
|
||||
MAX_FD=maximum
|
||||
|
||||
warn () {
|
||||
echo "$*"
|
||||
}
|
||||
} >&2
|
||||
|
||||
die () {
|
||||
echo
|
||||
echo "$*"
|
||||
echo
|
||||
exit 1
|
||||
}
|
||||
} >&2
|
||||
|
||||
# OS specific support (must be 'true' or 'false').
|
||||
cygwin=false
|
||||
msys=false
|
||||
darwin=false
|
||||
nonstop=false
|
||||
case "`uname`" in
|
||||
CYGWIN* )
|
||||
cygwin=true
|
||||
;;
|
||||
Darwin* )
|
||||
darwin=true
|
||||
;;
|
||||
MINGW* )
|
||||
msys=true
|
||||
;;
|
||||
NONSTOP* )
|
||||
nonstop=true
|
||||
;;
|
||||
case "$( uname )" in #(
|
||||
CYGWIN* ) cygwin=true ;; #(
|
||||
Darwin* ) darwin=true ;; #(
|
||||
MSYS* | MINGW* ) msys=true ;; #(
|
||||
NONSTOP* ) nonstop=true ;;
|
||||
esac
|
||||
|
||||
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||
|
@ -87,9 +119,9 @@ CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
|||
if [ -n "$JAVA_HOME" ] ; then
|
||||
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||
# IBM's JDK on AIX uses strange locations for the executables
|
||||
JAVACMD="$JAVA_HOME/jre/sh/java"
|
||||
JAVACMD=$JAVA_HOME/jre/sh/java
|
||||
else
|
||||
JAVACMD="$JAVA_HOME/bin/java"
|
||||
JAVACMD=$JAVA_HOME/bin/java
|
||||
fi
|
||||
if [ ! -x "$JAVACMD" ] ; then
|
||||
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
|
||||
|
@ -98,88 +130,120 @@ Please set the JAVA_HOME variable in your environment to match the
|
|||
location of your Java installation."
|
||||
fi
|
||||
else
|
||||
JAVACMD="java"
|
||||
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
JAVACMD=java
|
||||
if ! command -v java >/dev/null 2>&1
|
||||
then
|
||||
die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
fi
|
||||
|
||||
# Increase the maximum file descriptors if we can.
|
||||
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
|
||||
MAX_FD_LIMIT=`ulimit -H -n`
|
||||
if [ $? -eq 0 ] ; then
|
||||
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
|
||||
MAX_FD="$MAX_FD_LIMIT"
|
||||
fi
|
||||
ulimit -n $MAX_FD
|
||||
if [ $? -ne 0 ] ; then
|
||||
warn "Could not set maximum file descriptor limit: $MAX_FD"
|
||||
fi
|
||||
else
|
||||
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
|
||||
fi
|
||||
fi
|
||||
|
||||
# For Darwin, add options to specify how the application appears in the dock
|
||||
if $darwin; then
|
||||
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
|
||||
fi
|
||||
|
||||
# For Cygwin or MSYS, switch paths to Windows format before running java
|
||||
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
|
||||
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
|
||||
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
|
||||
|
||||
JAVACMD=`cygpath --unix "$JAVACMD"`
|
||||
|
||||
# We build the pattern for arguments to be converted via cygpath
|
||||
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
|
||||
SEP=""
|
||||
for dir in $ROOTDIRSRAW ; do
|
||||
ROOTDIRS="$ROOTDIRS$SEP$dir"
|
||||
SEP="|"
|
||||
done
|
||||
OURCYGPATTERN="(^($ROOTDIRS))"
|
||||
# Add a user-defined pattern to the cygpath arguments
|
||||
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
|
||||
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
|
||||
fi
|
||||
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||
i=0
|
||||
for arg in "$@" ; do
|
||||
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
|
||||
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
|
||||
|
||||
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
|
||||
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
|
||||
else
|
||||
eval `echo args$i`="\"$arg\""
|
||||
fi
|
||||
i=`expr $i + 1`
|
||||
done
|
||||
case $i in
|
||||
0) set -- ;;
|
||||
1) set -- "$args0" ;;
|
||||
2) set -- "$args0" "$args1" ;;
|
||||
3) set -- "$args0" "$args1" "$args2" ;;
|
||||
4) set -- "$args0" "$args1" "$args2" "$args3" ;;
|
||||
5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
|
||||
6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
|
||||
7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
|
||||
8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
|
||||
9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
|
||||
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
|
||||
case $MAX_FD in #(
|
||||
max*)
|
||||
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
|
||||
# shellcheck disable=SC2039,SC3045
|
||||
MAX_FD=$( ulimit -H -n ) ||
|
||||
warn "Could not query maximum file descriptor limit"
|
||||
esac
|
||||
case $MAX_FD in #(
|
||||
'' | soft) :;; #(
|
||||
*)
|
||||
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
|
||||
# shellcheck disable=SC2039,SC3045
|
||||
ulimit -n "$MAX_FD" ||
|
||||
warn "Could not set maximum file descriptor limit to $MAX_FD"
|
||||
esac
|
||||
fi
|
||||
|
||||
# Escape application args
|
||||
save () {
|
||||
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
|
||||
echo " "
|
||||
}
|
||||
APP_ARGS=`save "$@"`
|
||||
# Collect all arguments for the java command, stacking in reverse order:
|
||||
# * args from the command line
|
||||
# * the main class name
|
||||
# * -classpath
|
||||
# * -D...appname settings
|
||||
# * --module-path (only if needed)
|
||||
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
|
||||
|
||||
# Collect all arguments for the java command, following the shell quoting and substitution rules
|
||||
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
|
||||
# For Cygwin or MSYS, switch paths to Windows format before running java
|
||||
if "$cygwin" || "$msys" ; then
|
||||
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
|
||||
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
|
||||
|
||||
JAVACMD=$( cygpath --unix "$JAVACMD" )
|
||||
|
||||
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||
for arg do
|
||||
if
|
||||
case $arg in #(
|
||||
-*) false ;; # don't mess with options #(
|
||||
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
|
||||
[ -e "$t" ] ;; #(
|
||||
*) false ;;
|
||||
esac
|
||||
then
|
||||
arg=$( cygpath --path --ignore --mixed "$arg" )
|
||||
fi
|
||||
# Roll the args list around exactly as many times as the number of
|
||||
# args, so each arg winds up back in the position where it started, but
|
||||
# possibly modified.
|
||||
#
|
||||
# NB: a `for` loop captures its iteration list before it begins, so
|
||||
# changing the positional parameters here affects neither the number of
|
||||
# iterations, nor the values presented in `arg`.
|
||||
shift # remove old arg
|
||||
set -- "$@" "$arg" # push replacement arg
|
||||
done
|
||||
fi
|
||||
|
||||
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||
|
||||
# Collect all arguments for the java command:
|
||||
# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
|
||||
# and any embedded shellness will be escaped.
|
||||
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
|
||||
# treated as '${Hostname}' itself on the command line.
|
||||
|
||||
set -- \
|
||||
"-Dorg.gradle.appname=$APP_BASE_NAME" \
|
||||
-classpath "$CLASSPATH" \
|
||||
org.gradle.wrapper.GradleWrapperMain \
|
||||
"$@"
|
||||
|
||||
# Stop when "xargs" is not available.
|
||||
if ! command -v xargs >/dev/null 2>&1
|
||||
then
|
||||
die "xargs is not available"
|
||||
fi
|
||||
|
||||
# Use "xargs" to parse quoted args.
|
||||
#
|
||||
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
|
||||
#
|
||||
# In Bash we could simply go:
|
||||
#
|
||||
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
|
||||
# set -- "${ARGS[@]}" "$@"
|
||||
#
|
||||
# but POSIX shell has neither arrays nor command substitution, so instead we
|
||||
# post-process each arg (as a line of input to sed) to backslash-escape any
|
||||
# character that might be a shell metacharacter, then use eval to reverse
|
||||
# that process (while maintaining the separation between arguments), and wrap
|
||||
# the whole thing up as a single "set" statement.
|
||||
#
|
||||
# This will of course break if any of these variables contains a newline or
|
||||
# an unmatched quote.
|
||||
#
|
||||
|
||||
eval "set -- $(
|
||||
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
|
||||
xargs -n1 |
|
||||
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
|
||||
tr '\n' ' '
|
||||
)" '"$@"'
|
||||
|
||||
exec "$JAVACMD" "$@"
|
||||
|
|
54
gradlew.bat
vendored
54
gradlew.bat
vendored
|
@ -14,7 +14,7 @@
|
|||
@rem limitations under the License.
|
||||
@rem
|
||||
|
||||
@if "%DEBUG%" == "" @echo off
|
||||
@if "%DEBUG%"=="" @echo off
|
||||
@rem ##########################################################################
|
||||
@rem
|
||||
@rem Gradle startup script for Windows
|
||||
|
@ -25,7 +25,8 @@
|
|||
if "%OS%"=="Windows_NT" setlocal
|
||||
|
||||
set DIRNAME=%~dp0
|
||||
if "%DIRNAME%" == "" set DIRNAME=.
|
||||
if "%DIRNAME%"=="" set DIRNAME=.
|
||||
@rem This is normally unused
|
||||
set APP_BASE_NAME=%~n0
|
||||
set APP_HOME=%DIRNAME%
|
||||
|
||||
|
@ -40,13 +41,13 @@ if defined JAVA_HOME goto findJavaFromJavaHome
|
|||
|
||||
set JAVA_EXE=java.exe
|
||||
%JAVA_EXE% -version >NUL 2>&1
|
||||
if "%ERRORLEVEL%" == "0" goto init
|
||||
if %ERRORLEVEL% equ 0 goto execute
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
echo. 1>&2
|
||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
|
||||
echo. 1>&2
|
||||
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
|
||||
echo location of your Java installation. 1>&2
|
||||
|
||||
goto fail
|
||||
|
||||
|
@ -54,31 +55,16 @@ goto fail
|
|||
set JAVA_HOME=%JAVA_HOME:"=%
|
||||
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||
|
||||
if exist "%JAVA_EXE%" goto init
|
||||
if exist "%JAVA_EXE%" goto execute
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
echo. 1>&2
|
||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
|
||||
echo. 1>&2
|
||||
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
|
||||
echo location of your Java installation. 1>&2
|
||||
|
||||
goto fail
|
||||
|
||||
:init
|
||||
@rem Get command-line arguments, handling Windows variants
|
||||
|
||||
if not "%OS%" == "Windows_NT" goto win9xME_args
|
||||
|
||||
:win9xME_args
|
||||
@rem Slurp the command line arguments.
|
||||
set CMD_LINE_ARGS=
|
||||
set _SKIP=2
|
||||
|
||||
:win9xME_args_slurp
|
||||
if "x%~1" == "x" goto execute
|
||||
|
||||
set CMD_LINE_ARGS=%*
|
||||
|
||||
:execute
|
||||
@rem Setup the command line
|
||||
|
||||
|
@ -86,17 +72,19 @@ set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
|||
|
||||
|
||||
@rem Execute Gradle
|
||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
|
||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
|
||||
|
||||
:end
|
||||
@rem End local scope for the variables with windows NT shell
|
||||
if "%ERRORLEVEL%"=="0" goto mainEnd
|
||||
if %ERRORLEVEL% equ 0 goto mainEnd
|
||||
|
||||
:fail
|
||||
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||
rem the _cmd.exe /c_ return code!
|
||||
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
|
||||
exit /b 1
|
||||
set EXIT_CODE=%ERRORLEVEL%
|
||||
if %EXIT_CODE% equ 0 set EXIT_CODE=1
|
||||
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
|
||||
exit /b %EXIT_CODE%
|
||||
|
||||
:mainEnd
|
||||
if "%OS%"=="Windows_NT" endlocal
|
||||
|
|
|
@ -1 +1,14 @@
|
|||
rootProject.name = name
|
||||
dependencyResolutionManagement {
|
||||
versionCatalogs {
|
||||
libs {
|
||||
version('gradle', '8.7')
|
||||
}
|
||||
testLibs {
|
||||
version('junit', '5.10.2')
|
||||
library('junit-jupiter-api', 'org.junit.jupiter', 'junit-jupiter-api').versionRef('junit')
|
||||
library('junit-jupiter-engine', 'org.junit.jupiter', 'junit-jupiter-engine').versionRef('junit')
|
||||
library('junit4', 'junit', 'junit').version('4.13.2')
|
||||
library('hamcrest', 'org.hamcrest', 'hamcrest-library').version('2.2')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -79,7 +79,15 @@ public enum StandardSystemProperty {
|
|||
/** Name of JIT compiler to use. */
|
||||
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"),
|
||||
|
||||
/** Operating system name. */
|
||||
|
|
|
@ -480,8 +480,8 @@ public final class CacheBuilder<K, V> {
|
|||
this.maximumWeight);
|
||||
checkState(
|
||||
this.maximumSize == UNSET_INT, "maximum size was already set to %s", this.maximumSize);
|
||||
this.maximumWeight = maximumWeight;
|
||||
checkArgument(maximumWeight >= 0, "maximum weight must not be negative");
|
||||
this.maximumWeight = maximumWeight;
|
||||
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.CacheLoader.InvalidCacheLoadException;
|
||||
import com.google.common.cache.CacheLoader.UnsupportedLoadingOperationException;
|
||||
import com.google.common.cache.LocalCache.AbstractCacheSet;
|
||||
import com.google.common.collect.AbstractSequentialIterator;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
|
@ -4284,7 +4283,7 @@ class LocalCache<K, V> extends AbstractMap<K, V> implements ConcurrentMap<K, V>
|
|||
public Set<K> keySet() {
|
||||
// does not impact recency ordering
|
||||
Set<K> ks = keySet;
|
||||
return (ks != null) ? ks : (keySet = new KeySet(this));
|
||||
return (ks != null) ? ks : (keySet = new KeySet());
|
||||
}
|
||||
|
||||
Collection<V> values;
|
||||
|
@ -4293,7 +4292,7 @@ class LocalCache<K, V> extends AbstractMap<K, V> implements ConcurrentMap<K, V>
|
|||
public Collection<V> values() {
|
||||
// does not impact recency ordering
|
||||
Collection<V> vs = values;
|
||||
return (vs != null) ? vs : (values = new Values(this));
|
||||
return (vs != null) ? vs : (values = new Values());
|
||||
}
|
||||
|
||||
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() {
|
||||
// does not impact recency ordering
|
||||
Set<Entry<K, V>> es = entrySet;
|
||||
return (es != null) ? es : (entrySet = new EntrySet(this));
|
||||
return (es != null) ? es : (entrySet = new EntrySet());
|
||||
}
|
||||
|
||||
// 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> {
|
||||
final ConcurrentMap<?, ?> map;
|
||||
|
||||
AbstractCacheSet(ConcurrentMap<?, ?> map) {
|
||||
this.map = map;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int size() {
|
||||
return map.size();
|
||||
return LocalCache.this.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEmpty() {
|
||||
return map.isEmpty();
|
||||
return LocalCache.this.isEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear() {
|
||||
map.clear();
|
||||
LocalCache.this.clear();
|
||||
}
|
||||
|
||||
// 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> {
|
||||
|
||||
KeySet(ConcurrentMap<?, ?> map) {
|
||||
super(map);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<K> iterator() {
|
||||
return new KeyIterator();
|
||||
|
@ -4567,36 +4557,31 @@ class LocalCache<K, V> extends AbstractMap<K, V> implements ConcurrentMap<K, V>
|
|||
|
||||
@Override
|
||||
public boolean contains(Object o) {
|
||||
return map.containsKey(o);
|
||||
return LocalCache.this.containsKey(o);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean remove(Object o) {
|
||||
return map.remove(o) != null;
|
||||
return LocalCache.this.remove(o) != null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
final class Values extends AbstractCollection<V> {
|
||||
private final ConcurrentMap<?, ?> map;
|
||||
|
||||
Values(ConcurrentMap<?, ?> map) {
|
||||
this.map = map;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int size() {
|
||||
return map.size();
|
||||
return LocalCache.this.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEmpty() {
|
||||
return map.isEmpty();
|
||||
return LocalCache.this.isEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear() {
|
||||
map.clear();
|
||||
LocalCache.this.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -4612,7 +4597,7 @@ class LocalCache<K, V> extends AbstractMap<K, V> implements ConcurrentMap<K, V>
|
|||
|
||||
@Override
|
||||
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.
|
||||
|
@ -4632,10 +4617,6 @@ class LocalCache<K, V> extends AbstractMap<K, V> implements ConcurrentMap<K, V>
|
|||
|
||||
final class EntrySet extends AbstractCacheSet<Entry<K, V>> {
|
||||
|
||||
EntrySet(ConcurrentMap<?, ?> map) {
|
||||
super(map);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<Entry<K, V>> iterator() {
|
||||
return new EntryIterator();
|
||||
|
|
|
@ -88,6 +88,28 @@ final class CartesianList<E> extends AbstractList<List<E>> implements RandomAcce
|
|||
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
|
||||
public ImmutableList<E> get(final int index) {
|
||||
checkElementIndex(index, size());
|
||||
|
@ -117,8 +139,21 @@ final class CartesianList<E> extends AbstractList<List<E>> implements RandomAcce
|
|||
return axesSizeProduct[0];
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean contains(Object o) {
|
||||
return indexOf(o) != -1;
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,58 +20,291 @@ import static com.google.common.base.Preconditions.checkNotNull;
|
|||
|
||||
import com.google.common.annotations.GwtCompatible;
|
||||
import com.google.common.annotations.GwtIncompatible;
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.google.common.base.Supplier;
|
||||
import java.util.Collection;
|
||||
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.ToIntFunction;
|
||||
import java.util.stream.Collector;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/** Collectors utilities for {@code common.collect} internals. */
|
||||
@GwtCompatible
|
||||
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 =
|
||||
Collector.of(
|
||||
ImmutableList::<Object>builder,
|
||||
ImmutableList::builder,
|
||||
ImmutableList.Builder::add,
|
||||
ImmutableList.Builder::combine,
|
||||
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() {
|
||||
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(
|
||||
Function<? super T, ? extends K> keyFunction,
|
||||
Function<? super T, ? extends V> valueFunction) {
|
||||
Function<? super T, ? extends K> keyFunction,
|
||||
Function<? super T, ? extends V> valueFunction) {
|
||||
checkNotNull(keyFunction);
|
||||
checkNotNull(valueFunction);
|
||||
return Collector.of(
|
||||
ImmutableMap.Builder<K, V>::new,
|
||||
(builder, input) -> builder.put(keyFunction.apply(input), valueFunction.apply(input)),
|
||||
ImmutableMap.Builder::combine,
|
||||
ImmutableMap.Builder::build);
|
||||
ImmutableMap.Builder<K, V>::new,
|
||||
(builder, input) -> builder.put(keyFunction.apply(input), valueFunction.apply(input)),
|
||||
ImmutableMap.Builder::combine,
|
||||
ImmutableMap.Builder::build);
|
||||
}
|
||||
|
||||
private static final Collector<Object, ?, ImmutableSet<Object>> TO_IMMUTABLE_SET =
|
||||
Collector.of(
|
||||
ImmutableSet::<Object>builder,
|
||||
ImmutableSet.Builder::add,
|
||||
ImmutableSet.Builder::combine,
|
||||
ImmutableSet.Builder::build);
|
||||
public static <T, K, V> Collector<T, ?, ImmutableMap<K, V>> toImmutableMap(
|
||||
Function<? super T, ? extends K> keyFunction,
|
||||
Function<? super T, ? extends V> valueFunction,
|
||||
BinaryOperator<V> mergeFunction) {
|
||||
checkNotNull(keyFunction);
|
||||
checkNotNull(valueFunction);
|
||||
checkNotNull(mergeFunction);
|
||||
return Collectors.collectingAndThen(
|
||||
Collectors.toMap(keyFunction, valueFunction, mergeFunction, LinkedHashMap::new),
|
||||
ImmutableMap::copyOf);
|
||||
}
|
||||
|
||||
static <E> Collector<E, ?, ImmutableSet<E>> toImmutableSet() {
|
||||
return (Collector) TO_IMMUTABLE_SET;
|
||||
static <T, K, V> Collector<T, ?, ImmutableSortedMap<K, V>> toImmutableSortedMap(
|
||||
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(
|
||||
|
@ -92,43 +325,91 @@ final class CollectCollectors {
|
|||
ImmutableSortedMap.Builder::build,
|
||||
Collector.Characteristics.UNORDERED);
|
||||
}
|
||||
// Multimaps
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
@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,
|
||||
static <T, K, V> Collector<T, ?, ImmutableListMultimap<K, V>> toImmutableListMultimap(
|
||||
Function<? super T, ? extends K> keyFunction,
|
||||
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(valueFunction);
|
||||
checkNotNull(multimapSupplier);
|
||||
return Collector.of(
|
||||
ImmutableRangeMap::<K, V>builder,
|
||||
(builder, input) -> builder.put(keyFunction.apply(input), valueFunction.apply(input)),
|
||||
ImmutableRangeMap.Builder::combine,
|
||||
ImmutableRangeMap.Builder::build);
|
||||
multimapSupplier,
|
||||
(multimap, input) -> multimap.put(keyFunction.apply(input), valueFunction.apply(input)),
|
||||
(multimap1, multimap2) -> {
|
||||
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));
|
||||
}
|
||||
|
||||
/** 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}.
|
||||
*
|
||||
|
|
|
@ -190,4 +190,79 @@ public final class Comparators {
|
|||
checkNotNull(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.
|
||||
*/
|
||||
// TODO(kevinb): we can probably make these real @Overrides now
|
||||
/* @Override */
|
||||
@SuppressWarnings("MissingOverride") // Supermethod does not exist under GWT.
|
||||
abstract ContiguousSet<C> headSetImpl(C toElement, boolean inclusive);
|
||||
|
||||
/* @Override */
|
||||
@SuppressWarnings("MissingOverride") // Supermethod does not exist under GWT.
|
||||
abstract ContiguousSet<C> subSetImpl(
|
||||
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);
|
||||
|
||||
/**
|
||||
|
|
|
@ -786,7 +786,7 @@ public abstract class FluentIterable<E> implements Iterable<E> {
|
|||
checkNotNull(collection);
|
||||
Iterable<E> iterable = getDelegate();
|
||||
if (iterable instanceof Collection) {
|
||||
collection.addAll(Collections2.cast(iterable));
|
||||
collection.addAll((Collection<E>) iterable);
|
||||
} else {
|
||||
for (E item : iterable) {
|
||||
collection.add(item);
|
||||
|
|
|
@ -76,8 +76,8 @@ public abstract class ForwardingMap<K, V> extends ForwardingObject implements Ma
|
|||
|
||||
|
||||
@Override
|
||||
public V remove(Object object) {
|
||||
return delegate().remove(object);
|
||||
public V remove(Object key) {
|
||||
return delegate().remove(key);
|
||||
}
|
||||
|
||||
@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
|
||||
* bimap and its inverse in sync during serialization, the way AbstractBiMap does.
|
||||
*/
|
||||
private static class SerializedForm extends ImmutableMap.SerializedForm {
|
||||
SerializedForm(ImmutableBiMap<?, ?> bimap) {
|
||||
private static class SerializedForm<K, V> extends ImmutableMap.SerializedForm<K, V> {
|
||||
SerializedForm(ImmutableBiMap<K, V> bimap) {
|
||||
super(bimap);
|
||||
}
|
||||
|
||||
@Override
|
||||
Object readResolve() {
|
||||
Builder<Object, Object> builder = new Builder<>();
|
||||
return createMap(builder);
|
||||
Builder<K, V> makeBuilder(int size) {
|
||||
return new Builder<>();
|
||||
}
|
||||
|
||||
private static final long serialVersionUID = 0;
|
||||
|
@ -429,6 +428,6 @@ public abstract class ImmutableBiMap<K, V> extends ImmutableBiMapFauxverideShim<
|
|||
|
||||
@Override
|
||||
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.function.Function;
|
||||
import java.util.stream.Collector;
|
||||
import java.util.stream.Collectors;
|
||||
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(
|
||||
Function<? super T, ? extends K> keyFunction,
|
||||
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);
|
||||
return CollectCollectors.toImmutableListMultimap(keyFunction, valueFunction);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -127,14 +120,7 @@ public class ImmutableListMultimap<K, V> extends ImmutableMultimap<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(
|
||||
Multimaps.flatteningToMultimap(
|
||||
input -> checkNotNull(keyFunction.apply(input)),
|
||||
input -> valuesFunction.apply(input).peek(Preconditions::checkNotNull),
|
||||
MultimapBuilder.linkedHashKeys().arrayListValues()::<K, V>build),
|
||||
ImmutableListMultimap::copyOf);
|
||||
return CollectCollectors.flatteningToImmutableListMultimap(keyFunction, valuesFunction);
|
||||
}
|
||||
|
||||
/** Returns the empty multimap. */
|
||||
|
|
|
@ -36,7 +36,6 @@ import java.util.Collections;
|
|||
import java.util.Comparator;
|
||||
import java.util.EnumMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.SortedMap;
|
||||
import java.util.Spliterator;
|
||||
|
@ -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 V> valueFunction,
|
||||
BinaryOperator<V> mergeFunction) {
|
||||
checkNotNull(keyFunction);
|
||||
checkNotNull(valueFunction);
|
||||
checkNotNull(mergeFunction);
|
||||
return Collectors.collectingAndThen(
|
||||
Collectors.toMap(keyFunction, valueFunction, mergeFunction, LinkedHashMap::new),
|
||||
ImmutableMap::copyOf);
|
||||
return CollectCollectors.toImmutableMap(keyFunction, valueFunction, mergeFunction);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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
|
||||
* as implementation details.
|
||||
*/
|
||||
static class SerializedForm implements Serializable {
|
||||
private final Object[] keys;
|
||||
private final Object[] values;
|
||||
static class SerializedForm<K, V> implements Serializable {
|
||||
// This object retains references to collections returned by keySet() and value(). This saves
|
||||
// bytes when the both the map and its keySet or value collection are written to the same
|
||||
// instance of ObjectOutputStream.
|
||||
|
||||
SerializedForm(ImmutableMap<?, ?> map) {
|
||||
keys = new Object[map.size()];
|
||||
values = new Object[map.size()];
|
||||
int i = 0;
|
||||
for (Entry<?, ?> entry : map.entrySet()) {
|
||||
keys[i] = entry.getKey();
|
||||
values[i] = entry.getValue();
|
||||
i++;
|
||||
// TODO(b/160980469): remove support for the old serialization format after some time
|
||||
private static final boolean USE_LEGACY_SERIALIZATION = true;
|
||||
|
||||
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;
|
||||
for (Entry<?, ?> entry : map.entrySet()) {
|
||||
keys[i] = entry.getKey();
|
||||
values[i] = entry.getValue();
|
||||
i++;
|
||||
}
|
||||
this.keys = keys;
|
||||
this.values = values;
|
||||
return;
|
||||
}
|
||||
this.keys = map.keySet();
|
||||
this.values = map.values();
|
||||
}
|
||||
|
||||
Object readResolve() {
|
||||
Builder<Object, Object> builder = new Builder<>(keys.length);
|
||||
return createMap(builder);
|
||||
@SuppressWarnings("unchecked")
|
||||
final Object readResolve() {
|
||||
if (!(this.keys instanceof ImmutableSet)) {
|
||||
return legacyReadResolve();
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
Object createMap(Builder<Object, Object> builder) {
|
||||
@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++) {
|
||||
builder.put(keys[i], values[i]);
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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() {
|
||||
return new SerializedForm(this);
|
||||
return new SerializedForm<>(this);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -75,12 +75,6 @@ final class ImmutableMapKeySet<K, V> extends IndexedImmutableSet<K> {
|
|||
return true;
|
||||
}
|
||||
|
||||
@GwtIncompatible // serialization
|
||||
@Override
|
||||
Object writeReplace() {
|
||||
return new KeySetSerializedForm<K>(map);
|
||||
}
|
||||
|
||||
@GwtIncompatible // serialization
|
||||
private static class KeySetSerializedForm<K> implements Serializable {
|
||||
final ImmutableMap<K, ?> map;
|
||||
|
|
|
@ -100,12 +100,6 @@ final class ImmutableMapValues<K, V> extends ImmutableCollection<V> {
|
|||
map.forEach((k, v) -> action.accept(v));
|
||||
}
|
||||
|
||||
@GwtIncompatible // serialization
|
||||
@Override
|
||||
Object writeReplace() {
|
||||
return new SerializedForm<V>(map);
|
||||
}
|
||||
|
||||
@GwtIncompatible // serialization
|
||||
private static class SerializedForm<V> implements Serializable {
|
||||
final ImmutableMap<?, V> map;
|
||||
|
|
|
@ -64,7 +64,7 @@ public abstract class ImmutableMultiset<E> extends ImmutableMultisetGwtSerializa
|
|||
* @since 21.0
|
||||
*/
|
||||
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(
|
||||
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) -> copyFromEntries(multiset.entrySet()));
|
||||
return CollectCollectors.toImmutableMultiset(elementFunction, countFunction);
|
||||
}
|
||||
|
||||
/** Returns the empty immutable multiset. */
|
||||
|
|
|
@ -514,13 +514,11 @@ public abstract class ImmutableSet<E> extends ImmutableCollection<E> implements
|
|||
}
|
||||
|
||||
@Override
|
||||
|
||||
public Builder<E> add(E... elements) {
|
||||
super.add(elements);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
/**
|
||||
* Adds each element of {@code elements} to the {@code ImmutableSet}, ignoring duplicate
|
||||
* elements (only the first duplicate element is added).
|
||||
|
@ -529,7 +527,7 @@ public abstract class ImmutableSet<E> extends ImmutableCollection<E> implements
|
|||
* @return this {@code Builder} object
|
||||
* @throws NullPointerException if {@code elements} is null or contains a null element
|
||||
*/
|
||||
|
||||
@Override
|
||||
public Builder<E> addAll(Iterable<? extends E> elements) {
|
||||
super.addAll(elements);
|
||||
return this;
|
||||
|
|
|
@ -22,7 +22,6 @@ import com.google.common.annotations.Beta;
|
|||
import com.google.common.annotations.GwtCompatible;
|
||||
import com.google.common.annotations.GwtIncompatible;
|
||||
import com.google.common.base.MoreObjects;
|
||||
import com.google.common.base.Preconditions;
|
||||
|
||||
|
||||
|
||||
|
@ -38,7 +37,6 @@ import java.util.Map;
|
|||
import java.util.Map.Entry;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collector;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
|
||||
|
@ -87,13 +85,7 @@ public class ImmutableSetMultimap<K, V> extends ImmutableMultimap<K, V>
|
|||
public 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);
|
||||
return CollectCollectors.toImmutableSetMultimap(keyFunction, valueFunction);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -140,14 +132,7 @@ public class ImmutableSetMultimap<K, V> extends ImmutableMultimap<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(
|
||||
Multimaps.flatteningToMultimap(
|
||||
input -> checkNotNull(keyFunction.apply(input)),
|
||||
input -> valuesFunction.apply(input).peek(Preconditions::checkNotNull),
|
||||
MultimapBuilder.linkedHashKeys().linkedHashSetValues()::<K, V>build),
|
||||
ImmutableSetMultimap::copyOf);
|
||||
return CollectCollectors.flatteningToImmutableSetMultimap(keyFunction, valuesFunction);
|
||||
}
|
||||
|
||||
/** 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 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);
|
||||
return CollectCollectors.toImmutableSortedMap(comparator, keyFunction, valueFunction, mergeFunction);
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -916,19 +909,18 @@ public final class ImmutableSortedMap<K, V> extends ImmutableSortedMapFauxveride
|
|||
* are reconstructed using public factory methods. This ensures that the implementation types
|
||||
* remain as implementation details.
|
||||
*/
|
||||
private static class SerializedForm extends ImmutableMap.SerializedForm {
|
||||
private final Comparator<Object> comparator;
|
||||
private static class SerializedForm<K, V> extends ImmutableMap.SerializedForm<K, V> {
|
||||
private final Comparator<? super K> comparator;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
SerializedForm(ImmutableSortedMap<?, ?> sortedMap) {
|
||||
SerializedForm(ImmutableSortedMap<K, V> sortedMap) {
|
||||
super(sortedMap);
|
||||
comparator = (Comparator<Object>) sortedMap.comparator();
|
||||
comparator = sortedMap.comparator();
|
||||
}
|
||||
|
||||
@Override
|
||||
Object readResolve() {
|
||||
Builder<Object, Object> builder = new Builder<>(comparator);
|
||||
return createMap(builder);
|
||||
Builder<K, V> makeBuilder(int size) {
|
||||
return new Builder<>(comparator);
|
||||
}
|
||||
|
||||
private static final long serialVersionUID = 0;
|
||||
|
@ -936,7 +928,7 @@ public final class ImmutableSortedMap<K, V> extends ImmutableSortedMapFauxveride
|
|||
|
||||
@Override
|
||||
Object writeReplace() {
|
||||
return new SerializedForm(this);
|
||||
return new SerializedForm<>(this);
|
||||
}
|
||||
|
||||
// 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.base.MoreObjects;
|
||||
import com.google.common.collect.Tables.AbstractCell;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
@ -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 C> columnFunction,
|
||||
Function<? super T, ? extends V> valueFunction) {
|
||||
checkNotNull(rowFunction, "rowFunction");
|
||||
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());
|
||||
return TableCollectors.toImmutableTable(rowFunction, columnFunction, valueFunction);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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 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 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");
|
||||
}
|
||||
return TableCollectors.toImmutableTable(rowFunction, columnFunction, valueFunction, mergeFunction);
|
||||
}
|
||||
|
||||
/** 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) {
|
||||
ImmutableTable.Builder<R, C, V> builder = ImmutableTable.builder();
|
||||
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
|
||||
* implementations are available from the {@link Interners} class.
|
||||
* Provides similar behavior to {@link String#intern} for any immutable type. Common implementations
|
||||
* 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
|
||||
* @since 3.0
|
||||
|
|
|
@ -314,7 +314,7 @@ public final class Iterables {
|
|||
|
||||
public static <T> boolean addAll(Collection<T> addTo, Iterable<? extends T> elementsToAdd) {
|
||||
if (elementsToAdd instanceof Collection) {
|
||||
Collection<? extends T> c = Collections2.cast(elementsToAdd);
|
||||
Collection<? extends T> c = (Collection<? extends T>) elementsToAdd;
|
||||
return addTo.addAll(c);
|
||||
}
|
||||
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) {
|
||||
if (iterable instanceof Collection) {
|
||||
Collection<? extends T> c = Collections2.cast(iterable);
|
||||
Collection<? extends T> c = (Collection<? extends T>) iterable;
|
||||
if (c.isEmpty()) {
|
||||
return defaultValue;
|
||||
} else if (iterable instanceof List) {
|
||||
|
|
|
@ -126,8 +126,8 @@ public final class Lists {
|
|||
checkNotNull(elements); // for GWT
|
||||
// Let ArrayList's sizing logic work, if possible
|
||||
return (elements instanceof Collection)
|
||||
? new ArrayList<>(Collections2.cast(elements))
|
||||
: newArrayList(elements.iterator());
|
||||
? new ArrayList<>((Collection<? extends E>) elements)
|
||||
: newArrayList(elements.iterator());
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -264,8 +264,9 @@ public final class Lists {
|
|||
Iterable<? extends E> elements) {
|
||||
// We copy elements to an ArrayList first, rather than incurring the
|
||||
// quadratic cost of adding them to the COWAL directly.
|
||||
Collection<? extends E> elementsCollection =
|
||||
(elements instanceof Collection) ? Collections2.cast(elements) : newArrayList(elements);
|
||||
Collection<? extends E> elementsCollection = (elements instanceof Collection)
|
||||
? (Collection<? extends E>) elements
|
||||
: newArrayList(elements);
|
||||
return new CopyOnWriteArrayList<>(elementsCollection);
|
||||
}
|
||||
|
||||
|
|
|
@ -64,7 +64,7 @@ import java.util.concurrent.locks.ReentrantLock;
|
|||
* @author Charles Fry
|
||||
* @author Doug Lea ({@code ConcurrentHashMap})
|
||||
*/
|
||||
// TODO(kak/cpovirk): Consider removing from this class.
|
||||
// TODO(kak): Consider removing @CanIgnoreReturnValue from this class.
|
||||
@GwtIncompatible
|
||||
@SuppressWarnings("GuardedBy") // TODO(b/35466881): Fix or suppress.
|
||||
class MapMakerInternalMap<
|
||||
|
|
|
@ -172,37 +172,6 @@ public final class Maps {
|
|||
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
|
||||
* 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(
|
||||
java.util.function.Function<? super T, ? extends K> keyFunction,
|
||||
java.util.function.Function<? super T, ? extends V> valueFunction) {
|
||||
checkNotNull(keyFunction);
|
||||
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);
|
||||
return CollectCollectors.toImmutableEnumMap(keyFunction, valueFunction);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -253,19 +207,7 @@ public final class Maps {
|
|||
java.util.function.Function<? super T, ? extends K> keyFunction,
|
||||
java.util.function.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 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);
|
||||
return CollectCollectors.toImmutableEnumMap(keyFunction, valueFunction, mergeFunction);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -49,6 +49,7 @@ import java.util.NoSuchElementException;
|
|||
import java.util.Set;
|
||||
import java.util.SortedSet;
|
||||
import java.util.Spliterator;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.stream.Collector;
|
||||
import java.util.stream.Stream;
|
||||
|
@ -104,21 +105,11 @@ public final class Multimaps {
|
|||
*
|
||||
* @since 21.0
|
||||
*/
|
||||
@Beta
|
||||
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 V> valueFunction,
|
||||
java.util.function.Supplier<M> multimapSupplier) {
|
||||
checkNotNull(keyFunction);
|
||||
checkNotNull(valueFunction);
|
||||
checkNotNull(multimapSupplier);
|
||||
return Collector.of(
|
||||
multimapSupplier,
|
||||
(multimap, input) -> multimap.put(keyFunction.apply(input), valueFunction.apply(input)),
|
||||
(multimap1, multimap2) -> {
|
||||
multimap1.putAll(multimap2);
|
||||
return multimap1;
|
||||
});
|
||||
return CollectCollectors.toMultimap(keyFunction, valueFunction, multimapSupplier);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -695,6 +686,11 @@ public final class Multimaps {
|
|||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void forEach(BiConsumer<? super K, ? super V> consumer) {
|
||||
delegate.forEach(checkNotNull(consumer));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<V> get(K 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.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;
|
||||
});
|
||||
return CollectCollectors.toMultiset(elementFunction, countFunction, multisetSupplier);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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
|
||||
* 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)}
|
||||
* instead (but note that it does not guarantee which tied minimum element is returned).
|
||||
* <p><b>Note:</b> Consider using {@code Comparators.min(a, b, thisComparator)} instead. If {@code
|
||||
* 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 b value to compare.
|
||||
|
|
|
@ -74,7 +74,7 @@ public final class Queues {
|
|||
*/
|
||||
public static <E> ArrayDeque<E> newArrayDeque(Iterable<? extends E> elements) {
|
||||
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>();
|
||||
Iterables.addAll(deque, elements);
|
||||
|
@ -97,7 +97,7 @@ public final class Queues {
|
|||
public static <E> ConcurrentLinkedQueue<E> newConcurrentLinkedQueue(
|
||||
Iterable<? extends E> elements) {
|
||||
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>();
|
||||
Iterables.addAll(queue, elements);
|
||||
|
@ -137,7 +137,7 @@ public final class Queues {
|
|||
@GwtIncompatible // LinkedBlockingDeque
|
||||
public static <E> LinkedBlockingDeque<E> newLinkedBlockingDeque(Iterable<? extends E> elements) {
|
||||
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>();
|
||||
Iterables.addAll(deque, elements);
|
||||
|
@ -173,7 +173,7 @@ public final class Queues {
|
|||
@GwtIncompatible // LinkedBlockingQueue
|
||||
public static <E> LinkedBlockingQueue<E> newLinkedBlockingQueue(Iterable<? extends E> elements) {
|
||||
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>();
|
||||
Iterables.addAll(queue, elements);
|
||||
|
@ -207,7 +207,7 @@ public final class Queues {
|
|||
public static <E extends Comparable> PriorityBlockingQueue<E> newPriorityBlockingQueue(
|
||||
Iterable<? extends E> elements) {
|
||||
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>();
|
||||
Iterables.addAll(queue, elements);
|
||||
|
@ -237,7 +237,7 @@ public final class Queues {
|
|||
public static <E extends Comparable> PriorityQueue<E> newPriorityQueue(
|
||||
Iterable<? extends E> elements) {
|
||||
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>();
|
||||
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
|
||||
* continue as usual, and in the end the thread's interruption status will be set (no {@code
|
||||
* InterruptedException} is thrown).
|
||||
|
|
|
@ -576,6 +576,21 @@ public final class Range<C extends Comparable> extends RangeGwtSerializationDepe
|
|||
* @since 27.0
|
||||
*/
|
||||
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;
|
||||
Range<C> firstRange = isThisFirst ? this : otherRange;
|
||||
Range<C> secondRange = isThisFirst ? otherRange : this;
|
||||
|
|
|
@ -207,10 +207,10 @@ final class RegularImmutableMap<K, V> extends ImmutableMap<K, V> {
|
|||
}
|
||||
|
||||
@GwtCompatible(emulated = true)
|
||||
private static final class KeySet<K, V> extends IndexedImmutableSet<K> {
|
||||
private final RegularImmutableMap<K, V> map;
|
||||
private static final class KeySet<K> extends IndexedImmutableSet<K> {
|
||||
private final RegularImmutableMap<K, ?> map;
|
||||
|
||||
KeySet(RegularImmutableMap<K, V> map) {
|
||||
KeySet(RegularImmutableMap<K, ?> map) {
|
||||
this.map = map;
|
||||
}
|
||||
|
||||
|
@ -234,12 +234,6 @@ final class RegularImmutableMap<K, V> extends ImmutableMap<K, V> {
|
|||
return map.size();
|
||||
}
|
||||
|
||||
@GwtIncompatible // serialization
|
||||
@Override
|
||||
Object writeReplace() {
|
||||
return new SerializedForm<K>(map);
|
||||
}
|
||||
|
||||
@GwtIncompatible // serialization
|
||||
private static class SerializedForm<K> implements Serializable {
|
||||
final ImmutableMap<K, ?> map;
|
||||
|
@ -284,12 +278,6 @@ final class RegularImmutableMap<K, V> extends ImmutableMap<K, V> {
|
|||
return true;
|
||||
}
|
||||
|
||||
@GwtIncompatible // serialization
|
||||
@Override
|
||||
Object writeReplace() {
|
||||
return new SerializedForm<V>(map);
|
||||
}
|
||||
|
||||
@GwtIncompatible // serialization
|
||||
private static class SerializedForm<V> implements Serializable {
|
||||
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}
|
||||
* with an implementation specialized for enums. Unlike {@link ImmutableSet#toImmutableSet}, the
|
||||
|
@ -182,7 +146,7 @@ public final class Sets {
|
|||
* @since 21.0
|
||||
*/
|
||||
public static <E extends Enum<E>> Collector<E, ?, ImmutableSet<E>> toImmutableEnumSet() {
|
||||
return (Collector) Accumulator.TO_IMMUTABLE_ENUM_SET;
|
||||
return CollectCollectors.toImmutableEnumSet();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -254,8 +218,8 @@ public final class Sets {
|
|||
*/
|
||||
public static <E> HashSet<E> newHashSet(Iterable<? extends E> elements) {
|
||||
return (elements instanceof Collection)
|
||||
? new HashSet<E>(Collections2.cast(elements))
|
||||
: newHashSet(elements.iterator());
|
||||
? new HashSet<E>((Collection<? extends E>) elements)
|
||||
: newHashSet(elements.iterator());
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -359,7 +323,7 @@ public final class Sets {
|
|||
*/
|
||||
public static <E> LinkedHashSet<E> newLinkedHashSet(Iterable<? extends E> elements) {
|
||||
if (elements instanceof Collection) {
|
||||
return new LinkedHashSet<E>(Collections2.cast(elements));
|
||||
return new LinkedHashSet<E>((Collection<? extends E>) elements);
|
||||
}
|
||||
LinkedHashSet<E> set = newLinkedHashSet();
|
||||
Iterables.addAll(set, elements);
|
||||
|
@ -487,8 +451,8 @@ public final class Sets {
|
|||
// quadratic cost of adding them to the COWAS directly.
|
||||
Collection<? extends E> elementsCollection =
|
||||
(elements instanceof Collection)
|
||||
? Collections2.cast(elements)
|
||||
: Lists.newArrayList(elements);
|
||||
? (Collection<? extends E>) elements
|
||||
: Lists.newArrayList(elements);
|
||||
return new CopyOnWriteArraySet<E>(elementsCollection);
|
||||
}
|
||||
|
||||
|
@ -1427,6 +1391,25 @@ public final class Sets {
|
|||
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
|
||||
public boolean equals(Object object) {
|
||||
// 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) {
|
||||
if (obj instanceof PowerSet) {
|
||||
PowerSet<?> that = (PowerSet<?>) obj;
|
||||
return inputSet.equals(that.inputSet);
|
||||
return inputSet.keySet().equals(that.inputSet.keySet());
|
||||
}
|
||||
return super.equals(obj);
|
||||
}
|
||||
|
|
|
@ -42,6 +42,7 @@ final class SingletonImmutableBiMap<K, V> extends ImmutableBiMap<K, V> {
|
|||
checkEntryNotNull(singleKey, singleValue);
|
||||
this.singleKey = singleKey;
|
||||
this.singleValue = singleValue;
|
||||
this.inverse = null;
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
private transient ImmutableBiMap<V, K> lazyInverse;
|
||||
|
||||
@Override
|
||||
public ImmutableBiMap<V, K> inverse() {
|
||||
// racy single-check idiom
|
||||
ImmutableBiMap<V, K> result = inverse;
|
||||
if (result == null) {
|
||||
return inverse = new SingletonImmutableBiMap<>(singleValue, singleKey, this);
|
||||
if (inverse != null) {
|
||||
return inverse;
|
||||
} else {
|
||||
return result;
|
||||
// racy single-check idiom
|
||||
ImmutableBiMap<V, K> result = lazyInverse;
|
||||
if (result == null) {
|
||||
return lazyInverse = new SingletonImmutableBiMap<>(singleValue, singleKey, this);
|
||||
} else {
|
||||
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 V> valueFunction,
|
||||
java.util.function.Supplier<I> tableSupplier) {
|
||||
return toTable(
|
||||
rowFunction,
|
||||
columnFunction,
|
||||
valueFunction,
|
||||
(v1, v2) -> {
|
||||
throw new IllegalStateException("Conflicting values " + v1 + " and " + v2);
|
||||
},
|
||||
tableSupplier);
|
||||
return TableCollectors.toTable(rowFunction, columnFunction, valueFunction, tableSupplier);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -98,26 +91,7 @@ public final class Tables {
|
|||
java.util.function.Function<? super T, ? extends V> valueFunction,
|
||||
BinaryOperator<V> mergeFunction,
|
||||
java.util.function.Supplier<I> tableSupplier) {
|
||||
checkNotNull(rowFunction);
|
||||
checkNotNull(columnFunction);
|
||||
checkNotNull(valueFunction);
|
||||
checkNotNull(mergeFunction);
|
||||
checkNotNull(tableSupplier);
|
||||
return Collector.of(
|
||||
tableSupplier,
|
||||
(table, input) ->
|
||||
merge(
|
||||
table,
|
||||
rowFunction.apply(input),
|
||||
columnFunction.apply(input),
|
||||
valueFunction.apply(input),
|
||||
mergeFunction),
|
||||
(table1, table2) -> {
|
||||
for (Table.Cell<R, C, V> cell2 : table2.cellSet()) {
|
||||
merge(table1, cell2.getRowKey(), cell2.getColumnKey(), cell2.getValue(), mergeFunction);
|
||||
}
|
||||
return table1;
|
||||
});
|
||||
return TableCollectors.toTable(rowFunction, columnFunction, valueFunction, mergeFunction, tableSupplier);
|
||||
}
|
||||
|
||||
private static <R, C, V> void merge(
|
||||
|
|
|
@ -119,7 +119,6 @@ public final class TreeRangeMap<K extends Comparable, V> implements RangeMap<K,
|
|||
|
||||
@Override
|
||||
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()) {
|
||||
checkNotNull(value);
|
||||
remove(range);
|
||||
|
@ -129,6 +128,7 @@ public final class TreeRangeMap<K extends Comparable, V> implements RangeMap<K,
|
|||
|
||||
@Override
|
||||
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()) {
|
||||
put(range, value);
|
||||
return;
|
||||
|
@ -508,7 +508,7 @@ public final class TreeRangeMap<K extends Comparable, V> implements RangeMap<K,
|
|||
|
||||
@Override
|
||||
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);
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -892,7 +892,7 @@ public class TreeRangeSet<C extends Comparable<?>> extends AbstractRangeSet<C>
|
|||
"Cannot add range %s to subRangeSet(%s)",
|
||||
rangeToAdd,
|
||||
restriction);
|
||||
super.add(rangeToAdd);
|
||||
TreeRangeSet.this.add(rangeToAdd);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -23,9 +23,10 @@ import java.lang.annotation.Target;
|
|||
/**
|
||||
* 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
|
||||
* annotation is applied to methods with zero parameters, or more than one parameter, the object
|
||||
* containing the method will not be able to register for event delivery from the {@link EventBus}.
|
||||
* <p>The type of event will be indicated by the method's first (and only) parameter, which cannot
|
||||
* be primitive. If this annotation is applied to methods with zero parameters, or more than one
|
||||
* parameter, the object containing the method will not be able to register for event delivery from
|
||||
* the {@link EventBus}.
|
||||
*
|
||||
* <p>Unless also annotated with @{@link AllowConcurrentEvents}, event subscriber methods will be
|
||||
* 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.checkNotNull;
|
||||
import static com.google.common.base.Throwables.throwIfUnchecked;
|
||||
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
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.Maps;
|
||||
import com.google.common.collect.Multimap;
|
||||
import com.google.common.primitives.Primitives;
|
||||
import com.google.common.reflect.TypeToken;
|
||||
import com.google.common.util.concurrent.UncheckedExecutionException;
|
||||
|
||||
|
@ -170,7 +172,12 @@ final class SubscriberRegistry {
|
|||
}
|
||||
|
||||
private static ImmutableList<Method> getAnnotatedMethods(Class<?> clazz) {
|
||||
return subscriberMethodsCache.getUnchecked(clazz);
|
||||
try {
|
||||
return subscriberMethodsCache.getUnchecked(clazz);
|
||||
} catch (UncheckedExecutionException e) {
|
||||
throwIfUnchecked(e.getCause());
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
private static ImmutableList<Method> getAnnotatedMethodsNotCached(Class<?> clazz) {
|
||||
|
@ -187,7 +194,14 @@ final class SubscriberRegistry {
|
|||
+ "Subscriber methods must have exactly 1 parameter.",
|
||||
method,
|
||||
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);
|
||||
if (!identifiers.containsKey(ident)) {
|
||||
identifiers.put(ident, method);
|
||||
|
|
|
@ -44,7 +44,7 @@ import java.util.Set;
|
|||
abstract class AbstractBaseGraph<N> implements BaseGraph<N> {
|
||||
|
||||
/**
|
||||
* Returns the number of edges in this graph; used to calculate the size of {@link #edges()}. This
|
||||
* Returns the number of edges in this graph; used to calculate the size of {@code #edges()}. This
|
||||
* implementation requires O(|N|) time. Classes extending this one may manually keep track of the
|
||||
* number of edges as the graph is updated, and override this method for better performance.
|
||||
*/
|
||||
|
@ -59,7 +59,7 @@ abstract class AbstractBaseGraph<N> implements BaseGraph<N> {
|
|||
}
|
||||
|
||||
/**
|
||||
* An implementation of {@link BaseGraph#edges()} defined in terms of {@link #nodes()} and {@link
|
||||
* An implementation of {@link BaseGraph#edges()} defined in terms of {@code #nodes()} and {@link
|
||||
* #successors(Object)}.
|
||||
*/
|
||||
@Override
|
||||
|
|
|
@ -22,13 +22,10 @@ import static com.google.common.base.Preconditions.checkNotNull;
|
|||
import com.google.common.annotations.Beta;
|
||||
import com.google.common.collect.AbstractIterator;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.common.collect.UnmodifiableIterator;
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.Deque;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.Queue;
|
||||
import java.util.Set;
|
||||
|
||||
|
||||
|
@ -63,6 +60,11 @@ import java.util.Set;
|
|||
*/
|
||||
@Beta
|
||||
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}.
|
||||
|
@ -88,10 +90,14 @@ public abstract class Traverser<N> {
|
|||
*
|
||||
* @param graph {@link SuccessorsFunction} representing a general graph that may have cycles.
|
||||
*/
|
||||
public static <N> Traverser<N> forGraph(SuccessorsFunction<N> graph) {
|
||||
checkNotNull(graph);
|
||||
return new GraphTraverser<>(graph);
|
||||
}
|
||||
public static <N> Traverser<N> forGraph(final SuccessorsFunction<N> graph) {
|
||||
return new Traverser<N>(graph) {
|
||||
@Override
|
||||
Traversal<N> newTraversal() {
|
||||
return Traversal.inGraph(graph);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new traverser for a directed acyclic graph that has at most one path from the start
|
||||
|
@ -174,7 +180,12 @@ public abstract class Traverser<N> {
|
|||
if (tree instanceof Network) {
|
||||
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
|
||||
*/
|
||||
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
|
||||
|
@ -220,7 +233,15 @@ public abstract class Traverser<N> {
|
|||
* @see #breadthFirst(Object)
|
||||
* @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
|
||||
|
@ -253,7 +274,9 @@ public abstract class Traverser<N> {
|
|||
*
|
||||
* @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
|
||||
|
@ -265,7 +288,15 @@ public abstract class Traverser<N> {
|
|||
* @see #depthFirstPreOrder(Object)
|
||||
* @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
|
||||
|
@ -298,7 +329,9 @@ public abstract class Traverser<N> {
|
|||
*
|
||||
* @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
|
||||
|
@ -310,352 +343,156 @@ public abstract class Traverser<N> {
|
|||
* @see #depthFirstPostOrder(Object)
|
||||
* @since 24.1
|
||||
*/
|
||||
public abstract Iterable<N> depthFirstPostOrder(Iterable<? extends N> 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>() {
|
||||
@Override
|
||||
public Iterator<N> iterator() {
|
||||
return new BreadthFirstIterator(startNodes);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
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")
|
||||
private void checkThatNodeIsInGraph(N startNode) {
|
||||
// successors() throws an IllegalArgumentException for nodes that are not an element of the
|
||||
// graph.
|
||||
graph.successors(startNode);
|
||||
}
|
||||
|
||||
private final class BreadthFirstIterator extends UnmodifiableIterator<N> {
|
||||
private final Queue<N> queue = new ArrayDeque<>();
|
||||
private final Set<N> visited = new HashSet<>();
|
||||
|
||||
BreadthFirstIterator(Iterable<? extends N> roots) {
|
||||
for (N root : roots) {
|
||||
// add all roots to the queue, skipping duplicates
|
||||
if (visited.add(root)) {
|
||||
queue.add(root);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public final Iterable<N> depthFirstPostOrder(Iterable<? extends N> startNodes) {
|
||||
final ImmutableSet<N> validated = validate(startNodes);
|
||||
return new Iterable<N>() {
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
return !queue.isEmpty();
|
||||
public Iterator<N> iterator() {
|
||||
return newTraversal().postOrder(validated.iterator());
|
||||
}
|
||||
|
||||
@Override
|
||||
public N next() {
|
||||
N current = queue.remove();
|
||||
for (N neighbor : graph.successors(current)) {
|
||||
if (visited.add(neighbor)) {
|
||||
queue.add(neighbor);
|
||||
}
|
||||
}
|
||||
return current;
|
||||
}
|
||||
}
|
||||
|
||||
private final class DepthFirstIterator extends AbstractIterator<N> {
|
||||
private final Deque<NodeAndSuccessors> stack = new ArrayDeque<>();
|
||||
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;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected N computeNext() {
|
||||
while (true) {
|
||||
if (stack.isEmpty()) {
|
||||
return endOfData();
|
||||
}
|
||||
NodeAndSuccessors nodeAndSuccessors = stack.getFirst();
|
||||
boolean firstVisit = visited.add(nodeAndSuccessors.node);
|
||||
boolean lastVisit = !nodeAndSuccessors.successorIterator.hasNext();
|
||||
boolean produceNode =
|
||||
(firstVisit && order == Order.PREORDER) || (lastVisit && order == Order.POSTORDER);
|
||||
if (lastVisit) {
|
||||
stack.pop();
|
||||
} else {
|
||||
// we need to push a neighbor, but only if we haven't already seen it
|
||||
N successor = nodeAndSuccessors.successorIterator.next();
|
||||
if (!visited.contains(successor)) {
|
||||
stack.push(withSuccessors(successor));
|
||||
}
|
||||
}
|
||||
if (produceNode && nodeAndSuccessors.node != null) {
|
||||
return nodeAndSuccessors.node;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NodeAndSuccessors withSuccessors(N node) {
|
||||
return new NodeAndSuccessors(node, graph.successors(node));
|
||||
}
|
||||
|
||||
/** A simple tuple of a node and a partially iterated {@link Iterator} of its successors. */
|
||||
private final class NodeAndSuccessors {
|
||||
final N node;
|
||||
final Iterator<? 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;
|
||||
abstract Traversal<N> newTraversal();
|
||||
|
||||
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
|
||||
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 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
|
||||
protected N computeNext() {
|
||||
while (!stack.isEmpty()) {
|
||||
NodeAndChildren top = stack.getLast();
|
||||
if (top.childIterator.hasNext()) {
|
||||
N child = top.childIterator.next();
|
||||
stack.addLast(withChildren(child));
|
||||
} else {
|
||||
stack.removeLast();
|
||||
if (top.node != null) {
|
||||
return top.node;
|
||||
}
|
||||
}
|
||||
}
|
||||
return endOfData();
|
||||
}
|
||||
|
||||
NodeAndChildren withChildren(N node) {
|
||||
return new NodeAndChildren(node, tree.successors(node));
|
||||
}
|
||||
|
||||
/** A simple tuple of a node and a partially iterated {@link Iterator} of its children. */
|
||||
private final class NodeAndChildren {
|
||||
final N node;
|
||||
final Iterator<? extends N> childIterator;
|
||||
|
||||
NodeAndChildren(N node, Iterable<? extends N> children) {
|
||||
this.node = node;
|
||||
this.childIterator = children.iterator();
|
||||
}
|
||||
}
|
||||
@SuppressWarnings("CheckReturnValue")
|
||||
private ImmutableSet<N> validate(Iterable<? extends N> startNodes) {
|
||||
ImmutableSet<N> copy = ImmutableSet.copyOf(startNodes);
|
||||
for (N node : copy) {
|
||||
successorFunction.successors(node); // Will throw if node doesn't exist
|
||||
}
|
||||
return copy;
|
||||
}
|
||||
|
||||
private enum Order {
|
||||
PREORDER,
|
||||
POSTORDER
|
||||
/**
|
||||
* Abstracts away the difference between traversing a graph vs. a tree. For a tree, we just take
|
||||
* 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;
|
||||
|
||||
Traversal(SuccessorsFunction<N> successorFunction) {
|
||||
this.successorFunction = successorFunction;
|
||||
}
|
||||
|
||||
static <N> Traversal<N> inGraph(SuccessorsFunction<N> graph) {
|
||||
final Set<N> visited = new HashSet<>();
|
||||
return new Traversal<N>(graph) {
|
||||
@Override
|
||||
N visitNext(Deque<Iterator<? extends N>> horizon) {
|
||||
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
|
||||
N visitNext(Deque<Iterator<? extends N>> horizon) {
|
||||
Iterator<? extends N> top = horizon.getFirst();
|
||||
if (top.hasNext()) {
|
||||
return checkNotNull(top.next());
|
||||
}
|
||||
horizon.removeFirst();
|
||||
return null;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
final Iterator<N> breadthFirst(Iterator<? extends N> startNodes) {
|
||||
return topDown(startNodes, InsertionOrder.BACK);
|
||||
}
|
||||
|
||||
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
|
||||
protected N computeNext() {
|
||||
do {
|
||||
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();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
final Iterator<N> postOrder(Iterator<? extends N> startNodes) {
|
||||
final Deque<N> ancestorStack = new ArrayDeque<>();
|
||||
final Deque<Iterator<? extends N>> horizon = new ArrayDeque<>();
|
||||
horizon.add(startNodes);
|
||||
return new AbstractIterator<N>() {
|
||||
@Override
|
||||
protected N computeNext() {
|
||||
for (N next = visitNext(horizon); next != null; next = visitNext(horizon)) {
|
||||
Iterator<? extends N> successors = successorFunction.successors(next).iterator();
|
||||
if (!successors.hasNext()) {
|
||||
return next;
|
||||
}
|
||||
horizon.addFirst(successors);
|
||||
ancestorStack.push(next);
|
||||
}
|
||||
return ancestorStack.isEmpty() ? endOfData() : ancestorStack.pop();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
|
||||
/** Poor man's method reference for {@code Deque::addFirst} and {@code Deque::addLast}. */
|
||||
private enum InsertionOrder {
|
||||
FRONT {
|
||||
@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);
|
||||
}
|
||||
};
|
||||
|
||||
abstract <T> void insertInto(Deque<T> deque, T value);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -110,18 +110,18 @@ final class Crc32cHashFunction extends AbstractHashFunction {
|
|||
0xbe2da0a5, 0x4c4623a6, 0x5f16d052, 0xad7d5351
|
||||
};
|
||||
|
||||
private int crc = 0;
|
||||
private int crc = ~0;
|
||||
|
||||
@Override
|
||||
public void update(byte b) {
|
||||
crc ^= 0xFFFFFFFF;
|
||||
// See Hacker's Delight 2nd Edition, Figure 14-7.
|
||||
crc = ~((crc >>> 8) ^ CRC_TABLE[(crc ^ b) & 0xFF]);
|
||||
crc = (crc >>> 8) ^ CRC_TABLE[(crc ^ b) & 0xFF];
|
||||
}
|
||||
|
||||
@Override
|
||||
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.util.ArrayDeque;
|
||||
import java.util.Arrays;
|
||||
import java.util.Deque;
|
||||
import java.util.Queue;
|
||||
|
||||
/**
|
||||
* 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
|
||||
* 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 {
|
||||
// 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
|
||||
|
@ -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];
|
||||
int remaining = totalLen;
|
||||
while (remaining > 0) {
|
||||
byte[] buf = bufs.removeFirst();
|
||||
byte[] buf = bufs.remove();
|
||||
int bytesToCopy = Math.min(remaining, buf.length);
|
||||
int resultOffset = totalLen - remaining;
|
||||
System.arraycopy(buf, 0, result, resultOffset, bytesToCopy);
|
||||
|
@ -252,7 +252,7 @@ public final class ByteStreams {
|
|||
}
|
||||
|
||||
// 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(new byte[] {(byte) b});
|
||||
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.Predicate;
|
||||
import com.google.common.base.Splitter;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.common.collect.Lists;
|
||||
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
|
||||
* 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,
|
||||
* 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);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
* 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 =
|
||||
new SuccessorsFunction<File>() {
|
||||
@Override
|
||||
public Iterable<File> successors(File file) {
|
||||
return fileTreeChildren(file);
|
||||
}
|
||||
};
|
||||
file -> {
|
||||
// check isDirectory() just because it may be faster than listFiles() on a non-directory
|
||||
if (file.isDirectory()) {
|
||||
File[] files = file.listFiles();
|
||||
if (files != null) {
|
||||
return Collections.unmodifiableList(Arrays.asList(files));
|
||||
}
|
||||
}
|
||||
|
||||
return ImmutableList.of(); };
|
||||
|
||||
private static Iterable<File> fileTreeChildren(File file) {
|
||||
// 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 java.math.RoundingMode.CEILING;
|
||||
import static java.math.RoundingMode.FLOOR;
|
||||
import static java.math.RoundingMode.HALF_DOWN;
|
||||
import static java.math.RoundingMode.HALF_EVEN;
|
||||
|
||||
import com.google.common.annotations.Beta;
|
||||
|
@ -56,7 +57,7 @@ public final class BigIntegerMath {
|
|||
*/
|
||||
@Beta
|
||||
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
|
||||
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. */
|
||||
|
@ -306,6 +307,59 @@ public final class BigIntegerMath {
|
|||
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
|
||||
* RoundingMode}.
|
||||
|
@ -432,7 +486,7 @@ public final class BigIntegerMath {
|
|||
long numeratorAccum = n;
|
||||
long denominatorAccum = 1;
|
||||
|
||||
int bits = LongMath.log2(n, RoundingMode.CEILING);
|
||||
int bits = LongMath.log2(n, CEILING);
|
||||
|
||||
int numeratorBits = bits;
|
||||
|
||||
|
|
|
@ -29,6 +29,7 @@ import com.google.common.annotations.Beta;
|
|||
import com.google.common.annotations.GwtCompatible;
|
||||
import com.google.common.annotations.GwtIncompatible;
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import com.google.common.primitives.Longs;
|
||||
import com.google.common.primitives.UnsignedLongs;
|
||||
import java.math.BigInteger;
|
||||
import java.math.RoundingMode;
|
||||
|
@ -1004,10 +1005,30 @@ public final class LongMath {
|
|||
checkNonNegative("n", n);
|
||||
return false;
|
||||
}
|
||||
if (n == 2 || n == 3 || n == 5 || n == 7 || n == 11 || n == 13) {
|
||||
return true;
|
||||
if (n < 66) {
|
||||
// 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) {
|
||||
return false;
|
||||
}
|
||||
|
@ -1069,10 +1090,10 @@ public final class LongMath {
|
|||
@Override
|
||||
long mulMod(long a, long b, long m) {
|
||||
/*
|
||||
* NOTE(lowasser, 2015-Feb-12): Benchmarks suggest that changing this to
|
||||
* UnsignedLongs.remainder and increasing the threshold to 2^32 doesn't pay for itself, and
|
||||
* adding another enum constant hurts performance further -- I suspect because bimorphic
|
||||
* implementation is a sweet spot for the JVM.
|
||||
* lowasser, 2015-Feb-12: Benchmarks suggest that changing this to UnsignedLongs.remainder
|
||||
* and increasing the threshold to 2^32 doesn't pay for itself, and adding another enum
|
||||
* constant hurts performance further -- I suspect because bimorphic implementation is a
|
||||
* sweet spot for the JVM.
|
||||
*/
|
||||
return (a * b) % m;
|
||||
}
|
||||
|
@ -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() {}
|
||||
}
|
||||
|
|
|
@ -424,11 +424,11 @@ public final class Stats implements Serializable {
|
|||
return false;
|
||||
}
|
||||
Stats other = (Stats) obj;
|
||||
return (count == other.count)
|
||||
&& (doubleToLongBits(mean) == doubleToLongBits(other.mean))
|
||||
&& (doubleToLongBits(sumOfSquaresOfDeltas) == doubleToLongBits(other.sumOfSquaresOfDeltas))
|
||||
&& (doubleToLongBits(min) == doubleToLongBits(other.min))
|
||||
&& (doubleToLongBits(max) == doubleToLongBits(other.max));
|
||||
return count == other.count
|
||||
&& doubleToLongBits(mean) == doubleToLongBits(other.mean)
|
||||
&& doubleToLongBits(sumOfSquaresOfDeltas) == doubleToLongBits(other.sumOfSquaresOfDeltas)
|
||||
&& doubleToLongBits(min) == doubleToLongBits(other.min)
|
||||
&& 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";
|
||||
/** The HTTP {@code Origin} header field name. */
|
||||
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. */
|
||||
public static final String PROXY_AUTHORIZATION = "Proxy-Authorization";
|
||||
/** The HTTP {@code Range} header field name. */
|
||||
|
@ -266,6 +273,21 @@ public final class HttpHeaders {
|
|||
* @since 20.0
|
||||
*/
|
||||
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.
|
||||
*
|
||||
|
@ -389,6 +411,12 @@ public final class HttpHeaders {
|
|||
* @since 15.0
|
||||
*/
|
||||
@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
|
||||
* 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";
|
||||
|
||||
/**
|
||||
* 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>
|
||||
* 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.GwtIncompatible;
|
||||
import com.google.common.base.CharMatcher;
|
||||
import com.google.common.base.MoreObjects;
|
||||
import com.google.common.base.Splitter;
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.common.hash.Hashing;
|
||||
import com.google.common.io.ByteStreams;
|
||||
import com.google.common.primitives.Ints;
|
||||
|
@ -32,7 +32,6 @@ import java.net.InetAddress;
|
|||
import java.net.UnknownHostException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
|
||||
|
@ -104,6 +103,10 @@ public final class InetAddresses {
|
|||
private static final int IPV6_PART_COUNT = 8;
|
||||
private static final Splitter IPV4_SPLITTER = Splitter.on('.').limit(IPV4_PART_COUNT);
|
||||
private static final Splitter IPV6_SPLITTER = Splitter.on(':').limit(IPV6_PART_COUNT + 2);
|
||||
private static final 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 ANY4 = (Inet4Address) forString("0.0.0.0");
|
||||
|
||||
|
@ -196,81 +199,100 @@ public final class InetAddresses {
|
|||
}
|
||||
return textToNumericFormatV6(ipString);
|
||||
} else if (hasDot) {
|
||||
if (percentIndex != -1) {
|
||||
return null; // Scope IDs are not supported for IPV4
|
||||
}
|
||||
return textToNumericFormatV4(ipString);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static byte [] textToNumericFormatV4(String ipString) {
|
||||
byte[] bytes = new byte[IPV4_PART_COUNT];
|
||||
int i = 0;
|
||||
try {
|
||||
for (String octet : IPV4_SPLITTER.split(ipString)) {
|
||||
bytes[i++] = parseOctet(octet);
|
||||
}
|
||||
} catch (NumberFormatException ex) {
|
||||
return null;
|
||||
if (IPV4_DELIMITER_MATCHER.countIn(ipString) + 1 != IPV4_PART_COUNT) {
|
||||
return null; // Wrong number of parts
|
||||
}
|
||||
|
||||
return i == IPV4_PART_COUNT ? bytes : null;
|
||||
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) {
|
||||
return null;
|
||||
}
|
||||
start = end + 1;
|
||||
}
|
||||
return bytes;
|
||||
}
|
||||
|
||||
private static byte [] textToNumericFormatV6(String ipString) {
|
||||
// An address can have [2..8] colons, and N colons make N+1 parts.
|
||||
List<String> parts = IPV6_SPLITTER.splitToList(ipString);
|
||||
if (parts.size() < 3 || parts.size() > IPV6_PART_COUNT + 1) {
|
||||
private static byte[] textToNumericFormatV6(String ipString) {
|
||||
// An address can have [2..8] colons.
|
||||
int delimiterCount = IPV6_DELIMITER_MATCHER.countIn(ipString);
|
||||
if (delimiterCount < 2 || delimiterCount > IPV6_PART_COUNT) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Disregarding the endpoints, find "::" with nothing in between.
|
||||
// This indicates that a run of zeroes has been skipped.
|
||||
int skipIndex = -1;
|
||||
for (int i = 1; i < parts.size() - 1; i++) {
|
||||
if (parts.get(i).length() == 0) {
|
||||
if (skipIndex >= 0) {
|
||||
int partsSkipped = IPV6_PART_COUNT - (delimiterCount + 1); // estimate; may be modified later
|
||||
boolean hasSkip = false;
|
||||
// Scan for the appearance of ::, to mark a skip-format IPV6 string and adjust the partsSkipped
|
||||
// estimate.
|
||||
for (int i = 0; i < ipString.length() - 1; i++) {
|
||||
if (ipString.charAt(i) == IPV6_DELIMITER && ipString.charAt(i + 1) == IPV6_DELIMITER) {
|
||||
if (hasSkip) {
|
||||
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 "::"
|
||||
int partsLo; // Number of parts to copy from below/after the "::"
|
||||
if (skipIndex >= 0) {
|
||||
// If we found a "::", then check if it also covers the endpoints.
|
||||
partsHi = skipIndex;
|
||||
partsLo = parts.size() - skipIndex - 1;
|
||||
if (parts.get(0).length() == 0 && --partsHi != 0) {
|
||||
return null; // ^: requires ^::
|
||||
}
|
||||
if (Iterables.getLast(parts).length() == 0 && --partsLo != 0) {
|
||||
return null; // :$ requires ::$
|
||||
}
|
||||
} else {
|
||||
// Otherwise, allocate the entire address to partsHi. The endpoints
|
||||
// could still be empty, but parseHextet() will check for that.
|
||||
partsHi = parts.size();
|
||||
partsLo = 0;
|
||||
if (ipString.charAt(0) == IPV6_DELIMITER && ipString.charAt(1) != IPV6_DELIMITER) {
|
||||
return null; // ^: requires ^::
|
||||
}
|
||||
if (ipString.charAt(ipString.length() - 1) == IPV6_DELIMITER
|
||||
&& ipString.charAt(ipString.length() - 2) != IPV6_DELIMITER) {
|
||||
return null; // :$ requires ::$
|
||||
}
|
||||
if (hasSkip && partsSkipped <= 0) {
|
||||
return null; // :: must expand to at least one '0'
|
||||
}
|
||||
if (!hasSkip && delimiterCount + 1 != IPV6_PART_COUNT) {
|
||||
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);
|
||||
try {
|
||||
for (int i = 0; i < partsHi; i++) {
|
||||
rawBytes.putShort(parseHextet(parts.get(i)));
|
||||
// Iterate through the parts of the ip string.
|
||||
// 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;
|
||||
}
|
||||
for (int i = 0; i < partsSkipped; i++) {
|
||||
rawBytes.putShort((short) 0);
|
||||
}
|
||||
for (int i = partsLo; i > 0; i--) {
|
||||
rawBytes.putShort(parseHextet(parts.get(parts.size() - i)));
|
||||
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++) {
|
||||
rawBytes.putShort((short) 0);
|
||||
}
|
||||
|
||||
} else {
|
||||
rawBytes.putShort(parseHextet(ipString, start, end));
|
||||
}
|
||||
start = end + 1;
|
||||
}
|
||||
} catch (NumberFormatException ex) {
|
||||
return null;
|
||||
|
@ -291,23 +313,45 @@ public final class InetAddresses {
|
|||
return initialPart + penultimate + ":" + ultimate;
|
||||
}
|
||||
|
||||
private static byte parseOctet(String ipPart) {
|
||||
// Note: we already verified that this string contains only hex digits.
|
||||
int octet = Integer.parseInt(ipPart);
|
||||
private static byte parseOctet(String ipString, int start, int end) {
|
||||
// Note: we already verified that this string contains only hex digits, but the string may still
|
||||
// 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
|
||||
// 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();
|
||||
}
|
||||
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.
|
||||
int hextet = Integer.parseInt(ipPart, 16);
|
||||
if (hextet > 0xffff) {
|
||||
int length = end - start;
|
||||
if (length <= 0 || length > 4) {
|
||||
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;
|
||||
}
|
||||
|
||||
|
|
|
@ -101,6 +101,7 @@ public final class MediaType {
|
|||
private static final String IMAGE_TYPE = "image";
|
||||
private static final String TEXT_TYPE = "text";
|
||||
private static final String VIDEO_TYPE = "video";
|
||||
private static final String FONT_TYPE = "font";
|
||||
|
||||
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_VIDEO_TYPE = createConstant(VIDEO_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 */
|
||||
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");
|
||||
|
||||
/**
|
||||
* 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 subtype;
|
||||
private final ImmutableListMultimap<String, String> parameters;
|
||||
|
@ -931,6 +987,15 @@ public final class MediaType {
|
|||
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.
|
||||
*
|
||||
|
|
|
@ -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.checkNotNull;
|
||||
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.POSITIVE_INFINITY;
|
||||
|
||||
|
@ -251,8 +252,13 @@ public final class Doubles {
|
|||
*/
|
||||
@Beta
|
||||
public static double constrainToRange(double value, double min, double max) {
|
||||
checkArgument(min <= max, "min (%s) must be less than or equal to max (%s)", min, max);
|
||||
return Math.min(Math.max(value, min), max);
|
||||
// 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);
|
||||
}
|
||||
throw new IllegalArgumentException(
|
||||
lenientFormat("min (%s) must be less than or equal to max (%s)", min, max));
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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.checkNotNull;
|
||||
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.POSITIVE_INFINITY;
|
||||
|
||||
|
@ -246,8 +247,13 @@ public final class Floats {
|
|||
*/
|
||||
@Beta
|
||||
public static float constrainToRange(float value, float min, float max) {
|
||||
checkArgument(min <= max, "min (%s) must be less than or equal to max (%s)", min, max);
|
||||
return Math.min(Math.max(value, min), max);
|
||||
// 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);
|
||||
}
|
||||
throw new IllegalArgumentException(
|
||||
lenientFormat("min (%s) must be less than or equal to max (%s)", min, max));
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -30,9 +30,6 @@ import com.google.common.collect.ImmutableList;
|
|||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.common.collect.Maps;
|
||||
import com.google.common.collect.MultimapBuilder;
|
||||
import com.google.common.collect.SetMultimap;
|
||||
import com.google.common.collect.Sets;
|
||||
import com.google.common.io.ByteSource;
|
||||
import com.google.common.io.CharSource;
|
||||
import com.google.common.io.Resources;
|
||||
|
@ -46,7 +43,7 @@ import java.nio.charset.Charset;
|
|||
import java.util.Enumeration;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Map;
|
||||
import java.util.NoSuchElementException;
|
||||
import java.util.Set;
|
||||
import java.util.jar.Attributes;
|
||||
|
@ -78,14 +75,6 @@ import java.util.logging.Logger;
|
|||
public final class ClassPath {
|
||||
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. */
|
||||
private static final Splitter CLASS_PATH_ATTRIBUTE_SEPARATOR =
|
||||
Splitter.on(" ").omitEmptyStrings();
|
||||
|
@ -115,9 +104,21 @@ public final class ClassPath {
|
|||
* failed.
|
||||
*/
|
||||
public static ClassPath from(ClassLoader classloader) throws IOException {
|
||||
DefaultScanner scanner = new DefaultScanner();
|
||||
scanner.scan(classloader);
|
||||
return new ClassPath(scanner.getResources());
|
||||
ImmutableSet<LocationInfo> locations = locationsFrom(classloader);
|
||||
|
||||
// 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();
|
||||
}
|
||||
|
||||
/** Returns all top level classes loadable from the current class path. */
|
||||
public ImmutableSet<ClassInfo> getTopLevelClasses() {
|
||||
return FluentIterable.from(resources).filter(ClassInfo.class).filter(IS_TOP_LEVEL).toSet();
|
||||
/**
|
||||
* Returns all top level classes loadable from the current class path. Note that "top-level-ness"
|
||||
* 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}. */
|
||||
|
@ -178,19 +190,21 @@ public final class ClassPath {
|
|||
*/
|
||||
@Beta
|
||||
public static class ResourceInfo {
|
||||
private final File file;
|
||||
private final String resourceName;
|
||||
|
||||
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)) {
|
||||
return new ClassInfo(resourceName, loader);
|
||||
return new ClassInfo(file, resourceName, loader);
|
||||
} 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.loader = checkNotNull(loader);
|
||||
}
|
||||
|
@ -239,6 +253,11 @@ public final class ClassPath {
|
|||
return resourceName;
|
||||
}
|
||||
|
||||
/** Returns the file that includes this resource. */
|
||||
final File getFile() {
|
||||
return file;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return resourceName.hashCode();
|
||||
|
@ -269,8 +288,8 @@ public final class ClassPath {
|
|||
public static final class ClassInfo extends ResourceInfo {
|
||||
private final String className;
|
||||
|
||||
ClassInfo(String resourceName, ClassLoader loader) {
|
||||
super(resourceName, loader);
|
||||
ClassInfo(File file, String resourceName, ClassLoader loader) {
|
||||
super(file, resourceName, loader);
|
||||
this.className = getClassName(resourceName);
|
||||
}
|
||||
|
||||
|
@ -317,6 +336,18 @@ public final class ClassPath {
|
|||
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.
|
||||
*
|
||||
|
@ -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
|
||||
* respectively.
|
||||
|
||||
/*
|
||||
* Returns all locations that {@code classloader} and parent loaders load classes and resources
|
||||
* 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();
|
||||
for (Map.Entry<File, ClassLoader> entry : getClassPathEntries(classloader).entrySet()) {
|
||||
builder.add(new LocationInfo(entry.getKey(), entry.getValue()));
|
||||
}
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
// We only scan each file once independent of the classloader that resource might be associated
|
||||
// with.
|
||||
private final Set<File> scannedUris = Sets.newHashSet();
|
||||
/**
|
||||
* Represents a single location (a directory or a jar file) in the class path and is responsible
|
||||
* for scanning resources from this location.
|
||||
*/
|
||||
static final class LocationInfo {
|
||||
final File home;
|
||||
private final ClassLoader classloader;
|
||||
|
||||
public final void scan(ClassLoader classloader) throws IOException {
|
||||
for (Entry<File, ClassLoader> entry : getClassPathEntries(classloader).entrySet()) {
|
||||
scan(entry.getKey(), entry.getValue());
|
||||
}
|
||||
LocationInfo(File home, ClassLoader classloader) {
|
||||
this.home = checkNotNull(home);
|
||||
this.classloader = checkNotNull(classloader);
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
final void scan(File file, ClassLoader classloader) throws IOException {
|
||||
if (scannedUris.add(file.getCanonicalFile())) {
|
||||
scanFrom(file, classloader);
|
||||
}
|
||||
/** Returns the file this location is from. */
|
||||
public final File file() {
|
||||
return home;
|
||||
}
|
||||
|
||||
/** Called when a directory is scanned for resource files. */
|
||||
protected abstract void scanDirectory(ClassLoader loader, File directory) throws IOException;
|
||||
/** Scans this location and returns all scanned resources. */
|
||||
public ImmutableSet<ResourceInfo> scanResources() throws IOException {
|
||||
return scanResources(new HashSet<File>());
|
||||
}
|
||||
|
||||
/** Called when a jar file is scanned for resource entries. */
|
||||
protected abstract void scanJarFile(ClassLoader loader, JarFile file) 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 scanFrom(File file, ClassLoader classloader) throws IOException {
|
||||
private void scan(File file, Set<File> scannedUris, ImmutableSet.Builder<ResourceInfo> builder)
|
||||
throws IOException {
|
||||
try {
|
||||
if (!file.exists()) {
|
||||
return;
|
||||
|
@ -379,13 +439,15 @@ public final class ClassPath {
|
|||
return;
|
||||
}
|
||||
if (file.isDirectory()) {
|
||||
scanDirectory(classloader, file);
|
||||
scanDirectory(file, builder);
|
||||
} 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;
|
||||
try {
|
||||
jarFile = new JarFile(file);
|
||||
|
@ -395,142 +457,37 @@ public final class ClassPath {
|
|||
}
|
||||
try {
|
||||
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 {
|
||||
try {
|
||||
jarFile.close();
|
||||
} catch (IOException ignored) {
|
||||
} catch (IOException ignored) { // similar to try-with-resources, but don't fail scanning
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the class path URIs specified by the {@code Class-Path} manifest attribute, according
|
||||
* to <a
|
||||
* 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,
|
||||
* and an empty set will be returned.
|
||||
*/
|
||||
@VisibleForTesting
|
||||
static ImmutableSet<File> getClassPathFromManifest(File jarFile, Manifest manifest) {
|
||||
if (manifest == null) {
|
||||
return ImmutableSet.of();
|
||||
}
|
||||
ImmutableSet.Builder<File> builder = ImmutableSet.builder();
|
||||
String classpathAttribute =
|
||||
manifest.getMainAttributes().getValue(Attributes.Name.CLASS_PATH.toString());
|
||||
if (classpathAttribute != null) {
|
||||
for (String path : CLASS_PATH_ATTRIBUTE_SEPARATOR.split(classpathAttribute)) {
|
||||
URL url;
|
||||
try {
|
||||
url = getClassPathEntry(jarFile, path);
|
||||
} catch (MalformedURLException e) {
|
||||
// Ignore bad entry
|
||||
logger.warning("Invalid Class-Path entry: " + path);
|
||||
continue;
|
||||
}
|
||||
if (url.getProtocol().equals("file")) {
|
||||
builder.add(toFile(url));
|
||||
}
|
||||
}
|
||||
}
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
static ImmutableMap<File, ClassLoader> getClassPathEntries(ClassLoader classloader) {
|
||||
LinkedHashMap<File, ClassLoader> entries = Maps.newLinkedHashMap();
|
||||
// Search parent first, since it's the order ClassLoader#loadClass() uses.
|
||||
ClassLoader parent = classloader.getParent();
|
||||
if (parent != null) {
|
||||
entries.putAll(getClassPathEntries(parent));
|
||||
}
|
||||
for (URL url : getClassLoaderUrls(classloader)) {
|
||||
if (url.getProtocol().equals("file")) {
|
||||
File file = toFile(url);
|
||||
if (!entries.containsKey(file)) {
|
||||
entries.put(file, classloader);
|
||||
}
|
||||
}
|
||||
}
|
||||
return ImmutableMap.copyOf(entries);
|
||||
}
|
||||
|
||||
private static ImmutableList<URL> getClassLoaderUrls(ClassLoader classloader) {
|
||||
if (classloader instanceof URLClassLoader) {
|
||||
return ImmutableList.copyOf(((URLClassLoader) classloader).getURLs());
|
||||
}
|
||||
if (classloader.equals(ClassLoader.getSystemClassLoader())) {
|
||||
return parseJavaClassPath();
|
||||
}
|
||||
return ImmutableList.of();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the URLs in the class path specified by the {@code java.class.path} {@linkplain
|
||||
* System#getProperty system property}.
|
||||
*/
|
||||
@VisibleForTesting // TODO(b/65488446): Make this a public API.
|
||||
static ImmutableList<URL> parseJavaClassPath() {
|
||||
ImmutableList.Builder<URL> urls = ImmutableList.builder();
|
||||
for (String entry : Splitter.on(PATH_SEPARATOR.value()).split(JAVA_CLASS_PATH.value())) {
|
||||
try {
|
||||
try {
|
||||
urls.add(new File(entry).toURI().toURL());
|
||||
} catch (SecurityException e) { // File.toURI checks to see if the file is a directory
|
||||
urls.add(new URL("file", null, new File(entry).getAbsolutePath()));
|
||||
}
|
||||
} catch (MalformedURLException e) {
|
||||
logger.log(WARNING, "malformed classpath entry: " + entry, e);
|
||||
}
|
||||
}
|
||||
return urls.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the absolute uri of the Class-Path entry value as specified in <a
|
||||
* 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,
|
||||
* absolute urls are actually supported too (for example, in Maven surefire plugin).
|
||||
*/
|
||||
@VisibleForTesting
|
||||
static URL getClassPathEntry(File jarFile, String path) throws MalformedURLException {
|
||||
return new URL(jarFile.toURI().toURL(), path);
|
||||
}
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
static final class DefaultScanner extends Scanner {
|
||||
private final SetMultimap<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) {
|
||||
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;
|
||||
}
|
||||
resources.get(classloader).add(entry.getName());
|
||||
builder.add(ResourceInfo.of(new File(file.getName()), entry.getName(), classloader));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void scanDirectory(ClassLoader classloader, File directory) throws IOException {
|
||||
private void scanDirectory(File directory, ImmutableSet.Builder<ResourceInfo> builder)
|
||||
throws IOException {
|
||||
Set<File> currentPath = new HashSet<>();
|
||||
currentPath.add(directory.getCanonicalFile());
|
||||
scanDirectory(directory, classloader, "", currentPath);
|
||||
scanDirectory(directory, "", currentPath, builder);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -539,15 +496,17 @@ public final class ClassPath {
|
|||
* 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 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);
|
||||
|
@ -559,17 +518,132 @@ public final class ClassPath {
|
|||
if (f.isDirectory()) {
|
||||
File deref = f.getCanonicalFile();
|
||||
if (currentPath.add(deref)) {
|
||||
scanDirectory(deref, classloader, packagePrefix + name + "/", currentPath);
|
||||
scanDirectory(deref, packagePrefix + name + "/", currentPath, builder);
|
||||
currentPath.remove(deref);
|
||||
}
|
||||
} else {
|
||||
String resourceName = packagePrefix + name;
|
||||
if (!resourceName.equals(JarFile.MANIFEST_NAME)) {
|
||||
resources.get(classloader).add(resourceName);
|
||||
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
|
||||
* to <a
|
||||
* 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, and
|
||||
* an empty set will be returned.
|
||||
*/
|
||||
@VisibleForTesting
|
||||
static ImmutableSet<File> getClassPathFromManifest(File jarFile, Manifest manifest) {
|
||||
if (manifest == null) {
|
||||
return ImmutableSet.of();
|
||||
}
|
||||
ImmutableSet.Builder<File> builder = ImmutableSet.builder();
|
||||
String classpathAttribute =
|
||||
manifest.getMainAttributes().getValue(Attributes.Name.CLASS_PATH.toString());
|
||||
if (classpathAttribute != null) {
|
||||
for (String path : CLASS_PATH_ATTRIBUTE_SEPARATOR.split(classpathAttribute)) {
|
||||
URL url;
|
||||
try {
|
||||
url = getClassPathEntry(jarFile, path);
|
||||
} catch (MalformedURLException e) {
|
||||
// Ignore bad entry
|
||||
logger.warning("Invalid Class-Path entry: " + path);
|
||||
continue;
|
||||
}
|
||||
if (url.getProtocol().equals("file")) {
|
||||
builder.add(toFile(url));
|
||||
}
|
||||
}
|
||||
}
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
static ImmutableMap<File, ClassLoader> getClassPathEntries(ClassLoader classloader) {
|
||||
LinkedHashMap<File, ClassLoader> entries = Maps.newLinkedHashMap();
|
||||
// Search parent first, since it's the order ClassLoader#loadClass() uses.
|
||||
ClassLoader parent = classloader.getParent();
|
||||
if (parent != null) {
|
||||
entries.putAll(getClassPathEntries(parent));
|
||||
}
|
||||
for (URL url : getClassLoaderUrls(classloader)) {
|
||||
if (url.getProtocol().equals("file")) {
|
||||
File file = toFile(url);
|
||||
if (!entries.containsKey(file)) {
|
||||
entries.put(file, classloader);
|
||||
}
|
||||
}
|
||||
}
|
||||
return ImmutableMap.copyOf(entries);
|
||||
}
|
||||
|
||||
private static ImmutableList<URL> getClassLoaderUrls(ClassLoader classloader) {
|
||||
if (classloader instanceof URLClassLoader) {
|
||||
return ImmutableList.copyOf(((URLClassLoader) classloader).getURLs());
|
||||
}
|
||||
if (classloader.equals(ClassLoader.getSystemClassLoader())) {
|
||||
return parseJavaClassPath();
|
||||
}
|
||||
return ImmutableList.of();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the URLs in the class path specified by the {@code java.class.path} {@linkplain
|
||||
* System#getProperty system property}.
|
||||
*/
|
||||
@VisibleForTesting // TODO(b/65488446): Make this a public API.
|
||||
static ImmutableList<URL> parseJavaClassPath() {
|
||||
ImmutableList.Builder<URL> urls = ImmutableList.builder();
|
||||
for (String entry : Splitter.on(PATH_SEPARATOR.value()).split(JAVA_CLASS_PATH.value())) {
|
||||
try {
|
||||
try {
|
||||
urls.add(new File(entry).toURI().toURL());
|
||||
} catch (SecurityException e) { // File.toURI checks to see if the file is a directory
|
||||
urls.add(new URL("file", null, new File(entry).getAbsolutePath()));
|
||||
}
|
||||
} catch (MalformedURLException e) {
|
||||
logger.log(WARNING, "malformed classpath entry: " + entry, e);
|
||||
}
|
||||
}
|
||||
return urls.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the absolute uri of the Class-Path entry value as specified in <a
|
||||
* 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, absolute
|
||||
* urls are actually supported too (for example, in Maven surefire plugin).
|
||||
*/
|
||||
@VisibleForTesting
|
||||
static URL getClassPathEntry(File jarFile, String path) throws MalformedURLException {
|
||||
return new URL(jarFile.toURI().toURL(), path);
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
|
|
|
@ -33,6 +33,8 @@ import java.util.logging.Level;
|
|||
import java.util.logging.Logger;
|
||||
|
||||
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;
|
||||
|
||||
/**
|
||||
|
@ -368,8 +370,6 @@ public abstract class AbstractFuture<V> extends InternalFutureFailureAccess
|
|||
*
|
||||
* <p>The default {@link AbstractFuture} implementation throws {@code InterruptedException} if the
|
||||
* current thread is interrupted during the call, even if the value is already available.
|
||||
*
|
||||
* @throws CancellationException {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public V get(long timeout, TimeUnit unit)
|
||||
|
@ -396,7 +396,7 @@ public abstract class AbstractFuture<V> extends InternalFutureFailureAccess
|
|||
node.setNext(oldHead);
|
||||
if (ATOMIC_HELPER.casWaiters(this, oldHead, node)) {
|
||||
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.
|
||||
if (Thread.interrupted()) {
|
||||
removeWaiter(node);
|
||||
|
@ -478,8 +478,6 @@ public abstract class AbstractFuture<V> extends InternalFutureFailureAccess
|
|||
*
|
||||
* <p>The default {@link AbstractFuture} implementation throws {@code InterruptedException} if the
|
||||
* current thread is interrupted during the call, even if the value is already available.
|
||||
*
|
||||
* @throws CancellationException {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public V get() throws InterruptedException, ExecutionException {
|
||||
|
@ -561,6 +559,8 @@ public abstract class AbstractFuture<V> extends InternalFutureFailureAccess
|
|||
* #wasInterrupted} as necessary. This ensures that the work is done even if the future is
|
||||
* cancelled without a call to {@code cancel}, such as by calling {@code
|
||||
* setFuture(cancelledFuture)}.
|
||||
* <p>Beware of completing a future while holding a lock. Its listeners may do slow work or
|
||||
* acquire other locks, risking deadlocks.
|
||||
*/
|
||||
@Override
|
||||
public boolean cancel(boolean mayInterruptIfRunning) {
|
||||
|
@ -580,7 +580,7 @@ public abstract class AbstractFuture<V> extends InternalFutureFailureAccess
|
|||
while (true) {
|
||||
if (ATOMIC_HELPER.casValue(abstractFuture, localValue, valueToSet)) {
|
||||
rValue = true;
|
||||
// We call interuptTask before calling complete(), which is consistent with
|
||||
// We call interruptTask before calling complete(), which is consistent with
|
||||
// FutureTask
|
||||
if (mayInterruptIfRunning) {
|
||||
abstractFuture.interruptTask();
|
||||
|
@ -714,6 +714,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*}
|
||||
* 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
|
||||
* @return true if the attempt was accepted, completing the {@code Future}
|
||||
*/
|
||||
|
@ -747,6 +750,9 @@ public abstract class AbstractFuture<V> extends InternalFutureFailureAccess
|
|||
* invoke the {@link #interruptTask} method, and the {@link #wasInterrupted} method will not
|
||||
* 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
|
||||
* @return true if the attempt was accepted, indicating that the {@code Future} was not previously
|
||||
* cancelled or set.
|
||||
|
@ -1031,7 +1037,14 @@ public abstract class AbstractFuture<V> extends InternalFutureFailureAccess
|
|||
// TODO(user): move parts into a default method on ListenableFuture?
|
||||
@Override
|
||||
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()) {
|
||||
builder.append("CANCELLED");
|
||||
} else if (isDone()) {
|
||||
|
@ -1079,7 +1092,8 @@ public abstract class AbstractFuture<V> extends InternalFutureFailureAccess
|
|||
private void addDoneString(StringBuilder builder) {
|
||||
try {
|
||||
V value = getUninterruptibly(this);
|
||||
builder.append("SUCCESS, result=[").append(userObjectToString(value)).append("]");
|
||||
builder.append("SUCCESS, result=[");
|
||||
appendResultObject(builder, value);
|
||||
} catch (ExecutionException e) {
|
||||
builder.append("FAILURE, cause=[").append(e.getCause()).append("]");
|
||||
} catch (CancellationException e) {
|
||||
|
@ -1089,6 +1103,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. */
|
||||
private String userObjectToString(Object o) {
|
||||
// This is some basic recursion detection for when people create cycles via set/setFuture
|
||||
|
|
2158
src/main/java/com/google/common/util/concurrent/ClosingFuture.java
Normal file
2158
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 final Executor listenerExecutor;
|
||||
boolean thrownByExecute = true;
|
||||
|
||||
CombinedFutureInterruptibleTask(Executor listenerExecutor) {
|
||||
this.listenerExecutor = checkNotNull(listenerExecutor);
|
||||
|
@ -104,9 +103,7 @@ final class CombinedFuture<V> extends AggregateFuture<Object, V> {
|
|||
try {
|
||||
listenerExecutor.execute(this);
|
||||
} catch (RejectedExecutionException e) {
|
||||
if (thrownByExecute) {
|
||||
CombinedFuture.this.setException(e);
|
||||
}
|
||||
CombinedFuture.this.setException(e);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -153,7 +150,6 @@ final class CombinedFuture<V> extends AggregateFuture<Object, V> {
|
|||
|
||||
@Override
|
||||
ListenableFuture<V> runInterruptibly() throws Exception {
|
||||
thrownByExecute = false;
|
||||
ListenableFuture<V> result = callable.call();
|
||||
return checkNotNull(
|
||||
result,
|
||||
|
@ -184,7 +180,6 @@ final class CombinedFuture<V> extends AggregateFuture<Object, V> {
|
|||
|
||||
@Override
|
||||
V runInterruptibly() throws Exception {
|
||||
thrownByExecute = false;
|
||||
return callable.call();
|
||||
}
|
||||
|
||||
|
|
|
@ -15,11 +15,13 @@
|
|||
package com.google.common.util.concurrent;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
import static com.google.common.base.Preconditions.checkState;
|
||||
import static com.google.common.util.concurrent.ExecutionSequencer.RunningState.CANCELLED;
|
||||
import static com.google.common.util.concurrent.ExecutionSequencer.RunningState.NOT_RUN;
|
||||
import static com.google.common.util.concurrent.ExecutionSequencer.RunningState.STARTED;
|
||||
import static com.google.common.util.concurrent.Futures.immediateCancelledFuture;
|
||||
import static com.google.common.util.concurrent.Futures.immediateFuture;
|
||||
import static com.google.common.util.concurrent.Futures.immediateVoidFuture;
|
||||
import static com.google.common.util.concurrent.MoreExecutors.directExecutor;
|
||||
|
||||
import com.google.common.annotations.Beta;
|
||||
|
@ -28,13 +30,50 @@ import java.util.concurrent.Executor;
|
|||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
/**
|
||||
* Serializes execution of a set of operations. This class guarantees that a submitted callable will
|
||||
* not be called before previously submitted callables (and any {@code Future}s returned from them)
|
||||
* have completed.
|
||||
* Serializes execution of tasks, somewhat like an "asynchronous {@code synchronized} block." Each
|
||||
* {@linkplain #submit enqueued} callable will not be submitted to its associated executor until the
|
||||
* 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
|
||||
* 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.
|
||||
* <p>This class has limited support for cancellation and other "early completion":
|
||||
*
|
||||
* <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
|
||||
*/
|
||||
|
@ -48,15 +87,47 @@ public final class 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. */
|
||||
private final AtomicReference<ListenableFuture<Object>> ref =
|
||||
new AtomicReference<>(immediateFuture(null));
|
||||
private final AtomicReference<ListenableFuture<Void>> ref =
|
||||
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.
|
||||
|
@ -67,6 +138,7 @@ public final class ExecutionSequencer {
|
|||
*/
|
||||
public <T> ListenableFuture<T> submit(final Callable<T> callable, Executor executor) {
|
||||
checkNotNull(callable);
|
||||
checkNotNull(executor);
|
||||
return submitAsync(
|
||||
new AsyncCallable<T>() {
|
||||
@Override
|
||||
|
@ -92,12 +164,13 @@ public final class ExecutionSequencer {
|
|||
public <T> ListenableFuture<T> submitAsync(
|
||||
final AsyncCallable<T> callable, final Executor executor) {
|
||||
checkNotNull(callable);
|
||||
final AtomicReference<RunningState> runningState = new AtomicReference<>(NOT_RUN);
|
||||
checkNotNull(executor);
|
||||
final TaskNonReentrantExecutor taskExecutor = new TaskNonReentrantExecutor(executor, this);
|
||||
final AsyncCallable<T> task =
|
||||
new AsyncCallable<T>() {
|
||||
@Override
|
||||
public ListenableFuture<T> call() throws Exception {
|
||||
if (!runningState.compareAndSet(NOT_RUN, STARTED)) {
|
||||
if (!taskExecutor.trySetStarted()) {
|
||||
return immediateCancelledFuture();
|
||||
}
|
||||
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
|
||||
* 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.
|
||||
final ListenableFuture<T> taskFuture =
|
||||
Futures.submitAsync(
|
||||
task,
|
||||
new Executor() {
|
||||
@Override
|
||||
public void execute(Runnable runnable) {
|
||||
oldFuture.addListener(runnable, executor);
|
||||
}
|
||||
});
|
||||
final TrustedListenableFutureTask<T> taskFuture = TrustedListenableFutureTask.create(task);
|
||||
oldFuture.addListener(taskFuture, taskExecutor);
|
||||
|
||||
final ListenableFuture<T> outputFuture = Futures.nonCancellationPropagating(taskFuture);
|
||||
|
||||
|
@ -144,15 +210,39 @@ public final class ExecutionSequencer {
|
|||
new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (taskFuture.isDone()
|
||||
// If this CAS succeeds, we know that the provided callable will never be invoked,
|
||||
// so when oldFuture completes it is safe to allow the next submitted task to
|
||||
// proceed.
|
||||
|| (outputFuture.isCancelled() && runningState.compareAndSet(NOT_RUN, CANCELLED))) {
|
||||
if (taskFuture.isDone()) {
|
||||
// Since the value of oldFuture can only ever be immediateFuture(null) or setFuture of
|
||||
// a future that eventually came from immediateFuture(null), this doesn't leak
|
||||
// throwables or completion values.
|
||||
newFuture.setFuture(oldFuture);
|
||||
} 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;
|
||||
}
|
||||
|
||||
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:
|
||||
*
|
||||
* <ul>
|
||||
* <li><a href="http://dagger.dev/producers.html">Dagger Producers</a>
|
||||
* <li><a href="https://dagger.dev/producers.html">Dagger Producers</a>
|
||||
* </ul>
|
||||
*
|
||||
* <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.ImmediateFuture.ImmediateCancelledFuture;
|
||||
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.util.Collection;
|
||||
import java.util.List;
|
||||
|
@ -45,7 +46,6 @@ import java.util.concurrent.TimeUnit;
|
|||
import java.util.concurrent.TimeoutException;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
|
||||
/**
|
||||
* Static utility methods pertaining to the {@link Future} interface.
|
||||
*
|
||||
|
@ -60,7 +60,7 @@ import java.util.concurrent.atomic.AtomicInteger;
|
|||
* monitoring, debugging, and cancellation. Examples of frameworks include:
|
||||
*
|
||||
* <ul>
|
||||
* <li><a href="http://dagger.dev/producers.html">Dagger Producers</a>
|
||||
* <li><a href="https://dagger.dev/producers.html">Dagger Producers</a>
|
||||
* </ul>
|
||||
*
|
||||
* <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);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
|
@ -161,7 +172,7 @@ public final class Futures extends GwtFuturesCatchingSpecialization {
|
|||
* Executes {@code callable} on the specified {@code executor}, returning a {@code Future}.
|
||||
*
|
||||
* @throws RejectedExecutionException if the task cannot be scheduled for execution
|
||||
* @since NEXT
|
||||
* @since 28.2
|
||||
*/
|
||||
@Beta
|
||||
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.
|
||||
*
|
||||
* @throws RejectedExecutionException if the task cannot be scheduled for execution
|
||||
* @since NEXT
|
||||
* @since 28.2
|
||||
*/
|
||||
@Beta
|
||||
public static ListenableFuture<Void> submit(Runnable runnable, Executor executor) {
|
||||
|
@ -258,9 +269,7 @@ public final class Futures extends GwtFuturesCatchingSpecialization {
|
|||
* }</pre>
|
||||
*
|
||||
* <p>When selecting an executor, note that {@code directExecutor} is dangerous in some cases. See
|
||||
* the discussion in the {@link ListenableFuture#addListener ListenableFuture.addListener}
|
||||
* documentation. All its warnings about heavyweight listeners are also applicable to heavyweight
|
||||
* functions passed to this method.
|
||||
* the warnings the {@link MoreExecutors#directExecutor} documentation.
|
||||
*
|
||||
* @param input the primary input {@code Future}
|
||||
* @param exceptionType the exception type that triggers use of {@code fallback}. The exception
|
||||
|
@ -325,11 +334,7 @@ public final class Futures extends GwtFuturesCatchingSpecialization {
|
|||
* }</pre>
|
||||
*
|
||||
* <p>When selecting an executor, note that {@code directExecutor} is dangerous in some cases. See
|
||||
* the discussion in the {@link ListenableFuture#addListener ListenableFuture.addListener}
|
||||
* documentation. All its warnings about heavyweight listeners are also applicable to heavyweight
|
||||
* functions passed to this method. (Specifically, {@code directExecutor} functions should avoid
|
||||
* heavyweight operations inside {@code AsyncFunction.apply}. Any heavyweight operations should
|
||||
* occur in other threads responsible for completing the returned {@code Future}.)
|
||||
* the warnings the {@link MoreExecutors#directExecutor} documentation.
|
||||
*
|
||||
* @param input the primary input {@code Future}
|
||||
* @param exceptionType the exception type that triggers use of {@code fallback}. The exception
|
||||
|
@ -415,11 +420,7 @@ public final class Futures extends GwtFuturesCatchingSpecialization {
|
|||
* }</pre>
|
||||
*
|
||||
* <p>When selecting an executor, note that {@code directExecutor} is dangerous in some cases. See
|
||||
* the discussion in the {@link ListenableFuture#addListener ListenableFuture.addListener}
|
||||
* documentation. All its warnings about heavyweight listeners are also applicable to heavyweight
|
||||
* functions passed to this method. (Specifically, {@code directExecutor} functions should avoid
|
||||
* heavyweight operations inside {@code AsyncFunction.apply}. Any heavyweight operations should
|
||||
* occur in other threads responsible for completing the returned {@code Future}.)
|
||||
* the warnings the {@link MoreExecutors#directExecutor} documentation.
|
||||
*
|
||||
* <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
|
||||
|
@ -455,9 +456,7 @@ public final class Futures extends GwtFuturesCatchingSpecialization {
|
|||
* }</pre>
|
||||
*
|
||||
* <p>When selecting an executor, note that {@code directExecutor} is dangerous in some cases. See
|
||||
* the discussion in the {@link ListenableFuture#addListener ListenableFuture.addListener}
|
||||
* documentation. All its warnings about heavyweight listeners are also applicable to heavyweight
|
||||
* functions passed to this method.
|
||||
* the warnings the {@link MoreExecutors#directExecutor} documentation.
|
||||
*
|
||||
* <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
|
||||
|
@ -669,7 +668,6 @@ public final class Futures extends GwtFuturesCatchingSpecialization {
|
|||
* @since 20.0
|
||||
*/
|
||||
@Beta
|
||||
// TODO(cpovirk): Consider removing, especially if we provide run(Runnable)
|
||||
@GwtCompatible
|
||||
public static final class FutureCombiner<V> {
|
||||
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.
|
||||
*/
|
||||
// TODO(cpovirk): Remove this
|
||||
public <C> ListenableFuture<C> call(Callable<C> combiner, Executor executor) {
|
||||
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
|
||||
* 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.
|
||||
*
|
||||
* <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
|
||||
* 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:
|
||||
*
|
||||
* <pre>{@code
|
||||
|
@ -1023,9 +1025,7 @@ public final class Futures extends GwtFuturesCatchingSpecialization {
|
|||
* }</pre>
|
||||
*
|
||||
* <p>When selecting an executor, note that {@code directExecutor} is dangerous in some cases. See
|
||||
* the discussion in the {@link ListenableFuture#addListener ListenableFuture.addListener}
|
||||
* documentation. All its warnings about heavyweight listeners are also applicable to heavyweight
|
||||
* callbacks passed to this method.
|
||||
* the warnings the {@link MoreExecutors#directExecutor} documentation.
|
||||
*
|
||||
* <p>For a more general interface to attach a completion listener to a {@code Future}, see {@link
|
||||
* ListenableFuture#addListener addListener}.
|
||||
|
@ -1055,6 +1055,14 @@ public final class Futures extends GwtFuturesCatchingSpecialization {
|
|||
|
||||
@Override
|
||||
public void run() {
|
||||
if (future instanceof InternalFutureFailureAccess) {
|
||||
Throwable failure =
|
||||
InternalFutures.tryInternalFastPathGetFailure((InternalFutureFailureAccess) future);
|
||||
if (failure != null) {
|
||||
callback.onFailure(failure);
|
||||
return;
|
||||
}
|
||||
}
|
||||
final V value;
|
||||
try {
|
||||
value = getDone(future);
|
||||
|
@ -1093,7 +1101,6 @@ public final class Futures extends GwtFuturesCatchingSpecialization {
|
|||
* @throws IllegalStateException if the {@code Future} is not done
|
||||
* @since 20.0
|
||||
*/
|
||||
|
||||
// TODO(cpovirk): Consider calling getDone() in our own code.
|
||||
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
|
||||
* should throw IllegalStateException only when they use static state.
|
||||
*
|
||||
*
|
||||
* Why do we deviate here? The answer: We want for fluentFuture.getDone() to throw the same
|
||||
* exception as Futures.getDone(fluentFuture).
|
||||
*/
|
||||
|
@ -1154,7 +1160,6 @@ public final class Futures extends GwtFuturesCatchingSpecialization {
|
|||
* @since 19.0 (in 10.0 as {@code get})
|
||||
*/
|
||||
@Beta
|
||||
|
||||
@GwtIncompatible // reflection
|
||||
public static <V, X extends Exception> V getChecked(Future<V> future, Class<X> exceptionClass)
|
||||
throws X {
|
||||
|
@ -1205,7 +1210,6 @@ public final class Futures extends GwtFuturesCatchingSpecialization {
|
|||
* @since 28.0
|
||||
*/
|
||||
@Beta
|
||||
|
||||
@GwtIncompatible // reflection
|
||||
public static <V, X extends Exception> V getChecked(
|
||||
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)
|
||||
*/
|
||||
@Beta
|
||||
|
||||
@GwtIncompatible // reflection
|
||||
@SuppressWarnings("GoodTime") // should accept a java.time.Duration
|
||||
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}
|
||||
* @since 10.0
|
||||
*/
|
||||
|
||||
public static <V> V getUnchecked(Future<V> future) {
|
||||
checkNotNull(future);
|
||||
try {
|
||||
|
|
|
@ -40,7 +40,7 @@ import java.util.concurrent.RejectedExecutionException;
|
|||
* frameworks include:
|
||||
*
|
||||
* <ul>
|
||||
* <li><a href="http://dagger.dev/producers.html">Dagger Producers</a>
|
||||
* <li><a href="https://dagger.dev/producers.html">Dagger Producers</a>
|
||||
* </ul>
|
||||
*
|
||||
* <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
|
||||
* logged.
|
||||
*
|
||||
* <p>Note: For fast, lightweight listeners that would be safe to execute in any thread, consider
|
||||
* {@link MoreExecutors#directExecutor}. Otherwise, avoid it. Heavyweight {@code directExecutor}
|
||||
* listeners can cause problems, and these problems can be difficult to reproduce because they
|
||||
* depend on timing. For example:
|
||||
*
|
||||
* <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>Note: If your listener is lightweight -- and will not cause stack overflow by completing
|
||||
* more futures or adding more {@code directExecutor()} listeners inline -- consider {@link
|
||||
* MoreExecutors#directExecutor}. Otherwise, avoid it: See the warnings on the docs for {@code
|
||||
* directExecutor}.
|
||||
*
|
||||
* <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
|
||||
|
|
|
@ -14,11 +14,16 @@
|
|||
|
||||
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 java.util.concurrent.Callable;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.Executor;
|
||||
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
|
||||
|
@ -80,6 +85,19 @@ public class ListenableFutureTask<V> extends FutureTask<V> implements Listenable
|
|||
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. */
|
||||
@Override
|
||||
protected void done() {
|
||||
|
|
|
@ -35,21 +35,18 @@ import java.util.concurrent.TimeUnit;
|
|||
public interface ListeningExecutorService extends ExecutorService {
|
||||
/**
|
||||
* @return a {@code ListenableFuture} representing pending completion of the task
|
||||
* @throws RejectedExecutionException {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
<T> ListenableFuture<T> submit(Callable<T> task);
|
||||
|
||||
/**
|
||||
* @return a {@code ListenableFuture} representing pending completion of the task
|
||||
* @throws RejectedExecutionException {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
ListenableFuture<?> submit(Runnable task);
|
||||
|
||||
/**
|
||||
* @return a {@code ListenableFuture} representing pending completion of the task
|
||||
* @throws RejectedExecutionException {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
<T> ListenableFuture<T> submit(Runnable task, T result);
|
||||
|
@ -69,8 +66,6 @@ public interface ListeningExecutorService extends ExecutorService {
|
|||
* @return A list of {@code ListenableFuture} instances representing the tasks, in the same
|
||||
* sequential order as produced by the iterator for the given task list, each of which has
|
||||
* completed.
|
||||
* @throws RejectedExecutionException {@inheritDoc}
|
||||
* @throws NullPointerException if any task is null
|
||||
*/
|
||||
@Override
|
||||
<T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks)
|
||||
|
@ -92,8 +87,6 @@ public interface ListeningExecutorService extends ExecutorService {
|
|||
* sequential order as produced by the iterator for the given task list. If the operation did
|
||||
* not time out, each task will have completed. If it did time out, some of these tasks will
|
||||
* not have completed.
|
||||
* @throws RejectedExecutionException {@inheritDoc}
|
||||
* @throws NullPointerException if any task is null
|
||||
*/
|
||||
@Override
|
||||
<T> List<Future<T>> invokeAll(
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue