From bc85508c04038373201b75ca172e49e13c483723 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=CC=88rg=20Prante?= Date: Thu, 18 Mar 2021 22:19:14 +0100 Subject: [PATCH] update to 30.1 --- build.gradle | 2 + gradle.properties | 2 +- gradle/compile/java.gradle | 14 +- gradle/compile/repo.gradle | 4 + gradle/ide/idea.gradle | 1 - gradle/init/banner.gradle | 14 + gradle/publishing/publication.gradle | 14 +- gradle/wrapper/gradle-wrapper.jar | Bin 58910 -> 59203 bytes gradle/wrapper/gradle-wrapper.properties | 2 +- gradlew | 2 +- gradlew.bat | 21 +- .../common/base/StandardSystemProperty.java | 10 +- .../com/google/common/cache/CacheBuilder.java | 2 +- .../com/google/common/cache/LocalCache.java | 43 +- .../google/common/collect/CartesianList.java | 41 +- .../common/collect/CollectCollectors.java | 401 ++- .../google/common/collect/Collections2.java | 5 - .../google/common/collect/Comparators.java | 75 + .../google/common/collect/ContiguousSet.java | 7 +- .../google/common/collect/FluentIterable.java | 2 +- .../google/common/collect/ForwardingMap.java | 4 +- .../google/common/collect/ImmutableBiMap.java | 11 +- .../common/collect/ImmutableListMultimap.java | 18 +- .../google/common/collect/ImmutableMap.java | 88 +- .../common/collect/ImmutableMapKeySet.java | 6 - .../common/collect/ImmutableMapValues.java | 6 - .../common/collect/ImmutableMultiset.java | 14 +- .../google/common/collect/ImmutableSet.java | 4 +- .../common/collect/ImmutableSetMultimap.java | 19 +- .../common/collect/ImmutableSortedMap.java | 24 +- .../google/common/collect/ImmutableTable.java | 97 +- .../com/google/common/collect/Interner.java | 8 +- .../com/google/common/collect/Iterables.java | 4 +- .../java/com/google/common/collect/Lists.java | 9 +- .../common/collect/MapMakerInternalMap.java | 2 +- .../java/com/google/common/collect/Maps.java | 62 +- .../com/google/common/collect/Multimaps.java | 18 +- .../com/google/common/collect/Multisets.java | 11 +- .../com/google/common/collect/Ordering.java | 4 +- .../com/google/common/collect/Queues.java | 14 +- .../java/com/google/common/collect/Range.java | 15 + .../common/collect/RegularImmutableMap.java | 18 +- .../java/com/google/common/collect/Sets.java | 69 +- .../collect/SingletonImmutableBiMap.java | 16 +- .../common/collect/TableCollectors.java | 200 ++ .../com/google/common/collect/Tables.java | 30 +- .../google/common/collect/TreeRangeMap.java | 4 +- .../google/common/collect/TreeRangeSet.java | 2 +- .../com/google/common/eventbus/Subscribe.java | 7 +- .../common/eventbus/SubscriberRegistry.java | 18 +- .../com/google/common/graph/Traverser.java | 541 ++-- .../common/hash/Crc32cHashFunction.java | 6 +- .../com/google/common/io/ByteStreams.java | 10 +- src/main/java/com/google/common/io/Files.java | 52 +- .../google/common/math/BigDecimalMath.java | 81 + .../google/common/math/BigIntegerMath.java | 60 +- .../java/com/google/common/math/LongMath.java | 155 +- .../java/com/google/common/math/Stats.java | 10 +- .../google/common/math/ToDoubleRounder.java | 152 ++ .../com/google/common/net/HttpHeaders.java | 78 + .../com/google/common/net/InetAddresses.java | 172 +- .../java/com/google/common/net/MediaType.java | 65 + .../com/google/common/primitives/Doubles.java | 10 +- .../com/google/common/primitives/Floats.java | 10 +- .../com/google/common/reflect/ClassPath.java | 416 +-- .../util/concurrent/AbstractFuture.java | 44 +- .../common/util/concurrent/ClosingFuture.java | 2245 +++++++++++++++++ .../util/concurrent/CombinedFuture.java | 7 +- .../util/concurrent/ExecutionSequencer.java | 313 ++- .../common/util/concurrent/FluentFuture.java | 2 +- .../common/util/concurrent/Futures.java | 68 +- .../util/concurrent/ListenableFuture.java | 20 +- .../util/concurrent/ListenableFutureTask.java | 20 +- .../common/util/concurrent/MoreExecutors.java | 76 +- .../OverflowAvoidingLockSupport.java | 36 + .../util/concurrent/SequentialExecutor.java | 31 +- .../util/concurrent/ServiceManager.java | 57 +- .../util/concurrent/ServiceManagerBridge.java | 31 + .../util/concurrent/SmoothRateLimiter.java | 5 + .../concurrent/UncaughtExceptionHandlers.java | 1 - .../util/concurrent/Uninterruptibles.java | 107 +- 81 files changed, 4978 insertions(+), 1367 deletions(-) create mode 100644 gradle/compile/repo.gradle create mode 100644 gradle/init/banner.gradle create mode 100644 src/main/java/com/google/common/collect/TableCollectors.java create mode 100644 src/main/java/com/google/common/math/BigDecimalMath.java create mode 100644 src/main/java/com/google/common/math/ToDoubleRounder.java create mode 100644 src/main/java/com/google/common/util/concurrent/ClosingFuture.java create mode 100644 src/main/java/com/google/common/util/concurrent/OverflowAvoidingLockSupport.java create mode 100644 src/main/java/com/google/common/util/concurrent/ServiceManagerBridge.java diff --git a/build.gradle b/build.gradle index 6827ff4..32128e2 100644 --- a/build.gradle +++ b/build.gradle @@ -24,7 +24,9 @@ ext { } apply plugin: 'java-library' +apply from: rootProject.file('gradle/init/banner.gradle') apply from: rootProject.file('gradle/ide/idea.gradle') +apply from: rootProject.file('gradle/compile/repo.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') diff --git a/gradle.properties b/gradle.properties index a4d98ae..6363545 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,5 +1,5 @@ group = org.xbib name = guava -version = 29.0 +version = 30.1 gradle.wrapper.version = 6.6.1 diff --git a/gradle/compile/java.gradle b/gradle/compile/java.gradle index 3b74bfc..54f1f87 100644 --- a/gradle/compile/java.gradle +++ b/gradle/compile/java.gradle @@ -17,9 +17,7 @@ compileTestJava { jar { manifest { - attributes('Implementation-Title': project.name) attributes('Implementation-Version': project.version) - attributes('Implementation-Vendor': 'Jörg Prante') } } @@ -30,8 +28,18 @@ task sourcesJar(type: Jar, dependsOn: classes) { task javadocJar(type: Jar, dependsOn: javadoc) { classifier 'javadoc' + from javadoc.destinationDir } artifacts { archives sourcesJar, javadocJar -} \ No newline at end of file +} + +tasks.withType(JavaCompile) { + options.encoding('UTF-8') + options.compilerArgs << '-Xlint:all' +} + +javadoc { + options.addStringOption('Xdoclint:none', '-quiet') +} diff --git a/gradle/compile/repo.gradle b/gradle/compile/repo.gradle new file mode 100644 index 0000000..ec58acb --- /dev/null +++ b/gradle/compile/repo.gradle @@ -0,0 +1,4 @@ +repositories { + mavenLocal() + mavenCentral() +} diff --git a/gradle/ide/idea.gradle b/gradle/ide/idea.gradle index 847fb21..92625dc 100644 --- a/gradle/ide/idea.gradle +++ b/gradle/ide/idea.gradle @@ -7,7 +7,6 @@ idea { } } -// if project was not imported via idea pluginw clean { delete 'out' } diff --git a/gradle/init/banner.gradle b/gradle/init/banner.gradle new file mode 100644 index 0000000..fbac682 --- /dev/null +++ b/gradle/init/banner.gradle @@ -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 +} diff --git a/gradle/publishing/publication.gradle b/gradle/publishing/publication.gradle index c35fcb9..2e2b2c0 100644 --- a/gradle/publishing/publication.gradle +++ b/gradle/publishing/publication.gradle @@ -53,12 +53,14 @@ if (project.hasProperty("signing.keyId")) { } } -nexusPublishing { - repositories { - sonatype { - username = project.property('ossrhUsername') - password = project.property('ossrhPassword') - packageGroup = "org.xbib" +if (project.hasProperty("ossrhUsername")) { + nexusPublishing { + repositories { + sonatype { + username = project.property('ossrhUsername') + password = project.property('ossrhPassword') + packageGroup = "org.xbib" + } } } } diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 62d4c053550b91381bbd28b1afc82d634bf73a8a..e708b1c023ec8b20f512888fe07c5bd3ff77bb8f 100644 GIT binary patch delta 6656 zcmY+Ibx_pN*Z*PZ4(U#j1qtbvrOTyO8fghZ8kYJfEe%U|$dV!@ASKczEZq$fg48M@ z;LnHO_j#Uq?%bL4dY^md%$$4Y+&@nKC|1uHR&59YNhubGh72|a#ylPdh9V+akp|I; zPk^W-a00GrFMkz_NSADdv2G2-i6rb=cB_@WnG(**4ZO$=96R=t|NZ@|0_z&q3GwO^ ziUFcuj$a9QaZ3j?xt`5#q`sT-ufrtBP0nt3IA&dr*+VCsBzBVW?vZ6eZr0oD%t33z zm~-5IVsjy(F>;S~Pm@bxX85>Z*@(QL6i3JQc?1ryQFcC@X^2^mZWhFv|v? z49>l|nA&XNQ6#OvccUTyBMB*WO#NA;FW5|eE_K6dtVYP2G?uUZ09!`Iq1IF2gA(aS zLu@G^cQJmh=x?-YsYa@E6QnE5+1@ds&0f#OQRDl^GnIT_m84G5XY%W z;Ck6bk^Oeu*Ma-XmxI5GjqzWNbJMsQF4)WfMZEA{oxW0E32e)*JfG}3otPishIQBw zkBe6N#4pKPN>q1R6G1@5&(u#5yPEToMBB6_oEK|q z@(i5j!?;NNCv~=HvW%zF&1yWBq(nJa_#``G&SRmQvE|jePUPs{J!$TacM|e}Fsceb zx+76|mDp6@w>)^DIl{8?)6XYNRU|2plG8Jy&7(^9SdOWNKKJK&>0!z6XiN4J*Jkao z=E1y5x-XDC==Ub+8fLb#OW&{2ww{h^xlJFYAMOUd)}Xg@j?ak{7Kno6?9S~F?|6Df zHo|ijXX~`Sp;Vf!nR;m%vUhq>zvlRXsL0u*Tt?F#yR}3tF0#of{(UjitqST|!{aBA zicWh+URU}Jnc*sg9iMkf0pggpd?3TI*C-q$2QOdCC7rV+CHBmjS3O%a3VeZ$ZSs5ubJuJp%e%$LHgrj0niYjX;4kt z&2~j%@q3MO)-QGCA{>o%eZu){ou^MgC6~Z8Y=tc!qF=|TOlG3wJXbaLYr-;$Ch=2J z_UcE59Xzq&h0LsjLrcZrQSa}#=0~Lk|4?e4M z6d;v->NCC1oMti)RRc`Ys0?JXQjsZ@VdCy%Z)TptCrI>0Tte$pR!@yJesoU2dtyuW z7iFsE8)CkbiJP+OP28;(%?!9WddQZcAid@R@`*e%3W65$g9ee`zvwb(VPO+uVBq6p z{QDR%CR(2z@?&9Obm3xPi2lzvfip`7q`_7UDD|lRS}4=bsl3xQIOi0@GSvMuDQX}* z4B^(DI<${qUhcLqO`itJU;e<%%iS+R3I^_xIV1O%sp*x~;-dn` zt$8>RnSUh#rU3{-47067W^WNwTdq-t$-U>Hj%r!GD!gLa;kV zW5g6pCqV+!q8LgrI49(}fIc5K_`FLV4_E#XZ6{<>w8wzc%V9k!!Byg5-0WY+J?1*z%9~Aj4WQr1Jsn2(G!U8fFpi(wsy@JLg^d+IB0kl89 z0@Ssqf!L9JjYKK$J=978+NO*5^C)GPH2a%4hm$HROjM|N3g9ch9kDLh*nlwqy{mVM z`P(l#>3NnK%#O8tSb(VmZrG+`dRD#=Cc1P%(y5S?*Hj5E{vg&Eiw!YV>S#7_WRDVoFxT5m=gFi4)}y5V%KT8!xbsH_rmR& zsmM?%J}K$1l8d?2+m(}2c}-G`x>CY%Y&QBJRC$sKM}zN<9{IlF@yJEG<^0={$+`Hc zDodJ)gCADJ_bD#am(c2ojXKb|j+ENJ#58PAA&pZXufrFzBwnuuo+khfMgd!DMlU#v z9|JelQO~E2;d^w!RZJbt%IANIudpKSP)cssoWhq)>({nvcfCr0=9=FAIMuZm8Eo=} z|DND}8_PB5HqG(QwDvaM@orYBZ9kCkHV*rxKTy>q7n~0emErUwLbhq;VN<2nKT&*a2Ajz z;lKBzU2i8KLV`d)Y&ae)!HcGk$dO}Or%8KF@kE@jU1h@zwpw{6p4ME|uC$Za-ERR2 ztQvL&uOZLe(k{w_+J^ng+l}~N8MP>F1Z$fLu}D-WWaeu#XduP@#8JpmH(X>rIL)k3 zyXNyTIB1(IH%S&pQ{rWaTVfB$~-;RnlY z^(y7mR>@=brI>!TrA)BQsQ={b*6$=1Eqbuu6IdhJ&$YD$08AwtNr9*J?%-WT<;O1< zPl1<@yeqfZ>@s4azqTf<=I4(kU^+^Qkstm%WM-0_VLm({jFc8`5Df2Q1Y9zMZu0^! zsO_yh2Sz9K>Jq6fkYbBZocEJ6C!SdEzYDkiEtNJs{?!tA#e|oiN+VaaAobwKef_kUup&4scD?1+}Q8)DaekkMYn-FOS{J%NY za^mmJ^n`t*1p@hF*gl#L+5wr40*(ub4J#L|@oCl~@|4UvCjHBYDQv&S zhyGMAkRO^tF_dyi&XM)4mQ;k>kj?RgRo@-?==oD+ns*>bf@&fPXF|4U0&ib2 zo~1ZdmCPWf!W9#sGP@9X$;Rc`tjbz^&JY}z{}j9bl?;VC{x)TfQH$D^WowKL&4Zx@ zdSn+QV7H(e0xRfN6aBfH)Q=@weoD?dvu6^ZS)zqb>GwMmIuS8zJfaMUQx9>%k~w34 z3}_B2Jj~u=SnJ~vZPj*)UoDi_FtT=UAb#J^b4B%R6z3H%cj-1OCjU5F$ky>By1zsg z>2A0ccp29(Y<;my|J_g-r{1I@+*O$>!R3`_sFNP4e}LD1e1mM&SA`;;TR0I`_hESV zh4U*9ecK$0=lYk`{SR_cm$}iS*?yQR(}T-5ub?Wn^#RTe*^1~ya%`!xWq-F*WH@%nnZTNREA z3eUX2uM9b_w!Zo$nVTotEtzuL(88N)H~v_G=89|(@IFz~Wq6ME);z(!2^PkR2B&kE zxR)xV8PE|Hszyjp#jNf=ZIQ7JR~4Ls#Vd@mPF(7R5VO$akUq8JM+sn>ZVg(lJZ)5qjqdw(*7tuwjY#0tx+|!sTz9yV~%HOdrb#!5w9>*0LrCS z%wF$Yc6~hqVQZzoC^D<(-h0aOtk}kn<<*xF61HQr<5}efY{zXXA+PaJG7vT&{Oz(@Uu!V#Fp9%Ht!~@;6AcD z$lvlPu&yd(YnAHfpN51*)JN0aYw9gGk{NE7!Oqu4rBp}F30669;{zcH-a7w9KSpDQPIE_f9T zit? zJSjTKWbe{f{9BmSDAFO1(K0oqB4578tU0(oRBE^28X>xDA!1C&VJEiYak4_ZTM*7M`hv_ zw3;2ndv3X$zT!wa7TrId{gNE`Vxf}j5wsyX+;Kn<^$EJT`NzznjyYx=pYMkZjizEU zb;Gg8Pl_pqxg)9P)C)Hxh_-mQ;u-I_Ol>d^>q08zFF!>Z3j1-HmuME_TGZ*Ev;O0O z%e(edJfV<6t3&FKwtInnj9EeQhq9;o5oLJoiKwWF5bP2~Feh#P4oN()JT0pdq!9x* ze3D-1%AV#{G=Op$6q?*Z>s{qFn}cl@9#m@DK_Bs@fdwSN`Qe18_WnveRB583mdMG- z?<3pJC!YljOnO8=M=|Cg)jw;4>4sna`uI>Kh&F20jNOk9HX&}Ry|mHJ+?emHnbYLJ zwfkx@slh31+3nq-9G5FVDQBHWWY}&hJ-fpDf!lQdmw8dlTt#=)20X74S>c&kR(?PT zBg)Y%)q&|hW1K;`nJPAGF*c3{3`FvrhD9=Ld{3M*K&5$jRhXNsq$0CLXINax1AmXX ziF39vkNtcK6i^+G^AEY!WalGazOQ$_#tx?BQ{YY$&V&42sICVl8@AI6yv;sGnT;@f zL=}rZcJqNwrEEA=GDdEe8Z=f9>^?($oS8xGdFf1eUWTYtZF<3tu2V%noPBnd=thZ+ zO&xoc?jvXG7Xt!RTw#5VN50UjgqSntw9Y35*~pxz=8OzkXg{@S2J%+{l3Q>B_qbnl z20Deb7JM&ZSp`%X>xWpb>FF8q7Nq&4#a1}A-(-!aMDmVbz05D!NpUzVe{~72h%cOh zwQFNai2a$K|hFgDk(oPF_tuf{BV!=m0*xqSzGAJ(~XUh8rk#{YOg0ReK>4eJl z;-~u5v$}DM)#vER>F)-}y(X6rGkp<{AkiPM7rFgAV^)FUX8XmCKKaWlS4;MSEagj$ z#pvH`vLX1q{&eOm>htnk4hmv=_)ao!MCp}9ql5yfre&Py!~hBAGNBa}PH&J8K=~<% z&?!J-QaH|0bq_uo6rt*r-M>d7jm1cbW^T>s)S?L{n8v`^?VIPA+qi^6e@cM|5boqEO!p1e|_{7U3Yl6K?0xMN1bbjf0@$TE-T))w> zFe?E?g$PUT-)AJ(PS^By^D^Ed!K5iv$*_eW~VA(I3~UMy*ZcgVu0$XZC*_0PgDmUL)qTCn927LD~p$yXR_GCJ&iQ; z4*`%l-dC5pALH!y*nmhdHRh02QjW1vZL4ySucz*w3f|#`=u@@YvMV1?i!&DIa2+S< z8z!gvN3FV4I;%fl;ruFeV{jKjI~?GlgkmGBuJ<7vY|l3xMOc?S@Q#C(zo*m&JLrjT2rU9PYOniB8O~yO5<1CCcQz# z17B2m1Z{R!Y)UO#CU-Y&mOlv4*Gz%rC_YkRcO)jTUEWHDvv!GWmEihE>OKPx1J?Av z8J{-#7NsT>>R#*7**=QL)1@IR77G9JGZZiVt!=jD+i(oRV;I`JkiTSZkAXuHm-VG1 z+2-LD!!2dNEk@1@Rp|C$MD9mH^)H*G*wI(i*Rc6Vvdik+BDycYQ*=0JA3dxxha|Zg zCIW1Ye-DdpMGTEwbA^6hVC<(@0FL4dkDOYcxxC5c%MJQ^)zpA%>>~Q|Y=@)XW!px; z_Fx+xOo7>sz4QX|Ef~igE+uFnzFWP<-#||*V0`0p7E*+n5+awuOWmvR{-M*chIXgo zYiZvQMond#{F8+4Zh_;>MsaZUuhp=onH@P!7W>sq|CWv|u}Wg0vo&f4UtmLzhCwwu zJaR=IO;sQxS}h(K>9VZjnED+>9rGgB3ks+AwTy_EYH{oc)mo`451n&YH%A1@WC{;1 z=fB6n zIYp46_&u`COM&Di?$P}pPAlAF*Ss<)2Xc?=@_2|EMO?(A1u!Vc=-%bDAP#zDiYQvJ z0}+}3GaLxsMIlh6?f=iRs0K=RyvMOcWl*xqe-IBLv?K{S^hP)@K|$I+h_)pdD9r~! zxhw2u66+F(E`&6hY}B_qe>wil|#*0R0B;<@E?L zVrhXKfwRg0l8r>LuNs1QqW&39ME0sOXe8zycivGVqUOjEWpU)h|9fwp@d(8=M-WxY zeazSz6x5e`k821fgylLIbdqx~Kdh^Oj`Q!4vc*Km)^Tr-qRxPHozdvvU^#xNsKVr6aw8={70&S4y*5xeoF@Q^y596*09`XF56-N z1=Rm5?-An178o?$ix}y7gizQ9gEmGHF5AW+92DYaOcwEHnjAr~!vI>CK%h`E_tO8L Yte!%o?r4GTrVtxD61Ym!|5fq-1K$0e!T1w z1SC8j)_dObefzK9b=~*c&wBRW>;B{VGKiBofK!FMN5oJBE0V;;!kWUz!jc1W?5KdY zyZ3mCBHprpchz-9{ASiJJh&&h1|4rdw6wxD2+9= z#6#}Uq8&^1F3wgvGFoNDo?bIeEQXpcuAR0-+w$JWoK-@yUal1M&~W_O)r+Rx;{@hWH5n^oQWR36GMYBDDZyPK4L@WVjRrF+XlSzi4X4!_!U%Uujl6LHQ#|l(sUU%{ zefYd8jnVYP91K}Qn-OmmSLYFK1h~_}RPS~>+Xdz%dpvpJ{ll!IKX=JN99qowqslbO zV3DmqPZ}6>KB!9>jEObpi$u5oGPfO3O5!o3N2Mn`ozpje<}1I1H)m2rJDcB7AwXc6 z6j)tnPiql7#)r+b+p9?MVahp&=qJ^$oG+a^C*);FoJ!+V*^W+|2Olx5{*&$bXth)U zejc7mU6cBp?^Rj|dd{GL-0eHRTBi6_yJ&GLP5kIncv^z{?=0AVy^5{S8_n=rtua!J zFGY=A(yV^ZhB}1J_y(F`3QTu+zkHlw;1GiFeP&pw0N1k%NShHlO(4W+(!wy5phcg4 zA-|}(lE_1@@e6y`veg;v7m;q%(PFG&K3#}eRhJioXUU0jg_8{kn$;KVwf;zpL2X_( zC*_R#5*PaBaY73(x*oZ}oE#HPLJQRQ7brNK=v!lsu==lSG1(&q>F)`adBT~d*lMS| z%!%7(p~<7kWNmpZ5-N31*e=8`kih|g5lVrI%2wnLF-2D+G4k6@FrYsJ_80AJ}KMRi>) z-kIeHp{maorNWkF81v0FKgB==_6blyaF$5GaW)B!i4v*jNk6r)vU6?G$0pV8(Y+UK z5lgRVt%;N_gWp)^osv=h+^07UY6+$4^#t=M3>0i0`{`aEkFLL#a)93uXhYO+aKTtu zckg2T9S&GKNtZmdAS^8PzvDva-%-K&g9eqPXQ4$dM^inr@6Zl z{!Cq&C_+V;g*{>!0cZP}?ogDb$#ZS=n@NHE{>k@84lOkl&$Bt2NF)W%GClViJq14_ zQIfa^q+0aq){}CO8j%g%R9|;G0uJuND*HO$2i&U_uW_a5xJ33~(Vy?;%6_(2_Cuq1 zLhThN@xH7-BaNtkKTn^taQHrs$<<)euc6z(dhps>SM;^Wx=7;O&IfNVJq3wk4<1VS z-`*7W4DR_i^W4=dRh>AXi~J$K>`UqP>CKVVH&+T(ODhRJZO7DScU$F7D)di-%^8?O z6)Ux`zdrVOe1GNkPo0FgrrxSu1AGQkJe@pqu}8LkBDm+V!N_1l}`tjLW8${rgDLv3m@E*#zappt-Mm zSC<$o+6UO~w0C=(0$&*y**@nKe_Q{|eAuD!(0YL0_a{z%+sdfSyP={Nyd$re6Rzbp zvsgTY7~VflX0^Vf7qqomYZ_$ryrFVV2$sFyzw2r%Q8*uYDA+)iQdfKms_5(>!s#!( z!P5S(N0i9CKQKaqg(U%Gk#V3*?)lO6dLv`8KB~F<-%VhbtL8Rl>mEz+PN=qx&t*|= zQHV=qG)YKlPk4iCyWIUGjC?kpeA>hIBK*A?B0)rB=RqAal#D%1C9yVQwBcz${#Jb5 zR{TRmMrOrJsLc&6x9qDo@FJ^=do_Y?3oU0G^nV5_EU&+DS+VA7Tp{^TAF>yZbyM3c zf*1CqHY9T|aL_lyY7c)i!_MtGPA!sdy3|mrsKVj1mi&>dms@-ozSa}OZ?2I*tAndg z@S7er$t^d^-;!wLQbG60nWd@1pQVD7tw-G_B#OscoYyremiZ_hj8*sXqQdchuD^!R zpXGuSj5psk+jR>3rWu3^`17>j&*^9^rWbszP=Mf@5KIEj%b=z98v=Ymp%$FYt>%Ld zm8})EDbNOJu9n)gwhz_RS``#Ag)fr)3<*?(!9O~mTQWeh;8c;0@o=iBLQNqx3d_2#W7S9#FXzr6VXfs>4 z;QXw}-STvK9_-7H=uqgal2{GkbjVLN+=D5ddd)4^WvX;(NYA*X*(JxTdiUzqVJopd zQg#~psX4o<)cF>r=rxP`(Xsf<+HG-pf&7aFPL8z|-&B*P?Vmsu5d>Nlg^2$WRY!S@#`g2{81;(1w#o5HsvN}5pFZi});>|VK^kL{Zkx~wgn ztlZp;HW`H8(GdRfIwc~?#N6}o#h158ohI*GIsK%56I_9sf2k_K@4vD!l{(dX9E7PJ;w>$|Y;-VBJSO4@){07bo-89^LZ9g<<%;dOl zyIq{s8`8Ltp*GDwu(l_Z$6sA2nam$BM$Q~6TpZg)w2TtW?G5whV(lRwaf$6EU86is zBP9Rs&vS_~sk?Nn_b}^HkM8LiO@>J}=g(T4hLmvH@5Jj#2aHa~K)lD9VB0k>$V2BP zgh;(=y9Op(KQ=H5vj+%qs>?s4tYN~-Q|fyQePA)s?HrF~;l!+@t8VMzqUpqMLudFT z)=o~s!MM4XkgbetIsODwtQ=FF$IcIp&!pjh6Q6{tL+l*7GQ%8Wsg(tC#qU3oW$~n) zL=>XIxI}Hi7HS0F_mmi+(c%1HDuKiWm>|6Xa}nW7ei55ggru9)xjBvC#JcEIN*#cp zv*ACvr=HTC?dX9NNo9Yhulu_gX5Z~}QQ2&QZ&C77{(>Y3_ z6j5Z1Uc5FtPEpS_31HsgmSLHZijGb_p$WlRJ1p^_1!ZLP8kr6OtCEK7Qh267o$H>e zf<4cNGQRk{g5h$XfvTFQ@`qm@iju83-~}ebAYpZryARHVR$AEt3229U{y@Fp4 z-8FBBtGG&(hTyUdx5ZOfiz`c=<0F%+w|Fl=rWk{K7>70k04SN?RU(^mrKSeKDqA!K^Hsv8C?#ioj4@WUL zC*?{hTai6q0%_oBTqDHygp_Kl;({sAScYQIwMDM1U>{x0ww zve?_}E;DG?+|zsUrsph5X_G7l#Y~vqkq3@NNDabbw7|`eJBmn`Qrlr%?`va=mm$Mc{+FBbQbogAZ6{MuzT|P%QZZotd21eb1hfj|;GYAX&>bx#D5EB+=XMj2XJkpnyMUykaVo) zj3ZLqEl1&)Rturc8m@+uUuD^vaNaSxGwP4dq0-OSb~62lPv8E_K4usLvG{Qg zdR%z8dd2H!{JaT|X_bfm{##*W$YM;_J8Y8&Z)*ImOAf4+| zEyi)qK%Ld1bHuqD+}-WiCnjszDeC-%8g+8JRpG1bOc!xUGB?@?6f~FTrI%U#5R~YF z%t5(S2Q>?0`(XNHa8xKdTEZ~Z4SJOheit#ldfdg63}#W6j8kO;SjQD`vftxS+#x1B zYu|5szEvkyz|}|B3x|DNlyi$;+n+cW$Hu+?)=X1!sa%{H-^;oBO9XACZJ}wkQ!sTa zQ#J3h|HX{{&WwIG3h7d6aWktuJaO)ie6&=KJBoX@w(rBWfin`*a6OmCC5M0HzL(gv zY<*e4hmW>SWVhxk-`UGOAbD%Hk+uu<^7zJ_ytVXamfqCd0$g+W08>?QAB}Cv{b}eM z@X}ILg+uT%>-6`A25p@uhS3%;u>ccSq}8|H_^o&`nBT5S0y z;2H0I^(4MO*S+(4l$gULc4KSeKvidto5Nl0P|%9CqQ*ikY!w_GUlo}sb9HYB=L^oFpJ zfTQskXW!LFVnUo4(OHPDaZSf3zB|3{RGu1>ueE$(+dr?tT zp!SGlqDU8vu{5xLWSvj+j$arHglg54#Lx&TvuO3LIIU>hF9Uoj&=-b*Q?uYr`#V?xz?2 zhirZrv^eA{k%{hFh%9LYVXEYWd5#PuUd1QqaqB*J!CMXEM>fEB$@#1>mtB`Bfil}t zhhTIObqh5HRvT+4q_Do$Q*Jika?qV=Np-DtPkU z(KoXyWLfPwr@UY1)hBAvR3nCBZgd|CevTG?H~HqDF}dzy%2sd2`f{^CBbTk*^K~RO zN~O0+2EjAJlywF%SjgYz810l&G5AqzI<=Ber{912^PpSPRJl3dm8W@dKHL}7_@k3)Y!SXYkyxQy>Q4I2o zr`ev7fLF$1t96h|sH<-#*YzGD-b^3$_!#wsh(Yw;)b@udLz9mm`mFYh z1Zz24KIQJ(*_-E0(3&1InqG;U?wF)GYd>DFo(em`#|UaaYmkA9;GTX7b?0@C@QkTVpGD#mf$dQoRNV=n{^Zi_W*ps;3?^$s`0;ER7;==~OmQ~9 zS5P=FjxE5%|;xq6h4@!_h?@|aK&FYI2IT(OHXv2%1 zWEo-v!L7x^YT(xLVHlpJttcwaF@1Y;-S*q3CRa!g7xdzl|Jan>2#dI0`LKl!T1GMk zRKe4|bQO&ET}Z^Aiym*HII>cSxIzl|F~JEUGxz;+DB=8fxXhnBI4R12q6ews$lA`Jfi}r@A@-)6TOAUMNYFYJ zZ-Zd?lxFTyjN3mXnL!%#>Z%$0gJ4*9g;e;@zSmQ{eGGDaRRNM3s@6!;hYuVc=c+3B z=qzNNS~n^EsJU4aOGE|mdy={C^lPKEfPL-IJAsTpQsDgZ@~s+eHZYmp9yb=YW_4r?lqQaYZQ`nau){W`LY#P)>i zq^wHEuOYs#FlPZeMuT@Etb@~A6feCebq`miJE3w+gAL%bVF_s*5e*@)?xmKSo%I3? zLELHVdWia$}~s6 zr!^LfxSSB4Td&9iTXrzQpl5ZDo#SdmNr;23QsPHQ!x!UT9xtb!Ycz^JF8x)%cFOXK z^EXw%dRz_VD}7?RU^4{)1+xFO=z!EI8IUa3U*rag=1BpHX$Xi<__kSbS{y_xa*MJv z_`thq0Z^sPzjAk48ssDQj}!$N8Q$XC84(bU$t_Bm69Jf+C!h_}ep zwzpQj9sRA94<{x3{~z&ix-DwX;RAzka)4-#6ZHJqKh|SVuO|>Yrv+m30+!|sK<-|E z=)5E->#y<_1V|T1f%Af!ZYqXg}`O zI$qKOWdnclF`%_Z`WGOe{`A`l-#a?s=Q1a#@BOWmExH2;Wl`OB!B-%lq3nO{4=WO& z#k_x|N&(qzm*6S{G*|GCegF2N2ulC+(58z2DG~yUs}i8zvRf&$CJCaexJ6Xu!`qz( z)*v8*kAE#D0KCo*s{8^Rbg=`*E2MzeIt0|x55%n-gO&yX#$l=3W7-_~&(G8j1E(XB hw}tl`5K!1C(72%nnjQrp<7@!WCh47rWB+@R{{wClNUHz< diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 21e622d..33682bb 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ 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-6.6.1-all.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew index fbd7c51..4f906e0 100755 --- a/gradlew +++ b/gradlew @@ -130,7 +130,7 @@ fi 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 diff --git a/gradlew.bat b/gradlew.bat index a9f778a..ac1b06f 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -40,7 +40,7 @@ if defined JAVA_HOME goto findJavaFromJavaHome set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 -if "%ERRORLEVEL%" == "0" goto init +if "%ERRORLEVEL%" == "0" goto execute echo. echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. @@ -54,7 +54,7 @@ goto fail set JAVA_HOME=%JAVA_HOME:"=% set JAVA_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% @@ -64,21 +64,6 @@ echo location of your Java installation. goto fail -:init -@rem Get command-line arguments, handling Windows variants - -if not "%OS%" == "Windows_NT" goto win9xME_args - -:win9xME_args -@rem Slurp the command line arguments. -set CMD_LINE_ARGS= -set _SKIP=2 - -:win9xME_args_slurp -if "x%~1" == "x" goto execute - -set CMD_LINE_ARGS=%* - :execute @rem Setup the command line @@ -86,7 +71,7 @@ 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 diff --git a/src/main/java/com/google/common/base/StandardSystemProperty.java b/src/main/java/com/google/common/base/StandardSystemProperty.java index 4ffbf66..f0f9e9d 100644 --- a/src/main/java/com/google/common/base/StandardSystemProperty.java +++ b/src/main/java/com/google/common/base/StandardSystemProperty.java @@ -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 deprecated 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. */ diff --git a/src/main/java/com/google/common/cache/CacheBuilder.java b/src/main/java/com/google/common/cache/CacheBuilder.java index a945293..44c535f 100644 --- a/src/main/java/com/google/common/cache/CacheBuilder.java +++ b/src/main/java/com/google/common/cache/CacheBuilder.java @@ -480,8 +480,8 @@ public final class CacheBuilder { 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; } diff --git a/src/main/java/com/google/common/cache/LocalCache.java b/src/main/java/com/google/common/cache/LocalCache.java index a997ee1..dc4f6d2 100644 --- a/src/main/java/com/google/common/cache/LocalCache.java +++ b/src/main/java/com/google/common/cache/LocalCache.java @@ -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 extends AbstractMap implements ConcurrentMap public Set keySet() { // does not impact recency ordering Set ks = keySet; - return (ks != null) ? ks : (keySet = new KeySet(this)); + return (ks != null) ? ks : (keySet = new KeySet()); } Collection values; @@ -4293,7 +4292,7 @@ class LocalCache extends AbstractMap implements ConcurrentMap public Collection values() { // does not impact recency ordering Collection vs = values; - return (vs != null) ? vs : (values = new Values(this)); + return (vs != null) ? vs : (values = new Values()); } Set> entrySet; @@ -4303,7 +4302,7 @@ class LocalCache extends AbstractMap implements ConcurrentMap public Set> entrySet() { // does not impact recency ordering Set> 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 extends AbstractMap implements ConcurrentMap } abstract class AbstractCacheSet extends AbstractSet { - 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 extends AbstractMap implements ConcurrentMap final class KeySet extends AbstractCacheSet { - KeySet(ConcurrentMap map) { - super(map); - } - @Override public Iterator iterator() { return new KeyIterator(); @@ -4567,36 +4557,31 @@ class LocalCache extends AbstractMap implements ConcurrentMap @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 { - 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 extends AbstractMap implements ConcurrentMap @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 extends AbstractMap implements ConcurrentMap final class EntrySet extends AbstractCacheSet> { - EntrySet(ConcurrentMap map) { - super(map); - } - @Override public Iterator> iterator() { return new EntryIterator(); diff --git a/src/main/java/com/google/common/collect/CartesianList.java b/src/main/java/com/google/common/collect/CartesianList.java index 05ec425..93d3d6c 100644 --- a/src/main/java/com/google/common/collect/CartesianList.java +++ b/src/main/java/com/google/common/collect/CartesianList.java @@ -88,6 +88,28 @@ final class CartesianList extends AbstractList> 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 get(final int index) { checkElementIndex(index, size()); @@ -117,8 +139,21 @@ final class CartesianList extends AbstractList> 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; } } diff --git a/src/main/java/com/google/common/collect/CollectCollectors.java b/src/main/java/com/google/common/collect/CollectCollectors.java index 8e038c8..43a0ea4 100644 --- a/src/main/java/com/google/common/collect/CollectCollectors.java +++ b/src/main/java/com/google/common/collect/CollectCollectors.java @@ -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 Collector> toImmutableBiMap( - Function keyFunction, - Function valueFunction) { - checkNotNull(keyFunction); - checkNotNull(valueFunction); - return Collector.of( - ImmutableBiMap.Builder::new, - (builder, input) -> builder.put(keyFunction.apply(input), valueFunction.apply(input)), - ImmutableBiMap.Builder::combine, - ImmutableBiMap.Builder::build, - new Collector.Characteristics[0]); - } private static final Collector> TO_IMMUTABLE_LIST = Collector.of( - ImmutableList::builder, + ImmutableList::builder, ImmutableList.Builder::add, ImmutableList.Builder::combine, ImmutableList.Builder::build); + private static final Collector> TO_IMMUTABLE_SET = + Collector.of( + ImmutableSet::builder, + ImmutableSet.Builder::add, + ImmutableSet.Builder::combine, + ImmutableSet.Builder::build); + + @GwtIncompatible + private static final Collector>, ?, ImmutableRangeSet>> + TO_IMMUTABLE_RANGE_SET = + Collector.of( + ImmutableRangeSet::builder, + ImmutableRangeSet.Builder::add, + ImmutableRangeSet.Builder::combine, + ImmutableRangeSet.Builder::build); + + // Lists + + @SuppressWarnings({"rawtypes", "unchecked"}) static Collector> toImmutableList() { return (Collector) TO_IMMUTABLE_LIST; } + // Sets + + @SuppressWarnings({"rawtypes", "unchecked"}) + static Collector> toImmutableSet() { + return (Collector) TO_IMMUTABLE_SET; + } + + static Collector> toImmutableSortedSet( + Comparator comparator) { + checkNotNull(comparator); + return Collector.of( + () -> new ImmutableSortedSet.Builder(comparator), + ImmutableSortedSet.Builder::add, + ImmutableSortedSet.Builder::combine, + ImmutableSortedSet.Builder::build); + } + + @SuppressWarnings({"rawtypes", "unchecked"}) + static > Collector> toImmutableEnumSet() { + return (Collector) EnumSetAccumulator.TO_IMMUTABLE_ENUM_SET; + } + + private static final class EnumSetAccumulator> { + @SuppressWarnings({"rawtypes", "unchecked"}) + static final Collector, ?, ImmutableSet>> TO_IMMUTABLE_ENUM_SET = + (Collector) + Collector.>of( + EnumSetAccumulator::new, + EnumSetAccumulator::add, + EnumSetAccumulator::combine, + EnumSetAccumulator::toImmutableSet, + Collector.Characteristics.UNORDERED); + + private EnumSet set; + + void add(E e) { + if (set == null) { + set = EnumSet.of(e); + } else { + set.add(e); + } + } + + EnumSetAccumulator combine(EnumSetAccumulator other) { + if (this.set == null) { + return other; + } else if (other.set == null) { + return this; + } else { + this.set.addAll(other.set); + return this; + } + } + + ImmutableSet toImmutableSet() { + return (set == null) ? ImmutableSet.of() : ImmutableEnumSet.asImmutable(set); + } + } + + @GwtIncompatible + @SuppressWarnings({"rawtypes", "unchecked"}) + static > + Collector, ?, ImmutableRangeSet> toImmutableRangeSet() { + return (Collector) TO_IMMUTABLE_RANGE_SET; + } + + // Multisets + + static Collector> toImmutableMultiset( + Function elementFunction, ToIntFunction countFunction) { + checkNotNull(elementFunction); + checkNotNull(countFunction); + return Collector.of( + LinkedHashMultiset::create, + (multiset, t) -> + multiset.add(checkNotNull(elementFunction.apply(t)), countFunction.applyAsInt(t)), + (multiset1, multiset2) -> { + multiset1.addAll(multiset2); + return multiset1; + }, + (Multiset multiset) -> ImmutableMultiset.copyFromEntries(multiset.entrySet())); + } + + static > Collector toMultiset( + java.util.function.Function elementFunction, + java.util.function.ToIntFunction countFunction, + java.util.function.Supplier multisetSupplier) { + checkNotNull(elementFunction); + checkNotNull(countFunction); + checkNotNull(multisetSupplier); + return Collector.of( + multisetSupplier, + (ms, t) -> ms.add(elementFunction.apply(t), countFunction.applyAsInt(t)), + (ms1, ms2) -> { + ms1.addAll(ms2); + return ms1; + }); + } + + // Maps + static Collector> toImmutableMap( - Function keyFunction, - Function valueFunction) { + Function keyFunction, + Function valueFunction) { checkNotNull(keyFunction); checkNotNull(valueFunction); return Collector.of( - ImmutableMap.Builder::new, - (builder, input) -> builder.put(keyFunction.apply(input), valueFunction.apply(input)), - ImmutableMap.Builder::combine, - ImmutableMap.Builder::build); + ImmutableMap.Builder::new, + (builder, input) -> builder.put(keyFunction.apply(input), valueFunction.apply(input)), + ImmutableMap.Builder::combine, + ImmutableMap.Builder::build); } - private static final Collector> TO_IMMUTABLE_SET = - Collector.of( - ImmutableSet::builder, - ImmutableSet.Builder::add, - ImmutableSet.Builder::combine, - ImmutableSet.Builder::build); + public static Collector> toImmutableMap( + Function keyFunction, + Function valueFunction, + BinaryOperator mergeFunction) { + checkNotNull(keyFunction); + checkNotNull(valueFunction); + checkNotNull(mergeFunction); + return Collectors.collectingAndThen( + Collectors.toMap(keyFunction, valueFunction, mergeFunction, LinkedHashMap::new), + ImmutableMap::copyOf); + } - static Collector> toImmutableSet() { - return (Collector) TO_IMMUTABLE_SET; + static Collector> toImmutableSortedMap( + Comparator comparator, + Function keyFunction, + Function valueFunction, + BinaryOperator mergeFunction) { + checkNotNull(comparator); + checkNotNull(keyFunction); + checkNotNull(valueFunction); + checkNotNull(mergeFunction); + return Collectors.collectingAndThen( + Collectors.toMap( + keyFunction, valueFunction, mergeFunction, () -> new TreeMap(comparator)), + ImmutableSortedMap::copyOfSorted); + } + + static Collector> toImmutableBiMap( + Function keyFunction, + Function valueFunction) { + checkNotNull(keyFunction); + checkNotNull(valueFunction); + return Collector.of( + ImmutableBiMap.Builder::new, + (builder, input) -> builder.put(keyFunction.apply(input), valueFunction.apply(input)), + ImmutableBiMap.Builder::combine, + ImmutableBiMap.Builder::build, + new Collector.Characteristics[0]); + } + + static , V> Collector> toImmutableEnumMap( + Function keyFunction, + Function valueFunction) { + checkNotNull(keyFunction); + checkNotNull(valueFunction); + return Collector.of( + () -> + new EnumMapAccumulator( + (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 , V> Collector> toImmutableEnumMap( + Function keyFunction, + Function valueFunction, + BinaryOperator mergeFunction) { + checkNotNull(keyFunction); + checkNotNull(valueFunction); + checkNotNull(mergeFunction); + // not UNORDERED because we don't know if mergeFunction is commutative + return Collector.of( + () -> new EnumMapAccumulator(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, V> { + private final BinaryOperator mergeFunction; + private EnumMap map = null; + + EnumMapAccumulator(BinaryOperator mergeFunction) { + this.mergeFunction = mergeFunction; + } + + void put(K key, V value) { + if (map == null) { + map = new EnumMap<>(key.getDeclaringClass()); + } + map.merge(key, value, mergeFunction); + } + + EnumMapAccumulator combine(EnumMapAccumulator other) { + if (this.map == null) { + return other; + } else if (other.map == null) { + return this; + } else { + other.map.forEach(this::put); + return this; + } + } + + ImmutableMap toImmutableMap() { + return (map == null) ? ImmutableMap.of() : ImmutableEnumMap.asImmutable(map); + } + } + + @GwtIncompatible + static , V> + Collector> toImmutableRangeMap( + Function> keyFunction, + Function valueFunction) { + checkNotNull(keyFunction); + checkNotNull(valueFunction); + return Collector.of( + ImmutableRangeMap::builder, + (builder, input) -> builder.put(keyFunction.apply(input), valueFunction.apply(input)), + ImmutableRangeMap.Builder::combine, + ImmutableRangeMap.Builder::build); } static Collector> toImmutableSortedMap( @@ -92,43 +325,91 @@ final class CollectCollectors { ImmutableSortedMap.Builder::build, Collector.Characteristics.UNORDERED); } + // Multimaps - static Collector> toImmutableSortedSet( - Comparator comparator) { - checkNotNull(comparator); - return Collector.of( - () -> new ImmutableSortedSet.Builder(comparator), - ImmutableSortedSet.Builder::add, - ImmutableSortedSet.Builder::combine, - ImmutableSortedSet.Builder::build); - } - - @GwtIncompatible - private static final Collector, ?, ImmutableRangeSet> - TO_IMMUTABLE_RANGE_SET = - Collector.of( - ImmutableRangeSet::builder, - ImmutableRangeSet.Builder::add, - ImmutableRangeSet.Builder::combine, - ImmutableRangeSet.Builder::build); - - @GwtIncompatible - static > - Collector, ?, ImmutableRangeSet> toImmutableRangeSet() { - return (Collector) TO_IMMUTABLE_RANGE_SET; - } - - @GwtIncompatible - static , V> - Collector> toImmutableRangeMap( - Function> keyFunction, + static Collector> toImmutableListMultimap( + Function keyFunction, Function valueFunction) { + checkNotNull(keyFunction, "keyFunction"); + checkNotNull(valueFunction, "valueFunction"); + return Collector.of( + ImmutableListMultimap::builder, + (builder, t) -> builder.put(keyFunction.apply(t), valueFunction.apply(t)), + ImmutableListMultimap.Builder::combine, + ImmutableListMultimap.Builder::build); + } + + static Collector> flatteningToImmutableListMultimap( + Function keyFunction, + Function> valuesFunction) { + checkNotNull(keyFunction); + checkNotNull(valuesFunction); + return Collectors.collectingAndThen( + flatteningToMultimap( + input -> checkNotNull(keyFunction.apply(input)), + input -> valuesFunction.apply(input).peek(Preconditions::checkNotNull), + MultimapBuilder.linkedHashKeys().arrayListValues()::build), + ImmutableListMultimap::copyOf); + } + + static Collector> toImmutableSetMultimap( + Function keyFunction, + Function valueFunction) { + checkNotNull(keyFunction, "keyFunction"); + checkNotNull(valueFunction, "valueFunction"); + return Collector.of( + ImmutableSetMultimap::builder, + (builder, t) -> builder.put(keyFunction.apply(t), valueFunction.apply(t)), + ImmutableSetMultimap.Builder::combine, + ImmutableSetMultimap.Builder::build); + } + + static Collector> flatteningToImmutableSetMultimap( + Function keyFunction, + Function> valuesFunction) { + checkNotNull(keyFunction); + checkNotNull(valuesFunction); + return Collectors.collectingAndThen( + flatteningToMultimap( + input -> checkNotNull(keyFunction.apply(input)), + input -> valuesFunction.apply(input).peek(Preconditions::checkNotNull), + MultimapBuilder.linkedHashKeys().linkedHashSetValues()::build), + ImmutableSetMultimap::copyOf); + } + + static > Collector toMultimap( + java.util.function.Function keyFunction, + java.util.function.Function valueFunction, + java.util.function.Supplier multimapSupplier) { checkNotNull(keyFunction); checkNotNull(valueFunction); + checkNotNull(multimapSupplier); return Collector.of( - ImmutableRangeMap::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 > Collector flatteningToMultimap( + Function keyFunction, + Function> valueFunction, + Supplier multimapSupplier) { + checkNotNull(keyFunction); + checkNotNull(valueFunction); + checkNotNull(multimapSupplier); + return Collector.of( + multimapSupplier, + (multimap, input) -> { + K key = keyFunction.apply(input); + Collection valuesForKey = multimap.get(key); + valueFunction.apply(input).forEachOrdered(valuesForKey::add); + }, + (multimap1, multimap2) -> { + multimap1.putAll(multimap2); + return multimap1; + }); } } diff --git a/src/main/java/com/google/common/collect/Collections2.java b/src/main/java/com/google/common/collect/Collections2.java index 9b8fca6..8b57f50 100644 --- a/src/main/java/com/google/common/collect/Collections2.java +++ b/src/main/java/com/google/common/collect/Collections2.java @@ -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 Collection cast(Iterable iterable) { - return (Collection) iterable; - } - /** * Returns a {@link Collection} of all the permutations of the specified {@link Iterable}. * diff --git a/src/main/java/com/google/common/collect/Comparators.java b/src/main/java/com/google/common/collect/Comparators.java index 06b398f..b71e8b4 100644 --- a/src/main/java/com/google/common/collect/Comparators.java +++ b/src/main/java/com/google/common/collect/Comparators.java @@ -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. + * + *

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 + * {@code + * Comparators}. + * + * @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 mutually comparable. + * @since 30.0 + */ + @Beta + public static > 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. + * + *

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 + * {@code + * Comparators}. + * + * @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 mutually comparable using the given + * comparator. + * @since 30.0 + */ + @Beta + public static T min(T a, T b, Comparator 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. + * + *

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 + * {@code + * Comparators}. + * + * @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 mutually comparable. + * @since 30.0 + */ + @Beta + public static > 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. + * + *

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 + * {@code + * Comparators}. + * + * @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 mutually comparable using the given + * comparator. + * @since 30.0 + */ + @Beta + public static T max(T a, T b, Comparator comparator) { + return (comparator.compare(a, b) >= 0) ? a : b; + } } diff --git a/src/main/java/com/google/common/collect/ContiguousSet.java b/src/main/java/com/google/common/collect/ContiguousSet.java index 6755be6..d471d09 100644 --- a/src/main/java/com/google/common/collect/ContiguousSet.java +++ b/src/main/java/com/google/common/collect/ContiguousSet.java @@ -191,15 +191,14 @@ public abstract class ContiguousSet 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 headSetImpl(C toElement, boolean inclusive); - /* @Override */ + @SuppressWarnings("MissingOverride") // Supermethod does not exist under GWT. abstract ContiguousSet subSetImpl( C fromElement, boolean fromInclusive, C toElement, boolean toInclusive); - /* @Override */ + @SuppressWarnings("MissingOverride") // Supermethod does not exist under GWT. abstract ContiguousSet tailSetImpl(C fromElement, boolean inclusive); /** diff --git a/src/main/java/com/google/common/collect/FluentIterable.java b/src/main/java/com/google/common/collect/FluentIterable.java index 6ee1ebd..f47528d 100644 --- a/src/main/java/com/google/common/collect/FluentIterable.java +++ b/src/main/java/com/google/common/collect/FluentIterable.java @@ -786,7 +786,7 @@ public abstract class FluentIterable implements Iterable { checkNotNull(collection); Iterable iterable = getDelegate(); if (iterable instanceof Collection) { - collection.addAll(Collections2.cast(iterable)); + collection.addAll((Collection) iterable); } else { for (E item : iterable) { collection.add(item); diff --git a/src/main/java/com/google/common/collect/ForwardingMap.java b/src/main/java/com/google/common/collect/ForwardingMap.java index bdb9f0f..f6dc7b7 100644 --- a/src/main/java/com/google/common/collect/ForwardingMap.java +++ b/src/main/java/com/google/common/collect/ForwardingMap.java @@ -76,8 +76,8 @@ public abstract class ForwardingMap extends ForwardingObject implements Ma @Override - public V remove(Object object) { - return delegate().remove(object); + public V remove(Object key) { + return delegate().remove(key); } @Override diff --git a/src/main/java/com/google/common/collect/ImmutableBiMap.java b/src/main/java/com/google/common/collect/ImmutableBiMap.java index 67670c5..9c0f6a2 100644 --- a/src/main/java/com/google/common/collect/ImmutableBiMap.java +++ b/src/main/java/com/google/common/collect/ImmutableBiMap.java @@ -413,15 +413,14 @@ public abstract class ImmutableBiMap extends ImmutableBiMapFauxverideShim< *

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 extends ImmutableMap.SerializedForm { + SerializedForm(ImmutableBiMap bimap) { super(bimap); } @Override - Object readResolve() { - Builder builder = new Builder<>(); - return createMap(builder); + Builder makeBuilder(int size) { + return new Builder<>(); } private static final long serialVersionUID = 0; @@ -429,6 +428,6 @@ public abstract class ImmutableBiMap extends ImmutableBiMapFauxverideShim< @Override Object writeReplace() { - return new SerializedForm(this); + return new SerializedForm<>(this); } } diff --git a/src/main/java/com/google/common/collect/ImmutableListMultimap.java b/src/main/java/com/google/common/collect/ImmutableListMultimap.java index da1a8b5..711b30e 100644 --- a/src/main/java/com/google/common/collect/ImmutableListMultimap.java +++ b/src/main/java/com/google/common/collect/ImmutableListMultimap.java @@ -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 extends ImmutableMultimap public static Collector> toImmutableListMultimap( Function keyFunction, Function valueFunction) { - checkNotNull(keyFunction, "keyFunction"); - checkNotNull(valueFunction, "valueFunction"); - return Collector.of( - ImmutableListMultimap::builder, - (builder, t) -> builder.put(keyFunction.apply(t), valueFunction.apply(t)), - ImmutableListMultimap.Builder::combine, - ImmutableListMultimap.Builder::build); + return CollectCollectors.toImmutableListMultimap(keyFunction, valueFunction); } /** @@ -127,14 +120,7 @@ public class ImmutableListMultimap extends ImmutableMultimap Collector> flatteningToImmutableListMultimap( Function keyFunction, Function> valuesFunction) { - checkNotNull(keyFunction); - checkNotNull(valuesFunction); - return Collectors.collectingAndThen( - Multimaps.flatteningToMultimap( - input -> checkNotNull(keyFunction.apply(input)), - input -> valuesFunction.apply(input).peek(Preconditions::checkNotNull), - MultimapBuilder.linkedHashKeys().arrayListValues()::build), - ImmutableListMultimap::copyOf); + return CollectCollectors.flatteningToImmutableListMultimap(keyFunction, valuesFunction); } /** Returns the empty multimap. */ diff --git a/src/main/java/com/google/common/collect/ImmutableMap.java b/src/main/java/com/google/common/collect/ImmutableMap.java index f36e55b..3245752 100644 --- a/src/main/java/com/google/common/collect/ImmutableMap.java +++ b/src/main/java/com/google/common/collect/ImmutableMap.java @@ -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 implements Map, Serializable { Function keyFunction, Function valueFunction, BinaryOperator 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 implements Map, 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 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 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 builder = new Builder<>(keys.length); - return createMap(builder); + @SuppressWarnings("unchecked") + final Object readResolve() { + if (!(this.keys instanceof ImmutableSet)) { + return legacyReadResolve(); + } + + ImmutableSet keySet = (ImmutableSet) this.keys; + ImmutableCollection values = (ImmutableCollection) this.values; + + Builder builder = makeBuilder(keySet.size()); + + UnmodifiableIterator keyIter = keySet.iterator(); + UnmodifiableIterator valueIter = values.iterator(); + + while (keyIter.hasNext()) { + builder.put(keyIter.next(), valueIter.next()); + } + + return builder.build(); } - Object createMap(Builder builder) { + @SuppressWarnings("unchecked") + final Object legacyReadResolve() { + K[] keys = (K[]) this.keys; + V[] values = (V[]) this.values; + + Builder 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 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); } } diff --git a/src/main/java/com/google/common/collect/ImmutableMapKeySet.java b/src/main/java/com/google/common/collect/ImmutableMapKeySet.java index 3aa0bfa..69d45f4 100644 --- a/src/main/java/com/google/common/collect/ImmutableMapKeySet.java +++ b/src/main/java/com/google/common/collect/ImmutableMapKeySet.java @@ -75,12 +75,6 @@ final class ImmutableMapKeySet extends IndexedImmutableSet { return true; } - @GwtIncompatible // serialization - @Override - Object writeReplace() { - return new KeySetSerializedForm(map); - } - @GwtIncompatible // serialization private static class KeySetSerializedForm implements Serializable { final ImmutableMap map; diff --git a/src/main/java/com/google/common/collect/ImmutableMapValues.java b/src/main/java/com/google/common/collect/ImmutableMapValues.java index f641895..1aee8ed 100644 --- a/src/main/java/com/google/common/collect/ImmutableMapValues.java +++ b/src/main/java/com/google/common/collect/ImmutableMapValues.java @@ -100,12 +100,6 @@ final class ImmutableMapValues extends ImmutableCollection { map.forEach((k, v) -> action.accept(v)); } - @GwtIncompatible // serialization - @Override - Object writeReplace() { - return new SerializedForm(map); - } - @GwtIncompatible // serialization private static class SerializedForm implements Serializable { final ImmutableMap map; diff --git a/src/main/java/com/google/common/collect/ImmutableMultiset.java b/src/main/java/com/google/common/collect/ImmutableMultiset.java index 391cbd2..dd5ea4d 100644 --- a/src/main/java/com/google/common/collect/ImmutableMultiset.java +++ b/src/main/java/com/google/common/collect/ImmutableMultiset.java @@ -64,7 +64,7 @@ public abstract class ImmutableMultiset extends ImmutableMultisetGwtSerializa * @since 21.0 */ public static Collector> toImmutableMultiset() { - return toImmutableMultiset(Function.identity(), e -> 1); + return CollectCollectors.toImmutableMultiset(Function.identity(), e -> 1); } /** @@ -80,17 +80,7 @@ public abstract class ImmutableMultiset extends ImmutableMultisetGwtSerializa */ public static Collector> toImmutableMultiset( Function elementFunction, ToIntFunction countFunction) { - checkNotNull(elementFunction); - checkNotNull(countFunction); - return Collector.of( - LinkedHashMultiset::create, - (multiset, t) -> - multiset.add(checkNotNull(elementFunction.apply(t)), countFunction.applyAsInt(t)), - (multiset1, multiset2) -> { - multiset1.addAll(multiset2); - return multiset1; - }, - (Multiset multiset) -> copyFromEntries(multiset.entrySet())); + return CollectCollectors.toImmutableMultiset(elementFunction, countFunction); } /** Returns the empty immutable multiset. */ diff --git a/src/main/java/com/google/common/collect/ImmutableSet.java b/src/main/java/com/google/common/collect/ImmutableSet.java index 9926dc4..97d0114 100644 --- a/src/main/java/com/google/common/collect/ImmutableSet.java +++ b/src/main/java/com/google/common/collect/ImmutableSet.java @@ -514,13 +514,11 @@ public abstract class ImmutableSet extends ImmutableCollection implements } @Override - public Builder add(E... elements) { super.add(elements); return this; } - @Override /** * Adds each element of {@code elements} to the {@code ImmutableSet}, ignoring duplicate * elements (only the first duplicate element is added). @@ -529,7 +527,7 @@ public abstract class ImmutableSet extends ImmutableCollection implements * @return this {@code Builder} object * @throws NullPointerException if {@code elements} is null or contains a null element */ - + @Override public Builder addAll(Iterable elements) { super.addAll(elements); return this; diff --git a/src/main/java/com/google/common/collect/ImmutableSetMultimap.java b/src/main/java/com/google/common/collect/ImmutableSetMultimap.java index f424c14..89bf2c9 100644 --- a/src/main/java/com/google/common/collect/ImmutableSetMultimap.java +++ b/src/main/java/com/google/common/collect/ImmutableSetMultimap.java @@ -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 extends ImmutableMultimap public static Collector> toImmutableSetMultimap( Function keyFunction, Function valueFunction) { - checkNotNull(keyFunction, "keyFunction"); - checkNotNull(valueFunction, "valueFunction"); - return Collector.of( - ImmutableSetMultimap::builder, - (builder, t) -> builder.put(keyFunction.apply(t), valueFunction.apply(t)), - ImmutableSetMultimap.Builder::combine, - ImmutableSetMultimap.Builder::build); + return CollectCollectors.toImmutableSetMultimap(keyFunction, valueFunction); } /** @@ -140,14 +132,7 @@ public class ImmutableSetMultimap extends ImmutableMultimap Collector> flatteningToImmutableSetMultimap( Function keyFunction, Function> valuesFunction) { - checkNotNull(keyFunction); - checkNotNull(valuesFunction); - return Collectors.collectingAndThen( - Multimaps.flatteningToMultimap( - input -> checkNotNull(keyFunction.apply(input)), - input -> valuesFunction.apply(input).peek(Preconditions::checkNotNull), - MultimapBuilder.linkedHashKeys().linkedHashSetValues()::build), - ImmutableSetMultimap::copyOf); + return CollectCollectors.flatteningToImmutableSetMultimap(keyFunction, valuesFunction); } /** Returns the empty multimap. */ diff --git a/src/main/java/com/google/common/collect/ImmutableSortedMap.java b/src/main/java/com/google/common/collect/ImmutableSortedMap.java index a8d893b..0a317db 100644 --- a/src/main/java/com/google/common/collect/ImmutableSortedMap.java +++ b/src/main/java/com/google/common/collect/ImmutableSortedMap.java @@ -95,14 +95,7 @@ public final class ImmutableSortedMap extends ImmutableSortedMapFauxveride Function keyFunction, Function valueFunction, BinaryOperator mergeFunction) { - checkNotNull(comparator); - checkNotNull(keyFunction); - checkNotNull(valueFunction); - checkNotNull(mergeFunction); - return Collectors.collectingAndThen( - Collectors.toMap( - keyFunction, valueFunction, mergeFunction, () -> new TreeMap(comparator)), - ImmutableSortedMap::copyOfSorted); + return CollectCollectors.toImmutableSortedMap(comparator, keyFunction, valueFunction, mergeFunction); } /* @@ -916,19 +909,18 @@ public final class ImmutableSortedMap 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 comparator; + private static class SerializedForm extends ImmutableMap.SerializedForm { + private final Comparator comparator; @SuppressWarnings("unchecked") - SerializedForm(ImmutableSortedMap sortedMap) { + SerializedForm(ImmutableSortedMap sortedMap) { super(sortedMap); - comparator = (Comparator) sortedMap.comparator(); + comparator = sortedMap.comparator(); } @Override - Object readResolve() { - Builder builder = new Builder<>(comparator); - return createMap(builder); + Builder makeBuilder(int size) { + return new Builder<>(comparator); } private static final long serialVersionUID = 0; @@ -936,7 +928,7 @@ public final class ImmutableSortedMap 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 diff --git a/src/main/java/com/google/common/collect/ImmutableTable.java b/src/main/java/com/google/common/collect/ImmutableTable.java index 3beb9f9..9a81d3e 100644 --- a/src/main/java/com/google/common/collect/ImmutableTable.java +++ b/src/main/java/com/google/common/collect/ImmutableTable.java @@ -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 extends AbstractTable Function rowFunction, Function columnFunction, Function valueFunction) { - checkNotNull(rowFunction, "rowFunction"); - checkNotNull(columnFunction, "columnFunction"); - checkNotNull(valueFunction, "valueFunction"); - return Collector.of( - () -> new ImmutableTable.Builder(), - (builder, t) -> - builder.put(rowFunction.apply(t), columnFunction.apply(t), valueFunction.apply(t)), - (b1, b2) -> b1.combine(b2), - b -> b.build()); + return TableCollectors.toImmutableTable(rowFunction, columnFunction, valueFunction); } /** @@ -90,88 +80,7 @@ public abstract class ImmutableTable extends AbstractTable Function columnFunction, Function valueFunction, BinaryOperator mergeFunction) { - - checkNotNull(rowFunction, "rowFunction"); - checkNotNull(columnFunction, "columnFunction"); - checkNotNull(valueFunction, "valueFunction"); - checkNotNull(mergeFunction, "mergeFunction"); - - /* - * No mutable Table exactly matches the insertion order behavior of ImmutableTable.Builder, but - * the Builder can't efficiently support merging of duplicate values. Getting around this - * requires some work. - */ - - return Collector.of( - () -> new CollectorState() - /* GWT isn't currently playing nicely with constructor references? */ , - (state, input) -> - state.put( - rowFunction.apply(input), - columnFunction.apply(input), - valueFunction.apply(input), - mergeFunction), - (s1, s2) -> s1.combine(s2, mergeFunction), - state -> state.toTable()); - } - - private static final class CollectorState { - final List> insertionOrder = new ArrayList<>(); - final Table> table = HashBasedTable.create(); - - void put(R row, C column, V value, BinaryOperator merger) { - MutableCell oldCell = table.get(row, column); - if (oldCell == null) { - MutableCell cell = new MutableCell<>(row, column, value); - insertionOrder.add(cell); - table.put(row, column, cell); - } else { - oldCell.merge(value, merger); - } - } - - CollectorState combine(CollectorState other, BinaryOperator merger) { - for (MutableCell cell : other.insertionOrder) { - put(cell.getRowKey(), cell.getColumnKey(), cell.getValue(), merger); - } - return this; - } - - ImmutableTable toTable() { - return copyOf(insertionOrder); - } - } - - private static final class MutableCell extends AbstractCell { - private final R row; - private final C column; - private V value; - - MutableCell(R row, C column, V value) { - this.row = checkNotNull(row, "row"); - this.column = checkNotNull(column, "column"); - this.value = checkNotNull(value, "value"); - } - - @Override - public R getRowKey() { - return row; - } - - @Override - public C getColumnKey() { - return column; - } - - @Override - public V getValue() { - return value; - } - - void merge(V value, BinaryOperator mergeFunction) { - checkNotNull(value, "value"); - this.value = checkNotNull(mergeFunction.apply(this.value, value), "mergeFunction.apply"); - } + return TableCollectors.toImmutableTable(rowFunction, columnFunction, valueFunction, mergeFunction); } /** Returns an empty immutable table. */ @@ -209,7 +118,7 @@ public abstract class ImmutableTable extends AbstractTable } } - private static ImmutableTable copyOf( + public static ImmutableTable copyOf( Iterable> cells) { ImmutableTable.Builder builder = ImmutableTable.builder(); for (Cell cell : cells) { diff --git a/src/main/java/com/google/common/collect/Interner.java b/src/main/java/com/google/common/collect/Interner.java index a72a1dd..04ab0db 100644 --- a/src/main/java/com/google/common/collect/Interner.java +++ b/src/main/java/com/google/common/collect/Interner.java @@ -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. + * + *

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 diff --git a/src/main/java/com/google/common/collect/Iterables.java b/src/main/java/com/google/common/collect/Iterables.java index 8c185dd..f052779 100644 --- a/src/main/java/com/google/common/collect/Iterables.java +++ b/src/main/java/com/google/common/collect/Iterables.java @@ -314,7 +314,7 @@ public final class Iterables { public static boolean addAll(Collection addTo, Iterable elementsToAdd) { if (elementsToAdd instanceof Collection) { - Collection c = Collections2.cast(elementsToAdd); + Collection c = (Collection) elementsToAdd; return addTo.addAll(c); } return Iterators.addAll(addTo, checkNotNull(elementsToAdd).iterator()); @@ -814,7 +814,7 @@ public final class Iterables { */ public static T getLast(Iterable iterable, T defaultValue) { if (iterable instanceof Collection) { - Collection c = Collections2.cast(iterable); + Collection c = (Collection) iterable; if (c.isEmpty()) { return defaultValue; } else if (iterable instanceof List) { diff --git a/src/main/java/com/google/common/collect/Lists.java b/src/main/java/com/google/common/collect/Lists.java index af52f69..08cb4a2 100644 --- a/src/main/java/com/google/common/collect/Lists.java +++ b/src/main/java/com/google/common/collect/Lists.java @@ -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) elements) + : newArrayList(elements.iterator()); } /** @@ -264,8 +264,9 @@ public final class Lists { Iterable elements) { // We copy elements to an ArrayList first, rather than incurring the // quadratic cost of adding them to the COWAL directly. - Collection elementsCollection = - (elements instanceof Collection) ? Collections2.cast(elements) : newArrayList(elements); + Collection elementsCollection = (elements instanceof Collection) + ? (Collection) elements + : newArrayList(elements); return new CopyOnWriteArrayList<>(elementsCollection); } diff --git a/src/main/java/com/google/common/collect/MapMakerInternalMap.java b/src/main/java/com/google/common/collect/MapMakerInternalMap.java index cf618cb..7f572eb 100644 --- a/src/main/java/com/google/common/collect/MapMakerInternalMap.java +++ b/src/main/java/com/google/common/collect/MapMakerInternalMap.java @@ -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< diff --git a/src/main/java/com/google/common/collect/Maps.java b/src/main/java/com/google/common/collect/Maps.java index 25f8633..d490368 100644 --- a/src/main/java/com/google/common/collect/Maps.java +++ b/src/main/java/com/google/common/collect/Maps.java @@ -172,37 +172,6 @@ public final class Maps { return ImmutableEnumMap.asImmutable(enumMap); } - private static class Accumulator, V> { - private final BinaryOperator mergeFunction; - private EnumMap map = null; - - Accumulator(BinaryOperator mergeFunction) { - this.mergeFunction = mergeFunction; - } - - void put(K key, V value) { - if (map == null) { - map = new EnumMap<>(key.getDeclaringClass()); - } - map.merge(key, value, mergeFunction); - } - - Accumulator combine(Accumulator other) { - if (this.map == null) { - return other; - } else if (other.map == null) { - return this; - } else { - other.map.forEach(this::put); - return this; - } - } - - ImmutableMap toImmutableMap() { - return (map == null) ? ImmutableMap.of() : ImmutableEnumMap.asImmutable(map); - } - } - /** * Returns a {@link Collector} that accumulates elements into an {@code ImmutableMap} whose keys * and values are the result of applying the provided mapping functions to the input elements. The @@ -220,22 +189,7 @@ public final class Maps { public static , V> Collector> toImmutableEnumMap( java.util.function.Function keyFunction, java.util.function.Function valueFunction) { - checkNotNull(keyFunction); - checkNotNull(valueFunction); - return Collector.of( - () -> - new Accumulator( - (v1, v2) -> { - throw new IllegalArgumentException("Multiple values for key: " + v1 + ", " + v2); - }), - (accum, t) -> { - K key = checkNotNull(keyFunction.apply(t), "Null key for input %s", t); - V newValue = checkNotNull(valueFunction.apply(t), "Null value for input %s", t); - accum.put(key, newValue); - }, - Accumulator::combine, - Accumulator::toImmutableMap, - Collector.Characteristics.UNORDERED); + return CollectCollectors.toImmutableEnumMap(keyFunction, valueFunction); } /** @@ -253,19 +207,7 @@ public final class Maps { java.util.function.Function keyFunction, java.util.function.Function valueFunction, BinaryOperator mergeFunction) { - checkNotNull(keyFunction); - checkNotNull(valueFunction); - checkNotNull(mergeFunction); - // not UNORDERED because we don't know if mergeFunction is commutative - return Collector.of( - () -> new Accumulator(mergeFunction), - (accum, t) -> { - K key = checkNotNull(keyFunction.apply(t), "Null key for input %s", t); - V newValue = checkNotNull(valueFunction.apply(t), "Null value for input %s", t); - accum.put(key, newValue); - }, - Accumulator::combine, - Accumulator::toImmutableMap); + return CollectCollectors.toImmutableEnumMap(keyFunction, valueFunction, mergeFunction); } /** diff --git a/src/main/java/com/google/common/collect/Multimaps.java b/src/main/java/com/google/common/collect/Multimaps.java index adceb8e..0e37bf3 100644 --- a/src/main/java/com/google/common/collect/Multimaps.java +++ b/src/main/java/com/google/common/collect/Multimaps.java @@ -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 > Collector toMultimap( java.util.function.Function keyFunction, java.util.function.Function valueFunction, java.util.function.Supplier multimapSupplier) { - checkNotNull(keyFunction); - checkNotNull(valueFunction); - checkNotNull(multimapSupplier); - return Collector.of( - multimapSupplier, - (multimap, input) -> multimap.put(keyFunction.apply(input), valueFunction.apply(input)), - (multimap1, multimap2) -> { - multimap1.putAll(multimap2); - return multimap1; - }); + return CollectCollectors.toMultimap(keyFunction, valueFunction, multimapSupplier); } /** @@ -695,6 +686,11 @@ public final class Multimaps { return result; } + @Override + public void forEach(BiConsumer consumer) { + delegate.forEach(checkNotNull(consumer)); + } + @Override public Collection get(K key) { return unmodifiableValueCollection(delegate.get(key)); diff --git a/src/main/java/com/google/common/collect/Multisets.java b/src/main/java/com/google/common/collect/Multisets.java index 0a91c79..57d97dd 100644 --- a/src/main/java/com/google/common/collect/Multisets.java +++ b/src/main/java/com/google/common/collect/Multisets.java @@ -77,16 +77,7 @@ public final class Multisets { java.util.function.Function elementFunction, java.util.function.ToIntFunction countFunction, java.util.function.Supplier multisetSupplier) { - checkNotNull(elementFunction); - checkNotNull(countFunction); - checkNotNull(multisetSupplier); - return Collector.of( - multisetSupplier, - (ms, t) -> ms.add(elementFunction.apply(t), countFunction.applyAsInt(t)), - (ms1, ms2) -> { - ms1.addAll(ms2); - return ms1; - }); + return CollectCollectors.toMultiset(elementFunction, countFunction, multisetSupplier); } /** diff --git a/src/main/java/com/google/common/collect/Ordering.java b/src/main/java/com/google/common/collect/Ordering.java index 869887e..85f2465 100644 --- a/src/main/java/com/google/common/collect/Ordering.java +++ b/src/main/java/com/google/common/collect/Ordering.java @@ -582,8 +582,8 @@ public abstract class Ordering implements Comparator { *

Implementation note: this method is invoked by the default implementations of the * other {@code min} overloads, so overriding it will affect their behavior. * - *

Java 8 users: Use {@code Collections.min(Arrays.asList(a, b), thisComparator)} - * instead (but note that it does not guarantee which tied minimum element is returned). + *

Note: 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. diff --git a/src/main/java/com/google/common/collect/Queues.java b/src/main/java/com/google/common/collect/Queues.java index 738e3fa..29c7db4 100644 --- a/src/main/java/com/google/common/collect/Queues.java +++ b/src/main/java/com/google/common/collect/Queues.java @@ -74,7 +74,7 @@ public final class Queues { */ public static ArrayDeque newArrayDeque(Iterable elements) { if (elements instanceof Collection) { - return new ArrayDeque(Collections2.cast(elements)); + return new ArrayDeque((Collection) elements); } ArrayDeque deque = new ArrayDeque(); Iterables.addAll(deque, elements); @@ -97,7 +97,7 @@ public final class Queues { public static ConcurrentLinkedQueue newConcurrentLinkedQueue( Iterable elements) { if (elements instanceof Collection) { - return new ConcurrentLinkedQueue(Collections2.cast(elements)); + return new ConcurrentLinkedQueue((Collection) elements); } ConcurrentLinkedQueue queue = new ConcurrentLinkedQueue(); Iterables.addAll(queue, elements); @@ -137,7 +137,7 @@ public final class Queues { @GwtIncompatible // LinkedBlockingDeque public static LinkedBlockingDeque newLinkedBlockingDeque(Iterable elements) { if (elements instanceof Collection) { - return new LinkedBlockingDeque(Collections2.cast(elements)); + return new LinkedBlockingDeque((Collection) elements); } LinkedBlockingDeque deque = new LinkedBlockingDeque(); Iterables.addAll(deque, elements); @@ -173,7 +173,7 @@ public final class Queues { @GwtIncompatible // LinkedBlockingQueue public static LinkedBlockingQueue newLinkedBlockingQueue(Iterable elements) { if (elements instanceof Collection) { - return new LinkedBlockingQueue(Collections2.cast(elements)); + return new LinkedBlockingQueue((Collection) elements); } LinkedBlockingQueue queue = new LinkedBlockingQueue(); Iterables.addAll(queue, elements); @@ -207,7 +207,7 @@ public final class Queues { public static PriorityBlockingQueue newPriorityBlockingQueue( Iterable elements) { if (elements instanceof Collection) { - return new PriorityBlockingQueue(Collections2.cast(elements)); + return new PriorityBlockingQueue((Collection) elements); } PriorityBlockingQueue queue = new PriorityBlockingQueue(); Iterables.addAll(queue, elements); @@ -237,7 +237,7 @@ public final class Queues { public static PriorityQueue newPriorityQueue( Iterable elements) { if (elements instanceof Collection) { - return new PriorityQueue(Collections2.cast(elements)); + return new PriorityQueue((Collection) elements); } PriorityQueue queue = new PriorityQueue(); 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). diff --git a/src/main/java/com/google/common/collect/Range.java b/src/main/java/com/google/common/collect/Range.java index 9c32f14..b2b2c02 100644 --- a/src/main/java/com/google/common/collect/Range.java +++ b/src/main/java/com/google/common/collect/Range.java @@ -576,6 +576,21 @@ public final class Range extends RangeGwtSerializationDepe * @since 27.0 */ public Range gap(Range 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 firstRange = isThisFirst ? this : otherRange; Range secondRange = isThisFirst ? otherRange : this; diff --git a/src/main/java/com/google/common/collect/RegularImmutableMap.java b/src/main/java/com/google/common/collect/RegularImmutableMap.java index 4639bfd..3e19ed1 100644 --- a/src/main/java/com/google/common/collect/RegularImmutableMap.java +++ b/src/main/java/com/google/common/collect/RegularImmutableMap.java @@ -207,10 +207,10 @@ final class RegularImmutableMap extends ImmutableMap { } @GwtCompatible(emulated = true) - private static final class KeySet extends IndexedImmutableSet { - private final RegularImmutableMap map; + private static final class KeySet extends IndexedImmutableSet { + private final RegularImmutableMap map; - KeySet(RegularImmutableMap map) { + KeySet(RegularImmutableMap map) { this.map = map; } @@ -234,12 +234,6 @@ final class RegularImmutableMap extends ImmutableMap { return map.size(); } - @GwtIncompatible // serialization - @Override - Object writeReplace() { - return new SerializedForm(map); - } - @GwtIncompatible // serialization private static class SerializedForm implements Serializable { final ImmutableMap map; @@ -284,12 +278,6 @@ final class RegularImmutableMap extends ImmutableMap { return true; } - @GwtIncompatible // serialization - @Override - Object writeReplace() { - return new SerializedForm(map); - } - @GwtIncompatible // serialization private static class SerializedForm implements Serializable { final ImmutableMap map; diff --git a/src/main/java/com/google/common/collect/Sets.java b/src/main/java/com/google/common/collect/Sets.java index a80e1a2..8e8237c 100644 --- a/src/main/java/com/google/common/collect/Sets.java +++ b/src/main/java/com/google/common/collect/Sets.java @@ -138,42 +138,6 @@ public final class Sets { } } - private static final class Accumulator> { - static final Collector, ?, ImmutableSet>> TO_IMMUTABLE_ENUM_SET = - (Collector) - Collector.>of( - Accumulator::new, - Accumulator::add, - Accumulator::combine, - Accumulator::toImmutableSet, - Collector.Characteristics.UNORDERED); - - private EnumSet set; - - void add(E e) { - if (set == null) { - set = EnumSet.of(e); - } else { - set.add(e); - } - } - - Accumulator combine(Accumulator other) { - if (this.set == null) { - return other; - } else if (other.set == null) { - return this; - } else { - this.set.addAll(other.set); - return this; - } - } - - ImmutableSet toImmutableSet() { - return (set == null) ? ImmutableSet.of() : ImmutableEnumSet.asImmutable(set); - } - } - /** * Returns a {@code Collector} that accumulates the input elements into a new {@code ImmutableSet} * with an implementation specialized for enums. Unlike {@link ImmutableSet#toImmutableSet}, the @@ -182,7 +146,7 @@ public final class Sets { * @since 21.0 */ public static > Collector> toImmutableEnumSet() { - return (Collector) Accumulator.TO_IMMUTABLE_ENUM_SET; + return CollectCollectors.toImmutableEnumSet(); } /** @@ -254,8 +218,8 @@ public final class Sets { */ public static HashSet newHashSet(Iterable elements) { return (elements instanceof Collection) - ? new HashSet(Collections2.cast(elements)) - : newHashSet(elements.iterator()); + ? new HashSet((Collection) elements) + : newHashSet(elements.iterator()); } /** @@ -359,7 +323,7 @@ public final class Sets { */ public static LinkedHashSet newLinkedHashSet(Iterable elements) { if (elements instanceof Collection) { - return new LinkedHashSet(Collections2.cast(elements)); + return new LinkedHashSet((Collection) elements); } LinkedHashSet set = newLinkedHashSet(); Iterables.addAll(set, elements); @@ -487,8 +451,8 @@ public final class Sets { // quadratic cost of adding them to the COWAS directly. Collection elementsCollection = (elements instanceof Collection) - ? Collections2.cast(elements) - : Lists.newArrayList(elements); + ? (Collection) elements + : Lists.newArrayList(elements); return new CopyOnWriteArraySet(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); } diff --git a/src/main/java/com/google/common/collect/SingletonImmutableBiMap.java b/src/main/java/com/google/common/collect/SingletonImmutableBiMap.java index d605ba8..cd7a987 100644 --- a/src/main/java/com/google/common/collect/SingletonImmutableBiMap.java +++ b/src/main/java/com/google/common/collect/SingletonImmutableBiMap.java @@ -42,6 +42,7 @@ final class SingletonImmutableBiMap extends ImmutableBiMap { checkEntryNotNull(singleKey, singleValue); this.singleKey = singleKey; this.singleValue = singleValue; + this.inverse = null; } private SingletonImmutableBiMap(K singleKey, V singleValue, ImmutableBiMap inverse) { @@ -92,14 +93,21 @@ final class SingletonImmutableBiMap extends ImmutableBiMap { transient ImmutableBiMap inverse; + private transient ImmutableBiMap lazyInverse; + @Override public ImmutableBiMap inverse() { // racy single-check idiom - ImmutableBiMap 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 result = lazyInverse; + if (result == null) { + return lazyInverse = new SingletonImmutableBiMap<>(singleValue, singleKey, this); + } else { + return result; + } } } } diff --git a/src/main/java/com/google/common/collect/TableCollectors.java b/src/main/java/com/google/common/collect/TableCollectors.java new file mode 100644 index 0000000..f446d02 --- /dev/null +++ b/src/main/java/com/google/common/collect/TableCollectors.java @@ -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 Collector> toImmutableTable( + Function rowFunction, + Function columnFunction, + Function valueFunction) { + checkNotNull(rowFunction, "rowFunction"); + checkNotNull(columnFunction, "columnFunction"); + checkNotNull(valueFunction, "valueFunction"); + return Collector.of( + (Supplier>) ImmutableTable.Builder::new, + (builder, t) -> + builder.put(rowFunction.apply(t), columnFunction.apply(t), valueFunction.apply(t)), + ImmutableTable.Builder::combine, + ImmutableTable.Builder::build); + } + + static Collector> toImmutableTable( + Function rowFunction, + Function columnFunction, + Function valueFunction, + BinaryOperator mergeFunction) { + + checkNotNull(rowFunction, "rowFunction"); + checkNotNull(columnFunction, "columnFunction"); + checkNotNull(valueFunction, "valueFunction"); + checkNotNull(mergeFunction, "mergeFunction"); + + /* + * No mutable Table exactly matches the insertion order behavior of ImmutableTable.Builder, but + * the Builder can't efficiently support merging of duplicate values. Getting around this + * requires some work. + */ + + return Collector.of( + () -> new ImmutableTableCollectorState() + /* 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 > Collector toTable( + Function rowFunction, + Function columnFunction, + Function valueFunction, + Supplier tableSupplier) { + return toTable( + rowFunction, + columnFunction, + valueFunction, + (v1, v2) -> { + throw new IllegalStateException("Conflicting values " + v1 + " and " + v2); + }, + tableSupplier); + } + + static > Collector toTable( + Function rowFunction, + Function columnFunction, + Function valueFunction, + BinaryOperator mergeFunction, + Supplier 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 cell2 : table2.cellSet()) { + mergeTables( + table1, cell2.getRowKey(), cell2.getColumnKey(), cell2.getValue(), mergeFunction); + } + return table1; + }); + } + + private static final class ImmutableTableCollectorState { + final List> insertionOrder = new ArrayList<>(); + final Table> table = HashBasedTable.create(); + + void put(R row, C column, V value, BinaryOperator merger) { + MutableCell oldCell = table.get(row, column); + if (oldCell == null) { + MutableCell cell = new MutableCell<>(row, column, value); + insertionOrder.add(cell); + table.put(row, column, cell); + } else { + oldCell.merge(value, merger); + } + } + + ImmutableTableCollectorState combine( + ImmutableTableCollectorState other, BinaryOperator merger) { + for (MutableCell cell : other.insertionOrder) { + put(cell.getRowKey(), cell.getColumnKey(), cell.getValue(), merger); + } + return this; + } + + ImmutableTable toTable() { + return ImmutableTable.copyOf(insertionOrder); + } + } + + private static final class MutableCell extends AbstractCell { + private final R row; + private final C column; + private V value; + + MutableCell(R row, C column, V value) { + this.row = checkNotNull(row, "row"); + this.column = checkNotNull(column, "column"); + this.value = checkNotNull(value, "value"); + } + + @Override + public R getRowKey() { + return row; + } + + @Override + public C getColumnKey() { + return column; + } + + @Override + public V getValue() { + return value; + } + + void merge(V value, BinaryOperator mergeFunction) { + checkNotNull(value, "value"); + this.value = checkNotNull(mergeFunction.apply(this.value, value), "mergeFunction.apply"); + } + } + + private static void mergeTables( + Table table, R row, C column, V value, BinaryOperator mergeFunction) { + checkNotNull(value); + V oldValue = table.get(row, column); + if (oldValue == null) { + table.put(row, column, value); + } else { + V newValue = mergeFunction.apply(oldValue, value); + if (newValue == null) { + table.remove(row, column); + } else { + table.put(row, column, newValue); + } + } + } + + private TableCollectors() {} +} diff --git a/src/main/java/com/google/common/collect/Tables.java b/src/main/java/com/google/common/collect/Tables.java index 0cc25ee..b59f75a 100644 --- a/src/main/java/com/google/common/collect/Tables.java +++ b/src/main/java/com/google/common/collect/Tables.java @@ -68,14 +68,7 @@ public final class Tables { java.util.function.Function columnFunction, java.util.function.Function valueFunction, java.util.function.Supplier tableSupplier) { - return toTable( - rowFunction, - columnFunction, - valueFunction, - (v1, v2) -> { - throw new IllegalStateException("Conflicting values " + v1 + " and " + v2); - }, - tableSupplier); + return TableCollectors.toTable(rowFunction, columnFunction, valueFunction, tableSupplier); } /** @@ -98,26 +91,7 @@ public final class Tables { java.util.function.Function valueFunction, BinaryOperator mergeFunction, java.util.function.Supplier tableSupplier) { - checkNotNull(rowFunction); - checkNotNull(columnFunction); - checkNotNull(valueFunction); - checkNotNull(mergeFunction); - checkNotNull(tableSupplier); - return Collector.of( - tableSupplier, - (table, input) -> - merge( - table, - rowFunction.apply(input), - columnFunction.apply(input), - valueFunction.apply(input), - mergeFunction), - (table1, table2) -> { - for (Table.Cell cell2 : table2.cellSet()) { - merge(table1, cell2.getRowKey(), cell2.getColumnKey(), cell2.getValue(), mergeFunction); - } - return table1; - }); + return TableCollectors.toTable(rowFunction, columnFunction, valueFunction, mergeFunction, tableSupplier); } private static void merge( diff --git a/src/main/java/com/google/common/collect/TreeRangeMap.java b/src/main/java/com/google/common/collect/TreeRangeMap.java index a4a7745..c8458e1 100644 --- a/src/main/java/com/google/common/collect/TreeRangeMap.java +++ b/src/main/java/com/google/common/collect/TreeRangeMap.java @@ -119,7 +119,6 @@ public final class TreeRangeMap implements RangeMap 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 implements RangeMap 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 implements RangeMap range, V value) { - if (entriesByLowerBound.isEmpty() || range.isEmpty() || !subRange.encloses(range)) { + if (entriesByLowerBound.isEmpty() || !subRange.encloses(range)) { put(range, value); return; } diff --git a/src/main/java/com/google/common/collect/TreeRangeSet.java b/src/main/java/com/google/common/collect/TreeRangeSet.java index a0afe2a..753547c 100644 --- a/src/main/java/com/google/common/collect/TreeRangeSet.java +++ b/src/main/java/com/google/common/collect/TreeRangeSet.java @@ -892,7 +892,7 @@ public class TreeRangeSet> extends AbstractRangeSet "Cannot add range %s to subRangeSet(%s)", rangeToAdd, restriction); - super.add(rangeToAdd); + TreeRangeSet.this.add(rangeToAdd); } @Override diff --git a/src/main/java/com/google/common/eventbus/Subscribe.java b/src/main/java/com/google/common/eventbus/Subscribe.java index 37337e6..52b999d 100644 --- a/src/main/java/com/google/common/eventbus/Subscribe.java +++ b/src/main/java/com/google/common/eventbus/Subscribe.java @@ -23,9 +23,10 @@ import java.lang.annotation.Target; /** * Marks a method as an event subscriber. * - *

The type of event will be indicated by the method's first (and only) parameter. If this - * annotation is applied to methods with zero parameters, or more than one parameter, the object - * containing the method will not be able to register for event delivery from the {@link EventBus}. + *

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}. * *

Unless also annotated with @{@link AllowConcurrentEvents}, event subscriber methods will be * invoked serially by each event bus that they are registered with. diff --git a/src/main/java/com/google/common/eventbus/SubscriberRegistry.java b/src/main/java/com/google/common/eventbus/SubscriberRegistry.java index 575529e..15e13d8 100644 --- a/src/main/java/com/google/common/eventbus/SubscriberRegistry.java +++ b/src/main/java/com/google/common/eventbus/SubscriberRegistry.java @@ -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 getAnnotatedMethods(Class clazz) { - return subscriberMethodsCache.getUnchecked(clazz); + try { + return subscriberMethodsCache.getUnchecked(clazz); + } catch (UncheckedExecutionException e) { + throwIfUnchecked(e.getCause()); + throw e; + } } private static ImmutableList 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); diff --git a/src/main/java/com/google/common/graph/Traverser.java b/src/main/java/com/google/common/graph/Traverser.java index 4a341fd..1c3cb2d 100644 --- a/src/main/java/com/google/common/graph/Traverser.java +++ b/src/main/java/com/google/common/graph/Traverser.java @@ -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 { + private final SuccessorsFunction successorFunction; + + private Traverser(SuccessorsFunction successorFunction) { + this.successorFunction = checkNotNull(successorFunction); + } /** * Creates a new traverser for the given general {@code graph}. @@ -88,10 +90,14 @@ public abstract class Traverser { * * @param graph {@link SuccessorsFunction} representing a general graph that may have cycles. */ - public static Traverser forGraph(SuccessorsFunction graph) { - checkNotNull(graph); - return new GraphTraverser<>(graph); - } + public static Traverser forGraph(final SuccessorsFunction graph) { + return new Traverser(graph) { + @Override + Traversal 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 { if (tree instanceof Network) { checkArgument(((Network) tree).isDirected(), "Undirected networks can never be trees."); } - return new TreeTraverser<>(tree); + return new Traverser(tree) { + @Override + Traversal newTraversal() { + return Traversal.inTree(tree); + } + }; } /** @@ -208,7 +219,9 @@ public abstract class Traverser { * * @throws IllegalArgumentException if {@code startNode} is not an element of the graph */ - public abstract Iterable breadthFirst(N startNode); + public final Iterable 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 { * @see #breadthFirst(Object) * @since 24.1 */ - public abstract Iterable breadthFirst(Iterable startNodes); + public final Iterable breadthFirst(Iterable startNodes) { + final ImmutableSet validated = validate(startNodes); + return new Iterable() { + @Override + public Iterator 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 { * * @throws IllegalArgumentException if {@code startNode} is not an element of the graph */ - public abstract Iterable depthFirstPreOrder(N startNode); + public final Iterable 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 { * @see #depthFirstPreOrder(Object) * @since 24.1 */ - public abstract Iterable depthFirstPreOrder(Iterable startNodes); + public final Iterable depthFirstPreOrder(Iterable startNodes) { + final ImmutableSet validated = validate(startNodes); + return new Iterable() { + @Override + public Iterator 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 { * * @throws IllegalArgumentException if {@code startNode} is not an element of the graph */ - public abstract Iterable depthFirstPostOrder(N startNode); + public final Iterable 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 { * @see #depthFirstPostOrder(Object) * @since 24.1 */ - public abstract Iterable depthFirstPostOrder(Iterable startNodes); - - // Avoid subclasses outside of this class - private Traverser() {} - - private static final class GraphTraverser extends Traverser { - private final SuccessorsFunction graph; - - GraphTraverser(SuccessorsFunction graph) { - this.graph = checkNotNull(graph); - } - - @Override - public Iterable breadthFirst(final N startNode) { - checkNotNull(startNode); - return breadthFirst(ImmutableSet.of(startNode)); - } - - @Override - public Iterable breadthFirst(final Iterable startNodes) { - checkNotNull(startNodes); - if (Iterables.isEmpty(startNodes)) { - return ImmutableSet.of(); - } - for (N startNode : startNodes) { - checkThatNodeIsInGraph(startNode); - } - return new Iterable() { - @Override - public Iterator iterator() { - return new BreadthFirstIterator(startNodes); - } - }; - } - - @Override - public Iterable depthFirstPreOrder(final N startNode) { - checkNotNull(startNode); - return depthFirstPreOrder(ImmutableSet.of(startNode)); - } - - @Override - public Iterable depthFirstPreOrder(final Iterable startNodes) { - checkNotNull(startNodes); - if (Iterables.isEmpty(startNodes)) { - return ImmutableSet.of(); - } - for (N startNode : startNodes) { - checkThatNodeIsInGraph(startNode); - } - return new Iterable() { - @Override - public Iterator iterator() { - return new DepthFirstIterator(startNodes, Order.PREORDER); - } - }; - } - - @Override - public Iterable depthFirstPostOrder(final N startNode) { - checkNotNull(startNode); - return depthFirstPostOrder(ImmutableSet.of(startNode)); - } - - @Override - public Iterable depthFirstPostOrder(final Iterable startNodes) { - checkNotNull(startNodes); - if (Iterables.isEmpty(startNodes)) { - return ImmutableSet.of(); - } - for (N startNode : startNodes) { - checkThatNodeIsInGraph(startNode); - } - return new Iterable() { - @Override - public Iterator iterator() { - return new DepthFirstIterator(startNodes, Order.POSTORDER); - } - }; - } - - @SuppressWarnings("CheckReturnValue") - private void checkThatNodeIsInGraph(N startNode) { - // successors() throws an IllegalArgumentException for nodes that are not an element of the - // graph. - graph.successors(startNode); - } - - private final class BreadthFirstIterator extends UnmodifiableIterator { - private final Queue queue = new ArrayDeque<>(); - private final Set visited = new HashSet<>(); - - BreadthFirstIterator(Iterable roots) { - for (N root : roots) { - // add all roots to the queue, skipping duplicates - if (visited.add(root)) { - queue.add(root); - } - } - } - + public final Iterable depthFirstPostOrder(Iterable startNodes) { + final ImmutableSet validated = validate(startNodes); + return new Iterable() { @Override - public boolean hasNext() { - return !queue.isEmpty(); + public Iterator 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 { - private final Deque stack = new ArrayDeque<>(); - private final Set visited = new HashSet<>(); - private final Order order; - - DepthFirstIterator(Iterable roots, Order order) { - stack.push(new NodeAndSuccessors(null, roots)); - this.order = order; - } - - @Override - protected N computeNext() { - while (true) { - if (stack.isEmpty()) { - return endOfData(); - } - NodeAndSuccessors nodeAndSuccessors = stack.getFirst(); - boolean firstVisit = visited.add(nodeAndSuccessors.node); - boolean lastVisit = !nodeAndSuccessors.successorIterator.hasNext(); - boolean produceNode = - (firstVisit && order == Order.PREORDER) || (lastVisit && order == Order.POSTORDER); - if (lastVisit) { - stack.pop(); - } else { - // we need to push a neighbor, but only if we haven't already seen it - N successor = nodeAndSuccessors.successorIterator.next(); - if (!visited.contains(successor)) { - stack.push(withSuccessors(successor)); - } - } - if (produceNode && nodeAndSuccessors.node != null) { - return nodeAndSuccessors.node; - } - } - } - - NodeAndSuccessors withSuccessors(N node) { - return new NodeAndSuccessors(node, graph.successors(node)); - } - - /** A simple tuple of a node and a partially iterated {@link Iterator} of its successors. */ - private final class NodeAndSuccessors { - final N node; - final Iterator successorIterator; - - NodeAndSuccessors(N node, Iterable successors) { - this.node = node; - this.successorIterator = successors.iterator(); - } - } - } + }; } - private static final class TreeTraverser extends Traverser { - private final SuccessorsFunction tree; + abstract Traversal newTraversal(); - TreeTraverser(SuccessorsFunction tree) { - this.tree = checkNotNull(tree); - } - - @Override - public Iterable breadthFirst(final N startNode) { - checkNotNull(startNode); - return breadthFirst(ImmutableSet.of(startNode)); - } - - @Override - public Iterable breadthFirst(final Iterable startNodes) { - checkNotNull(startNodes); - if (Iterables.isEmpty(startNodes)) { - return ImmutableSet.of(); - } - for (N startNode : startNodes) { - checkThatNodeIsInTree(startNode); - } - return new Iterable() { - @Override - public Iterator iterator() { - return new BreadthFirstIterator(startNodes); - } - }; - } - - @Override - public Iterable depthFirstPreOrder(final N startNode) { - checkNotNull(startNode); - return depthFirstPreOrder(ImmutableSet.of(startNode)); - } - - @Override - public Iterable depthFirstPreOrder(final Iterable startNodes) { - checkNotNull(startNodes); - if (Iterables.isEmpty(startNodes)) { - return ImmutableSet.of(); - } - for (N node : startNodes) { - checkThatNodeIsInTree(node); - } - return new Iterable() { - @Override - public Iterator iterator() { - return new DepthFirstPreOrderIterator(startNodes); - } - }; - } - - @Override - public Iterable depthFirstPostOrder(final N startNode) { - checkNotNull(startNode); - return depthFirstPostOrder(ImmutableSet.of(startNode)); - } - - @Override - public Iterable depthFirstPostOrder(final Iterable startNodes) { - checkNotNull(startNodes); - if (Iterables.isEmpty(startNodes)) { - return ImmutableSet.of(); - } - for (N startNode : startNodes) { - checkThatNodeIsInTree(startNode); - } - return new Iterable() { - @Override - public Iterator iterator() { - return new DepthFirstPostOrderIterator(startNodes); - } - }; - } - - @SuppressWarnings("CheckReturnValue") - private void checkThatNodeIsInTree(N startNode) { - // successors() throws an IllegalArgumentException for nodes that are not an element of the - // graph. - tree.successors(startNode); - } - - private final class BreadthFirstIterator extends UnmodifiableIterator { - private final Queue queue = new ArrayDeque<>(); - - BreadthFirstIterator(Iterable roots) { - for (N root : roots) { - queue.add(root); - } - } - - @Override - public boolean hasNext() { - return !queue.isEmpty(); - } - - @Override - public N next() { - N current = queue.remove(); - Iterables.addAll(queue, tree.successors(current)); - return current; - } - } - - private final class DepthFirstPreOrderIterator extends UnmodifiableIterator { - private final Deque> stack = new ArrayDeque<>(); - - DepthFirstPreOrderIterator(Iterable roots) { - stack.addLast(roots.iterator()); - } - - @Override - public boolean hasNext() { - return !stack.isEmpty(); - } - - @Override - public N next() { - Iterator iterator = stack.getLast(); // throws NoSuchElementException if empty - N result = checkNotNull(iterator.next()); - if (!iterator.hasNext()) { - stack.removeLast(); - } - Iterator childIterator = tree.successors(result).iterator(); - if (childIterator.hasNext()) { - stack.addLast(childIterator); - } - return result; - } - } - - private final class DepthFirstPostOrderIterator extends AbstractIterator { - private final ArrayDeque stack = new ArrayDeque<>(); - - DepthFirstPostOrderIterator(Iterable roots) { - stack.addLast(new NodeAndChildren(null, roots)); - } - - @Override - protected N computeNext() { - while (!stack.isEmpty()) { - NodeAndChildren top = stack.getLast(); - if (top.childIterator.hasNext()) { - N child = top.childIterator.next(); - stack.addLast(withChildren(child)); - } else { - stack.removeLast(); - if (top.node != null) { - return top.node; - } - } - } - return endOfData(); - } - - NodeAndChildren withChildren(N node) { - return new NodeAndChildren(node, tree.successors(node)); - } - - /** A simple tuple of a node and a partially iterated {@link Iterator} of its children. */ - private final class NodeAndChildren { - final N node; - final Iterator childIterator; - - NodeAndChildren(N node, Iterable children) { - this.node = node; - this.childIterator = children.iterator(); - } - } + @SuppressWarnings("CheckReturnValue") + private ImmutableSet validate(Iterable startNodes) { + ImmutableSet 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 { + final SuccessorsFunction successorFunction; + + Traversal(SuccessorsFunction successorFunction) { + this.successorFunction = successorFunction; + } + + static Traversal inGraph(SuccessorsFunction graph) { + final Set visited = new HashSet<>(); + return new Traversal(graph) { + @Override + N visitNext(Deque> horizon) { + Iterator top = horizon.getFirst(); + while (top.hasNext()) { + N element = checkNotNull(top.next()); + if (visited.add(element)) { + return element; + } + } + horizon.removeFirst(); + return null; + } + }; + } + + static Traversal inTree(SuccessorsFunction tree) { + return new Traversal(tree) { + @Override + N visitNext(Deque> horizon) { + Iterator top = horizon.getFirst(); + if (top.hasNext()) { + return checkNotNull(top.next()); + } + horizon.removeFirst(); + return null; + } + }; + } + + final Iterator breadthFirst(Iterator startNodes) { + return topDown(startNodes, InsertionOrder.BACK); + } + + final Iterator preOrder(Iterator 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 topDown(Iterator startNodes, final InsertionOrder order) { + final Deque> horizon = new ArrayDeque<>(); + horizon.add(startNodes); + return new AbstractIterator() { + @Override + protected N computeNext() { + do { + N next = visitNext(horizon); + if (next != null) { + Iterator 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 postOrder(Iterator startNodes) { + final Deque ancestorStack = new ArrayDeque<>(); + final Deque> horizon = new ArrayDeque<>(); + horizon.add(startNodes); + return new AbstractIterator() { + @Override + protected N computeNext() { + for (N next = visitNext(horizon); next != null; next = visitNext(horizon)) { + Iterator 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. + * + *

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> horizon); + } + + /** Poor man's method reference for {@code Deque::addFirst} and {@code Deque::addLast}. */ + private enum InsertionOrder { + FRONT { + @Override + void insertInto(Deque deque, T value) { + deque.addFirst(value); + } + }, + BACK { + @Override + void insertInto(Deque deque, T value) { + deque.addLast(value); + } + }; + + abstract void insertInto(Deque deque, T value); } } diff --git a/src/main/java/com/google/common/hash/Crc32cHashFunction.java b/src/main/java/com/google/common/hash/Crc32cHashFunction.java index 6e9c2df..f25922a 100644 --- a/src/main/java/com/google/common/hash/Crc32cHashFunction.java +++ b/src/main/java/com/google/common/hash/Crc32cHashFunction.java @@ -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); } } } diff --git a/src/main/java/com/google/common/io/ByteStreams.java b/src/main/java/com/google/common/io/ByteStreams.java index bd9a05d..6c69ad2 100644 --- a/src/main/java/com/google/common/io/ByteStreams.java +++ b/src/main/java/com/google/common/io/ByteStreams.java @@ -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 bufs, int totalLen) + private static byte[] toByteArrayInternal(InputStream in, Queue 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 bufs, int totalLen) { + private static byte[] combineBuffers(Queue 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 bufs = new ArrayDeque(TO_BYTE_ARRAY_DEQUE_SIZE + 2); + Queue bufs = new ArrayDeque(TO_BYTE_ARRAY_DEQUE_SIZE + 2); bufs.add(bytes); bufs.add(new byte[] {(byte) b}); return toByteArrayInternal(in, bufs, bytes.length + 1); diff --git a/src/main/java/com/google/common/io/Files.java b/src/main/java/com/google/common/io/Files.java index f0d6df1..7811820 100644 --- a/src/main/java/com/google/common/io/Files.java +++ b/src/main/java/com/google/common/io/Files.java @@ -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. * + *

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. + * *

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. - * - *

Warning: {@code File} provides no support for symbolic links, and as such there is no - * way to ensure that a symbolic link to a directory is not followed when traversing the tree. In - * this case, iterables created by this traverser could contain files that are outside of the - * given directory or even be infinite if there is a symbolic link loop. - * - * @since 15.0 - * @deprecated The returned {@link TreeTraverser} type is deprecated. Use the replacement method - * {@link #fileTraverser()} instead with the same semantics as this method. - */ - @Deprecated - static TreeTraverser fileTreeTraverser() { - return FILE_TREE_TRAVERSER; - } - - private static final TreeTraverser FILE_TREE_TRAVERSER = - new TreeTraverser() { - @Override - public Iterable children(File file) { - return fileTreeChildren(file); - } - - @Override - public String toString() { - return "Files.fileTreeTraverser()"; - } - }; - /** * Returns a {@link Traverser} instance for the file and directory tree. The returned traverser * starts from a {@link File} and will return all files and directories it encounters. @@ -870,12 +846,16 @@ public final class Files { } private static final SuccessorsFunction FILE_TREE = - new SuccessorsFunction() { - @Override - public Iterable 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 fileTreeChildren(File file) { // check isDirectory() just because it may be faster than listFiles() on a non-directory diff --git a/src/main/java/com/google/common/math/BigDecimalMath.java b/src/main/java/com/google/common/math/BigDecimalMath.java new file mode 100644 index 0000000..b5c23f8 --- /dev/null +++ b/src/main/java/com/google/common/math/BigDecimalMath.java @@ -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}. + * + *

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}. + * + *

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 { + 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); + } + } +} diff --git a/src/main/java/com/google/common/math/BigIntegerMath.java b/src/main/java/com/google/common/math/BigIntegerMath.java index b0c0766..2b293a0 100644 --- a/src/main/java/com/google/common/math/BigIntegerMath.java +++ b/src/main/java/com/google/common/math/BigIntegerMath.java @@ -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}. + * + *

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}. + * + *

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 { + 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; diff --git a/src/main/java/com/google/common/math/LongMath.java b/src/main/java/com/google/common/math/LongMath.java index b16db71..8b3115f 100644 --- a/src/main/java/com/google/common/math/LongMath.java +++ b/src/main/java/com/google/common/math/LongMath.java @@ -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}. + * + *

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() {} } diff --git a/src/main/java/com/google/common/math/Stats.java b/src/main/java/com/google/common/math/Stats.java index f0678bc..690a700 100644 --- a/src/main/java/com/google/common/math/Stats.java +++ b/src/main/java/com/google/common/math/Stats.java @@ -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); } /** diff --git a/src/main/java/com/google/common/math/ToDoubleRounder.java b/src/main/java/com/google/common/math/ToDoubleRounder.java new file mode 100644 index 0000000..7525e3f --- /dev/null +++ b/src/main/java/com/google/common/math/ToDoubleRounder.java @@ -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> { + /** + * 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"); + } +} diff --git a/src/main/java/com/google/common/net/HttpHeaders.java b/src/main/java/com/google/common/net/HttpHeaders.java index b9fe1ec..56f9b84 100644 --- a/src/main/java/com/google/common/net/HttpHeaders.java +++ b/src/main/java/com/google/common/net/HttpHeaders.java @@ -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 {@code Origin-Isolation} 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 {@code + * Cross-Origin-Embedder-Policy} header field name. + * + * @since 30.0 + */ + public static final String CROSS_ORIGIN_EMBEDDER_POLICY = "Cross-Origin-Embedder-Policy"; + /** + * The HTTP {@code + * Cross-Origin-Embedder-Policy-Report-Only} 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 {@code * Public-Key-Pins-Report-Only} header field name. @@ -459,6 +487,56 @@ public final class HttpHeaders { */ public static final String X_MOZ = "X-Moz"; + /** + * The HTTP {@code Sec-CH-UA} + * header field name. + * + * @since 30.0 + */ + public static final String SEC_CH_UA = "Sec-CH-UA"; + /** + * The HTTP {@code + * Sec-CH-UA-Arch} header field name. + * + * @since 30.0 + */ + public static final String SEC_CH_UA_ARCH = "Sec-CH-UA-Arch"; + /** + * The HTTP {@code + * Sec-CH-UA-Model} header field name. + * + * @since 30.0 + */ + public static final String SEC_CH_UA_MODEL = "Sec-CH-UA-Model"; + /** + * The HTTP {@code + * Sec-CH-UA-Platform} header field name. + * + * @since 30.0 + */ + public static final String SEC_CH_UA_PLATFORM = "Sec-CH-UA-Platform"; + /** + * The HTTP {@code + * Sec-CH-UA-Platform-Version} header field name. + * + * @since 30.0 + */ + public static final String SEC_CH_UA_PLATFORM_VERSION = "Sec-CH-UA-Platform-Version"; + /** + * The HTTP {@code + * Sec-CH-UA-Full-Version} header field name. + * + * @since 30.0 + */ + public static final String SEC_CH_UA_FULL_VERSION = "Sec-CH-UA-Full-Version"; + /** + * The HTTP {@code + * Sec-CH-UA-Mobile} header field name. + * + * @since 30.0 + */ + public static final String SEC_CH_UA_MOBILE = "Sec-CH-UA-Mobile"; + /** * The HTTP {@code Sec-Fetch-Dest} * header field name. diff --git a/src/main/java/com/google/common/net/InetAddresses.java b/src/main/java/com/google/common/net/InetAddresses.java index 8b91975..2cb03dd 100644 --- a/src/main/java/com/google/common/net/InetAddresses.java +++ b/src/main/java/com/google/common/net/InetAddresses.java @@ -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 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; } diff --git a/src/main/java/com/google/common/net/MediaType.java b/src/main/java/com/google/common/net/MediaType.java index 1ae84f8..43da2ac 100644 --- a/src/main/java/com/google/common/net/MediaType.java +++ b/src/main/java/com/google/common/net/MediaType.java @@ -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 RFC + * 8081. + * + * @since 30.0 + */ + public static final MediaType FONT_COLLECTION = createConstant(FONT_TYPE, "collection"); + + /** + * Open Type Font Format (OTF) as defined by + * RFC 8081. + * + * @since 30.0 + */ + public static final MediaType FONT_OTF = createConstant(FONT_TYPE, "otf"); + + /** + * Spline or Scalable Font Format (SFNT). RFC 8081 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"); + + /** + * True Type Font Format (TTF) as defined by + * RFC 8081. + * + * @since 30.0 + */ + public static final MediaType FONT_TTF = createConstant(FONT_TYPE, "ttf"); + + /** + * Web Open Font Format (WOFF). RFC 8081 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"); + + /** + * Web Open Font Format (WOFF2). + * RFC 8081 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 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. * diff --git a/src/main/java/com/google/common/primitives/Doubles.java b/src/main/java/com/google/common/primitives/Doubles.java index 7637966..5593371 100644 --- a/src/main/java/com/google/common/primitives/Doubles.java +++ b/src/main/java/com/google/common/primitives/Doubles.java @@ -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)); } /** diff --git a/src/main/java/com/google/common/primitives/Floats.java b/src/main/java/com/google/common/primitives/Floats.java index d5e136e..85b8b94 100644 --- a/src/main/java/com/google/common/primitives/Floats.java +++ b/src/main/java/com/google/common/primitives/Floats.java @@ -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)); } /** diff --git a/src/main/java/com/google/common/reflect/ClassPath.java b/src/main/java/com/google/common/reflect/ClassPath.java index 0f51a91..18a3232 100644 --- a/src/main/java/com/google/common/reflect/ClassPath.java +++ b/src/main/java/com/google/common/reflect/ClassPath.java @@ -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 IS_TOP_LEVEL = - new Predicate() { - @Override - public boolean apply(ClassInfo info) { - return info.className.indexOf('$') == -1; - } - }; - /** Separator for the Class-Path manifest attribute value in jar files. */ private static final Splitter CLASS_PATH_ATTRIBUTE_SEPARATOR = Splitter.on(" ").omitEmptyStrings(); @@ -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 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 scanned = new HashSet<>(); + for (LocationInfo location : locations) { + scanned.add(location.file()); + } + + // Scan all locations + ImmutableSet.Builder 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 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 getTopLevelClasses() { + return FluentIterable.from(resources) + .filter(ClassInfo.class) + .filter( + new Predicate() { + @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 locationsFrom(ClassLoader classloader) { + ImmutableSet.Builder builder = ImmutableSet.builder(); + for (Map.Entry 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 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 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 scanResources() throws IOException { + return scanResources(new HashSet()); + } - /** 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. + * + *

This file and jar files from "Class-Path" entry in the scanned manifest files will be + * added to {@code scannedFiles}. + * + *

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. + * + *

Note that when you call {@code location.scanResources(scannedFiles)}, the location will + * always be scanned even if {@code scannedFiles} already contains it. + */ + public ImmutableSet scanResources(Set scannedFiles) throws IOException { + ImmutableSet.Builder 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 scannedUris, ImmutableSet.Builder 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 scannedUris, ImmutableSet.Builder 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 JAR - * File Specification. If {@code manifest} is null, it means the jar file has no manifest, - * and an empty set will be returned. - */ - @VisibleForTesting - static ImmutableSet getClassPathFromManifest(File jarFile, Manifest manifest) { - if (manifest == null) { - return ImmutableSet.of(); - } - ImmutableSet.Builder builder = ImmutableSet.builder(); - String classpathAttribute = - manifest.getMainAttributes().getValue(Attributes.Name.CLASS_PATH.toString()); - if (classpathAttribute != null) { - for (String path : CLASS_PATH_ATTRIBUTE_SEPARATOR.split(classpathAttribute)) { - URL url; - try { - url = getClassPathEntry(jarFile, path); - } catch (MalformedURLException e) { - // Ignore bad entry - logger.warning("Invalid Class-Path entry: " + path); - continue; - } - if (url.getProtocol().equals("file")) { - builder.add(toFile(url)); - } - } - } - return builder.build(); - } - - @VisibleForTesting - static ImmutableMap getClassPathEntries(ClassLoader classloader) { - LinkedHashMap entries = Maps.newLinkedHashMap(); - // Search parent first, since it's the order ClassLoader#loadClass() uses. - ClassLoader parent = classloader.getParent(); - if (parent != null) { - entries.putAll(getClassPathEntries(parent)); - } - for (URL url : getClassLoaderUrls(classloader)) { - if (url.getProtocol().equals("file")) { - File file = toFile(url); - if (!entries.containsKey(file)) { - entries.put(file, classloader); - } - } - } - return ImmutableMap.copyOf(entries); - } - - private static ImmutableList getClassLoaderUrls(ClassLoader classloader) { - if (classloader instanceof URLClassLoader) { - return ImmutableList.copyOf(((URLClassLoader) classloader).getURLs()); - } - if (classloader.equals(ClassLoader.getSystemClassLoader())) { - return parseJavaClassPath(); - } - return ImmutableList.of(); - } - - /** - * Returns the URLs in the class path specified by the {@code java.class.path} {@linkplain - * System#getProperty system property}. - */ - @VisibleForTesting // TODO(b/65488446): Make this a public API. - static ImmutableList parseJavaClassPath() { - ImmutableList.Builder urls = ImmutableList.builder(); - for (String entry : Splitter.on(PATH_SEPARATOR.value()).split(JAVA_CLASS_PATH.value())) { - try { - try { - urls.add(new File(entry).toURI().toURL()); - } catch (SecurityException e) { // File.toURI checks to see if the file is a directory - urls.add(new URL("file", null, new File(entry).getAbsolutePath())); - } - } catch (MalformedURLException e) { - logger.log(WARNING, "malformed classpath entry: " + entry, e); - } - } - return urls.build(); - } - - /** - * Returns the absolute uri of the Class-Path entry value as specified in JAR - * File Specification. Even though the specification only talks about relative urls, - * absolute urls are actually supported too (for example, in Maven surefire plugin). - */ - @VisibleForTesting - static URL getClassPathEntry(File jarFile, String path) throws MalformedURLException { - return new URL(jarFile.toURI().toURL(), path); - } - } - - @VisibleForTesting - static final class DefaultScanner extends Scanner { - private final SetMultimap resources = - MultimapBuilder.hashKeys().linkedHashSetValues().build(); - - ImmutableSet getResources() { - ImmutableSet.Builder builder = ImmutableSet.builder(); - for (Entry entry : resources.entries()) { - builder.add(ResourceInfo.of(entry.getValue(), entry.getKey())); - } - return builder.build(); - } - - @Override - protected void scanJarFile(ClassLoader classloader, JarFile file) { + private void scanJarFile(JarFile file, ImmutableSet.Builder builder) { Enumeration entries = file.entries(); while (entries.hasMoreElements()) { JarEntry entry = entries.nextElement(); if (entry.isDirectory() || entry.getName().equals(JarFile.MANIFEST_NAME)) { continue; } - resources.get(classloader).add(entry.getName()); + 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 builder) + throws IOException { Set 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 currentPath) - throws IOException { + File directory, + String packagePrefix, + Set currentPath, + ImmutableSet.Builder 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 JAR + * File Specification. If {@code manifest} is null, it means the jar file has no manifest, and + * an empty set will be returned. + */ + @VisibleForTesting + static ImmutableSet getClassPathFromManifest(File jarFile, Manifest manifest) { + if (manifest == null) { + return ImmutableSet.of(); + } + ImmutableSet.Builder builder = ImmutableSet.builder(); + String classpathAttribute = + manifest.getMainAttributes().getValue(Attributes.Name.CLASS_PATH.toString()); + if (classpathAttribute != null) { + for (String path : CLASS_PATH_ATTRIBUTE_SEPARATOR.split(classpathAttribute)) { + URL url; + try { + url = getClassPathEntry(jarFile, path); + } catch (MalformedURLException e) { + // Ignore bad entry + logger.warning("Invalid Class-Path entry: " + path); + continue; + } + if (url.getProtocol().equals("file")) { + builder.add(toFile(url)); + } + } + } + return builder.build(); + } + + @VisibleForTesting + static ImmutableMap getClassPathEntries(ClassLoader classloader) { + LinkedHashMap entries = Maps.newLinkedHashMap(); + // Search parent first, since it's the order ClassLoader#loadClass() uses. + ClassLoader parent = classloader.getParent(); + if (parent != null) { + entries.putAll(getClassPathEntries(parent)); + } + for (URL url : getClassLoaderUrls(classloader)) { + if (url.getProtocol().equals("file")) { + File file = toFile(url); + if (!entries.containsKey(file)) { + entries.put(file, classloader); + } + } + } + return ImmutableMap.copyOf(entries); + } + + private static ImmutableList getClassLoaderUrls(ClassLoader classloader) { + if (classloader instanceof URLClassLoader) { + return ImmutableList.copyOf(((URLClassLoader) classloader).getURLs()); + } + if (classloader.equals(ClassLoader.getSystemClassLoader())) { + return parseJavaClassPath(); + } + return ImmutableList.of(); + } + + + /** + * Returns the URLs in the class path specified by the {@code java.class.path} {@linkplain + * System#getProperty system property}. + */ + @VisibleForTesting // TODO(b/65488446): Make this a public API. + static ImmutableList parseJavaClassPath() { + ImmutableList.Builder urls = ImmutableList.builder(); + for (String entry : Splitter.on(PATH_SEPARATOR.value()).split(JAVA_CLASS_PATH.value())) { + try { + try { + urls.add(new File(entry).toURI().toURL()); + } catch (SecurityException e) { // File.toURI checks to see if the file is a directory + urls.add(new URL("file", null, new File(entry).getAbsolutePath())); + } + } catch (MalformedURLException e) { + logger.log(WARNING, "malformed classpath entry: " + entry, e); + } + } + return urls.build(); + } + + /** + * Returns the absolute uri of the Class-Path entry value as specified in JAR + * File Specification. Even though the specification only talks about relative urls, absolute + * urls are actually supported too (for example, in Maven surefire plugin). + */ + @VisibleForTesting + static URL getClassPathEntry(File jarFile, String path) throws MalformedURLException { + return new URL(jarFile.toURI().toURL(), path); } @VisibleForTesting diff --git a/src/main/java/com/google/common/util/concurrent/AbstractFuture.java b/src/main/java/com/google/common/util/concurrent/AbstractFuture.java index 0c1537c..4bf4789 100644 --- a/src/main/java/com/google/common/util/concurrent/AbstractFuture.java +++ b/src/main/java/com/google/common/util/concurrent/AbstractFuture.java @@ -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; /** @@ -396,7 +398,7 @@ public abstract class AbstractFuture 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); @@ -561,6 +563,8 @@ public abstract class AbstractFuture 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)}. + *

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 +584,7 @@ public abstract class AbstractFuture 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 +718,9 @@ public abstract class AbstractFuture 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}. * + *

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 +754,9 @@ public abstract class AbstractFuture extends InternalFutureFailureAccess * invoke the {@link #interruptTask} method, and the {@link #wasInterrupted} method will not * return {@code true}. * + *

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 +1041,14 @@ public abstract class AbstractFuture 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 +1096,8 @@ public abstract class AbstractFuture 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 +1107,24 @@ public abstract class AbstractFuture 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 diff --git a/src/main/java/com/google/common/util/concurrent/ClosingFuture.java b/src/main/java/com/google/common/util/concurrent/ClosingFuture.java new file mode 100644 index 0000000..4eabfe2 --- /dev/null +++ b/src/main/java/com/google/common/util/concurrent/ClosingFuture.java @@ -0,0 +1,2245 @@ +/* + * Copyright (C) 2017 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.util.concurrent; + +import static com.google.common.base.Functions.constant; +import static com.google.common.base.MoreObjects.toStringHelper; +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.base.Preconditions.checkState; +import static com.google.common.collect.Lists.asList; +import static com.google.common.util.concurrent.ClosingFuture.State.CLOSED; +import static com.google.common.util.concurrent.ClosingFuture.State.CLOSING; +import static com.google.common.util.concurrent.ClosingFuture.State.OPEN; +import static com.google.common.util.concurrent.ClosingFuture.State.SUBSUMED; +import static com.google.common.util.concurrent.ClosingFuture.State.WILL_CLOSE; +import static com.google.common.util.concurrent.ClosingFuture.State.WILL_CREATE_VALUE_AND_CLOSER; +import static com.google.common.util.concurrent.Futures.getDone; +import static com.google.common.util.concurrent.Futures.immediateFuture; +import static com.google.common.util.concurrent.Futures.nonCancellationPropagating; +import static com.google.common.util.concurrent.MoreExecutors.directExecutor; +import static java.util.logging.Level.FINER; +import static java.util.logging.Level.SEVERE; +import static java.util.logging.Level.WARNING; + +import com.google.common.annotations.Beta; +import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.Function; +import com.google.common.collect.FluentIterable; +import com.google.common.collect.ImmutableList; +import com.google.common.util.concurrent.ClosingFuture.Combiner.AsyncCombiningCallable; +import com.google.common.util.concurrent.ClosingFuture.Combiner.CombiningCallable; +import com.google.common.util.concurrent.Futures.FutureCombiner; +import java.io.Closeable; +import java.util.IdentityHashMap; +import java.util.concurrent.Callable; +import java.util.concurrent.CancellationException; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Executor; +import java.util.concurrent.Future; +import java.util.concurrent.RejectedExecutionException; +import java.util.concurrent.atomic.AtomicReference; +import java.util.logging.Logger; + +/** + * A step in a pipeline of an asynchronous computation. When the last step in the computation is + * complete, some objects captured during the computation are closed. + * + *

A pipeline of {@code ClosingFuture}s is a tree of steps. Each step represents either an + * asynchronously-computed intermediate value, or else an exception that indicates the failure or + * cancellation of the operation so far. The only way to extract the value or exception from a step + * is by declaring that step to be the last step of the pipeline. Nevertheless, we refer to the + * "value" of a successful step or the "result" (value or exception) of any step. + * + *

    + *
  1. A pipeline starts at its leaf step (or steps), which is created from either a callable + * block or a {@link ListenableFuture}. + *
  2. Each other step is derived from one or more input steps. At each step, zero or more objects + * can be captured for later closing. + *
  3. There is one last step (the root of the tree), from which you can extract the final result + * of the computation. After that result is available (or the computation fails), all objects + * captured by any of the steps in the pipeline are closed. + *
+ * + *

Starting a pipeline

+ * + * Start a {@code ClosingFuture} pipeline {@linkplain #submit(ClosingCallable, Executor) from a + * callable block} that may capture objects for later closing. To start a pipeline from a {@link + * ListenableFuture} that doesn't create resources that should be closed later, you can use {@link + * #from(ListenableFuture)} instead. + * + *

Derived steps

+ * + * A {@code ClosingFuture} step can be derived from one or more input {@code ClosingFuture} steps in + * ways similar to {@link FluentFuture}s: + * + *
    + *
  • by transforming the value from a successful input step, + *
  • by catching the exception from a failed input step, or + *
  • by combining the results of several input steps. + *
+ * + * Each derivation can capture the next value or any intermediate objects for later closing. + * + *

A step can be the input to at most one derived step. Once you transform its value, catch its + * exception, or combine it with others, you cannot do anything else with it, including declare it + * to be the last step of the pipeline. + * + *

Transforming

+ * + * To derive the next step by asynchronously applying a function to an input step's value, call + * {@link #transform(ClosingFunction, Executor)} or {@link #transformAsync(AsyncClosingFunction, + * Executor)} on the input step. + * + *

Catching

+ * + * To derive the next step from a failed input step, call {@link #catching(Class, ClosingFunction, + * Executor)} or {@link #catchingAsync(Class, AsyncClosingFunction, Executor)} on the input step. + * + *

Combining

+ * + * To derive a {@code ClosingFuture} from two or more input steps, pass the input steps to {@link + * #whenAllComplete(Iterable)} or {@link #whenAllSucceed(Iterable)} or its overloads. + * + *

Cancelling

+ * + * Any step in a pipeline can be {@linkplain #cancel(boolean) cancelled}, even after another step + * has been derived, with the same semantics as cancelling a {@link Future}. In addition, a + * successfully cancelled step will immediately start closing all objects captured for later closing + * by it and by its input steps. + * + *

Ending a pipeline

+ * + * Each {@code ClosingFuture} pipeline must be ended. To end a pipeline, decide whether you want to + * close the captured objects automatically or manually. + * + *

Automatically closing

+ * + * You can extract a {@link Future} that represents the result of the last step in the pipeline by + * calling {@link #finishToFuture()}. All objects the pipeline has captured for closing will begin + * to be closed asynchronously after the returned {@code Future} is done: the future + * completes before closing starts, rather than once it has finished. + * + *
{@code
+ * FluentFuture userName =
+ *     ClosingFuture.submit(
+ *             closer -> closer.eventuallyClose(database.newTransaction(), closingExecutor),
+ *             executor)
+ *         .transformAsync((closer, transaction) -> transaction.queryClosingFuture("..."), executor)
+ *         .transform((closer, result) -> result.get("userName"), directExecutor())
+ *         .catching(DBException.class, e -> "no user", directExecutor())
+ *         .finishToFuture();
+ * }
+ * + * In this example, when the {@code userName} {@link Future} is done, the transaction and the query + * result cursor will both be closed, even if the operation is cancelled or fails. + * + *

Manually closing

+ * + * If you want to close the captured objects manually, after you've used the final result, call + * {@link #finishToValueAndCloser(ValueAndCloserConsumer, Executor)} to get an object that holds the + * final result. You then call {@link ValueAndCloser#closeAsync()} to close the captured objects. + * + *
{@code
+ *     ClosingFuture.submit(
+ *             closer -> closer.eventuallyClose(database.newTransaction(), closingExecutor),
+ *             executor)
+ *     .transformAsync((closer, transaction) -> transaction.queryClosingFuture("..."), executor)
+ *     .transform((closer, result) -> result.get("userName"), directExecutor())
+ *     .catching(DBException.class, e -> "no user", directExecutor())
+ *     .finishToValueAndCloser(
+ *         valueAndCloser -> this.userNameValueAndCloser = valueAndCloser, executor);
+ *
+ * // later
+ * try { // get() will throw if the operation failed or was cancelled.
+ *   UserName userName = userNameValueAndCloser.get();
+ *   // do something with userName
+ * } finally {
+ *   userNameValueAndCloser.closeAsync();
+ * }
+ * }
+ * + * In this example, when {@code userNameValueAndCloser.closeAsync()} is called, the transaction and + * the query result cursor will both be closed, even if the operation is cancelled or fails. + * + *

Note that if you don't call {@code closeAsync()}, the captured objects will not be closed. The + * automatic-closing approach described above is safer. + * + * @param the type of the value of this step + * @since 30.0 + */ +// TODO(dpb): Consider reusing one CloseableList for the entire pipeline, modulo combinations. +@Beta // @Beta for one release. +// TODO(dpb): GWT compatibility. +public final class ClosingFuture { + + private static final Logger logger = Logger.getLogger(ClosingFuture.class.getName()); + + /** + * An object that can capture objects to be closed later, when a {@link ClosingFuture} pipeline is + * done. + */ + public static final class DeferredCloser { + private final CloseableList list; + + DeferredCloser(CloseableList list) { + this.list = list; + } + + /** + * Captures an object to be closed when a {@link ClosingFuture} pipeline is done. + * + *

For users of the {@code -jre} flavor of Guava, the object can be any {@code + * AutoCloseable}. For users of the {@code -android} flavor, the object must be a {@code + * Closeable}. (For more about the flavors, see Adding Guava to your + * build.) + * + *

Be careful when targeting an older SDK than you are building against (most commonly when + * building for Android): Ensure that any object you pass implements the interface not just in + * your current SDK version but also at the oldest version you support. For example, API Level 16 is the first version + * in which {@code Cursor} is {@code Closeable}. To support older versions, pass a wrapper + * {@code Closeable} with a method reference like {@code cursor::close}. + * + *

Note that this method is still binary-compatible between flavors because the erasure of + * its parameter type is {@code Object}, not {@code AutoCloseable} or {@code Closeable}. + * + * @param closeable the object to be closed (see notes above) + * @param closingExecutor the object will be closed on this executor + * @return the first argument + */ + public C eventuallyClose( + C closeable, Executor closingExecutor) { + checkNotNull(closingExecutor); + if (closeable != null) { + list.add(closeable, closingExecutor); + } + return closeable; + } + } + + /** + * An operation that computes a result. + * + * @param the type of the result + */ + @FunctionalInterface + public interface ClosingCallable { + /** + * Computes a result, or throws an exception if unable to do so. + * + *

Any objects that are passed to {@link DeferredCloser#eventuallyClose(Closeable, Executor) + * closer.eventuallyClose()} will be closed when the {@link ClosingFuture} pipeline is done (but + * not before this method completes), even if this method throws or the pipeline is cancelled. + */ + V call(DeferredCloser closer) throws Exception; + } + + /** + * An operation that computes a {@link ClosingFuture} of a result. + * + * @param the type of the result + * @since 30.1 + */ + @FunctionalInterface + public interface AsyncClosingCallable { + /** + * Computes a result, or throws an exception if unable to do so. + * + *

Any objects that are passed to {@link DeferredCloser#eventuallyClose(Closeable, Executor) + * closer.eventuallyClose()} will be closed when the {@link ClosingFuture} pipeline is done (but + * not before this method completes), even if this method throws or the pipeline is cancelled. + */ + ClosingFuture call(DeferredCloser closer) throws Exception; + } + + /** + * A function from an input to a result. + * + * @param the type of the input to the function + * @param the type of the result of the function + */ + @FunctionalInterface + public interface ClosingFunction { + + /** + * Applies this function to an input, or throws an exception if unable to do so. + * + *

Any objects that are passed to {@link DeferredCloser#eventuallyClose(Closeable, Executor) + * closer.eventuallyClose()} will be closed when the {@link ClosingFuture} pipeline is done (but + * not before this method completes), even if this method throws or the pipeline is cancelled. + */ + U apply(DeferredCloser closer, T input) throws Exception; + } + + /** + * A function from an input to a {@link ClosingFuture} of a result. + * + * @param the type of the input to the function + * @param the type of the result of the function + */ + @FunctionalInterface + public interface AsyncClosingFunction { + /** + * Applies this function to an input, or throws an exception if unable to do so. + * + *

Any objects that are passed to {@link DeferredCloser#eventuallyClose(Closeable, Executor) + * closer.eventuallyClose()} will be closed when the {@link ClosingFuture} pipeline is done (but + * not before this method completes), even if this method throws or the pipeline is cancelled. + */ + ClosingFuture apply(DeferredCloser closer, T input) throws Exception; + } + + /** + * An object that holds the final result of an asynchronous {@link ClosingFuture} operation and + * allows the user to close all the closeable objects that were captured during it for later + * closing. + * + *

The asynchronous operation will have completed before this object is created. + * + * @param the type of the value of a successful operation + * @see ClosingFuture#finishToValueAndCloser(ValueAndCloserConsumer, Executor) + */ + public static final class ValueAndCloser { + + private final ClosingFuture closingFuture; + + ValueAndCloser(ClosingFuture closingFuture) { + this.closingFuture = checkNotNull(closingFuture); + } + + /** + * Returns the final value of the associated {@link ClosingFuture}, or throws an exception as + * {@link Future#get()} would. + * + *

Because the asynchronous operation has already completed, this method is synchronous and + * returns immediately. + * + * @throws CancellationException if the computation was cancelled + * @throws ExecutionException if the computation threw an exception + */ + public V get() throws ExecutionException { + return getDone(closingFuture.future); + } + + /** + * Starts closing all closeable objects captured during the {@link ClosingFuture}'s asynchronous + * operation on the {@link Executor}s specified by calls to {@link + * DeferredCloser#eventuallyClose(Closeable, Executor)}. + * + *

If any such calls specified {@link MoreExecutors#directExecutor()}, those objects will be + * closed synchronously. + * + *

Idempotent: objects will be closed at most once. + */ + public void closeAsync() { + closingFuture.close(); + } + } + + /** + * Represents an operation that accepts a {@link ValueAndCloser} for the last step in a {@link + * ClosingFuture} pipeline. + * + * @param the type of the final value of a successful pipeline + * @see ClosingFuture#finishToValueAndCloser(ValueAndCloserConsumer, Executor) + */ + @FunctionalInterface + public interface ValueAndCloserConsumer { + + /** Accepts a {@link ValueAndCloser} for the last step in a {@link ClosingFuture} pipeline. */ + void accept(ValueAndCloser valueAndCloser); + } + + /** + * Starts a {@link ClosingFuture} pipeline by submitting a callable block to an executor. + * + * @throws RejectedExecutionException if the task cannot be scheduled for + * execution + */ + public static ClosingFuture submit(ClosingCallable callable, Executor executor) { + return new ClosingFuture<>(callable, executor); + } + + /** + * Starts a {@link ClosingFuture} pipeline by submitting a callable block to an executor. + * + * @throws RejectedExecutionException if the task cannot be scheduled for + * execution + * @since 30.1 + */ + public static ClosingFuture submitAsync( + AsyncClosingCallable callable, Executor executor) { + return new ClosingFuture<>(callable, executor); + } + + /** + * Starts a {@link ClosingFuture} pipeline with a {@link ListenableFuture}. + * + *

{@code future}'s value will not be closed when the pipeline is done even if {@code V} + * implements {@link Closeable}. In order to start a pipeline with a value that will be closed + * when the pipeline is done, use {@link #submit(ClosingCallable, Executor)} instead. + */ + public static ClosingFuture from(ListenableFuture future) { + return new ClosingFuture(future); + } + + /** + * Starts a {@link ClosingFuture} pipeline with a {@link ListenableFuture}. + * + *

If {@code future} succeeds, its value will be closed (using {@code closingExecutor)} when + * the pipeline is done, even if the pipeline is canceled or fails. + * + *

Cancelling the pipeline will not cancel {@code future}, so that the pipeline can access its + * value in order to close it. + * + * @param future the future to create the {@code ClosingFuture} from. For discussion of the + * future's result type {@code C}, see {@link DeferredCloser#eventuallyClose(Closeable, + * Executor)}. + * @param closingExecutor the future's result will be closed on this executor + * @deprecated Creating {@link Future}s of closeable types is dangerous in general because the + * underlying value may never be closed if the {@link Future} is canceled after its operation + * begins. Consider replacing code that creates {@link ListenableFuture}s of closeable types, + * including those that pass them to this method, with {@link #submit(ClosingCallable, + * Executor)} in order to ensure that resources do not leak. Or, to start a pipeline with a + * {@link ListenableFuture} that doesn't create values that should be closed, use {@link + * ClosingFuture#from}. + */ + @Deprecated + public static + ClosingFuture eventuallyClosing( + ListenableFuture future, final Executor closingExecutor) { + checkNotNull(closingExecutor); + final ClosingFuture closingFuture = new ClosingFuture<>(nonCancellationPropagating(future)); + Futures.addCallback( + future, + new FutureCallback() { + @Override + public void onSuccess(AutoCloseable result) { + closingFuture.closeables.closer.eventuallyClose(result, closingExecutor); + } + + @Override + public void onFailure(Throwable t) {} + }, + directExecutor()); + return closingFuture; + } + + /** + * Starts specifying how to combine {@link ClosingFuture}s into a single pipeline. + * + * @throws IllegalStateException if a {@code ClosingFuture} has already been derived from any of + * the {@code futures}, or if any has already been {@linkplain #finishToFuture() finished} + */ + public static Combiner whenAllComplete(Iterable> futures) { + return new Combiner(false, futures); + } + + /** + * Starts specifying how to combine {@link ClosingFuture}s into a single pipeline. + * + * @throws IllegalStateException if a {@code ClosingFuture} has already been derived from any of + * the arguments, or if any has already been {@linkplain #finishToFuture() finished} + */ + public static Combiner whenAllComplete( + ClosingFuture future1, ClosingFuture... moreFutures) { + return whenAllComplete(asList(future1, moreFutures)); + } + + /** + * Starts specifying how to combine {@link ClosingFuture}s into a single pipeline, assuming they + * all succeed. If any fail, the resulting pipeline will fail. + * + * @throws IllegalStateException if a {@code ClosingFuture} has already been derived from any of + * the {@code futures}, or if any has already been {@linkplain #finishToFuture() finished} + */ + public static Combiner whenAllSucceed(Iterable> futures) { + return new Combiner(true, futures); + } + + /** + * Starts specifying how to combine two {@link ClosingFuture}s into a single pipeline, assuming + * they all succeed. If any fail, the resulting pipeline will fail. + * + *

Calling this method allows you to use lambdas or method references typed with the types of + * the input {@link ClosingFuture}s. + * + * @throws IllegalStateException if a {@code ClosingFuture} has already been derived from any of + * the arguments, or if any has already been {@linkplain #finishToFuture() finished} + */ + public static Combiner2 whenAllSucceed( + ClosingFuture future1, ClosingFuture future2) { + return new Combiner2<>(future1, future2); + } + + /** + * Starts specifying how to combine three {@link ClosingFuture}s into a single pipeline, assuming + * they all succeed. If any fail, the resulting pipeline will fail. + * + *

Calling this method allows you to use lambdas or method references typed with the types of + * the input {@link ClosingFuture}s. + * + * @throws IllegalStateException if a {@code ClosingFuture} has already been derived from any of + * the arguments, or if any has already been {@linkplain #finishToFuture() finished} + */ + public static Combiner3 whenAllSucceed( + ClosingFuture future1, ClosingFuture future2, ClosingFuture future3) { + return new Combiner3<>(future1, future2, future3); + } + + /** + * Starts specifying how to combine four {@link ClosingFuture}s into a single pipeline, assuming + * they all succeed. If any fail, the resulting pipeline will fail. + * + *

Calling this method allows you to use lambdas or method references typed with the types of + * the input {@link ClosingFuture}s. + * + * @throws IllegalStateException if a {@code ClosingFuture} has already been derived from any of + * the arguments, or if any has already been {@linkplain #finishToFuture() finished} + */ + public static Combiner4 whenAllSucceed( + ClosingFuture future1, + ClosingFuture future2, + ClosingFuture future3, + ClosingFuture future4) { + return new Combiner4<>(future1, future2, future3, future4); + } + + /** + * Starts specifying how to combine five {@link ClosingFuture}s into a single pipeline, assuming + * they all succeed. If any fail, the resulting pipeline will fail. + * + *

Calling this method allows you to use lambdas or method references typed with the types of + * the input {@link ClosingFuture}s. + * + * @throws IllegalStateException if a {@code ClosingFuture} has already been derived from any of + * the arguments, or if any has already been {@linkplain #finishToFuture() finished} + */ + public static Combiner5 whenAllSucceed( + ClosingFuture future1, + ClosingFuture future2, + ClosingFuture future3, + ClosingFuture future4, + ClosingFuture future5) { + return new Combiner5<>(future1, future2, future3, future4, future5); + } + + /** + * Starts specifying how to combine {@link ClosingFuture}s into a single pipeline, assuming they + * all succeed. If any fail, the resulting pipeline will fail. + * + * @throws IllegalStateException if a {@code ClosingFuture} has already been derived from any of + * the arguments, or if any has already been {@linkplain #finishToFuture() finished} + */ + public static Combiner whenAllSucceed( + ClosingFuture future1, + ClosingFuture future2, + ClosingFuture future3, + ClosingFuture future4, + ClosingFuture future5, + ClosingFuture future6, + ClosingFuture... moreFutures) { + return whenAllSucceed( + FluentIterable.of(future1, future2, future3, future4, future5, future6) + .append(moreFutures)); + } + + private final AtomicReference state = new AtomicReference<>(OPEN); + private final CloseableList closeables = new CloseableList(); + private final FluentFuture future; + + private ClosingFuture(ListenableFuture future) { + this.future = FluentFuture.from(future); + } + + private ClosingFuture(final ClosingCallable callable, Executor executor) { + checkNotNull(callable); + TrustedListenableFutureTask task = + TrustedListenableFutureTask.create( + new Callable() { + @Override + public V call() throws Exception { + return callable.call(closeables.closer); + } + + @Override + public String toString() { + return callable.toString(); + } + }); + executor.execute(task); + this.future = task; + } + + private ClosingFuture(final AsyncClosingCallable callable, Executor executor) { + checkNotNull(callable); + TrustedListenableFutureTask task = + TrustedListenableFutureTask.create( + new AsyncCallable() { + @Override + public ListenableFuture call() throws Exception { + CloseableList newCloseables = new CloseableList(); + try { + ClosingFuture closingFuture = callable.call(newCloseables.closer); + closingFuture.becomeSubsumedInto(closeables); + return closingFuture.future; + } finally { + closeables.add(newCloseables, directExecutor()); + } + } + + @Override + public String toString() { + return callable.toString(); + } + }); + executor.execute(task); + this.future = task; + } + + /** + * Returns a future that finishes when this step does. Calling {@code get()} on the returned + * future returns {@code null} if the step is successful or throws the same exception that would + * be thrown by calling {@code finishToFuture().get()} if this were the last step. Calling {@code + * cancel()} on the returned future has no effect on the {@code ClosingFuture} pipeline. + * + *

{@code statusFuture} differs from most methods on {@code ClosingFuture}: You can make calls + * to {@code statusFuture} in addition to the call you make to {@link #finishToFuture()} or + * a derivation method on the same instance. This is important because calling {@code + * statusFuture} alone does not provide a way to close the pipeline. + */ + public ListenableFuture statusFuture() { + return nonCancellationPropagating(future.transform(constant(null), directExecutor())); + } + + /** + * Returns a new {@code ClosingFuture} pipeline step derived from this one by applying a function + * to its value. The function can use a {@link DeferredCloser} to capture objects to be closed + * when the pipeline is done. + * + *

If this {@code ClosingFuture} fails, the function will not be called, and the derived {@code + * ClosingFuture} will be equivalent to this one. + * + *

If the function throws an exception, that exception is used as the result of the derived + * {@code ClosingFuture}. + * + *

Example usage: + * + *

{@code
+   * ClosingFuture> rowsFuture =
+   *     queryFuture.transform((closer, result) -> result.getRows(), executor);
+   * }
+ * + *

When selecting an executor, note that {@code directExecutor} is dangerous in some cases. See + * the discussion in the {@link ListenableFuture#addListener} documentation. All its warnings + * about heavyweight listeners are also applicable to heavyweight functions passed to this method. + * + *

After calling this method, you may not call {@link #finishToFuture()}, {@link + * #finishToValueAndCloser(ValueAndCloserConsumer, Executor)}, or any other derivation method on + * this {@code ClosingFuture}. + * + * @param function transforms the value of this step to the value of the derived step + * @param executor executor to run the function in + * @return the derived step + * @throws IllegalStateException if a {@code ClosingFuture} has already been derived from this + * one, or if this {@code ClosingFuture} has already been {@linkplain #finishToFuture() + * finished} + */ + public ClosingFuture transform( + final ClosingFunction function, Executor executor) { + checkNotNull(function); + AsyncFunction applyFunction = + new AsyncFunction() { + @Override + public ListenableFuture apply(V input) throws Exception { + return closeables.applyClosingFunction(function, input); + } + + @Override + public String toString() { + return function.toString(); + } + }; + // TODO(dpb): Switch to future.transformSync when that exists (passing a throwing function). + return derive(future.transformAsync(applyFunction, executor)); + } + + /** + * Returns a new {@code ClosingFuture} pipeline step derived from this one by applying a function + * that returns a {@code ClosingFuture} to its value. The function can use a {@link + * DeferredCloser} to capture objects to be closed when the pipeline is done (other than those + * captured by the returned {@link ClosingFuture}). + * + *

If this {@code ClosingFuture} succeeds, the derived one will be equivalent to the one + * returned by the function. + * + *

If this {@code ClosingFuture} fails, the function will not be called, and the derived {@code + * ClosingFuture} will be equivalent to this one. + * + *

If the function throws an exception, that exception is used as the result of the derived + * {@code ClosingFuture}. But if the exception is thrown after the function creates a {@code + * ClosingFuture}, then none of the closeable objects in that {@code ClosingFuture} will be + * closed. + * + *

Usage guidelines for this method: + * + *

    + *
  • Use this method only when calling an API that returns a {@link ListenableFuture} or a + * {@code ClosingFuture}. If possible, prefer calling {@link #transform(ClosingFunction, + * Executor)} instead, with a function that returns the next value directly. + *
  • Call {@link DeferredCloser#eventuallyClose(Closeable, Executor) closer.eventuallyClose()} + * for every closeable object this step creates in order to capture it for later closing. + *
  • Return a {@code ClosingFuture}. To turn a {@link ListenableFuture} into a {@code + * ClosingFuture} call {@link #from(ListenableFuture)}. + *
  • In case this step doesn't create new closeables, you can adapt an API that returns a + * {@link ListenableFuture} to return a {@code ClosingFuture} by wrapping it with a call to + * {@link #withoutCloser(AsyncFunction)} + *
+ * + *

Example usage: + * + *

{@code
+   * // Result.getRowsClosingFuture() returns a ClosingFuture.
+   * ClosingFuture> rowsFuture =
+   *     queryFuture.transformAsync((closer, result) -> result.getRowsClosingFuture(), executor);
+   *
+   * // Result.writeRowsToOutputStreamFuture() returns a ListenableFuture that resolves to the
+   * // number of written rows. openOutputFile() returns a FileOutputStream (which implements
+   * // Closeable).
+   * ClosingFuture rowsFuture2 =
+   *     queryFuture.transformAsync(
+   *         (closer, result) -> {
+   *           FileOutputStream fos = closer.eventuallyClose(openOutputFile(), closingExecutor);
+   *           return ClosingFuture.from(result.writeRowsToOutputStreamFuture(fos));
+   *      },
+   *      executor);
+   *
+   * // Result.getRowsFuture() returns a ListenableFuture (no new closeables are created).
+   * ClosingFuture> rowsFuture3 =
+   *     queryFuture.transformAsync(withoutCloser(Result::getRowsFuture), executor);
+   *
+   * }
+ * + *

When selecting an executor, note that {@code directExecutor} is dangerous in some cases. See + * the discussion in the {@link 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 AsyncClosingFunction.apply}. Any heavyweight operations should occur in other threads + * responsible for completing the returned {@code ClosingFuture}.) + * + *

After calling this method, you may not call {@link #finishToFuture()}, {@link + * #finishToValueAndCloser(ValueAndCloserConsumer, Executor)}, or any other derivation method on + * this {@code ClosingFuture}. + * + * @param function transforms the value of this step to a {@code ClosingFuture} with the value of + * the derived step + * @param executor executor to run the function in + * @return the derived step + * @throws IllegalStateException if a {@code ClosingFuture} has already been derived from this + * one, or if this {@code ClosingFuture} has already been {@linkplain #finishToFuture() + * finished} + */ + public ClosingFuture transformAsync( + final AsyncClosingFunction function, Executor executor) { + checkNotNull(function); + AsyncFunction applyFunction = + new AsyncFunction() { + @Override + public ListenableFuture apply(V input) throws Exception { + return closeables.applyAsyncClosingFunction(function, input); + } + + @Override + public String toString() { + return function.toString(); + } + }; + return derive(future.transformAsync(applyFunction, executor)); + } + + /** + * Returns an {@link AsyncClosingFunction} that applies an {@link AsyncFunction} to an input, + * ignoring the DeferredCloser and returning a {@code ClosingFuture} derived from the returned + * {@link ListenableFuture}. + * + *

Use this method to pass a transformation to {@link #transformAsync(AsyncClosingFunction, + * Executor)} or to {@link #catchingAsync(Class, AsyncClosingFunction, Executor)} as long as it + * meets these conditions: + * + *

    + *
  • It does not need to capture any {@link Closeable} objects by calling {@link + * DeferredCloser#eventuallyClose(Closeable, Executor)}. + *
  • It returns a {@link ListenableFuture}. + *
+ * + *

Example usage: + * + *

{@code
+   * // Result.getRowsFuture() returns a ListenableFuture.
+   * ClosingFuture> rowsFuture =
+   *     queryFuture.transformAsync(withoutCloser(Result::getRowsFuture), executor);
+   * }
+ * + * @param function transforms the value of a {@code ClosingFuture} step to a {@link + * ListenableFuture} with the value of a derived step + */ + public static AsyncClosingFunction withoutCloser( + final AsyncFunction function) { + checkNotNull(function); + return new AsyncClosingFunction() { + @Override + public ClosingFuture apply(DeferredCloser closer, V input) throws Exception { + return ClosingFuture.from(function.apply(input)); + } + }; + } + + /** + * Returns a new {@code ClosingFuture} pipeline step derived from this one by applying a function + * to its exception if it is an instance of a given exception type. The function can use a {@link + * DeferredCloser} to capture objects to be closed when the pipeline is done. + * + *

If this {@code ClosingFuture} succeeds or fails with a different exception type, the + * function will not be called, and the derived {@code ClosingFuture} will be equivalent to this + * one. + * + *

If the function throws an exception, that exception is used as the result of the derived + * {@code ClosingFuture}. + * + *

Example usage: + * + *

{@code
+   * ClosingFuture queryFuture =
+   *     queryFuture.catching(
+   *         QueryException.class, (closer, x) -> Query.emptyQueryResult(), executor);
+   * }
+ * + *

When selecting an executor, note that {@code directExecutor} is dangerous in some cases. See + * the discussion in the {@link ListenableFuture#addListener} documentation. All its warnings + * about heavyweight listeners are also applicable to heavyweight functions passed to this method. + * + *

After calling this method, you may not call {@link #finishToFuture()}, {@link + * #finishToValueAndCloser(ValueAndCloserConsumer, Executor)}, or any other derivation method on + * this {@code ClosingFuture}. + * + * @param exceptionType the exception type that triggers use of {@code fallback}. The exception + * type is matched against this step's exception. "This step's exception" means the cause of + * the {@link ExecutionException} thrown by {@link Future#get()} on the {@link Future} + * underlying this step or, if {@code get()} throws a different kind of exception, that + * exception itself. To avoid hiding bugs and other unrecoverable errors, callers should + * prefer more specific types, avoiding {@code Throwable.class} in particular. + * @param fallback the function to be called if this step fails with the expected exception type. + * The function's argument is this step's exception. "This step's exception" means the cause + * of the {@link ExecutionException} thrown by {@link Future#get()} on the {@link Future} + * underlying this step or, if {@code get()} throws a different kind of exception, that + * exception itself. + * @param executor the executor that runs {@code fallback} if the input fails + */ + public ClosingFuture catching( + Class exceptionType, ClosingFunction fallback, Executor executor) { + return catchingMoreGeneric(exceptionType, fallback, executor); + } + + // Avoids generic type capture inconsistency problems where |? extends V| is incompatible with V. + private ClosingFuture catchingMoreGeneric( + Class exceptionType, final ClosingFunction fallback, Executor executor) { + checkNotNull(fallback); + AsyncFunction applyFallback = + new AsyncFunction() { + @Override + public ListenableFuture apply(X exception) throws Exception { + return closeables.applyClosingFunction(fallback, exception); + } + + @Override + public String toString() { + return fallback.toString(); + } + }; + // TODO(dpb): Switch to future.catchingSync when that exists (passing a throwing function). + return derive(future.catchingAsync(exceptionType, applyFallback, executor)); + } + + /** + * Returns a new {@code ClosingFuture} pipeline step derived from this one by applying a function + * that returns a {@code ClosingFuture} to its exception if it is an instance of a given exception + * type. The function can use a {@link DeferredCloser} to capture objects to be closed when the + * pipeline is done (other than those captured by the returned {@link ClosingFuture}). + * + *

If this {@code ClosingFuture} fails with an exception of the given type, the derived {@code + * ClosingFuture} will be equivalent to the one returned by the function. + * + *

If this {@code ClosingFuture} succeeds or fails with a different exception type, the + * function will not be called, and the derived {@code ClosingFuture} will be equivalent to this + * one. + * + *

If the function throws an exception, that exception is used as the result of the derived + * {@code ClosingFuture}. But if the exception is thrown after the function creates a {@code + * ClosingFuture}, then none of the closeable objects in that {@code ClosingFuture} will be + * closed. + * + *

Usage guidelines for this method: + * + *

    + *
  • Use this method only when calling an API that returns a {@link ListenableFuture} or a + * {@code ClosingFuture}. If possible, prefer calling {@link #catching(Class, + * ClosingFunction, Executor)} instead, with a function that returns the next value + * directly. + *
  • Call {@link DeferredCloser#eventuallyClose(Closeable, Executor) closer.eventuallyClose()} + * for every closeable object this step creates in order to capture it for later closing. + *
  • Return a {@code ClosingFuture}. To turn a {@link ListenableFuture} into a {@code + * ClosingFuture} call {@link #from(ListenableFuture)}. + *
  • In case this step doesn't create new closeables, you can adapt an API that returns a + * {@link ListenableFuture} to return a {@code ClosingFuture} by wrapping it with a call to + * {@link #withoutCloser(AsyncFunction)} + *
+ * + *

Example usage: + * + *

{@code
+   * // Fall back to a secondary input stream in case of IOException.
+   * ClosingFuture inputFuture =
+   *     firstInputFuture.catchingAsync(
+   *         IOException.class, (closer, x) -> secondaryInputStreamClosingFuture(), executor);
+   * }
+   * }
+ * + *

When selecting an executor, note that {@code directExecutor} is dangerous in some cases. See + * the discussion in the {@link 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 AsyncClosingFunction.apply}. Any heavyweight operations should occur in other threads + * responsible for completing the returned {@code ClosingFuture}.) + * + *

After calling this method, you may not call {@link #finishToFuture()}, {@link + * #finishToValueAndCloser(ValueAndCloserConsumer, Executor)}, or any other derivation method on + * this {@code ClosingFuture}. + * + * @param exceptionType the exception type that triggers use of {@code fallback}. The exception + * type is matched against this step's exception. "This step's exception" means the cause of + * the {@link ExecutionException} thrown by {@link Future#get()} on the {@link Future} + * underlying this step or, if {@code get()} throws a different kind of exception, that + * exception itself. To avoid hiding bugs and other unrecoverable errors, callers should + * prefer more specific types, avoiding {@code Throwable.class} in particular. + * @param fallback the function to be called if this step fails with the expected exception type. + * The function's argument is this step's exception. "This step's exception" means the cause + * of the {@link ExecutionException} thrown by {@link Future#get()} on the {@link Future} + * underlying this step or, if {@code get()} throws a different kind of exception, that + * exception itself. + * @param executor the executor that runs {@code fallback} if the input fails + */ + // TODO(dpb): Should this do something special if the function throws CancellationException or + // ExecutionException? + public ClosingFuture catchingAsync( + Class exceptionType, + AsyncClosingFunction fallback, + Executor executor) { + return catchingAsyncMoreGeneric(exceptionType, fallback, executor); + } + + // Avoids generic type capture inconsistency problems where |? extends V| is incompatible with V. + private ClosingFuture catchingAsyncMoreGeneric( + Class exceptionType, + final AsyncClosingFunction fallback, + Executor executor) { + checkNotNull(fallback); + AsyncFunction asyncFunction = + new AsyncFunction() { + @Override + public ListenableFuture apply(X exception) throws Exception { + return closeables.applyAsyncClosingFunction(fallback, exception); + } + + @Override + public String toString() { + return fallback.toString(); + } + }; + return derive(future.catchingAsync(exceptionType, asyncFunction, executor)); + } + + /** + * Marks this step as the last step in the {@code ClosingFuture} pipeline. + * + *

The returned {@link Future} is completed when the pipeline's computation completes, or when + * the pipeline is cancelled. + * + *

All objects the pipeline has captured for closing will begin to be closed asynchronously + * after the returned {@code Future} is done: the future completes before closing starts, + * rather than once it has finished. + * + *

After calling this method, you may not call {@link + * #finishToValueAndCloser(ValueAndCloserConsumer, Executor)}, this method, or any other + * derivation method on this {@code ClosingFuture}. + * + * @return a {@link Future} that represents the final value or exception of the pipeline + */ + public FluentFuture finishToFuture() { + if (compareAndUpdateState(OPEN, WILL_CLOSE)) { + logger.log(FINER, "will close {0}", this); + future.addListener( + new Runnable() { + @Override + public void run() { + checkAndUpdateState(WILL_CLOSE, CLOSING); + close(); + checkAndUpdateState(CLOSING, CLOSED); + } + }, + directExecutor()); + } else { + switch (state.get()) { + case SUBSUMED: + throw new IllegalStateException( + "Cannot call finishToFuture() after deriving another step"); + + case WILL_CREATE_VALUE_AND_CLOSER: + throw new IllegalStateException( + "Cannot call finishToFuture() after calling finishToValueAndCloser()"); + + case WILL_CLOSE: + case CLOSING: + case CLOSED: + throw new IllegalStateException("Cannot call finishToFuture() twice"); + + case OPEN: + throw new AssertionError(); + } + } + return future; + } + + /** + * Marks this step as the last step in the {@code ClosingFuture} pipeline. When this step is done, + * {@code receiver} will be called with an object that contains the result of the operation. The + * receiver can store the {@link ValueAndCloser} outside the receiver for later synchronous use. + * + *

After calling this method, you may not call {@link #finishToFuture()}, this method again, or + * any other derivation method on this {@code ClosingFuture}. + * + * @param consumer a callback whose method will be called (using {@code executor}) when this + * operation is done + */ + public void finishToValueAndCloser( + final ValueAndCloserConsumer consumer, Executor executor) { + checkNotNull(consumer); + if (!compareAndUpdateState(OPEN, WILL_CREATE_VALUE_AND_CLOSER)) { + switch (state.get()) { + case SUBSUMED: + throw new IllegalStateException( + "Cannot call finishToValueAndCloser() after deriving another step"); + + case WILL_CLOSE: + case CLOSING: + case CLOSED: + throw new IllegalStateException( + "Cannot call finishToValueAndCloser() after calling finishToFuture()"); + + case WILL_CREATE_VALUE_AND_CLOSER: + throw new IllegalStateException("Cannot call finishToValueAndCloser() twice"); + + case OPEN: + break; + } + throw new AssertionError(state); + } + future.addListener( + new Runnable() { + @Override + public void run() { + provideValueAndCloser(consumer, ClosingFuture.this); + } + }, + executor); + } + + private static void provideValueAndCloser( + ValueAndCloserConsumer consumer, ClosingFuture closingFuture) { + consumer.accept(new ValueAndCloser(closingFuture)); + } + + /** + * Attempts to cancel execution of this step. This attempt will fail if the step has already + * completed, has already been cancelled, or could not be cancelled for some other reason. If + * successful, and this step has not started when {@code cancel} is called, this step should never + * run. + * + *

If successful, causes the objects captured by this step (if already started) and its input + * step(s) for later closing to be closed on their respective {@link Executor}s. If any such calls + * specified {@link MoreExecutors#directExecutor()}, those objects will be closed synchronously. + * + * @param mayInterruptIfRunning {@code true} if the thread executing this task should be + * interrupted; otherwise, in-progress tasks are allowed to complete, but the step will be + * cancelled regardless + * @return {@code false} if the step could not be cancelled, typically because it has already + * completed normally; {@code true} otherwise + */ + public boolean cancel(boolean mayInterruptIfRunning) { + logger.log(FINER, "cancelling {0}", this); + boolean cancelled = future.cancel(mayInterruptIfRunning); + if (cancelled) { + close(); + } + return cancelled; + } + + private void close() { + logger.log(FINER, "closing {0}", this); + closeables.close(); + } + + private ClosingFuture derive(FluentFuture future) { + ClosingFuture derived = new ClosingFuture<>(future); + becomeSubsumedInto(derived.closeables); + return derived; + } + + private void becomeSubsumedInto(CloseableList otherCloseables) { + checkAndUpdateState(OPEN, SUBSUMED); + otherCloseables.add(closeables, directExecutor()); + } + + /** + * An object that can return the value of the {@link ClosingFuture}s that are passed to {@link + * #whenAllComplete(Iterable)} or {@link #whenAllSucceed(Iterable)}. + * + *

Only for use by a {@link CombiningCallable} or {@link AsyncCombiningCallable} object. + */ + public static final class Peeker { + private final ImmutableList> futures; + private volatile boolean beingCalled; + + private Peeker(ImmutableList> futures) { + this.futures = checkNotNull(futures); + } + + /** + * Returns the value of {@code closingFuture}. + * + * @throws ExecutionException if {@code closingFuture} is a failed step + * @throws CancellationException if the {@code closingFuture}'s future was cancelled + * @throws IllegalArgumentException if {@code closingFuture} is not one of the futures passed to + * {@link #whenAllComplete(Iterable)} or {@link #whenAllComplete(Iterable)} + * @throws IllegalStateException if called outside of a call to {@link + * CombiningCallable#call(DeferredCloser, Peeker)} or {@link + * AsyncCombiningCallable#call(DeferredCloser, Peeker)} + */ + public final D getDone(ClosingFuture closingFuture) + throws ExecutionException { + checkState(beingCalled); + checkArgument(futures.contains(closingFuture)); + return Futures.getDone(closingFuture.future); + } + + private V call( + CombiningCallable combiner, CloseableList closeables) throws Exception { + beingCalled = true; + CloseableList newCloseables = new CloseableList(); + try { + return combiner.call(newCloseables.closer, this); + } finally { + closeables.add(newCloseables, directExecutor()); + beingCalled = false; + } + } + + private FluentFuture callAsync( + AsyncCombiningCallable combiner, CloseableList closeables) throws Exception { + beingCalled = true; + CloseableList newCloseables = new CloseableList(); + try { + ClosingFuture closingFuture = combiner.call(newCloseables.closer, this); + closingFuture.becomeSubsumedInto(closeables); + return closingFuture.future; + } finally { + closeables.add(newCloseables, directExecutor()); + beingCalled = false; + } + } + } + + /** + * A builder of a {@link ClosingFuture} step that is derived from more than one input step. + * + *

See {@link #whenAllComplete(Iterable)} and {@link #whenAllSucceed(Iterable)} for how to + * instantiate this class. + * + *

Example: + * + *

{@code
+   * final ClosingFuture file1ReaderFuture = ...;
+   * final ClosingFuture file2ReaderFuture = ...;
+   * ListenableFuture numberOfDifferentLines =
+   *       ClosingFuture.whenAllSucceed(file1ReaderFuture, file2ReaderFuture)
+   *           .call(
+   *               (closer, peeker) -> {
+   *                 BufferedReader file1Reader = peeker.getDone(file1ReaderFuture);
+   *                 BufferedReader file2Reader = peeker.getDone(file2ReaderFuture);
+   *                 return countDifferentLines(file1Reader, file2Reader);
+   *               },
+   *               executor)
+   *           .closing(executor);
+   * }
+ */ + // TODO(cpovirk): Use simple name instead of fully qualified after we stop building with JDK 8. + public static class Combiner { + + private final CloseableList closeables = new CloseableList(); + + /** + * An operation that returns a result and may throw an exception. + * + * @param the type of the result + */ + @FunctionalInterface + public interface CombiningCallable { + /** + * Computes a result, or throws an exception if unable to do so. + * + *

Any objects that are passed to {@link DeferredCloser#eventuallyClose(Closeable, + * Executor) closer.eventuallyClose()} will be closed when the {@link ClosingFuture} pipeline + * is done (but not before this method completes), even if this method throws or the pipeline + * is cancelled. + * + * @param peeker used to get the value of any of the input futures + */ + V call(DeferredCloser closer, Peeker peeker) throws Exception; + } + + /** + * An operation that returns a {@link ClosingFuture} result and may throw an exception. + * + * @param the type of the result + */ + @FunctionalInterface + public interface AsyncCombiningCallable { + /** + * Computes a {@link ClosingFuture} result, or throws an exception if unable to do so. + * + *

Any objects that are passed to {@link DeferredCloser#eventuallyClose(Closeable, + * Executor) closer.eventuallyClose()} will be closed when the {@link ClosingFuture} pipeline + * is done (but not before this method completes), even if this method throws or the pipeline + * is cancelled. + * + * @param peeker used to get the value of any of the input futures + */ + ClosingFuture call(DeferredCloser closer, Peeker peeker) throws Exception; + } + + private final boolean allMustSucceed; + protected final ImmutableList> inputs; + + private Combiner(boolean allMustSucceed, Iterable> inputs) { + this.allMustSucceed = allMustSucceed; + this.inputs = ImmutableList.copyOf(inputs); + for (ClosingFuture input : inputs) { + input.becomeSubsumedInto(closeables); + } + } + + /** + * Returns a new {@code ClosingFuture} pipeline step derived from the inputs by applying a + * combining function to their values. The function can use a {@link DeferredCloser} to capture + * objects to be closed when the pipeline is done. + * + *

If this combiner was returned by a {@link #whenAllSucceed} method and any of the inputs + * fail, so will the returned step. + * + *

If the combiningCallable throws a {@code CancellationException}, the pipeline will be + * cancelled. + * + *

If the combiningCallable throws an {@code ExecutionException}, the cause of the thrown + * {@code ExecutionException} will be extracted and used as the failure of the derived step. + */ + public ClosingFuture call( + final CombiningCallable combiningCallable, Executor executor) { + Callable callable = + new Callable() { + @Override + public V call() throws Exception { + return new Peeker(inputs).call(combiningCallable, closeables); + } + + @Override + public String toString() { + return combiningCallable.toString(); + } + }; + ClosingFuture derived = new ClosingFuture<>(futureCombiner().call(callable, executor)); + derived.closeables.add(closeables, directExecutor()); + return derived; + } + + /** + * Returns a new {@code ClosingFuture} pipeline step derived from the inputs by applying a + * {@code ClosingFuture}-returning function to their values. The function can use a {@link + * DeferredCloser} to capture objects to be closed when the pipeline is done (other than those + * captured by the returned {@link ClosingFuture}). + * + *

If this combiner was returned by a {@link #whenAllSucceed} method and any of the inputs + * fail, so will the returned step. + * + *

If the combiningCallable throws a {@code CancellationException}, the pipeline will be + * cancelled. + * + *

If the combiningCallable throws an {@code ExecutionException}, the cause of the thrown + * {@code ExecutionException} will be extracted and used as the failure of the derived step. + * + *

If the combiningCallable throws any other exception, it will be used as the failure of the + * derived step. + * + *

If an exception is thrown after the combiningCallable creates a {@code ClosingFuture}, + * then none of the closeable objects in that {@code ClosingFuture} will be closed. + * + *

Usage guidelines for this method: + * + *

    + *
  • Use this method only when calling an API that returns a {@link ListenableFuture} or a + * {@code ClosingFuture}. If possible, prefer calling {@link #call(CombiningCallable, + * Executor)} instead, with a function that returns the next value directly. + *
  • Call {@link DeferredCloser#eventuallyClose(Closeable, Executor) + * closer.eventuallyClose()} for every closeable object this step creates in order to + * capture it for later closing. + *
  • Return a {@code ClosingFuture}. To turn a {@link ListenableFuture} into a {@code + * ClosingFuture} call {@link #from(ListenableFuture)}. + *
+ * + *

The same warnings about doing heavyweight operations within {@link + * ClosingFuture#transformAsync(AsyncClosingFunction, Executor)} apply here. + */ + public ClosingFuture callAsync( + final AsyncCombiningCallable combiningCallable, Executor executor) { + AsyncCallable asyncCallable = + new AsyncCallable() { + @Override + public ListenableFuture call() throws Exception { + return new Peeker(inputs).callAsync(combiningCallable, closeables); + } + + @Override + public String toString() { + return combiningCallable.toString(); + } + }; + ClosingFuture derived = + new ClosingFuture<>(futureCombiner().callAsync(asyncCallable, executor)); + derived.closeables.add(closeables, directExecutor()); + return derived; + } + + private FutureCombiner futureCombiner() { + return allMustSucceed + ? Futures.whenAllSucceed(inputFutures()) + : Futures.whenAllComplete(inputFutures()); + } + + private static final Function, FluentFuture> INNER_FUTURE = + new Function, FluentFuture>() { + @Override + public FluentFuture apply(ClosingFuture future) { + return future.future; + } + }; + + private ImmutableList> inputFutures() { + return FluentIterable.from(inputs).transform(INNER_FUTURE).toList(); + } + } + + /** + * A generic {@link Combiner} that lets you use a lambda or method reference to combine two {@link + * ClosingFuture}s. Use {@link #whenAllSucceed(ClosingFuture, ClosingFuture)} to start this + * combination. + * + * @param the type returned by the first future + * @param the type returned by the second future + */ + public static final class Combiner2 + extends Combiner { + + /** + * A function that returns a value when applied to the values of the two futures passed to + * {@link #whenAllSucceed(ClosingFuture, ClosingFuture)}. + * + * @param the type returned by the first future + * @param the type returned by the second future + * @param the type returned by the function + */ + @FunctionalInterface + public interface ClosingFunction2< + V1 extends Object, V2 extends Object, U extends Object> { + + /** + * Applies this function to two inputs, or throws an exception if unable to do so. + * + *

Any objects that are passed to {@link DeferredCloser#eventuallyClose(Closeable, + * Executor) closer.eventuallyClose()} will be closed when the {@link ClosingFuture} pipeline + * is done (but not before this method completes), even if this method throws or the pipeline + * is cancelled. + */ + U apply(DeferredCloser closer, V1 value1, V2 value2) throws Exception; + } + + /** + * A function that returns a {@link ClosingFuture} when applied to the values of the two futures + * passed to {@link #whenAllSucceed(ClosingFuture, ClosingFuture)}. + * + * @param the type returned by the first future + * @param the type returned by the second future + * @param the type returned by the function + */ + @FunctionalInterface + public interface AsyncClosingFunction2< + V1 extends Object, V2 extends Object, U extends Object> { + + /** + * Applies this function to two inputs, or throws an exception if unable to do so. + * + *

Any objects that are passed to {@link DeferredCloser#eventuallyClose(Closeable, + * Executor) closer.eventuallyClose()} will be closed when the {@link ClosingFuture} pipeline + * is done (but not before this method completes), even if this method throws or the pipeline + * is cancelled. + */ + ClosingFuture apply(DeferredCloser closer, V1 value1, V2 value2) throws Exception; + } + + private final ClosingFuture future1; + private final ClosingFuture future2; + + private Combiner2(ClosingFuture future1, ClosingFuture future2) { + super(true, ImmutableList.of(future1, future2)); + this.future1 = future1; + this.future2 = future2; + } + + /** + * Returns a new {@code ClosingFuture} pipeline step derived from the inputs by applying a + * combining function to their values. The function can use a {@link DeferredCloser} to capture + * objects to be closed when the pipeline is done. + * + *

If this combiner was returned by {@link #whenAllSucceed(ClosingFuture, ClosingFuture)} and + * any of the inputs fail, so will the returned step. + * + *

If the function throws a {@code CancellationException}, the pipeline will be cancelled. + * + *

If the function throws an {@code ExecutionException}, the cause of the thrown {@code + * ExecutionException} will be extracted and used as the failure of the derived step. + */ + public ClosingFuture call( + final ClosingFunction2 function, Executor executor) { + return call( + new CombiningCallable() { + @Override + public U call(DeferredCloser closer, Peeker peeker) throws Exception { + return function.apply(closer, peeker.getDone(future1), peeker.getDone(future2)); + } + + @Override + public String toString() { + return function.toString(); + } + }, + executor); + } + + /** + * Returns a new {@code ClosingFuture} pipeline step derived from the inputs by applying a + * {@code ClosingFuture}-returning function to their values. The function can use a {@link + * DeferredCloser} to capture objects to be closed when the pipeline is done (other than those + * captured by the returned {@link ClosingFuture}). + * + *

If this combiner was returned by {@link #whenAllSucceed(ClosingFuture, ClosingFuture)} and + * any of the inputs fail, so will the returned step. + * + *

If the function throws a {@code CancellationException}, the pipeline will be cancelled. + * + *

If the function throws an {@code ExecutionException}, the cause of the thrown {@code + * ExecutionException} will be extracted and used as the failure of the derived step. + * + *

If the function throws any other exception, it will be used as the failure of the derived + * step. + * + *

If an exception is thrown after the function creates a {@code ClosingFuture}, then none of + * the closeable objects in that {@code ClosingFuture} will be closed. + * + *

Usage guidelines for this method: + * + *

    + *
  • Use this method only when calling an API that returns a {@link ListenableFuture} or a + * {@code ClosingFuture}. If possible, prefer calling {@link #call(CombiningCallable, + * Executor)} instead, with a function that returns the next value directly. + *
  • Call {@link DeferredCloser#eventuallyClose(Closeable, Executor) + * closer.eventuallyClose()} for every closeable object this step creates in order to + * capture it for later closing. + *
  • Return a {@code ClosingFuture}. To turn a {@link ListenableFuture} into a {@code + * ClosingFuture} call {@link #from(ListenableFuture)}. + *
+ * + *

The same warnings about doing heavyweight operations within {@link + * ClosingFuture#transformAsync(AsyncClosingFunction, Executor)} apply here. + */ + public ClosingFuture callAsync( + final AsyncClosingFunction2 function, Executor executor) { + return callAsync( + new AsyncCombiningCallable() { + @Override + public ClosingFuture call(DeferredCloser closer, Peeker peeker) throws Exception { + return function.apply(closer, peeker.getDone(future1), peeker.getDone(future2)); + } + + @Override + public String toString() { + return function.toString(); + } + }, + executor); + } + } + + /** + * A generic {@link Combiner} that lets you use a lambda or method reference to combine three + * {@link ClosingFuture}s. Use {@link #whenAllSucceed(ClosingFuture, ClosingFuture, + * ClosingFuture)} to start this combination. + * + * @param the type returned by the first future + * @param the type returned by the second future + * @param the type returned by the third future + */ + public static final class Combiner3< + V1 extends Object, V2 extends Object, V3 extends Object> + extends Combiner { + /** + * A function that returns a value when applied to the values of the three futures passed to + * {@link #whenAllSucceed(ClosingFuture, ClosingFuture, ClosingFuture)}. + * + * @param the type returned by the first future + * @param the type returned by the second future + * @param the type returned by the third future + * @param the type returned by the function + */ + @FunctionalInterface + public interface ClosingFunction3< + V1 extends Object, + V2 extends Object, + V3 extends Object, + U extends Object> { + /** + * Applies this function to three inputs, or throws an exception if unable to do so. + * + *

Any objects that are passed to {@link DeferredCloser#eventuallyClose(Closeable, + * Executor) closer.eventuallyClose()} will be closed when the {@link ClosingFuture} pipeline + * is done (but not before this method completes), even if this method throws or the pipeline + * is cancelled. + */ + U apply(DeferredCloser closer, V1 value1, V2 value2, V3 v3) throws Exception; + } + + /** + * A function that returns a {@link ClosingFuture} when applied to the values of the three + * futures passed to {@link #whenAllSucceed(ClosingFuture, ClosingFuture, ClosingFuture)}. + * + * @param the type returned by the first future + * @param the type returned by the second future + * @param the type returned by the third future + * @param the type returned by the function + */ + @FunctionalInterface + public interface AsyncClosingFunction3< + V1 extends Object, + V2 extends Object, + V3 extends Object, + U extends Object> { + /** + * Applies this function to three inputs, or throws an exception if unable to do so. + * + *

Any objects that are passed to {@link DeferredCloser#eventuallyClose(Closeable, + * Executor) closer.eventuallyClose()} will be closed when the {@link ClosingFuture} pipeline + * is done (but not before this method completes), even if this method throws or the pipeline + * is cancelled. + */ + ClosingFuture apply(DeferredCloser closer, V1 value1, V2 value2, V3 value3) + throws Exception; + } + + private final ClosingFuture future1; + private final ClosingFuture future2; + private final ClosingFuture future3; + + private Combiner3( + ClosingFuture future1, ClosingFuture future2, ClosingFuture future3) { + super(true, ImmutableList.of(future1, future2, future3)); + this.future1 = future1; + this.future2 = future2; + this.future3 = future3; + } + + /** + * Returns a new {@code ClosingFuture} pipeline step derived from the inputs by applying a + * combining function to their values. The function can use a {@link DeferredCloser} to capture + * objects to be closed when the pipeline is done. + * + *

If this combiner was returned by {@link #whenAllSucceed(ClosingFuture, ClosingFuture, + * ClosingFuture)} and any of the inputs fail, so will the returned step. + * + *

If the function throws a {@code CancellationException}, the pipeline will be cancelled. + * + *

If the function throws an {@code ExecutionException}, the cause of the thrown {@code + * ExecutionException} will be extracted and used as the failure of the derived step. + */ + public ClosingFuture call( + final ClosingFunction3 function, Executor executor) { + return call( + new CombiningCallable() { + @Override + public U call(DeferredCloser closer, Peeker peeker) throws Exception { + return function.apply( + closer, + peeker.getDone(future1), + peeker.getDone(future2), + peeker.getDone(future3)); + } + + @Override + public String toString() { + return function.toString(); + } + }, + executor); + } + + /** + * Returns a new {@code ClosingFuture} pipeline step derived from the inputs by applying a + * {@code ClosingFuture}-returning function to their values. The function can use a {@link + * DeferredCloser} to capture objects to be closed when the pipeline is done (other than those + * captured by the returned {@link ClosingFuture}). + * + *

If this combiner was returned by {@link #whenAllSucceed(ClosingFuture, ClosingFuture, + * ClosingFuture)} and any of the inputs fail, so will the returned step. + * + *

If the function throws a {@code CancellationException}, the pipeline will be cancelled. + * + *

If the function throws an {@code ExecutionException}, the cause of the thrown {@code + * ExecutionException} will be extracted and used as the failure of the derived step. + * + *

If the function throws any other exception, it will be used as the failure of the derived + * step. + * + *

If an exception is thrown after the function creates a {@code ClosingFuture}, then none of + * the closeable objects in that {@code ClosingFuture} will be closed. + * + *

Usage guidelines for this method: + * + *

    + *
  • Use this method only when calling an API that returns a {@link ListenableFuture} or a + * {@code ClosingFuture}. If possible, prefer calling {@link #call(CombiningCallable, + * Executor)} instead, with a function that returns the next value directly. + *
  • Call {@link DeferredCloser#eventuallyClose(Closeable, Executor) + * closer.eventuallyClose()} for every closeable object this step creates in order to + * capture it for later closing. + *
  • Return a {@code ClosingFuture}. To turn a {@link ListenableFuture} into a {@code + * ClosingFuture} call {@link #from(ListenableFuture)}. + *
+ * + *

The same warnings about doing heavyweight operations within {@link + * ClosingFuture#transformAsync(AsyncClosingFunction, Executor)} apply here. + */ + public ClosingFuture callAsync( + final AsyncClosingFunction3 function, Executor executor) { + return callAsync( + new AsyncCombiningCallable() { + @Override + public ClosingFuture call(DeferredCloser closer, Peeker peeker) throws Exception { + return function.apply( + closer, + peeker.getDone(future1), + peeker.getDone(future2), + peeker.getDone(future3)); + } + + @Override + public String toString() { + return function.toString(); + } + }, + executor); + } + } + + /** + * A generic {@link Combiner} that lets you use a lambda or method reference to combine four + * {@link ClosingFuture}s. Use {@link #whenAllSucceed(ClosingFuture, ClosingFuture, ClosingFuture, + * ClosingFuture)} to start this combination. + * + * @param the type returned by the first future + * @param the type returned by the second future + * @param the type returned by the third future + * @param the type returned by the fourth future + */ + public static final class Combiner4< + V1 extends Object, + V2 extends Object, + V3 extends Object, + V4 extends Object> + extends Combiner { + /** + * A function that returns a value when applied to the values of the four futures passed to + * {@link #whenAllSucceed(ClosingFuture, ClosingFuture, ClosingFuture, ClosingFuture)}. + * + * @param the type returned by the first future + * @param the type returned by the second future + * @param the type returned by the third future + * @param the type returned by the fourth future + * @param the type returned by the function + */ + @FunctionalInterface + public interface ClosingFunction4< + V1 extends Object, + V2 extends Object, + V3 extends Object, + V4 extends Object, + U extends Object> { + /** + * Applies this function to four inputs, or throws an exception if unable to do so. + * + *

Any objects that are passed to {@link DeferredCloser#eventuallyClose(Closeable, + * Executor) closer.eventuallyClose()} will be closed when the {@link ClosingFuture} pipeline + * is done (but not before this method completes), even if this method throws or the pipeline + * is cancelled. + */ + U apply(DeferredCloser closer, V1 value1, V2 value2, V3 value3, V4 value4) throws Exception; + } + + /** + * A function that returns a {@link ClosingFuture} when applied to the values of the four + * futures passed to {@link #whenAllSucceed(ClosingFuture, ClosingFuture, ClosingFuture, + * ClosingFuture)}. + * + * @param the type returned by the first future + * @param the type returned by the second future + * @param the type returned by the third future + * @param the type returned by the fourth future + * @param the type returned by the function + */ + @FunctionalInterface + public interface AsyncClosingFunction4< + V1 extends Object, + V2 extends Object, + V3 extends Object, + V4 extends Object, + U extends Object> { + /** + * Applies this function to four inputs, or throws an exception if unable to do so. + * + *

Any objects that are passed to {@link DeferredCloser#eventuallyClose(Closeable, + * Executor) closer.eventuallyClose()} will be closed when the {@link ClosingFuture} pipeline + * is done (but not before this method completes), even if this method throws or the pipeline + * is cancelled. + */ + ClosingFuture apply(DeferredCloser closer, V1 value1, V2 value2, V3 value3, V4 value4) + throws Exception; + } + + private final ClosingFuture future1; + private final ClosingFuture future2; + private final ClosingFuture future3; + private final ClosingFuture future4; + + private Combiner4( + ClosingFuture future1, + ClosingFuture future2, + ClosingFuture future3, + ClosingFuture future4) { + super(true, ImmutableList.of(future1, future2, future3, future4)); + this.future1 = future1; + this.future2 = future2; + this.future3 = future3; + this.future4 = future4; + } + + /** + * Returns a new {@code ClosingFuture} pipeline step derived from the inputs by applying a + * combining function to their values. The function can use a {@link DeferredCloser} to capture + * objects to be closed when the pipeline is done. + * + *

If this combiner was returned by {@link #whenAllSucceed(ClosingFuture, ClosingFuture, + * ClosingFuture, ClosingFuture)} and any of the inputs fail, so will the returned step. + * + *

If the function throws a {@code CancellationException}, the pipeline will be cancelled. + * + *

If the function throws an {@code ExecutionException}, the cause of the thrown {@code + * ExecutionException} will be extracted and used as the failure of the derived step. + */ + public ClosingFuture call( + final ClosingFunction4 function, Executor executor) { + return call( + new CombiningCallable() { + @Override + public U call(DeferredCloser closer, Peeker peeker) throws Exception { + return function.apply( + closer, + peeker.getDone(future1), + peeker.getDone(future2), + peeker.getDone(future3), + peeker.getDone(future4)); + } + + @Override + public String toString() { + return function.toString(); + } + }, + executor); + } + + /** + * Returns a new {@code ClosingFuture} pipeline step derived from the inputs by applying a + * {@code ClosingFuture}-returning function to their values. The function can use a {@link + * DeferredCloser} to capture objects to be closed when the pipeline is done (other than those + * captured by the returned {@link ClosingFuture}). + * + *

If this combiner was returned by {@link #whenAllSucceed(ClosingFuture, ClosingFuture, + * ClosingFuture, ClosingFuture)} and any of the inputs fail, so will the returned step. + * + *

If the function throws a {@code CancellationException}, the pipeline will be cancelled. + * + *

If the function throws an {@code ExecutionException}, the cause of the thrown {@code + * ExecutionException} will be extracted and used as the failure of the derived step. + * + *

If the function throws any other exception, it will be used as the failure of the derived + * step. + * + *

If an exception is thrown after the function creates a {@code ClosingFuture}, then none of + * the closeable objects in that {@code ClosingFuture} will be closed. + * + *

Usage guidelines for this method: + * + *

    + *
  • Use this method only when calling an API that returns a {@link ListenableFuture} or a + * {@code ClosingFuture}. If possible, prefer calling {@link #call(CombiningCallable, + * Executor)} instead, with a function that returns the next value directly. + *
  • Call {@link DeferredCloser#eventuallyClose(Closeable, Executor) + * closer.eventuallyClose()} for every closeable object this step creates in order to + * capture it for later closing. + *
  • Return a {@code ClosingFuture}. To turn a {@link ListenableFuture} into a {@code + * ClosingFuture} call {@link #from(ListenableFuture)}. + *
+ * + *

The same warnings about doing heavyweight operations within {@link + * ClosingFuture#transformAsync(AsyncClosingFunction, Executor)} apply here. + */ + public ClosingFuture callAsync( + final AsyncClosingFunction4 function, Executor executor) { + return callAsync( + new AsyncCombiningCallable() { + @Override + public ClosingFuture call(DeferredCloser closer, Peeker peeker) throws Exception { + return function.apply( + closer, + peeker.getDone(future1), + peeker.getDone(future2), + peeker.getDone(future3), + peeker.getDone(future4)); + } + + @Override + public String toString() { + return function.toString(); + } + }, + executor); + } + } + + /** + * A generic {@link Combiner} that lets you use a lambda or method reference to combine five + * {@link ClosingFuture}s. Use {@link #whenAllSucceed(ClosingFuture, ClosingFuture, ClosingFuture, + * ClosingFuture, ClosingFuture)} to start this combination. + * + * @param the type returned by the first future + * @param the type returned by the second future + * @param the type returned by the third future + * @param the type returned by the fourth future + * @param the type returned by the fifth future + */ + public static final class Combiner5< + V1 extends Object, + V2 extends Object, + V3 extends Object, + V4 extends Object, + V5 extends Object> + extends Combiner { + /** + * A function that returns a value when applied to the values of the five futures passed to + * {@link #whenAllSucceed(ClosingFuture, ClosingFuture, ClosingFuture, ClosingFuture, + * ClosingFuture)}. + * + * @param the type returned by the first future + * @param the type returned by the second future + * @param the type returned by the third future + * @param the type returned by the fourth future + * @param the type returned by the fifth future + * @param the type returned by the function + */ + @FunctionalInterface + public interface ClosingFunction5< + V1 extends Object, + V2 extends Object, + V3 extends Object, + V4 extends Object, + V5 extends Object, + U extends Object> { + /** + * Applies this function to five inputs, or throws an exception if unable to do so. + * + *

Any objects that are passed to {@link DeferredCloser#eventuallyClose(Closeable, + * Executor) closer.eventuallyClose()} will be closed when the {@link ClosingFuture} pipeline + * is done (but not before this method completes), even if this method throws or the pipeline + * is cancelled. + */ + U apply(DeferredCloser closer, V1 value1, V2 value2, V3 value3, V4 value4, V5 value5) + throws Exception; + } + + /** + * A function that returns a {@link ClosingFuture} when applied to the values of the five + * futures passed to {@link #whenAllSucceed(ClosingFuture, ClosingFuture, ClosingFuture, + * ClosingFuture, ClosingFuture)}. + * + * @param the type returned by the first future + * @param the type returned by the second future + * @param the type returned by the third future + * @param the type returned by the fourth future + * @param the type returned by the fifth future + * @param the type returned by the function + */ + @FunctionalInterface + public interface AsyncClosingFunction5< + V1 extends Object, + V2 extends Object, + V3 extends Object, + V4 extends Object, + V5 extends Object, + U extends Object> { + /** + * Applies this function to five inputs, or throws an exception if unable to do so. + * + *

Any objects that are passed to {@link DeferredCloser#eventuallyClose(Closeable, + * Executor) closer.eventuallyClose()} will be closed when the {@link ClosingFuture} pipeline + * is done (but not before this method completes), even if this method throws or the pipeline + * is cancelled. + */ + ClosingFuture apply( + DeferredCloser closer, V1 value1, V2 value2, V3 value3, V4 value4, V5 value5) + throws Exception; + } + + private final ClosingFuture future1; + private final ClosingFuture future2; + private final ClosingFuture future3; + private final ClosingFuture future4; + private final ClosingFuture future5; + + private Combiner5( + ClosingFuture future1, + ClosingFuture future2, + ClosingFuture future3, + ClosingFuture future4, + ClosingFuture future5) { + super(true, ImmutableList.of(future1, future2, future3, future4, future5)); + this.future1 = future1; + this.future2 = future2; + this.future3 = future3; + this.future4 = future4; + this.future5 = future5; + } + + /** + * Returns a new {@code ClosingFuture} pipeline step derived from the inputs by applying a + * combining function to their values. The function can use a {@link DeferredCloser} to capture + * objects to be closed when the pipeline is done. + * + *

If this combiner was returned by {@link #whenAllSucceed(ClosingFuture, ClosingFuture, + * ClosingFuture, ClosingFuture, ClosingFuture)} and any of the inputs fail, so will the + * returned step. + * + *

If the function throws a {@code CancellationException}, the pipeline will be cancelled. + * + *

If the function throws an {@code ExecutionException}, the cause of the thrown {@code + * ExecutionException} will be extracted and used as the failure of the derived step. + */ + public ClosingFuture call( + final ClosingFunction5 function, Executor executor) { + return call( + new CombiningCallable() { + @Override + public U call(DeferredCloser closer, Peeker peeker) throws Exception { + return function.apply( + closer, + peeker.getDone(future1), + peeker.getDone(future2), + peeker.getDone(future3), + peeker.getDone(future4), + peeker.getDone(future5)); + } + + @Override + public String toString() { + return function.toString(); + } + }, + executor); + } + + /** + * Returns a new {@code ClosingFuture} pipeline step derived from the inputs by applying a + * {@code ClosingFuture}-returning function to their values. The function can use a {@link + * DeferredCloser} to capture objects to be closed when the pipeline is done (other than those + * captured by the returned {@link ClosingFuture}). + * + *

If this combiner was returned by {@link #whenAllSucceed(ClosingFuture, ClosingFuture, + * ClosingFuture, ClosingFuture, ClosingFuture)} and any of the inputs fail, so will the + * returned step. + * + *

If the function throws a {@code CancellationException}, the pipeline will be cancelled. + * + *

If the function throws an {@code ExecutionException}, the cause of the thrown {@code + * ExecutionException} will be extracted and used as the failure of the derived step. + * + *

If the function throws any other exception, it will be used as the failure of the derived + * step. + * + *

If an exception is thrown after the function creates a {@code ClosingFuture}, then none of + * the closeable objects in that {@code ClosingFuture} will be closed. + * + *

Usage guidelines for this method: + * + *

    + *
  • Use this method only when calling an API that returns a {@link ListenableFuture} or a + * {@code ClosingFuture}. If possible, prefer calling {@link #call(CombiningCallable, + * Executor)} instead, with a function that returns the next value directly. + *
  • Call {@link DeferredCloser#eventuallyClose(Closeable, Executor) + * closer.eventuallyClose()} for every closeable object this step creates in order to + * capture it for later closing. + *
  • Return a {@code ClosingFuture}. To turn a {@link ListenableFuture} into a {@code + * ClosingFuture} call {@link #from(ListenableFuture)}. + *
+ * + *

The same warnings about doing heavyweight operations within {@link + * ClosingFuture#transformAsync(AsyncClosingFunction, Executor)} apply here. + */ + public ClosingFuture callAsync( + final AsyncClosingFunction5 function, Executor executor) { + return callAsync( + new AsyncCombiningCallable() { + @Override + public ClosingFuture call(DeferredCloser closer, Peeker peeker) throws Exception { + return function.apply( + closer, + peeker.getDone(future1), + peeker.getDone(future2), + peeker.getDone(future3), + peeker.getDone(future4), + peeker.getDone(future5)); + } + + @Override + public String toString() { + return function.toString(); + } + }, + executor); + } + } + + @Override + public String toString() { + // TODO(dpb): Better toString, in the style of Futures.transform etc. + return toStringHelper(this).add("state", state.get()).addValue(future).toString(); + } + + @Override + protected void finalize() { + if (state.get().equals(OPEN)) { + logger.log(SEVERE, "Uh oh! An open ClosingFuture has leaked and will close: {0}", this); + FluentFuture unused = finishToFuture(); + } + } + + private static void closeQuietly(final AutoCloseable closeable, Executor executor) { + if (closeable == null) { + return; + } + try { + executor.execute( + new Runnable() { + @Override + public void run() { + try { + closeable.close(); + } catch (Exception e) { + logger.log(WARNING, "thrown by close()", e); + } + } + }); + } catch (RejectedExecutionException e) { + if (logger.isLoggable(WARNING)) { + logger.log( + WARNING, String.format("while submitting close to %s; will close inline", executor), e); + } + closeQuietly(closeable, directExecutor()); + } + } + + private void checkAndUpdateState(State oldState, State newState) { + checkState( + compareAndUpdateState(oldState, newState), + "Expected state to be %s, but it was %s", + oldState, + newState); + } + + private boolean compareAndUpdateState(State oldState, State newState) { + return state.compareAndSet(oldState, newState); + } + + // TODO(dpb): Should we use a pair of ArrayLists instead of an IdentityHashMap? + private static final class CloseableList extends IdentityHashMap + implements Closeable { + private final DeferredCloser closer = new DeferredCloser(this); + private volatile boolean closed; + private volatile CountDownLatch whenClosed; + + ListenableFuture applyClosingFunction( + ClosingFunction transformation, V input) throws Exception { + // TODO(dpb): Consider ways to defer closing without creating a separate CloseableList. + CloseableList newCloseables = new CloseableList(); + try { + return immediateFuture(transformation.apply(newCloseables.closer, input)); + } finally { + add(newCloseables, directExecutor()); + } + } + + FluentFuture applyAsyncClosingFunction( + AsyncClosingFunction transformation, V input) throws Exception { + // TODO(dpb): Consider ways to defer closing without creating a separate CloseableList. + CloseableList newCloseables = new CloseableList(); + try { + ClosingFuture closingFuture = transformation.apply(newCloseables.closer, input); + closingFuture.becomeSubsumedInto(newCloseables); + return closingFuture.future; + } finally { + add(newCloseables, directExecutor()); + } + } + + @Override + public void close() { + if (closed) { + return; + } + synchronized (this) { + if (closed) { + return; + } + closed = true; + } + for (Entry entry : entrySet()) { + closeQuietly(entry.getKey(), entry.getValue()); + } + clear(); + if (whenClosed != null) { + whenClosed.countDown(); + } + } + + void add(AutoCloseable closeable, Executor executor) { + checkNotNull(executor); + if (closeable == null) { + return; + } + synchronized (this) { + if (!closed) { + put(closeable, executor); + return; + } + } + closeQuietly(closeable, executor); + } + + /** + * Returns a latch that reaches zero when this objects' deferred closeables have been closed. + */ + CountDownLatch whenClosedCountDown() { + if (closed) { + return new CountDownLatch(0); + } + synchronized (this) { + if (closed) { + return new CountDownLatch(0); + } + checkState(whenClosed == null); + return whenClosed = new CountDownLatch(1); + } + } + } + + /** + * Returns an object that can be used to wait until this objects' deferred closeables have all had + * {@link Runnable}s that close them submitted to each one's closing {@link Executor}. + */ + @VisibleForTesting + CountDownLatch whenClosedCountDown() { + return closeables.whenClosedCountDown(); + } + + /** The state of a {@link CloseableList}. */ + enum State { + /** The {@link CloseableList} has not been subsumed or closed. */ + OPEN, + + /** + * The {@link CloseableList} has been subsumed into another. It may not be closed or subsumed + * into any other. + */ + SUBSUMED, + + /** + * Some {@link ListenableFuture} has a callback attached that will close the {@link + * CloseableList}, but it has not yet run. The {@link CloseableList} may not be subsumed. + */ + WILL_CLOSE, + + /** + * The callback that closes the {@link CloseableList} is running, but it has not completed. The + * {@link CloseableList} may not be subsumed. + */ + CLOSING, + + /** The {@link CloseableList} has been closed. It may not be further subsumed. */ + CLOSED, + + /** + * {@link ClosingFuture#finishToValueAndCloser(ValueAndCloserConsumer, Executor)} has been + * called. The step may not be further subsumed, nor may {@link #finishToFuture()} be called. + */ + WILL_CREATE_VALUE_AND_CLOSER, + } +} diff --git a/src/main/java/com/google/common/util/concurrent/CombinedFuture.java b/src/main/java/com/google/common/util/concurrent/CombinedFuture.java index b46c5ad..cec43f4 100644 --- a/src/main/java/com/google/common/util/concurrent/CombinedFuture.java +++ b/src/main/java/com/google/common/util/concurrent/CombinedFuture.java @@ -89,7 +89,6 @@ final class CombinedFuture extends AggregateFuture { private abstract class CombinedFutureInterruptibleTask extends InterruptibleTask { private final Executor listenerExecutor; - boolean thrownByExecute = true; CombinedFutureInterruptibleTask(Executor listenerExecutor) { this.listenerExecutor = checkNotNull(listenerExecutor); @@ -104,9 +103,7 @@ final class CombinedFuture extends AggregateFuture { try { listenerExecutor.execute(this); } catch (RejectedExecutionException e) { - if (thrownByExecute) { - CombinedFuture.this.setException(e); - } + CombinedFuture.this.setException(e); } } @@ -153,7 +150,6 @@ final class CombinedFuture extends AggregateFuture { @Override ListenableFuture runInterruptibly() throws Exception { - thrownByExecute = false; ListenableFuture result = callable.call(); return checkNotNull( result, @@ -184,7 +180,6 @@ final class CombinedFuture extends AggregateFuture { @Override V runInterruptibly() throws Exception { - thrownByExecute = false; return callable.call(); } diff --git a/src/main/java/com/google/common/util/concurrent/ExecutionSequencer.java b/src/main/java/com/google/common/util/concurrent/ExecutionSequencer.java index 88929b6..9d050e2 100644 --- a/src/main/java/com/google/common/util/concurrent/ExecutionSequencer.java +++ b/src/main/java/com/google/common/util/concurrent/ExecutionSequencer.java @@ -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). * - *

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. + *

This class has limited support for cancellation and other "early completion": + * + *

    + *
  • 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 unstarted 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: Even if you cancel every precededing {@code + * Future} returned by this class, the next task may still have to wait.. + *
  • Once an {@code AsyncCallable} returns a {@code Future}, this class considers that task to + * be "done" as soon as that {@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: Your {@code AsyncCallable} should not complete its {@code Future} until it is + * safe for the next task to start. + *
+ * + *

An additional limitation: this class serializes execution of tasks but not any + * listeners of those tasks. + * + *

This class is similar to {@link MoreExecutors#newSequentialExecutor}. This class is different + * in a few ways: + * + *

    + *
  • Each task may be associated with a different executor. + *
  • Tasks may be of type {@code AsyncCallable}. + *
  • Running tasks cannot be interrupted. (Note that {@code newSequentialExecutor} does + * not return {@code Future} objects, so it doesn't support interruption directly, either. + * However, utilities that use that executor have the ability to interrupt tasks + * running on it. This class, by contrast, does not expose an {@code Executor} API.) + *
+ * + *

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> ref = - new AtomicReference<>(immediateFuture(null)); + private final AtomicReference> 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: + * + *

    + *
  • 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 + *
  • 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. + *
  • If this field's value is some other thread object, we know that it's not our thread. + *
  • 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 + *
  • 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 + *
+ * + * 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 ListenableFuture submit(final Callable callable, Executor executor) { checkNotNull(callable); + checkNotNull(executor); return submitAsync( new AsyncCallable() { @Override @@ -92,12 +164,13 @@ public final class ExecutionSequencer { public ListenableFuture submitAsync( final AsyncCallable callable, final Executor executor) { checkNotNull(callable); - final AtomicReference runningState = new AtomicReference<>(NOT_RUN); + checkNotNull(executor); + final TaskNonReentrantExecutor taskExecutor = new TaskNonReentrantExecutor(executor, this); final AsyncCallable task = new AsyncCallable() { @Override public ListenableFuture 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 newFuture = SettableFuture.create(); + final SettableFuture newFuture = SettableFuture.create(); - final ListenableFuture oldFuture = ref.getAndSet(newFuture); + final ListenableFuture oldFuture = ref.getAndSet(newFuture); // Invoke our task once the previous future completes. - final ListenableFuture taskFuture = - Futures.submitAsync( - task, - new Executor() { - @Override - public void execute(Runnable runnable) { - oldFuture.addListener(runnable, executor); - } - }); + final TrustedListenableFutureTask taskFuture = TrustedListenableFutureTask.create(task); + oldFuture.addListener(taskFuture, taskExecutor); final ListenableFuture 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(). + * + *

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 + 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); + } + } } diff --git a/src/main/java/com/google/common/util/concurrent/FluentFuture.java b/src/main/java/com/google/common/util/concurrent/FluentFuture.java index 615f7e0..5574cc2 100644 --- a/src/main/java/com/google/common/util/concurrent/FluentFuture.java +++ b/src/main/java/com/google/common/util/concurrent/FluentFuture.java @@ -49,7 +49,7 @@ import java.util.concurrent.TimeoutException; * debugging, and cancellation. Examples of frameworks include: * *

* *

{@link java.util.concurrent.CompletableFuture} / {@link java.util.concurrent.CompletionStage} diff --git a/src/main/java/com/google/common/util/concurrent/Futures.java b/src/main/java/com/google/common/util/concurrent/Futures.java index e57044b..640f5aa 100644 --- a/src/main/java/com/google/common/util/concurrent/Futures.java +++ b/src/main/java/com/google/common/util/concurrent/Futures.java @@ -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: * * * *

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}. 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 immediateVoidFuture() { + return (ListenableFuture) 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 ListenableFuture submit(Callable 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 submit(Runnable runnable, Executor executor) { @@ -258,9 +269,7 @@ public final class Futures extends GwtFuturesCatchingSpecialization { * } * *

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 { * } * *

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 { * } * *

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. * *

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 { * } * *

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. * *

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 { private final boolean allMustSucceed; @@ -713,7 +711,6 @@ public final class Futures extends GwtFuturesCatchingSpecialization { * *

Canceling this future will attempt to cancel all the component futures. */ - // TODO(cpovirk): Remove this public ListenableFuture call(Callable combiner, Executor executor) { return new CombinedFuture(futures, allMustSucceed, executor, combiner); } @@ -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. * *

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. * + *

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. + * *

Example: * *

{@code
@@ -1023,9 +1025,7 @@ public final class Futures extends GwtFuturesCatchingSpecialization {
    * }
* *

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. * *

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 getDone(Future 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 getChecked(Future future, Class exceptionClass) throws X { @@ -1205,7 +1210,6 @@ public final class Futures extends GwtFuturesCatchingSpecialization { * @since 28.0 */ @Beta - @GwtIncompatible // reflection public static V getChecked( Future future, Class 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 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 getUnchecked(Future future) { checkNotNull(future); try { diff --git a/src/main/java/com/google/common/util/concurrent/ListenableFuture.java b/src/main/java/com/google/common/util/concurrent/ListenableFuture.java index 371c5b5..9fbdb77 100644 --- a/src/main/java/com/google/common/util/concurrent/ListenableFuture.java +++ b/src/main/java/com/google/common/util/concurrent/ListenableFuture.java @@ -40,7 +40,7 @@ import java.util.concurrent.RejectedExecutionException; * frameworks include: * *

* *

The main purpose of {@link #addListener addListener} is to support this chaining. You will @@ -112,20 +112,10 @@ public interface ListenableFuture extends Future { * thrown by {@linkplain MoreExecutors#directExecutor direct execution}) will be caught and * logged. * - *

Note: For fast, lightweight listeners that would be safe to execute in any thread, consider - * {@link MoreExecutors#directExecutor}. Otherwise, avoid it. Heavyweight {@code directExecutor} - * listeners can cause problems, and these problems can be difficult to reproduce because they - * depend on timing. For example: - * - *

    - *
  • The listener may be executed by the caller of {@code addListener}. That caller may be a - * UI thread or other latency-sensitive thread. This can harm UI responsiveness. - *
  • The listener may be executed by the thread that completes this {@code Future}. That - * thread may be an internal system thread such as an RPC network thread. Blocking that - * thread may stall progress of the whole system. It may even cause a deadlock. - *
  • The listener may delay other listeners, even listeners that are not themselves {@code - * directExecutor} listeners. - *
+ *

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}. * *

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 diff --git a/src/main/java/com/google/common/util/concurrent/ListenableFutureTask.java b/src/main/java/com/google/common/util/concurrent/ListenableFutureTask.java index e423275..c08f9f0 100644 --- a/src/main/java/com/google/common/util/concurrent/ListenableFutureTask.java +++ b/src/main/java/com/google/common/util/concurrent/ListenableFutureTask.java @@ -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 extends FutureTask 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() { diff --git a/src/main/java/com/google/common/util/concurrent/MoreExecutors.java b/src/main/java/com/google/common/util/concurrent/MoreExecutors.java index bb319f8..1b9c4ed 100644 --- a/src/main/java/com/google/common/util/concurrent/MoreExecutors.java +++ b/src/main/java/com/google/common/util/concurrent/MoreExecutors.java @@ -27,8 +27,6 @@ import com.google.common.base.Throwables; import com.google.common.collect.Lists; import com.google.common.collect.Queues; import com.google.common.util.concurrent.ForwardingListenableFuture.SimpleForwardingListenableFuture; - - import java.lang.reflect.InvocationTargetException; import java.time.Duration; import java.util.Collection; @@ -49,12 +47,11 @@ import java.util.concurrent.ScheduledFuture; import java.util.concurrent.ScheduledThreadPoolExecutor; import java.util.concurrent.ThreadFactory; import java.util.concurrent.ThreadPoolExecutor; -import java.util.concurrent.ThreadPoolExecutor.CallerRunsPolicy; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; /** - * Factory and utility methods for {@link java.util.concurrent.Executor}, {@link ExecutorService}, + * Factory and utility methods for {@link Executor}, {@link ExecutorService}, * and {@link ThreadFactory}. * * @author Eric Fellheimer @@ -309,10 +306,8 @@ public final class MoreExecutors { * - Shutdown: runningTasks > 0 and shutdown == true * - Terminated: runningTasks == 0 and shutdown == true */ - private int runningTasks = 0; - private boolean shutdown = false; @Override @@ -401,10 +396,11 @@ public final class MoreExecutors { /** * Creates an executor service that runs each task in the thread that invokes {@code - * execute/submit}, as in {@link CallerRunsPolicy} This applies both to individually submitted - * tasks and to collections of tasks submitted via {@code invokeAll} or {@code invokeAny}. In the - * latter case, tasks will run serially on the calling thread. Tasks are run to completion before - * a {@code Future} is returned to the caller (unless the executor has been shutdown). + * execute/submit}, as in {@code ThreadPoolExecutor.CallerRunsPolicy}. This applies both to + * individually submitted tasks and to collections of tasks submitted via {@code invokeAll} or + * {@code invokeAny}. In the latter case, tasks will run serially on the calling thread. Tasks are + * run to completion before a {@code Future} is returned to the caller (unless the executor has + * been shutdown). * *

Although all tasks are immediately executed in the thread that submitted the task, this * {@code ExecutorService} imposes a small locking overhead on each task submission in order to @@ -431,7 +427,33 @@ public final class MoreExecutors { /** * Returns an {@link Executor} that runs each task in the thread that invokes {@link - * Executor#execute execute}, as in {@link CallerRunsPolicy}. + * Executor#execute execute}, as in {@code ThreadPoolExecutor.CallerRunsPolicy}. + * + *

This executor is appropriate for tasks that are lightweight and not deeply chained. + * Inappropriate {@code directExecutor} usage can cause problems, and these problems can be + * difficult to reproduce because they depend on timing. For example: + * + *

    + *
  • A call like {@code future.transform(function, directExecutor())} may execute the function + * immediately in the thread that is calling {@code transform}. (This specific case happens + * if the future is already completed.) If {@code transform} call was made from a UI thread + * or other latency-sensitive thread, a heavyweight function can harm responsiveness. + *
  • If the task will be executed later, consider which thread will trigger the execution -- + * since that thread will execute the task inline. If the thread is a shared system thread + * like an RPC network thread, a heavyweight task can stall progress of the whole system or + * even deadlock it. + *
  • If many tasks will be triggered by the same event, one heavyweight task may delay other + * tasks -- even tasks that are not themselves {@code directExecutor} tasks. + *
  • If many such tasks are chained together (such as with {@code + * future.transform(...).transform(...).transform(...)....}), they may overflow the stack. + * (In simple cases, callers can avoid this by registering all tasks with the same {@link + * MoreExecutors#newSequentialExecutor} wrapper around {@code directExecutor()}. More + * complex cases may require using thread pools or making deeper changes.) + *
+ * + * Additionally, beware of executing tasks with {@code directExecutor} while holding a lock. Since + * the task you submit to the executor (or any other arbitrary work the executor does) may do slow + * work or acquire other locks, you risk deadlocks. * *

This instance is equivalent to: * @@ -446,7 +468,6 @@ public final class MoreExecutors { *

This should be preferred to {@link #newDirectExecutorService()} because implementing the * {@link ExecutorService} subinterface necessitates significant performance overhead. * - * * @since 18.0 */ public static Executor directExecutor() { @@ -700,7 +721,8 @@ public final class MoreExecutors { * An implementation of {@link ExecutorService#invokeAny} for {@link ListeningExecutorService} * implementations. */ - @GwtIncompatible static T invokeAnyImpl( + @GwtIncompatible + static T invokeAnyImpl( ListeningExecutorService executorService, Collection> tasks, boolean timed, @@ -715,7 +737,8 @@ public final class MoreExecutors { * implementations. */ @SuppressWarnings("GoodTime") // should accept a java.time.Duration - @GwtIncompatible static T invokeAnyImpl( + @GwtIncompatible + static T invokeAnyImpl( ListeningExecutorService executorService, Collection> tasks, boolean timed, @@ -907,7 +930,6 @@ public final class MoreExecutors { * right before each task is run. The renaming is best effort, if a {@link SecurityManager} * prevents the renaming then it will be skipped but the tasks will still execute. * - * * @param executor The executor to decorate * @param nameSupplier The source of names for each task */ @@ -931,7 +953,6 @@ public final class MoreExecutors { * right before each task is run. The renaming is best effort, if a {@link SecurityManager} * prevents the renaming then it will be skipped but the tasks will still execute. * - * * @param service The executor to decorate * @param nameSupplier The source of names for each task */ @@ -961,7 +982,6 @@ public final class MoreExecutors { * right before each task is run. The renaming is best effort, if a {@link SecurityManager} * prevents the renaming then it will be skipped but the tasks will still execute. * - * * @param service The executor to decorate * @param nameSupplier The source of names for each task */ @@ -1007,7 +1027,6 @@ public final class MoreExecutors { * @since 28.0 */ @Beta - @GwtIncompatible // java.time.Duration public static boolean shutdownAndAwaitTermination(ExecutorService service, Duration timeout) { return shutdownAndAwaitTermination(service, toNanosSaturated(timeout), TimeUnit.NANOSECONDS); @@ -1038,7 +1057,6 @@ public final class MoreExecutors { * @since 17.0 */ @Beta - @GwtIncompatible // concurrency @SuppressWarnings("GoodTime") // should accept a java.time.Duration public static boolean shutdownAndAwaitTermination( @@ -1078,26 +1096,12 @@ public final class MoreExecutors { return delegate; } return new Executor() { - boolean thrownFromDelegate = true; - @Override - public void execute(final Runnable command) { + public void execute(Runnable command) { try { - delegate.execute( - new Runnable() { - @Override - public void run() { - thrownFromDelegate = false; - command.run(); - } - }); + delegate.execute(command); } catch (RejectedExecutionException e) { - if (thrownFromDelegate) { - // wrap exception? - future.setException(e); - } - // otherwise it must have been thrown from a transitive call and the delegate runnable - // should have handled it. + future.setException(e); } } }; diff --git a/src/main/java/com/google/common/util/concurrent/OverflowAvoidingLockSupport.java b/src/main/java/com/google/common/util/concurrent/OverflowAvoidingLockSupport.java new file mode 100644 index 0000000..8f1ccac --- /dev/null +++ b/src/main/java/com/google/common/util/concurrent/OverflowAvoidingLockSupport.java @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2020 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.util.concurrent; + +import static java.lang.Math.min; + +import java.util.concurrent.locks.LockSupport; + +/** + * Works around an android bug, where parking for more than INT_MAX seconds can produce an abort + * signal on 32 bit devices running Android Q. + */ +final class OverflowAvoidingLockSupport { + // Represents the max nanoseconds representable on a linux timespec with a 32 bit tv_sec + static final long MAX_NANOSECONDS_THRESHOLD = (1L + Integer.MAX_VALUE) * 1_000_000_000L - 1L; + + private OverflowAvoidingLockSupport() {} + + static void parkNanos(Object blocker, long nanos) { + // Even in the extremely unlikely event that a thread unblocks itself early after only 68 years, + // this is indistinguishable from a spurious wakeup, which LockSupport allows. + LockSupport.parkNanos(blocker, min(nanos, MAX_NANOSECONDS_THRESHOLD)); + } +} diff --git a/src/main/java/com/google/common/util/concurrent/SequentialExecutor.java b/src/main/java/com/google/common/util/concurrent/SequentialExecutor.java index 6b1d564..f258f47 100644 --- a/src/main/java/com/google/common/util/concurrent/SequentialExecutor.java +++ b/src/main/java/com/google/common/util/concurrent/SequentialExecutor.java @@ -19,11 +19,10 @@ import static com.google.common.util.concurrent.SequentialExecutor.WorkerRunning import static com.google.common.util.concurrent.SequentialExecutor.WorkerRunningState.QUEUED; import static com.google.common.util.concurrent.SequentialExecutor.WorkerRunningState.QUEUING; import static com.google.common.util.concurrent.SequentialExecutor.WorkerRunningState.RUNNING; +import static java.lang.System.identityHashCode; import com.google.common.annotations.GwtIncompatible; import com.google.common.base.Preconditions; - - import java.util.ArrayDeque; import java.util.Deque; import java.util.concurrent.Executor; @@ -87,7 +86,7 @@ final class SequentialExecutor implements Executor { * Adds a task to the queue and makes sure a worker thread is running. * *

If this method throws, e.g. a {@code RejectedExecutionException} from the delegate executor, - * execution of tasks will stop until a call to this method or to {@link #resume()} is made. + * execution of tasks will stop until a call to this method is made. */ @Override public void execute(final Runnable task) { @@ -116,6 +115,11 @@ final class SequentialExecutor implements Executor { public void run() { task.run(); } + + @Override + public String toString() { + return task.toString(); + } }; queue.add(submittedTask); workerRunningState = QUEUING; @@ -160,8 +164,9 @@ final class SequentialExecutor implements Executor { } /** Worker that runs tasks from {@link #queue} until it is empty. */ - private final class QueueWorker implements Runnable { + Runnable task; + @Override public void run() { try { @@ -193,7 +198,6 @@ final class SequentialExecutor implements Executor { boolean hasSetRunning = false; try { while (true) { - Runnable task; synchronized (queue) { // Choose whether this thread will run or not after acquiring the lock on the first // iteration @@ -224,6 +228,8 @@ final class SequentialExecutor implements Executor { task.run(); } catch (RuntimeException e) { log.log(Level.SEVERE, "Exception while executing runnable " + task, e); + } finally { + task = null; } } } finally { @@ -235,5 +241,20 @@ final class SequentialExecutor implements Executor { } } } + + @SuppressWarnings("GuardedBy") + @Override + public String toString() { + Runnable currentlyRunning = task; + if (currentlyRunning != null) { + return "SequentialExecutorWorker{running=" + currentlyRunning + "}"; + } + return "SequentialExecutorWorker{state=" + workerRunningState + "}"; + } + } + + @Override + public String toString() { + return "SequentialExecutor@" + identityHashCode(this) + "{" + executor + "}"; } } diff --git a/src/main/java/com/google/common/util/concurrent/ServiceManager.java b/src/main/java/com/google/common/util/concurrent/ServiceManager.java index 65536ae..b10386a 100644 --- a/src/main/java/com/google/common/util/concurrent/ServiceManager.java +++ b/src/main/java/com/google/common/util/concurrent/ServiceManager.java @@ -31,7 +31,6 @@ import static com.google.common.util.concurrent.Service.State.STOPPING; import static com.google.common.util.concurrent.Service.State.TERMINATED; import static java.util.concurrent.TimeUnit.MILLISECONDS; -import com.google.common.annotations.Beta; import com.google.common.annotations.GwtIncompatible; import com.google.common.base.Function; import com.google.common.base.MoreObjects; @@ -40,7 +39,6 @@ import com.google.common.collect.Collections2; import com.google.common.collect.ImmutableCollection; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; -import com.google.common.collect.ImmutableMultimap; import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSetMultimap; import com.google.common.collect.Lists; @@ -51,9 +49,6 @@ import com.google.common.collect.Multiset; import com.google.common.collect.Ordering; import com.google.common.collect.SetMultimap; import com.google.common.util.concurrent.Service.State; - - - import java.lang.ref.WeakReference; import java.time.Duration; import java.util.Collections; @@ -121,9 +116,8 @@ import java.util.logging.Logger; * @author Luke Sandberg * @since 14.0 */ -@Beta @GwtIncompatible -public final class ServiceManager { +public final class ServiceManager implements ServiceManagerBridge { private static final Logger logger = Logger.getLogger(ServiceManager.class.getName()); private static final ListenerCallQueue.Event HEALTHY_EVENT = new ListenerCallQueue.Event() { @@ -159,7 +153,6 @@ public final class ServiceManager { * @author Luke Sandberg * @since 15.0 (present as an interface in 14.0) */ - @Beta // Should come out of Beta when ServiceManager does public abstract static class Listener { /** * Called when the service initially becomes healthy. @@ -245,8 +238,9 @@ public final class ServiceManager { * during {@code Executor.execute} (e.g., a {@code RejectedExecutionException}) will be caught and * logged. * - *

For fast, lightweight listeners that would be safe to execute in any thread, consider - * calling {@link #addListener(Listener)}. + *

When selecting an executor, note that {@code directExecutor} is dangerous in some cases. See + * the discussion in the {@link ListenableFuture#addListener ListenableFuture.addListener} + * documentation. * * @param listener the listener to run when the manager changes state * @param executor the executor in which the listeners callback methods will be run. @@ -255,26 +249,6 @@ public final class ServiceManager { state.addListener(listener, executor); } - /** - * Registers a {@link Listener} to be run when this {@link ServiceManager} changes state. The - * listener will not have previous state changes replayed, so it is suggested that listeners are - * added before any of the managed services are {@linkplain Service#startAsync started}. - * - *

{@code addListener} guarantees execution ordering across calls to a given listener but not - * across calls to multiple listeners. Specifically, a given listener will have its callbacks - * invoked in the same order as the underlying service enters those states. Additionally, at most - * one of the listener's callbacks will execute at once. However, multiple listeners' callbacks - * may execute concurrently, and listeners may execute in an order different from the one in which - * they were registered. - * - *

RuntimeExceptions thrown by a listener will be caught and logged. - * - * @param listener the listener to run when the manager changes state - */ - public void addListener(Listener listener) { - state.addListener(listener, directExecutor()); - } - /** * Initiates service {@linkplain Service#startAsync startup} on all the services being managed. It * is only valid to call this method if all of the services are {@linkplain State#NEW new}. @@ -283,7 +257,6 @@ public final class ServiceManager { * @throws IllegalStateException if any of the Services are not {@link State#NEW new} when the * method is called. */ - public ServiceManager startAsync() { for (Service service : services) { State state = service.state(); @@ -353,7 +326,6 @@ public final class ServiceManager { * * @return this */ - public ServiceManager stopAsync() { for (Service service : services) { service.stopAsync(); @@ -363,8 +335,8 @@ public final class ServiceManager { /** * Waits for the all the services to reach a terminal state. After this method returns all - * services will either be {@linkplain Service.State#TERMINATED terminated} or {@linkplain - * Service.State#FAILED failed}. + * services will either be {@linkplain State#TERMINATED terminated} or {@linkplain + * State#FAILED failed}. */ public void awaitStopped() { state.awaitStopped(); @@ -372,8 +344,8 @@ public final class ServiceManager { /** * Waits for the all the services to reach a terminal state for no more than the given time. After - * this method returns all services will either be {@linkplain Service.State#TERMINATED - * terminated} or {@linkplain Service.State#FAILED failed}. + * this method returns all services will either be {@linkplain State#TERMINATED + * terminated} or {@linkplain State#FAILED failed}. * * @param timeout the maximum time to wait * @throws TimeoutException if not all of the services have stopped within the deadline @@ -385,8 +357,8 @@ public final class ServiceManager { /** * Waits for the all the services to reach a terminal state for no more than the given time. After - * this method returns all services will either be {@linkplain Service.State#TERMINATED - * terminated} or {@linkplain Service.State#FAILED failed}. + * this method returns all services will either be {@linkplain State#TERMINATED + * terminated} or {@linkplain State#FAILED failed}. * * @param timeout the maximum time to wait * @param unit the time unit of the timeout argument @@ -417,8 +389,11 @@ public final class ServiceManager { * *

N.B. This snapshot is guaranteed to be consistent, i.e. the set of states returned will * correspond to a point in time view of the services. + * + * @since 29.0 (present with return type {@code ImmutableMultimap} since 14.0) */ - public ImmutableMultimap servicesByState() { + @Override + public ImmutableSetMultimap servicesByState() { return state.servicesByState(); } @@ -479,7 +454,6 @@ public final class ServiceManager { */ final Monitor.Guard awaitHealthGuard = new AwaitHealthGuard(); - final class AwaitHealthGuard extends Monitor.Guard { AwaitHealthGuard() { super(ServiceManagerState.this.monitor); @@ -498,7 +472,6 @@ public final class ServiceManager { /** Controls how long to wait for all services to reach a terminal state. */ final Monitor.Guard stoppedGuard = new StoppedGuard(); - final class StoppedGuard extends Monitor.Guard { StoppedGuard() { super(ServiceManagerState.this.monitor); @@ -615,7 +588,7 @@ public final class ServiceManager { } } - ImmutableMultimap servicesByState() { + ImmutableSetMultimap servicesByState() { ImmutableSetMultimap.Builder builder = ImmutableSetMultimap.builder(); monitor.enter(); try { diff --git a/src/main/java/com/google/common/util/concurrent/ServiceManagerBridge.java b/src/main/java/com/google/common/util/concurrent/ServiceManagerBridge.java new file mode 100644 index 0000000..a9300a6 --- /dev/null +++ b/src/main/java/com/google/common/util/concurrent/ServiceManagerBridge.java @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2020 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.util.concurrent; + +import com.google.common.annotations.GwtIncompatible; +import com.google.common.collect.ImmutableMultimap; +import com.google.common.util.concurrent.Service.State; + +/** + * Superinterface of {@link ServiceManager} to introduce a bridge method for {@code + * servicesByState()}, to ensure binary compatibility with older Guava versions that specified + * {@code servicesByState()} to return {@code ImmutableMultimap}. + */ +@GwtIncompatible +interface ServiceManagerBridge { + ImmutableMultimap servicesByState(); +} diff --git a/src/main/java/com/google/common/util/concurrent/SmoothRateLimiter.java b/src/main/java/com/google/common/util/concurrent/SmoothRateLimiter.java index d78d7da..4905dfc 100644 --- a/src/main/java/com/google/common/util/concurrent/SmoothRateLimiter.java +++ b/src/main/java/com/google/common/util/concurrent/SmoothRateLimiter.java @@ -31,6 +31,11 @@ abstract class SmoothRateLimiter extends RateLimiter { * compute, for an incoming request, the appropriate throttle time, and make the calling thread * wait as much. * + * The primary feature of a RateLimiter is its "stable rate", the maximum rate that it should + * allow in normal conditions. This is enforced by "throttling" incoming requests as needed. For + * example, we could compute the appropriate throttle time for an incoming request, and make the + * calling thread wait for that time. + * * The simplest way to maintain a rate of QPS is to keep the timestamp of the last granted * request, and ensure that (1/QPS) seconds have elapsed since then. For example, for a rate of * QPS=5 (5 tokens per second), if we ensure that a request isn't granted earlier than 200ms after diff --git a/src/main/java/com/google/common/util/concurrent/UncaughtExceptionHandlers.java b/src/main/java/com/google/common/util/concurrent/UncaughtExceptionHandlers.java index 6e8abba..c0c2600 100644 --- a/src/main/java/com/google/common/util/concurrent/UncaughtExceptionHandlers.java +++ b/src/main/java/com/google/common/util/concurrent/UncaughtExceptionHandlers.java @@ -65,7 +65,6 @@ public final class UncaughtExceptionHandlers { @Override public void uncaughtException(Thread t, Throwable e) { try { - // cannot use FormattingLogger due to a dependency loop logger.log( SEVERE, String.format(Locale.ROOT, "Caught an exception in %s. Shutting down.", t), e); } catch (Throwable errorInLogging) { diff --git a/src/main/java/com/google/common/util/concurrent/Uninterruptibles.java b/src/main/java/com/google/common/util/concurrent/Uninterruptibles.java index 4ed6faf..8c1d17f 100644 --- a/src/main/java/com/google/common/util/concurrent/Uninterruptibles.java +++ b/src/main/java/com/google/common/util/concurrent/Uninterruptibles.java @@ -14,6 +14,7 @@ package com.google.common.util.concurrent; +import static com.google.common.base.Verify.verify; import static com.google.common.util.concurrent.Internal.toNanosSaturated; import static java.util.concurrent.TimeUnit.NANOSECONDS; @@ -21,17 +22,18 @@ import com.google.common.annotations.Beta; import com.google.common.annotations.GwtCompatible; import com.google.common.annotations.GwtIncompatible; import com.google.common.base.Preconditions; - import java.time.Duration; import java.util.concurrent.BlockingQueue; import java.util.concurrent.CancellationException; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; import java.util.concurrent.Future; import java.util.concurrent.Semaphore; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import java.util.concurrent.locks.Condition; +import java.util.concurrent.locks.Lock; /** * Utilities for treating interruptible operations as uninterruptible. In all cases, if a thread is @@ -73,7 +75,6 @@ public final class Uninterruptibles { * * @since 28.0 */ - // TODO(cpovirk): Consider being more strict. @GwtIncompatible // concurrency @Beta public static boolean awaitUninterruptibly(CountDownLatch latch, Duration timeout) { @@ -84,7 +85,6 @@ public final class Uninterruptibles { * Invokes {@code latch.}{@link CountDownLatch#await(long, TimeUnit) await(timeout, unit)} * uninterruptibly. */ - // TODO(cpovirk): Consider being more strict. @GwtIncompatible // concurrency @SuppressWarnings("GoodTime") // should accept a java.time.Duration public static boolean awaitUninterruptibly(CountDownLatch latch, long timeout, TimeUnit unit) { @@ -228,7 +228,6 @@ public final class Uninterruptibles { * @throws ExecutionException if the computation threw an exception * @throws CancellationException if the computation was cancelled */ - public static V getUninterruptibly(Future future) throws ExecutionException { boolean interrupted = false; try { @@ -265,7 +264,6 @@ public final class Uninterruptibles { * @throws TimeoutException if the wait timed out * @since 28.0 */ - @GwtIncompatible // java.time.Duration @Beta public static V getUninterruptibly(Future future, Duration timeout) @@ -291,7 +289,6 @@ public final class Uninterruptibles { * @throws CancellationException if the computation was cancelled * @throws TimeoutException if the wait timed out */ - @GwtIncompatible // TODO @SuppressWarnings("GoodTime") // should accept a java.time.Duration public static V getUninterruptibly(Future future, long timeout, TimeUnit unit) @@ -471,6 +468,104 @@ public final class Uninterruptibles { } } + /** + * Invokes {@code lock.}{@link Lock#tryLock(long, TimeUnit) tryLock(timeout, unit)} + * uninterruptibly. + * + * @since 30.0 + */ + @GwtIncompatible // concurrency + @Beta + public static boolean tryLockUninterruptibly(Lock lock, Duration timeout) { + return tryLockUninterruptibly(lock, toNanosSaturated(timeout), TimeUnit.NANOSECONDS); + } + + /** + * Invokes {@code lock.}{@link Lock#tryLock(long, TimeUnit) tryLock(timeout, unit)} + * uninterruptibly. + * + * @since 30.0 + */ + @GwtIncompatible // concurrency + @SuppressWarnings("GoodTime") // should accept a java.time.Duration + public static boolean tryLockUninterruptibly(Lock lock, long timeout, TimeUnit unit) { + boolean interrupted = false; + try { + long remainingNanos = unit.toNanos(timeout); + long end = System.nanoTime() + remainingNanos; + + while (true) { + try { + return lock.tryLock(remainingNanos, NANOSECONDS); + } catch (InterruptedException e) { + interrupted = true; + remainingNanos = end - System.nanoTime(); + } + } + } finally { + if (interrupted) { + Thread.currentThread().interrupt(); + } + } + } + + /** + * Invokes {@code executor.}{@link ExecutorService#awaitTermination(long, TimeUnit) + * awaitTermination(long, TimeUnit)} uninterruptibly with no timeout. + * + * @since 30.0 + */ + @Beta + @GwtIncompatible // concurrency + public static void awaitTerminationUninterruptibly(ExecutorService executor) { + // TODO(cpovirk): We could optimize this to avoid calling nanoTime() at all. + verify(awaitTerminationUninterruptibly(executor, Long.MAX_VALUE, NANOSECONDS)); + } + + /** + * Invokes {@code executor.}{@link ExecutorService#awaitTermination(long, TimeUnit) + * awaitTermination(long, TimeUnit)} uninterruptibly. + * + * @since 30.0 + */ + @Beta + @GwtIncompatible // concurrency + public static boolean awaitTerminationUninterruptibly( + ExecutorService executor, Duration timeout) { + return awaitTerminationUninterruptibly(executor, toNanosSaturated(timeout), NANOSECONDS); + } + + /** + * Invokes {@code executor.}{@link ExecutorService#awaitTermination(long, TimeUnit) + * awaitTermination(long, TimeUnit)} uninterruptibly. + * + * @since 30.0 + */ + @Beta + @GwtIncompatible // concurrency + @SuppressWarnings("GoodTime") + public static boolean awaitTerminationUninterruptibly( + ExecutorService executor, long timeout, TimeUnit unit) { + boolean interrupted = false; + try { + long remainingNanos = unit.toNanos(timeout); + long end = System.nanoTime() + remainingNanos; + + while (true) { + try { + return executor.awaitTermination(remainingNanos, NANOSECONDS); + } catch (InterruptedException e) { + interrupted = true; + remainingNanos = end - System.nanoTime(); + } + } + } finally { + if (interrupted) { + Thread.currentThread().interrupt(); + } + } + } + // TODO(user): Add support for waitUninterruptibly. private Uninterruptibles() {}