Permalink
Browse files

Initial commit

  • Loading branch information...
1 parent 681fc0a commit 984cfa5e1f27bd56d77177b29f104234c2b73ebb @mschneiderwng mschneiderwng committed Jul 19, 2013
Showing with 8,524 additions and 0 deletions.
  1. +7 −0 .gitignore
  2. +86 −0 build.sbt
  3. +202 −0 license.txt
  4. +1 −0 project/plugins.sbt
  5. +38 −0 src/main/scala/org/feijoas/mango/common/annotations/Beta.scala
  6. +125 −0 src/main/scala/org/feijoas/mango/common/base/Functions.scala
  7. +83 −0 src/main/scala/org/feijoas/mango/common/base/Optional.scala
  8. +402 −0 src/main/scala/org/feijoas/mango/common/base/Preconditions.scala
  9. +349 −0 src/main/scala/org/feijoas/mango/common/base/Predicates.scala
  10. +172 −0 src/main/scala/org/feijoas/mango/common/base/Suppliers.scala
  11. +87 −0 src/main/scala/org/feijoas/mango/common/base/Ticker.scala
  12. +30 −0 src/main/scala/org/feijoas/mango/common/base/package.scala
  13. +155 −0 src/main/scala/org/feijoas/mango/common/cache/Cache.scala
  14. +531 −0 src/main/scala/org/feijoas/mango/common/cache/CacheBuilder.scala
  15. +153 −0 src/main/scala/org/feijoas/mango/common/cache/CacheLoader.scala
  16. +61 −0 src/main/scala/org/feijoas/mango/common/cache/CacheLoaderWrapper.scala
  17. +220 −0 src/main/scala/org/feijoas/mango/common/cache/CacheStats.scala
  18. +87 −0 src/main/scala/org/feijoas/mango/common/cache/CacheWrapper.scala
  19. +189 −0 src/main/scala/org/feijoas/mango/common/cache/LoadingCache.scala
  20. +65 −0 src/main/scala/org/feijoas/mango/common/cache/LoadingCacheWrapper.scala
  21. +121 −0 src/main/scala/org/feijoas/mango/common/cache/RemovalCause.scala
  22. +59 −0 src/main/scala/org/feijoas/mango/common/cache/RemovalListener.scala
  23. +73 −0 src/main/scala/org/feijoas/mango/common/cache/RemovalNotification.scala
  24. +52 −0 src/main/scala/org/feijoas/mango/common/cache/Weigher.scala
  25. +40 −0 src/main/scala/org/feijoas/mango/common/cache/package.scala
  26. +45 −0 src/main/scala/org/feijoas/mango/common/convert/Decorators.scala
  27. +33 −0 src/main/scala/org/feijoas/mango/common/convert/package.scala
  28. +152 −0 src/main/scala/org/feijoas/mango/common/hash/BloomFilter.scala
  29. +180 −0 src/main/scala/org/feijoas/mango/common/hash/Funnel.scala
  30. +187 −0 src/main/scala/org/feijoas/mango/common/util/concurrent/Futures.scala
  31. +121 −0 src/test/scala/org/feijoas/mango/common/base/FunctionsTest.scala
  32. +87 −0 src/test/scala/org/feijoas/mango/common/base/OptionalTest.scala
  33. +311 −0 src/test/scala/org/feijoas/mango/common/base/PreconditionsTest.scala
  34. +292 −0 src/test/scala/org/feijoas/mango/common/base/PredicatesTest.scala
  35. +264 −0 src/test/scala/org/feijoas/mango/common/base/SuppliersTest.scala
  36. +441 −0 src/test/scala/org/feijoas/mango/common/cache/CacheBuilderTest.scala
  37. +91 −0 src/test/scala/org/feijoas/mango/common/cache/CacheLoaderWrapperTest.scala
  38. +87 −0 src/test/scala/org/feijoas/mango/common/cache/CacheStatsMatcher.scala
  39. +117 −0 src/test/scala/org/feijoas/mango/common/cache/CacheStatsTest.scala
  40. +83 −0 src/test/scala/org/feijoas/mango/common/cache/CacheTest.scala
  41. +154 −0 src/test/scala/org/feijoas/mango/common/cache/CacheWrapperBehaviour.scala
  42. +112 −0 src/test/scala/org/feijoas/mango/common/cache/CacheWrapperTest.scala
  43. +78 −0 src/test/scala/org/feijoas/mango/common/cache/LoadingCacheTest.scala
  44. +1,445 −0 src/test/scala/org/feijoas/mango/common/cache/LoadingCacheWrapperTest.scala
  45. +55 −0 src/test/scala/org/feijoas/mango/common/cache/RemovalCauseTest.scala
  46. +85 −0 src/test/scala/org/feijoas/mango/common/cache/RemovalListenerTest.scala
  47. +73 −0 src/test/scala/org/feijoas/mango/common/cache/RemovalNotificationTest.scala
  48. +47 −0 src/test/scala/org/feijoas/mango/common/cache/WeigherTest.scala
  49. +222 −0 src/test/scala/org/feijoas/mango/common/hash/BloomFilterTest.scala
  50. +93 −0 src/test/scala/org/feijoas/mango/common/hash/FunnelTest.scala
  51. +281 −0 src/test/scala/org/feijoas/mango/common/util/concurrent/FuturesTest.scala
View
7 .gitignore
@@ -0,0 +1,7 @@
+target/
+project/boot/
+*.jar
+.classpath
+.project
+.cache
+.worksheet
View
86 build.sbt
@@ -0,0 +1,86 @@
+//
+// Copyright (C) 2013 The Mango Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+name := "mango"
+
+organization := "org.feijoas.mango"
+
+version := "0.7"
+
+scalaVersion := "2.10.1"
+
+sbtVersion := "0.12"
+
+// publish only if all tests succeed
+publish <<= publish dependsOn (test in Test)
+
+(Keys.`package` in Compile) <<= (Keys.`package` in Compile) dependsOn (test in Test)
+
+// resolvers
+resolvers += Classpaths.typesafeSnapshots
+
+resolvers ++= Seq(
+ "Sonatype Snapshots" at "http://oss.sonatype.org/content/repositories/snapshots",
+ "Sonatype Releases" at "http://oss.sonatype.org/content/repositories/releases"
+)
+
+// compile dependencies
+libraryDependencies += "com.google.guava" % "guava" % "14.0.1"
+
+libraryDependencies += "com.google.code.findbugs" % "jsr305" % "1.3.+"
+
+// test dependencies
+libraryDependencies += "com.google.guava" % "guava-testlib" % "14.0.1" % "test"
+
+libraryDependencies += "junit" % "junit" % "4.11" % "test"
+
+libraryDependencies += "org.scalatest" % "scalatest_2.10" % "2.0.M5b" % "test"
+
+libraryDependencies += "org.scalacheck" %% "scalacheck" % "1.10.0" % "test"
+
+libraryDependencies += "org.scalamock" %% "scalamock-scalatest-support" % "3.0.1" % "test"
+
+libraryDependencies += "org.mockito" % "mockito-core" % "1.9.5" % "test"
+
+// Scala compiler options
+scalacOptions ++= Seq("-unchecked", "-deprecation", "-feature", "-language:implicitConversions,reflectiveCalls,postfixOps,higherKinds,existentials")
+
+// publish test jar, sources, and docs
+publishArtifact in Test := false
+
+// Scaladoc title
+scalacOptions in (Compile, doc) ++= Opts.doc.title("Mango")
+
+// Scaladoc title page
+scalacOptions in (Compile, doc) ++= Seq("-doc-root-content", "rootdoc.txt")
+
+// Show durations for tests
+testOptions in Test += Tests.Argument("-oD")
+
+// Disable parallel execution of tests
+parallelExecution in Test := false
+
+// Using the Scala version in output paths and artifacts
+crossPaths := true
+
+// SNAPSHOT versions go to the /snapshot repository while other versions go to the /releases repository
+publishTo <<= version { (v: String) =>
+ val nexus = "https://oss.sonatype.org/"
+ if (v.trim.endsWith("SNAPSHOT"))
+ Some("snapshots" at nexus + "content/repositories/snapshots")
+ else
+ Some("releases" at nexus + "service/local/staging/deploy/maven2")
+}
View
202 license.txt
@@ -0,0 +1,202 @@
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
View
1 project/plugins.sbt
@@ -0,0 +1 @@
+addSbtPlugin("com.typesafe.sbteclipse" % "sbteclipse-plugin" % "2.1.2")
View
38 src/main/scala/org/feijoas/mango/common/annotations/Beta.scala
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2013 The Mango Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ * The code of this project is a port of (or wrapper around) the Guava-libraries.
+ * See http://code.google.com/p/guava-libraries/
+ *
+ * @author Markus Schneider
+ */
+package org.feijoas.mango.common.annotations
+
+import scala.annotation.meta.{beanGetter, beanSetter, field, getter, setter}
+
+/** Signifies that a public API (public class, method or field) is subject to
+ * incompatible changes, or even removal, in a future release. An API bearing
+ * this annotation is exempt from any compatibility guarantees made by its
+ * containing library. Note that the presence of this annotation implies nothing
+ * about the quality or performance of the API in question, only the fact that
+ * it is not "API-frozen."
+ *
+ * @author Markus Schneider
+ * @since 0.7 (copied from guava-libraries)
+ */
+@field @getter @setter @beanGetter @beanSetter
+class Beta extends scala.annotation.StaticAnnotation
View
125 src/main/scala/org/feijoas/mango/common/base/Functions.scala
@@ -0,0 +1,125 @@
+/*
+ * Copyright (C) 2013 The Mango Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ * The code of this project is a port of (or wrapper around) the guava-libraries.
+ * See http://code.google.com/p/guava-libraries/
+ *
+ * @author Markus Schneider
+ */
+package org.feijoas.mango.common.base
+
+import java.util.concurrent.Callable
+
+import org.feijoas.mango.common.base.Preconditions.checkNotNull
+import org.feijoas.mango.common.convert.{ AsJava, AsScala }
+
+import com.google.common.base.{ Function => GuavaFunction }
+
+/** Utility functions for the work with Guava `Function[T, R]`
+ *
+ * Usage example for conversion between Guava and Mango:
+ * {{{
+ * // convert a Guava function to a Scala function
+ * val fnc: Function[T, R] = { (arg: T) => ... }.asJava
+ *
+ * // convert a Scala function to a Guava function
+ * val fnc: (T => R) = SomeGuavaFunction.asScala
+ *
+ * // convert a Scala function to a Callable
+ * val callable: Callable[R] = { () => n }.asJava
+ *
+ * // convert a Scala functio to a Runnable
+ * val runnable: Runnable = { () => ... }.asJava
+ * }}}
+ *
+ * @author Markus Schneider
+ * @since 0.7
+ */
+final object Functions {
+
+ /** Adds an `asScala` method that wraps a Guava `Function[T, R]` in
+ * a Scala function `T => R`.
+ *
+ * All calls to the Scala function are forwarded to the provided Guava function.
+ * @param fnc the Guava `Function[T, R]` to wrap in a Scala funcion `T => R`
+ * @return An object with an `asScala` method that returns a Scala
+ * `T => R` view of the argument.
+ */
+ implicit def asMangoFunctionConverter[T, R](fnc: GuavaFunction[T, R]): AsScala[T => R] = {
+ def convert(fnc: GuavaFunction[T, R]): (T => R) = fnc match {
+ case gf: AsGuavaFunction[T, R] => gf.delegate
+ case _ => AsMangoFunction(fnc)
+ }
+ new AsScala(convert(fnc))
+ }
+
+ /** Adds an `asJava` method that wraps a Scala function `T => R` in
+ * a Guava `Function[T, R]`.
+ *
+ * All calls to the Guava function are forwarded to the provided Scala function.
+ * @param fnc the Scala function `T => R` to wrap in a Guava `Function[T, R]`
+ * @return An object with an `asJava` method that returns a Guava `Function[T, R]`
+ * view of the argument.
+ */
+ implicit def asGuavaFunctionConverter[T, R](fnc: T => R): AsJava[GuavaFunction[T, R]] = {
+ def convert(fnc: T => R) = fnc match {
+ case cf: AsMangoFunction[T, R] => cf.delegate
+ case _ => AsGuavaFunction(fnc)
+ }
+ new AsJava(convert(fnc))
+ }
+
+ /** Adds an `asJava` method that wraps a Scala function `() => Unit` in
+ * a Java `Runnable`.
+ *
+ * All calls to the `Runnable` are forwarded to the provided Scala function.
+ * @param fnc the Scala function `() => Unit` to wrap in a Java `Runnable`
+ * @return An object with an `asJava` method that returns a Java `Runnable`
+ * view of the argument.
+ */
+ implicit def asRunnableConverter(fnc: () => Unit): AsJava[Runnable] = new AsJava(
+ new Runnable() {
+ override def run() = fnc()
+ })
+
+ /** Adds an `asJava` method that wraps a Scala function `() => R` in
+ * a Java `Callable`.
+ *
+ * All calls to the `Callable` are forwarded to the provided Scala function.
+ * @param fnc the Scala function `() => R` to wrap in a Java `Callable`
+ * @return An object with an `asJava` method that returns a Java `Callable`
+ * view of the argument.
+ */
+ implicit def asCallableConverter[R](fnc: () => R): AsJava[Callable[R]] = new AsJava(
+ new Callable[R]() {
+ def call() = fnc()
+ })
+}
+
+/** Wraps a Guava `Function` in a Scala function */
+@SerialVersionUID(1L)
+private[mango] case class AsMangoFunction[R, T](delegate: GuavaFunction[R, T]) extends Function1[R, T] with Serializable {
+ checkNotNull(delegate)
+ override def apply(input: R) = delegate.apply(input)
+}
+
+/** Wraps a Scala function in a Guava `Function` */
+@SerialVersionUID(1L)
+private[mango] case class AsGuavaFunction[T, R](delegate: R => T) extends GuavaFunction[R, T] with Serializable {
+ checkNotNull(delegate)
+ override def apply(input: R) = delegate(input)
+}
View
83 src/main/scala/org/feijoas/mango/common/base/Optional.scala
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2013 The Mango Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ * The code of this project is a port of (or wrapper around) the guava-libraries.
+ * See http://code.google.com/p/guava-libraries/
+ *
+ * @author Markus Schneider
+ */
+package org.feijoas.mango.common.base
+
+import org.feijoas.mango.common.base.Preconditions.checkNotNull
+import org.feijoas.mango.common.convert.{ AsJava, AsScala }
+
+import com.google.common.base.{ Optional => GuavaOptional }
+
+/** Utility functions for the work with `Option[T]` and `Optional[T]`
+ *
+ * Usage example for conversion between Guava and Mango:
+ * {{{
+ * // convert a Guava Optional[T] to a Scala Option[T]
+ * Optional.of("some").asScala
+ * Optional.absent().asScala
+ *
+ * // convert a Scala Option[T] to a Guava Optional[T]
+ * Some("some").asJava
+ * None.asJava
+ * }}}
+ *
+ * @author Markus Schneider
+ * @since 0.7
+ */
+final object Optional {
+
+ /** Adds an `asJava` method that converts a Scala `Option[T]` to
+ * a Guava `Optional[T]`.
+ *
+ * The returned Guava `Optional[T]` contains the same reference as in the
+ * Scala `Option[T]` or `GuavaOptional.absent()` if the `Option[T]` is None.
+ *
+ * @param option the Scala `Option[T]` to convert to a Guava `Optional[T]`
+ * @return An object with an `asJava` method that returns a Guava `Optional[T]`
+ * view of the argument
+ */
+ implicit def asGuavaOptionalConverter[T](option: Option[T]): AsJava[GuavaOptional[T]] = {
+ def convert(option: Option[T]): GuavaOptional[T] = checkNotNull(option) match {
+ case Some(value) => GuavaOptional.of(value)
+ case None => GuavaOptional.absent()
+ }
+ new AsJava(convert(option))
+ }
+
+ /** Adds an `asScala` method that converts a Guava `Optional[T]` to
+ * a Scala `Option[T]`.
+ *
+ * The returned Scala `Option[T]` contains the same reference as in the
+ * Guava `Optional[T]` or `None` if the `Optional[T]` is absent.
+ *
+ * @param option the Guava `Optional[T]` to convert to a Scala `Option[T]`
+ * @return An object with an `asScala` method that returns a Scala `Option[T]`
+ * view of the argument
+ */
+ implicit def asMangoOptionConverter[T](option: GuavaOptional[T]): AsScala[Option[T]] = {
+ def convert(option: GuavaOptional[T]) = option.isPresent() match {
+ case true => Some(option.get())
+ case false => None
+ }
+ new AsScala(convert(option))
+ }
+}
View
402 src/main/scala/org/feijoas/mango/common/base/Preconditions.scala
@@ -0,0 +1,402 @@
+/*
+ * Copyright (C) 2013 The Mango Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ * The code of this project is a port of (or wrapper around) the guava-libraries.
+ * See http://code.google.com/p/guava-libraries/
+ *
+ * @author Markus Schneider
+ */
+package org.feijoas.mango.common.base
+
+import javax.annotation.Nullable
+
+/** Simple static methods to be called at the start of your own methods to verify
+ * correct arguments and state. This allows constructs such as
+ *
+ * {{{
+ * if (count <= 0) {
+ * throw new IllegalArgumentException("must be positive: " + count)
+ * }
+ * }}}
+ *
+ * to be replaced with the more compact
+ * {{{
+ * checkArgument(count > 0, "must be positive: %s", count)
+ * }}}
+ *
+ * Note that the sense of the expression is inverted with [[Preconditions]]
+ * you declare what you expect to be ''true'', just as you do with an
+ * <a href="http://java.sun.com/j2se/1.5.0/docs/guide/language/assert.html">
+ * {@code assert}</a> or a JUnit {@code assertTrue} call.
+ *
+ * <p><b>Warning:</b> only the {@code "%s"} specifier is recognized as a
+ * placeholder in these messages.
+ *
+ * <p>Take care not to confuse precondition checking with other similar types
+ * of checks! Precondition exceptions -- including those provided here, but also
+ * {@link IndexOutOfBoundsException}, {@link NoSuchElementException}, {@link
+ * UnsupportedOperationException} and others -- are used to signal that the
+ * <i>calling method</i> has made an error. This tells the caller that it should
+ * not have invoked the method when it did, with the arguments it did, or
+ * perhaps ever. Postcondition or other invariant failures should not throw
+ * these types of exceptions.
+ *
+ * See also <a href=
+ * "http://code.google.com/p/guava-libraries/wiki/PreconditionsExplained">
+ * the Guava User Guide
+ * </a> on using [[Preconditions]]
+ *
+ * @author Markus Schneider
+ * @since 0.7 (copied from guava-libraries)
+ */
+final object Preconditions {
+
+ /** Ensures the truth of an expression involving one or more parameters to the
+ * calling method.
+ *
+ * @param expression an expression: Boolean
+ * @throws IllegalArgumentException if {@code expression} is false
+ */
+ def checkArgument(expression: Boolean) = {
+ if (!expression)
+ throw new IllegalArgumentException()
+ }
+
+ /** Ensures the truth of an expression involving one or more parameters to the
+ * calling method.
+ *
+ * @param expression an expression: Boolean
+ * @param errorMessage the exception message to use if the check fails will
+ * be converted to a string using `String#valueOf(Any)`
+ * @throws IllegalArgumentException if {@code expression} is false
+ */
+ def checkArgument(expression: Boolean, errorMessage: Any) = {
+ if (!expression)
+ throw new IllegalArgumentException(String.valueOf(errorMessage))
+ }
+
+ /** Ensures the truth of an expression involving one or more parameters to the
+ * calling method.
+ *
+ * @param expression an expression: Boolean
+ * @param errorMessageTemplate a template for the exception message should the
+ * check fail. The message is formed by replacing each {@code %s}
+ * placeholder in the template with an argument. These are matched by
+ * position - the first {@code %s} gets {@code errorMessageArgs[0]}, etc.
+ * Unmatched arguments will be appended to the formatted message in square
+ * braces. Unmatched placeholders will be left as-is. Arguments are converted
+ * to strings using `String#valueOf(Any)`.
+ * @param errorMessageArg the first argument to be substituted into the message
+ * template.
+ * @param moreArgs the other arguments to be substituted into the message
+ * template.
+ * @throws IllegalArgumentException if {@code expression} is false
+ * @throws NullPointerException if the check fails and either {@code
+ * errorMessageTemplate} or {@code errorMessageArgs} is null (don't let
+ * this happen)
+ */
+ def checkArgument(expression: Boolean,
+ errorMessageTemplate: String,
+ errorMessageArg: Any,
+ moreArgs: Any*) = {
+ if (!expression)
+ throw new IllegalArgumentException(format(errorMessageTemplate, errorMessageArg +: moreArgs: _*))
+ }
+
+ /** Ensures the truth of an expression involving the state of the calling
+ * instance, but not involving any parameters to the calling method.
+ *
+ * @param expression an expression: Boolean
+ * @throws IllegalStateException if {@code expression} is false
+ */
+ def checkState(expression: Boolean) = {
+ if (!expression)
+ throw new IllegalStateException()
+ }
+
+ /** Ensures the truth of an expression involving the state of the calling
+ * instance, but not involving any parameters to the calling method.
+ *
+ * @param expression an expression: Boolean
+ * @param errorMessage the exception message to use if the check fails will
+ * be converted to a string using `String#valueOf(Any)`
+ * @throws IllegalStateException if {@code expression} is false
+ */
+ def checkState(expression: Boolean, errorMessage: Any) = {
+ if (!expression)
+ throw new IllegalStateException(String.valueOf(errorMessage))
+ }
+
+ /** Ensures the truth of an expression involving the state of the calling
+ * instance, but not involving any parameters to the calling method.
+ *
+ * @param expression a expression: Boolean
+ * @param errorMessageTemplate a template for the exception message should the
+ * check fail. The message is formed by replacing each {@code %s}
+ * placeholder in the template with an argument. These are matched by
+ * position - the first {@code %s} gets {@code errorMessageArgs[0]}, etc.
+ * Unmatched arguments will be appended to the formatted message in square
+ * braces. Unmatched placeholders will be left as-is. Arguments are converted
+ * to strings using `String#valueOf(Any)`.
+ * @param errorMessageArg the first argument to be substituted into the message
+ * template.
+ * @param moreArgs the other arguments to be substituted into the message
+ * template.
+ * @throws IllegalStateException if {@code expression} is false
+ * @throws NullPointerException if the check fails and either {@code
+ * errorMessageTemplate} or {@code errorMessageArgs} is null (don't let
+ * this happen)
+ */
+ def checkState(expression: Boolean,
+ errorMessageTemplate: String,
+ errorMessageArg: Any,
+ moreArgs: Any*) = {
+ if (!expression)
+ throw new IllegalStateException(format(errorMessageTemplate, errorMessageArg +: moreArgs: _*))
+ }
+
+ /** Ensures that an objecreference: T passed as a parameter to the calling
+ * method is not null.
+ *
+ * @param reference an objecreference: T
+ * @return the non-null reference that was validated
+ * @throws NullPointerException if {@code reference} is null
+ */
+ def checkNotNull[T](reference: T): T = {
+ if (reference == null) {
+ throw new NullPointerException()
+ }
+ return reference
+ }
+
+ /** Ensures that an objecreference: T passed as a parameter to the calling
+ * method is not null.
+ *
+ * @param reference an objecreference: T
+ * @param errorMessage the exception message to use if the check fails will
+ * be converted to a string using `String#valueOf(Any)`
+ * @return the non-null reference that was validated
+ * @throws NullPointerException if {@code reference} is null
+ */
+ def checkNotNull[T](reference: T, errorMessage: Any): T = {
+ if (reference == null) {
+ throw new NullPointerException(String.valueOf(errorMessage))
+ }
+ return reference
+ }
+
+ /** Ensures that an objecreference: T passed as a parameter to the calling
+ * method is not null.
+ *
+ * @param reference an objecreference: T
+ * @param errorMessageTemplate a template for the exception message should the
+ * check fail. The message is formed by replacing each {@code %s}
+ * placeholder in the template with an argument. These are matched by
+ * position - the first {@code %s} gets {@code errorMessageArgs[0]}, etc.
+ * Unmatched arguments will be appended to the formatted message in square
+ * braces. Unmatched placeholders will be left as-is. Arguments are converted
+ * to strings using `String#valueOf(Any)`.
+ * @param errorMessageArg the first argument to be substituted into the message
+ * template.
+ * @param moreArgs the other arguments to be substituted into the message
+ * template.
+ * @return the non-null reference that was validated
+ * @throws NullPointerException if {@code reference} is null
+ */
+ def checkNotNull[T](reference: T,
+ errorMessageTemplate: String,
+ errorMessageArg: Any,
+ moreArgs: Any*): T = {
+ if (reference == null) {
+ throw new NullPointerException(format(errorMessageTemplate, errorMessageArg +: moreArgs: _*))
+ }
+ return reference
+ }
+
+ /** Ensures that {@code index} specifies a valid <i>element</i> in an array,
+ * list or string of size {@code size}. An element index may range from zero,
+ * inclusive, to {@code size}, exclusive.
+ *
+ * @param index a user-supplied index identifying an element of an array, list
+ * or string
+ * @param size the size of that array, list or string
+ * @return the value of {@code index}
+ * @throws IndexOutOfBoundsException if {@code index} is negative or is not
+ * less than {@code size}
+ * @throws IllegalArgumentException if {@code size} is negative
+ */
+ def checkElementIndex(index: Int, size: Int): Int = {
+ return checkElementIndex(index, size, "index")
+ }
+
+ /** Ensures that {@code index} specifies a valid <i>element</i> in an array,
+ * list or string of size {@code size}. An element index may range from zero,
+ * inclusive, to {@code size}, exclusive.
+ *
+ * @param index a user-supplied index identifying an element of an array, list
+ * or string
+ * @param size the size of that array, list or string
+ * @param desc the text to use to describe this index in an error message
+ * @return the value of {@code index}
+ * @throws IndexOutOfBoundsException if {@code index} is negative or is not
+ * less than {@code size}
+ * @throws IllegalArgumentException if {@code size} is negative
+ */
+ def checkElementIndex(
+ index: Int, size: Int, @Nullable desc: String): Int = {
+ if (index < 0 || index >= size) {
+ throw new IndexOutOfBoundsException(badElementIndex(index, size, desc))
+ }
+ return index
+ }
+
+ private def badElementIndex(index: Int, size: Int, desc: String): String = {
+ if (index < 0) {
+ return format("%s (%s) must not be negative", desc, index)
+ } else if (size < 0) {
+ throw new IllegalArgumentException("negative size: " + size)
+ } else { // index >= size
+ return format("%s (%s) must be less than size (%s)", desc, index, size)
+ }
+ }
+
+ /** Ensures that {@code index} specifies a valid <i>position</i> in an array,
+ * list or string of size {@code size}. A position index may range from zero
+ * to {@code size}, inclusive.
+ *
+ * @param index a user-supplied index identifying a position in an array, list
+ * or string
+ * @param size the size of that array, list or string
+ * @return the value of {@code index}
+ * @throws IndexOutOfBoundsException if {@code index} is negative or is
+ * greater than {@code size}
+ * @throws IllegalArgumentException if {@code size} is negative
+ */
+ def checkPositionIndex(index: Int, size: Int): Int = {
+ checkPositionIndex(index, size, "index")
+ }
+
+ /** Ensures that {@code index} specifies a valid <i>position</i> in an array,
+ * list or string of size {@code size}. A position index may range from zero
+ * to {@code size}, inclusive.
+ *
+ * @param index a user-supplied index identifying a position in an array, list
+ * or string
+ * @param size the size of that array, list or string
+ * @param desc the text to use to describe this index in an error message
+ * @return the value of {@code index}
+ * @throws IndexOutOfBoundsException if {@code index} is negative or is
+ * greater than {@code size}
+ * @throws IllegalArgumentException if {@code size} is negative
+ */
+ def checkPositionIndex(index: Int, size: Int, desc: String): Int = {
+ if (index < 0 || index > size) {
+ throw new IndexOutOfBoundsException(badPositionIndex(index, size, desc))
+ }
+ return index
+ }
+
+ private def badPositionIndex(index: Int, size: Int, desc: String): String = {
+ if (index < 0) {
+ return format("%s (%s) must not be negative", desc, index)
+ } else if (size < 0) {
+ throw new IllegalArgumentException("negative size: " + size)
+ } else { // index > size
+ return format("%s (%s) must not be greater than size (%s)",
+ desc, index, size)
+ }
+ }
+
+ /** Ensures that {@code start} and {@code end} specify a valid <i>positions</i>
+ * in an array, list or string of size {@code size}, and are in order. A
+ * position index may range from zero to {@code size}, inclusive.
+ *
+ * @param start a user-supplied index identifying a starting position in an
+ * array, list or string
+ * @param end a user-supplied index identifying a ending position in an array,
+ * list or string
+ * @param size the size of that array, list or string
+ * @throws IndexOutOfBoundsException if either index is negative or is
+ * greater than {@code size}, or if {@code end} is less than {@code start}
+ * @throws IllegalArgumentException if {@code size} is negative
+ */
+ def checkPositionIndexes(start: Int, end: Int, size: Int) = {
+ if (start < 0 || end < start || end > size) {
+ throw new IndexOutOfBoundsException(badPositionIndexes(start, end, size))
+ }
+ }
+
+ private def badPositionIndexes(start: Int, end: Int, size: Int): String = {
+ if (start < 0 || start > size) {
+ return badPositionIndex(start, size, "start index")
+ }
+ if (end < 0 || end > size) {
+ return badPositionIndex(end, size, "end index")
+ }
+ // end < start
+ return format("end index (%s) must not be less than start index (%s)",
+ end, start)
+ }
+
+ /** Substitutes each {@code %s} in {@code template} with an argument. These
+ * are matched by position - the first {@code %s} gets {@code args[0]}, etc.
+ * If there are more arguments than placeholders, the unmatched arguments will
+ * be appended to the end of the formatted message in square braces.
+ *
+ * @param template a non-null string containing 0 or more {@code %s}
+ * placeholders.
+ * @param args the arguments to be substituted into the message
+ * template. Arguments are converted to strings using
+ * {@link String#valueOf(Object)}. Arguments can be null.
+ */
+ private def format(templateStr: String, @Nullable args: Any*): String = {
+ val template = String.valueOf(templateStr) // null -> "null"
+
+ // start substituting the arguments into the '%s' placeholders
+ val builder = new StringBuilder(template.length() + 16 * args.length)
+ var templateStart = 0
+ var i = 0
+ var stop = false
+ while (stop == false && i < args.length) {
+ var placeholderStart = template.indexOf("%s", templateStart)
+ if (placeholderStart == -1) {
+ stop = true
+ } else {
+ builder.append(template.substring(templateStart, placeholderStart))
+ builder.append(args(i))
+ templateStart = placeholderStart + 2
+ i = i + 1
+ }
+ }
+ builder.append(template.substring(templateStart))
+
+ // if we run out of placeholders, append the extra args in square braces
+ if (i < args.length) {
+ builder.append(" [")
+ builder.append(args(i))
+ i = i + 1
+ while (i < args.length) {
+ builder.append(", ")
+ builder.append(args(i))
+ i = i + 1
+ }
+ builder.append(']')
+ }
+
+ return builder.toString()
+ }
+}
View
349 src/main/scala/org/feijoas/mango/common/base/Predicates.scala
@@ -0,0 +1,349 @@
+/*
+ * Copyright (C) 2013 The Mango Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ * The code of this project is a port of (or wrapper around) the Guava-libraries.
+ * See http://code.google.com/p/guava-libraries/
+ *
+ * @author Markus Schneider
+ */
+package org.feijoas.mango.common.base
+
+import java.util.regex.Pattern
+import javax.annotation.Nullable
+
+import scala.collection.immutable
+
+import org.feijoas.mango.common.base.Preconditions.checkNotNull
+import org.feijoas.mango.common.convert.{ AsJava, AsScala }
+
+import com.google.common.base.{ Predicate => GuavaPredicate }
+
+/** Utility functions for the work with Guava `Predicate[T]`
+ *
+ * Usage example for conversion between Guava and Mango:
+ * {{{
+ * // convert a Guava Predicate[T] to a Scala function T => Boolean
+ * import com.google.common.base.{ Predicate => GuavaPredicate }
+ * val pred: GuavaPredicate[Any] = { (arg: Any) => true }.asJava
+ *
+ * // convert a Scala function T => Boolean to a Guava Predicate[T]
+ * import com.google.common.base.{ Predicates => GuavaPredicates }
+ * val pred: Any => Boolean = GuavaPredicates.alwaysTrue().asScala
+ * }}}
+ *
+ * @author Markus Schneider
+ * @since 0.7
+ */
+final object Predicates {
+
+ /** Returns a predicate that always evaluates to `false`.
+ */
+ case object alwaysFalse extends (Any => Boolean) {
+ override def apply(ref: Any) = false
+ override def toString = "alwaysFalse"
+ }
+
+ /** Returns a predicate that always evaluates to `true`.
+ */
+ case object alwaysTrue extends (Any => Boolean) {
+ override def apply(ref: Any) = true
+ override def toString = "alwaysTrue"
+ }
+
+ /** Returns a predicate that evaluates to `true` if the object reference
+ * being tested is not `null`.
+ */
+ case object notNull extends (Any => Boolean) {
+ override def apply(ref: Any) = ref != null
+ override def toString = "notNull"
+ }
+
+ /** Returns a predicate that evaluates to `true` if the object reference
+ * being tested is `null`.
+ */
+ case object isNull extends (Any => Boolean) {
+ override def apply(ref: Any) = ref == null
+ override def toString = "isNull"
+ }
+
+ /** Returns a predicate that evaluates to `true` if the object reference
+ * being tested is `null`.
+ */
+ private[mango] case class NotPredicate[T](val predicate: T => Boolean) extends (T => Boolean) {
+ checkNotNull(predicate)
+ override def apply(arg: T) = !predicate(arg)
+ override def toString = "Not(" + predicate.toString + ")"
+ }
+
+ /** Returns a predicate that evaluates to {@code true} if each of its
+ * components evaluates to {@code true}. The components are evaluated in
+ * order, and evaluation will be "short-circuited" as soon as a false
+ * predicate is found. It defensively copies the array passed in, so future
+ * changes to it won't alter the behavior of this predicate. If {@code
+ * components} is empty, the returned predicate will always evaluate to {@code
+ * true}.
+ */
+ private[mango] case class AndPredicate[T](px: Seq[(T => Boolean)]) extends (T => Boolean) {
+ checkNotNull(px)
+ override def apply(arg: T) = px.indexWhere(f => !f(arg)) == -1
+ override def toString = px.mkString("And(", ",", ")")
+ override def hashCode = px.hashCode() + 0x12472c2c
+ }
+
+ /** Returns a predicate that evaluates to {@code true} if any one of its
+ * components evaluates to {@code true}. The components are evaluated in
+ * order, and evaluation will be "short-circuited" as soon as a
+ * true predicate is found. It defensively copies the iterable passed in, so
+ * future changes to it won't alter the behavior of this predicate. If {@code
+ * components} is empty, the returned predicate will always evaluate to {@code
+ * false}.
+ */
+ private[mango] case class OrPredicate[T](px: immutable.Seq[(T => Boolean)]) extends (T => Boolean) {
+ checkNotNull(px)
+ override def apply(arg: T) = px.indexWhere(f => f(arg)) != -1
+ override def toString = px.mkString("Or(", ",", ")")
+ override def hashCode = px.hashCode() + 0x053c91cf
+ }
+
+ /** Returns a predicate that evaluates to (((x1 xor x2) xor x3) xor x4)...
+ * for all elements `xi` in the sequence. It defensively copies the iterable
+ * passed in, so future changes to it won't alter the behavior of this predicate.
+ * If {@code components} is empty, the returned predicate will always evaluate
+ * to {@code false}.
+ */
+ private[mango] case class XorPredicate[T](px: Seq[(T => Boolean)]) extends (T => Boolean) {
+ checkNotNull(px)
+ private val xor = px.reduceLeft[T => Boolean]((acc, fnc) => ((arg: T) => fnc(arg) != acc(arg)))
+ override def apply(arg: T) = xor(arg)
+ override def toString = px.mkString("Xor(", ",", ")")
+ override def hashCode = px.hashCode + 0x75bcf4d
+ }
+
+ private[mango] case class InstanceOfPredicate(clazz: Class[_]) extends (Any => Boolean) {
+ checkNotNull(clazz)
+ override def apply(arg: Any) = arg != null && arg.getClass == clazz
+ override def toString = "InstanceOf(" + clazz + ")"
+ }
+
+ /** Returns a predicate that evaluates to {@code true} if the object being
+ * tested {@code equals()} the given target or both are null.
+ */
+ private[mango] case class EqualToPredicate[T](@Nullable target: T) extends (T => Boolean) {
+ override def apply(arg: T) = target == arg
+ override def toString = "IsEqualTo(" + target + ")"
+ }
+
+ /** Returns a predicate that evaluates to {@code true} if the
+ * {@code CharSequence} being tested contains any match for the given
+ * regular expression pattern. The test used is equivalent to
+ * {@code pattern.matcher(arg).find()}
+ */
+ private[mango] case class ContainsPatternPredicate(pattern: Pattern) extends (String => Boolean) {
+ checkNotNull(pattern)
+ override def apply(arg: String) = pattern.matcher(arg).find()
+ override def toString = "ContainsPattern(" + pattern + ")"
+ }
+
+ /** Returns a predicate that evaluates to {@code true} if the given predicate
+ * evaluates to {@code false}.
+ */
+ def not[T](predicate: T => Boolean): T => Boolean = checkNotNull(predicate) match {
+ case `alwaysFalse` => alwaysTrue
+ case `alwaysTrue` => alwaysFalse
+ case NotPredicate(inner) => inner
+ case _ => NotPredicate(predicate)
+ }
+
+ /** Returns a predicate that evaluates to {@code true} if each of its
+ * components evaluates to {@code true}. The components are evaluated in
+ * order, and evaluation will be "short-circuited" as soon as a false
+ * predicate is found. It defensively copies the array passed in, so future
+ * changes to it won't alter the behavior of this predicate. If {@code
+ * components} is empty, the returned predicate will always evaluate to {@code
+ * true}.
+ */
+ def and[T](predicates: Seq[(T => Boolean)]): T => Boolean = {
+ checkNotNull(predicates)
+ if (predicates.isEmpty)
+ return alwaysFalse
+
+ predicates match {
+ case s: immutable.Seq[_] => AndPredicate(s)
+ case _ => AndPredicate(immutable.Seq() ++ predicates)
+ }
+ }
+
+ /** Returns a predicate that evaluates to {@code true} if each of its
+ * components evaluates to {@code true}. The components are evaluated in
+ * order, and evaluation will be "short-circuited" as soon as a false
+ * predicate is found. It defensively copies the array passed in, so future
+ * changes to it won't alter the behavior of this predicate. If {@code
+ * components} is empty, the returned predicate will always evaluate to {@code
+ * true}.
+ */
+ def and[T](fst: T => Boolean, rest: (T => Boolean)*): T => Boolean = {
+ checkNotNull(fst)
+ checkNotNull(rest)
+ val seq: Seq[(T => Boolean)] = fst +: rest
+ and(seq)
+ }
+
+ /** Returns a predicate that evaluates to {@code true} if any one of its
+ * components evaluates to {@code true}. The components are evaluated in
+ * order, and evaluation will be "short-circuited" as soon as a
+ * true predicate is found. It defensively copies the iterable passed in, so
+ * future changes to it won't alter the behavior of this predicate. If {@code
+ * components} is empty, the returned predicate will always evaluate to {@code
+ * false}.
+ */
+ def or[T](predicates: Seq[T => Boolean]): T => Boolean = {
+ checkNotNull(predicates)
+ if (predicates.isEmpty)
+ return alwaysFalse
+
+ predicates match {
+ case s: immutable.Seq[_] => OrPredicate(s)
+ case _ => OrPredicate(immutable.Seq() ++ predicates)
+ }
+ }
+
+ /** Returns a predicate that evaluates to {@code true} if any one of its
+ * components evaluates to {@code true}. The components are evaluated in
+ * order, and evaluation will be "short-circuited" as soon as a
+ * true predicate is found. It defensively copies the iterable passed in, so
+ * future changes to it won't alter the behavior of this predicate. If {@code
+ * components} is empty, the returned predicate will always evaluate to {@code
+ * false}.
+ */
+ def or[T](fst: T => Boolean, rest: (T => Boolean)*): T => Boolean = {
+ checkNotNull(fst)
+ checkNotNull(rest)
+ val seq: Seq[(T => Boolean)] = fst +: rest
+ or(seq)
+ }
+
+ /** Returns a predicate that evaluates to (((x1 xor x2) xor x3) xor x4)...
+ * for all elements `xi` in the sequence. It defensively copies the iterable
+ * passed in, so future changes to it won't alter the behavior of this predicate.
+ * If {@code components} is empty, the returned predicate will always evaluate
+ * to {@code false}.
+ */
+ def xor[T](predicates: Seq[T => Boolean]): T => Boolean = {
+ checkNotNull(predicates)
+ if (predicates.isEmpty)
+ return alwaysFalse
+
+ predicates match {
+ case s: immutable.Seq[_] => XorPredicate(s)
+ case _ => XorPredicate(immutable.Seq() ++ predicates)
+ }
+ }
+
+ /** Returns a predicate that evaluates to (((x1 xor x2) xor x3) xor x4)...
+ * for all elements `xi` in the sequence. It defensively copies the iterable
+ * passed in, so future changes to it won't alter the behavior of this predicate.
+ * If {@code components} is empty, the returned predicate will always evaluate
+ * to {@code false}.
+ */
+ def xor[T](fst: T => Boolean, rest: (T => Boolean)*): T => Boolean = {
+ checkNotNull(fst)
+ checkNotNull(rest)
+ val seq: Seq[(T => Boolean)] = fst +: rest
+ xor(seq)
+ }
+
+ /** Returns a predicate that evaluates to {@code true} if the object being
+ * tested {@code equals()} the given target or both are null.
+ */
+ def equalTo[T](target: T): (T => Boolean) = EqualToPredicate(target)
+
+ /** Returns a predicate that evaluates to {@code true} if the class being
+ * tested is assignable from the given class. The returned predicate
+ * does not allow null inputs.
+ */
+ def assignableFrom(clazz: Class[_]): Class[_] => Boolean = {
+ checkNotNull(clazz)
+ (arg: Class[_]) => arg != null && clazz.isAssignableFrom(arg)
+ }
+
+ /** Returns a predicate that evaluates to {@code true} if the object reference
+ * being tested is a member of the given collection. It does not defensively
+ * copy the collection passed in, so future changes to it will alter the
+ * behavior of the predicate.
+ *
+ * The test used is equivalent to `coll.contains(arg)`
+ *
+ * @param target the collection that may contain the function input
+ */
+ def in[T](coll: Seq[T]): T => Boolean = {
+ checkNotNull(coll)
+ (arg: T) => coll.contains(arg)
+ }
+
+ /** Returns a predicate that evaluates to {@code true} if the
+ * {@code String} being tested contains any match for the given
+ * regular expression pattern. The test used is equivalent to
+ * {@code Pattern.compile(pattern).matcher(arg).find()}
+ *
+ * @throws java.util.regex.PatternSyntaxException if the pattern is invalid
+ */
+ def containsPattern(pattern: String): String => Boolean = {
+ checkNotNull(pattern)
+ containsPattern(Pattern.compile(pattern))
+ }
+
+ /** Returns a predicate that evaluates to {@code true} if the
+ * {@code CharSequence} being tested contains any match for the given
+ * regular expression pattern. The test used is equivalent to
+ * {@code pattern.matcher(arg).find()}
+ */
+ def containsPattern(pattern: Pattern): (String => Boolean) = ContainsPatternPredicate(pattern)
+
+ /** Adds an `asJava` method that wraps a Scala function `T => Boolean` in
+ * a Guava `Predicate[T]`.
+ *
+ * The returned Guava `Predicate[T]` forwards all calls of the `apply` method
+ * to the given Scala function `T => Boolean`.
+ *
+ * @param pred the Scala function `T => Boolean` to wrap in a Guava `Predicate[T]`
+ * @return An object with an `asJava` method that returns a Guava `Predicate[T]`
+ * view of the argument
+ */
+ implicit def asGuavaPredicateConverter[T](pred: T => Boolean): AsJava[GuavaPredicate[T]] = {
+ checkNotNull(pred)
+ def wrap(pred: T => Boolean): GuavaPredicate[T] = new GuavaPredicate[T] {
+ override def apply(arg: T): Boolean = pred(arg)
+ }
+ new AsJava(wrap(pred))
+ }
+
+ /** Adds an `asScala` method that wraps a Guava `Predicate[T]` in
+ * a Scala function `T => Boolean`.
+ *
+ * The returned Scala function `T => Boolean` forwards all calls of the `apply` method
+ * to the given Guava `Predicate[T]`.
+ *
+ * @param pred the Guava `Predicate[T]` to wrap in a Scala function `T => Boolean`
+ * @return An object with an `asScala` method that returns a Scala function `T => Boolean`
+ * view of the argument
+ */
+ implicit def asMangoPredicateConverter[T](pred: GuavaPredicate[T]): AsScala[T => Boolean] = {
+ checkNotNull(pred)
+ new AsScala((arg: T) => pred.apply(arg))
+ }
+}
+
View
172 src/main/scala/org/feijoas/mango/common/base/Suppliers.scala
@@ -0,0 +1,172 @@
+/*
+ * Copyright (C) 2013 The Mango Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ * The code of this project is a port of (or wrapper around) the Guava-libraries.
+ * See http://code.google.com/p/guava-libraries/
+ *
+ * @author Markus Schneider
+ */
+package org.feijoas.mango.common.base
+
+import java.util.concurrent.TimeUnit
+import org.feijoas.mango.common.base.Preconditions.checkNotNull
+import org.feijoas.mango.common.convert.{ AsJava, AsScala }
+import com.google.common.base.{ Supplier => GuavaSupplier, Suppliers => GuavaSuppliers }
+
+/** Utility functions for the work with suppliers which are functions of
+ * the type `() => T`
+ *
+ * Usage example for conversion between Guava and Mango:
+ * {{{
+ * // convert a Guava Supplier[T] to a Scala function () => T
+ * import com.google.common.base.{ Supplier => GuavaSupplier }
+ * val fnc: () => T = ...
+ * val supplier: GuavaSupplier[T] = fnc.asJava
+ *
+ * // convert a Scala function () => T to a Guava Supplier[T]
+ * val guavaSupplier: GuavaSupplier[T] = ...
+ * val supplier: () => T = guavaSupplier.asScala
+ * }}}
+ *
+ * @author Markus Schneider
+ * @since 0.7
+ */
+final object Suppliers {
+
+ /** Returns a function which caches the instance retrieved during the first
+ * evaluation and returns that value on subsequent evaluations. See:
+ * [[http://en.wikipedia.org/wiki/Memoization memoization]]
+ *
+ * <p>The returned function is thread-safe. The functions's serialized form
+ * does not contain the cached value, which will be recalculated when
+ * the reserialized instance is evaluated.
+ *
+ * <p>If `f` is an instance created by an earlier call to
+ * `memoize, it is returned directly.
+ */
+ def memoize[T](f: () => T): () => T = f match {
+ case _: MemoizingSupplier[T] => f
+ case _ => MemoizingSupplier(checkNotNull(f))
+ }
+
+ /** Returns a supplier that caches the instance supplied by the delegate and
+ * removes the cached value after the specified time has passed. Subsequent
+ * evaluations return the cached value if the expiration time has
+ * not passed. After the expiration time, a new value is retrieved, cached,
+ * and returned. See:
+ * [[http://en.wikipedia.org/wiki/Memoization memoization]]
+ *
+ * <p>The returned function is thread-safe. The function's serialized form
+ * does not contain the cached value, which will be recalculated when
+ * the reserialized instance is evaluated.
+ *
+ * @param duration the length of time after a value is created that it
+ * should stop being returned by subsequent evaluations
+ * @param unit the unit that {@code duration} is expressed in
+ * @throws IllegalArgumentException if {@code duration} is not positive
+ */
+ def memoizeWithExpiration[T](f: () => T, duration: Long, unit: TimeUnit): () => T = {
+ ExpiringMemoizingSupplier[T](checkNotNull(f), duration, unit)
+ }
+
+ /** Returns a function whose `apply()` method synchronizes on
+ * `f` before calling it, making it thread-safe.
+ */
+ def synchronizedSupplier[T](f: () => T): () => T = f match {
+ case _: ThreadSafeSupplier[T] => f
+ case _ => ThreadSafeSupplier(checkNotNull(f))
+ }
+
+ /** Adds an `asJava` method that wraps a Scala function `() => T` in
+ * a Guava `Supplier[T]`.
+ *
+ * The returned Guava `Supplier[T]` forwards all calls of the `apply` method
+ * to the given Scala function `() => T`.
+ *
+ * @param fnc the Scala function `() => T` to wrap in a Guava `Supplier[T]`
+ * @return An object with an `asJava` method that returns a Guava `Supplier[T]`
+ * view of the argument
+ */
+ implicit final def asGuavaSupplierConverter[T](fnc: () => T): AsJava[GuavaSupplier[T]] = {
+ def convert(fnc: () => T): GuavaSupplier[T] = fnc match {
+ case s: AsMangoSupplier[T] => s.delegate
+ case _ => AsGuavaSupplier(fnc)
+ }
+ new AsJava(convert(fnc))
+ }
+
+ /** Adds an `asScala` method that wraps a Guava `Supplier[T]` in
+ * a Scala function `() => T`.
+ *
+ * The returned Scala function `() => T` forwards all calls of the `apply` method
+ * to the given Guava `Supplier[T]`.
+ *
+ * @param pred the Guava `Supplier[T]` to wrap in a Scala function `() => T`
+ * @return An object with an `asScala` method that returns a Scala function `() => T`
+ * view of the argument
+ */
+ implicit final def asMangoSupplierConverter[T](fnc: GuavaSupplier[T]): AsScala[() => T] = {
+ def convert(fnc: GuavaSupplier[T]) = fnc match {
+ case AsGuavaSupplier(delegate) => delegate
+ case _ => AsMangoSupplier(fnc)
+ }
+ new AsScala(convert(fnc))
+ }
+}
+
+// visible for testing
+@SerialVersionUID(1L)
+private[mango] case class MemoizingSupplier[T](f: () => T) extends (() => T) with Serializable {
+ import org.feijoas.mango.common.base.Suppliers._
+ private val delegate = GuavaSuppliers.memoize(f.asJava)
+ override def apply(): T = delegate.get()
+ override def toString = "Suppliers.memoize(" + f + ")"
+}
+
+// visible for testing
+@SerialVersionUID(1L)
+private[mango] case class ExpiringMemoizingSupplier[T](f: () => T, duration: Long, unit: TimeUnit) extends (() => T) with Serializable {
+ import org.feijoas.mango.common.base.Suppliers._
+ private val delegate = GuavaSuppliers.memoizeWithExpiration(f.asJava, duration, unit)
+ override def apply(): T = delegate.get()
+ override def toString = "Suppliers.memoizeWithExpiration(" + f + ", " + duration + ", " + unit + ")"
+}
+
+// visible for testing
+@SerialVersionUID(1L)
+private[mango] case class ThreadSafeSupplier[T](f: () => T) extends (() => T) with Serializable {
+ override def apply(): T = f.synchronized { f() }
+ override def toString = "Suppliers.synchronizedSupplier(" + f + ")"
+}
+
+/** Wraps a Scala function in a Guava `Supplier`
+ */
+@SerialVersionUID(1L)
+private[mango] case class AsGuavaSupplier[T](delegate: () => T)
+ extends GuavaSupplier[T] with Serializable {
+ checkNotNull(delegate)
+ override def get() = delegate()
+}
+
+/** Wraps a Guava `Supplier` in a Scala function
+ */
+@SerialVersionUID(1L)
+private[mango] case class AsMangoSupplier[T](delegate: GuavaSupplier[T])
+ extends (() => T) with Serializable {
+ checkNotNull(delegate)
+ override def apply() = delegate.get()
+}
View
87 src/main/scala/org/feijoas/mango/common/base/Ticker.scala
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2013 The Mango Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ * The code of this project is a port of (or wrapper around) the Guava-libraries.
+ * See http://code.google.com/p/guava-libraries/
+ *
+ * @author Markus Schneider
+ */
+package org.feijoas.mango.common.base
+
+import org.feijoas.mango.common.annotations.Beta
+import com.google.common.base.{ Ticker => GuavaTicker }
+import org.feijoas.mango.common.convert.{ AsJava, AsScala }
+
+/** A time source; returns a time value representing the number of nanoseconds elapsed since some
+ * fixed but arbitrary point in time. Note that most users should use `Stopwatch` instead of
+ * interacting with this class directly.
+ *
+ * <p><b>Warning:</b> this interface can only be used to measure elapsed time, not wall time.
+ *
+ * @author Markus Schneider
+ * @since 0.7 (copied from guava-libraries)
+ */
+@Beta
+trait Ticker {
+
+ /** Returns the number of nanoseconds elapsed since this ticker's fixed
+ * point of reference.
+ */
+ def read(): Long
+}
+
+/** Factory for [[Ticker]] instances. */
+object Ticker {
+
+ /** A ticker that reads the current time using `System#nanoTime`.
+ */
+ def systemTicker() = GuavaTicker.systemTicker().asScala
+
+ /** Adds an `asJava` method that wraps a Mango `Ticker` in
+ * a Guava `Ticker`.
+ *
+ * The returned Guava `Ticker` forwards all calls of the `read` method
+ * to the given Scala `ticker`.
+ *
+ * @param ticker the Mango `Ticker` to wrap in a Guava `Ticker`
+ * @return An object with an `asJava` method that returns a Guava `Ticker`
+ * view of the argument
+ */
+ implicit final def asGuavaTickerConverter[T](ticker: Ticker): AsJava[GuavaTicker] = {
+ def convert(ticker: Ticker): GuavaTicker = new GuavaTicker {
+ override def read() = ticker.read()
+ }
+ new AsJava(convert(ticker))
+ }
+
+ /** Adds an `asScala` method that wraps a Guava `Ticker` in
+ * a Mango `Ticker`.
+ *
+ * The returned Mango `Ticker` forwards all calls of the `read` method
+ * to the given Guava `ticker`.
+ *
+ * @param ticker the Guava `Ticker` to wrap in a Mango `Ticker`
+ * @return An object with an `asScala` method that returns a Mango `Ticker`
+ * view of the argument
+ */
+ implicit final def asMangoTickerConverter[T](ticker: GuavaTicker): AsScala[Ticker] = {
+ def convert(ticker: GuavaTicker): Ticker = new Ticker {
+ override def read() = ticker.read()
+ }
+ new AsScala(convert(ticker))
+ }
+}
View
30 src/main/scala/org/feijoas/mango/common/base/package.scala
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2013 The Mango Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ * The code of this project is a port of (or wrapper around) the Guava-libraries.
+ * See http://code.google.com/p/guava-libraries/
+ *
+ * @author Markus Schneider
+ */
+package org.feijoas.mango.common
+
+/** Basic utility libraries and interfaces.
+ *
+ * @author Markus Schneider
+ * @since 0.7
+ */
+package object base
View
155 src/main/scala/org/feijoas/mango/common/cache/Cache.scala
@@ -0,0 +1,155 @@
+/*
+ * Copyright (C) 2013 The Mango Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ * The code of this project is a port of (or wrapper around) the Guava-libraries.
+ * See http://code.google.com/p/guava-libraries/
+ *
+ * @author Markus Schneider
+ */
+package org.feijoas.mango.common.cache
+
+import java.util.concurrent.ExecutionException
+
+import scala.Option.option2Iterable
+import scala.annotation.meta.{ beanGetter, beanSetter, field, getter, setter }
+import scala.collection.{ concurrent, immutable }
+
+import org.feijoas.mango.common.annotations.Beta
+import org.feijoas.mango.common.convert.AsScala
+
+import com.google.common.cache.{ Cache => GuavaCache }
+
+/** A semi-persistent mapping from keys to values. Cache entries are manually added using
+ * `getOrElseUpdate(key, loader)` or `put(key, value)`, and are stored in the cache until
+ * either evicted or manually invalidated.
+ *
+ * Implementations of this interface are expected to be thread-safe, and can be safely accessed
+ * by multiple concurrent threads.
+ *
+ * @author Markus Schneider
+ * @since 0.7 (copied from Guava-libraries)
+ */
+@Beta
+trait Cache[K, V] {
+
+ /** Returns an [[http://www.scala-lang.org/api/current/index.html#scala.Option Option]] which is `Some(value)` with the value associated with
+ * `key` in this cache, or `None` if there is no cached value for `key`.
+ */
+ def getIfPresent(key: K): Option[V]
+
+ /** Returns the value associated with `key` in this cache, obtaining that value from
+ * `loader` if necessary. No observable state associated with this cache is modified
+ * until loading completes. This method provides a simple substitute for the conventional
+ * `if cached, return; otherwise create, cache and return` pattern.
+ *
+ * '''Warning:''' as with `[[CacheLoader]]#load`, `loader` '''must not''' return
+ * `null`; it may either return a non-null value or throw an exception.
+ * The recommend way would be to use a `Cache[K, Option[V]]` with `loader`
+ * return `None` instead of throwing an exception.
+ *
+ * This method is equivalent to `Cache.get(K, Callable<? extends V>)` from
+ * the Guava-Libraries.
+ *
+ * @throws ExecutionException if a checked exception was thrown while loading the value
+ * @throws UncheckedExecutionException if an unchecked exception was thrown while loading the
+ * value
+ * @throws ExecutionError if an error was thrown while loading the value
+ */
+ @throws[ExecutionException]
+ def getOrElseUpdate(key: K, loader: () => V): V
+
+ /** Returns a map of the values associated with `keys` in this cache.
+ * The returned map will only contain entries which are already present in the cache.
+ */
+ def getAllPresent(keys: Traversable[K]): immutable.Map[K, V] = {
+ def getOptional(key: K): Option[(K, V)] = getIfPresent(key) match {
+ case Some(value) => Some((key, value))
+ case _ => None
+ }
+
+ immutable.Map() ++ keys.flatMap(getOptional)
+ }
+
+ /** Adds a new key/value pair to this cache. If the cache previously contained a
+ * value associated with the `key`, the old value is replaced by the `value`.
+ *
+ * Prefer `getOrElseUpdate(key, loader)` when using the conventional
+ * `if cached, return; otherwise create, cache and return` pattern.
+ *
+ */
+ def put(key: K, value: V): Unit
+
+ /** Puts all key/value pairs from the specified `Traversable` to the cache.
+ * The effect of this call is equivalent to that of calling `put(key, value)` for
+ * each `(key, value)` in the `Traversable`. The behavior of this operation is undefined
+ * if the specified map is modified while the operation is in progress.
+ */
+ def putAll(kvs: Traversable[(K, V)]): Unit = {
+ kvs.seq foreach { kv => put(kv._1, kv._2) }
+ }
+
+ /** Discards any cached value for key `key`.
+ */
+ def invalidate(key: K): Unit
+
+ /** Discards any cached values for keys `keys`.
+ */
+ def invalidateAll(keys: Traversable[K]): Unit = { keys foreach invalidate }
+
+ /** Discards all entries in the cache.
+ */
+ def invalidateAll(): Unit
+
+ /** Returns the approximate number of entries in this cache.
+ */
+ def size(): Long
+
+ /** Returns a current snapshot of this cache's cumulative statistics. All stats are initialized
+ * to zero, and are monotonically increasing over the lifetime of the cache.
+ */
+ def stats(): CacheStats
+
+ /** Performs any pending maintenance operations needed by the cache. Exactly which activities are
+ * performed -- if any -- is implementation-dependent.
+ */
+ def cleanUp(): Unit = {}
+
+ /** Returns a view of the entries stored in this cache as a thread-safe map. Modifications made to
+ * the map directly affect the cache.
+ */
+ def asMap(): concurrent.Map[K, V]
+}
+
+/** Utility functions to convert between Guava `Cache[K, V]` and `Cache[K, V]`
+ * and vice versa.
+ */
+object Cache {
+
+ /** Adds an `asScala` method that wraps Guava `Cache[K, V]` in a Mango `Cache[K, V]`
+ * using a `CacheWrapper[K, V]`.
+ *
+ * The returned Mango `Cache[K, V]` forwards all method calls to the provided
+ * Guava `Cache[K, V]`.
+ *
+ * @param cache the Guava `Cache[K, V]` to wrap in a Mango `Cache[K, V]`
+ * @return An object with an `asScala` method that returns a Mango `Cache[K, V]`
+ * view of the argument
+ */
+ implicit def asCacheConverter[K, V](cache: GuavaCache[K, V]): AsScala[Cache[K, V]] = {
+ new AsScala(CacheWrapper(cache))
+ }
+}
View
531 src/main/scala/org/feijoas/mango/common/cache/CacheBuilder.scala
@@ -0,0 +1,531 @@
+/*
+ * Copyright (C) 2013 The Mango Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ * The code of this project is a port of (or wrapper around) the Guava-libraries.
+ * See http://code.google.com/p/guava-libraries/
+ *
+ * @author Markus Schneider
+ */
+package org.feijoas.mango.common.cache
+
+import scala.concurrent.duration._
+import com.google.common.cache.{ CacheBuilder => GuavaCacheBuilder }
+import org.feijoas.mango.common.base.Ticker
+import org.feijoas.mango.common.base.Preconditions._
+import org.feijoas.mango.common.cache._
+import org.feijoas.mango.common.cache.CacheWrapper._
+import org.feijoas.mango.common.cache.CacheLoaderWrapper._
+import org.feijoas.mango.common.cache.LoadingCacheWrapper._
+import org.feijoas.mango.common.cache.Weigher._
+import org.feijoas.mango.common.cache.RemovalListener._
+
+/** A builder of [[LoadingCache]] and [[Cache]] instances having any combination of the
+ * following features:
+ *
+ * <ul>
+ * <li>automatic loading of entries into the cache
+ * <li>least-recently-used eviction when a maximum size is exceeded
+ * <li>time-based expiration of entries, measured since last access or last write
+ * <li>keys automatically wrapped in {@linkplain WeakReference weak} references
+ * <li>values automatically wrapped in {@linkplain WeakReference weak} or
+ * {@linkplain SoftReference soft} references
+ * <li>notification of evicted (or otherwise removed) entries
+ * <li>accumulation of cache access statistics
+ * </ul>
+ *
+ * These features are all optional; caches can be created using all or none of them. By default
+ * cache instances created by {@code CacheBuilder} will not perform any type of eviction.
+ *
+ * Usage example:
+ * {{{
+ *
+ * val createExpensiveGraph: Key => Graph = ...
+ *
+ * val graphs: LoadingCache[Key, Graph] = CacheBuilder.newBuilder()
+ * .maximumSize(10000)
+ * .expireAfterWrite(10, TimeUnit.MINUTES)
+ * .removalListener(MY_LISTENER)
+ * .build(createExpensiveGraph)
+ *
+ * }}}
+ *
+ * This class just delegates to the [[http://code.google.com/p/guava-libraries/ CacheBuilder implementation in Guava]]
+ *
+ *
+ * '''Note:''' by default, the returned cache uses equality comparisons to determine
+ * equality for keys or values. However, if `weakKeys` was specified, the cache uses identity (`eq`)
+ * comparisons instead for keys. Likewise, if `weakValues` or `softValues` was
+ * specified, the cache uses identity comparisons for values.
+ *
+ *
+ * Entries are automatically evicted from the cache when any of
+ * <ul>
+ * <li>`maximumSize(Long)`
+ * <li>`maximumWeight(Long)`
+ * <li>`expireAfterWrite`
+ * <li>`expireAfterAccess`
+ * <li>`weakKeys`
+ * <li>`weakValues`
+ * <li>`softValues`
+ * </ul>
+ * are requested.
+ *
+ *
+ * If `maximumSize` or `maximumWeight` is requested entries may be evicted on each cache
+ * modification.
+ *
+ * If `expireAfterWrite` or `#expireAfterAccess` is requested entries may be evicted on each
+ * cache modification, on occasional cache accesses, or on calls to `Cache.cleanUp()`. Expired
+ * entries may be counted in `Cache.size()`}, but will never be visible to read or write
+ * operations.
+ *
+ * If `weakKeys`, `weakValues`, or `softValues` are requested, it is possible for a key or value present in
+ * the cache to be reclaimed by the garbage collector. Entries with reclaimed keys or values may be
+ * removed from the cache on each cache modification, on occasional cache accesses, or on calls to
+ * `Cache.cleanUp()`; such entries may be counted in `Cache.size()`, but will never be
+ * visible to read or write operations.
+ *
+ * Certain cache configurations will result in the accrual of periodic maintenance tasks which
+ * will be performed during write operations, or during occasional read operations in the absense of
+ * writes. The `Cache.cleanUp()` method of the returned cache will also perform maintenance, but
+ * calling it should not be necessary with a high throughput cache. Only caches built with
+ * `removalListener`, `expireAfterWrite`, `expireAfterAccess`, `weakKeys`, `weakValues`, or
+ * `softValues` perform periodic maintenance.
+ *
+ * The caches produced by [[CacheBuilder]] are serializable, and the deserialized caches
+ * retain all the configuration properties of the original cache. Note that the serialized form does
+ * <i>not</i> include cache contents, but only configuration.
+ *
+ * <p>'''Warning:''' This implementation differs from Guava. It is truly immutable and returns a new
+ * instance on each method call. Use the standard method-chaining idiom,
+ * as illustrated in the documentation above. This has the advantage that it is
+ * not possible to create a cache whose key or value type is incompatible with e.g. the weigher. This
+ * `CacheBuilder` is also contravariant in both type `K` and `V`.
+ *
+ *
+ * See the [[http://code.google.com/p/guava-libraries/ Guava User Guide]] for
+ * more information.
+ *
+ * @author Markus Schneider
+ * @since 0.7 (copied from Guava-libraries)
+ *
+ * @tparam K the key of the cache
+ * @tparam V the value of the cache
+ */
+final case class CacheBuilder[-K, -V] private (
+ private val withWeigher: Option[(K, V) => Int],
+ private val withRemovalListener: Option[RemovalNotification[K, V] => Unit],
+ private val withInitialCapacity: Option[Int],
+ private val withConcurrencyLevel: Option[Int],
+ private val withMaximumSize: Option[Long],
+ private val withMaximumWeight: Option[Long],
+ private val withWeakKeys: Option[Boolean],
+ private val withWeakValues: Option[Boolean],
+ private val withSoftValues: Option[Boolean],
+ private val withExpireAfterWrite: Option[(Long, TimeUnit)],
+ private val withExpireAfterAccess: Option[(Long, TimeUnit)],
+ private val withRefreshAfterWrite: Option[(Long, TimeUnit)],
+ private val withTicker: Option[Ticker],
+ private val withRecordStats: Option[Boolean]) {
+
+ /** Builds a cache which does not automatically load values when keys are requested.
+ *
+ * <p>Consider `build(CacheLoader)` instead, if it is feasible to implement a
+ * [[CacheLoader]].
+ *
+ * <p>This method does not alter the state of this [[CacheBuilder]] instance, so it can be
+ * invoked again to create multiple independent caches.
+ *
+ * @return a cache having the requested features
+ */
+ def build[K1 <: K, V1 <: V](): Cache[K1, V1] = {
+ val guavaBuilder: GuavaCacheBuilder[K1, V1] = CacheBuilder.createGuavaBuilder(this)
+ CacheWrapper(guavaBuilder.build())
+ }
+
+ /** Builds a cache, which either returns an already-loaded value for a given key or atomically
+ * computes or retrieves it using the supplied `loader` (the function used to compute corresponding values).
+ * If another thread is currently
+ * loading the value for this key, simply waits for that thread to finish and returns its
+ * loaded value. Note that multiple threads can concurrently load values for distinct keys.
+ *
+ * <p>This method does not alter the state of this [[CacheBuilder]] instance, so it can be
+ * invoked again to create multiple independent caches.
+ *
+ * @param loader the cache loader used to obtain new values
+ * @return a cache having the requested features
+ */
+ def build[K1 <: K, V1 <: V](loader: K1 => V1): LoadingCache[K1, V1] = {
+ build(CacheLoader.from(loader))
+ }
+
+ /** Builds a cache, which either returns an already-loaded value for a given key or atomically
+ * computes or retrieves it using the supplied [[CacheLoader]]. If another thread is currently
+ * loading the value for this key, simply waits for that thread to finish and returns its
+ * loaded value. Note that multiple threads can concurrently load values for distinct keys.
+ *
+ * <p>This method does not alter the state of this [[CacheBuilder]] instance, so it can be
+ * invoked again to create multiple independent caches.
+ *
+ * @param loader the cache loader used to obtain new values
+ * @return a cache having the requested features
+ */
+ def build[K1 <: K, V1 <: V](loader: CacheLoader[K1, V1]): LoadingCache[K1, V1] = {
+ checkNotNull(loader)
+ val guavaBuilder: GuavaCacheBuilder[K1, V1] = CacheBuilder.createGuavaBuilder(this)
+ LoadingCacheWrapper(guavaBuilder.build(loader.asJava))
+ }
+
+ /** Specifies the weigher to use in determining the weight of entries. Entry weight is taken
+ * into consideration by `maximumWeight(Long)` when determining which entries to evict, and
+ * use of this method requires a corresponding call to `maximumWeight(Long)` prior to
+ * calling `build`. Weights are measured and recorded when entries are inserted into the
+ * cache, and are thus effectively static during the lifetime of a cache entry.
+ *
+ * <p>When the weight of an entry is zero it will not be considered for size-based eviction
+ * (though it still may be evicted by other means).
+ *
+ * @param weigher the weigher to use in calculating the weight of cache entries
+ */
+ def weigher[K1 <: K, V1 <: V](weigher: (K1, V1) => Int): CacheBuilder[K1, V1] = {
+ checkNotNull(weigher)
+ checkState(withWeigher == None, "weigher was already set")
+ checkState(withMaximumSize == None, "weigher can not be combined with maximum size")
+ copy(withWeigher = Some(weigher))
+ }
+
+ /** Specifies a listener instance that caches should notify each time an entry is removed for any
+ * [[RemovalCause]] reason. Each cache created by this builder will invoke this listener
+ * as part of the routine maintenance described in the class documentation above.
+ *
+ * <p>'''Warning:''' any exception thrown by `listener` will <i>not</i> be propagated to
+ * the [[Cache]] user, only logged via a `Logger`.
+ *
+ * @return the cache builder reference that should be used instead of {@code this} for any
+ * remaining configuration and cache building
+ */
+ def removalListener[K1 <: K, V1 <: V](listener: (RemovalNotification[K1, V1]) => Unit): CacheBuilder[K1, V1] = {
+ checkNotNull(listener)
+ checkState(withRemovalListener == None, "removal listener was already set")
+ copy(withRemovalListener = Some(listener))
+ }
+
+ /** Sets the minimum total size for the internal hash tables. For example, if the initial capacity
+ * is `60`, and the concurrency level is `8`, then eight segments are created, each
+ * having a hash table of size eight. Providing a large enough estimate at construction time
+ * avoids the need for expensive resizing operations later, but setting this value unnecessarily
+ * high wastes memory.
+ *
+ * @throws IllegalArgumentException if {@code initialCapacity} is negative
+ */
+ def initialCapacity(initialCapacity: Int): CacheBuilder[K, V] = {
+ checkNotNull(initialCapacity)
+ checkState(withInitialCapacity == None, "initial capacity was already set to %s", withInitialCapacity)
+ checkArgument(initialCapacity >= 0)
+ copy(withInitialCapacity = Some(initialCapacity))
+ }
+
+ /** Guides the allowed concurrency among update operations. Used as a hint for internal sizing. The
+ * table is internally partitioned to try to permit the indicated number of concurrent updates
+ * without contention. Because assignment of entries to these partitions is not necessarily
+ * uniform, the actual concurrency observed may vary. Ideally, you should choose a value to
+ * accommodate as many threads as will ever concurrently modify the table. Using a significantly
+ * higher value than you need can waste space and time, and a significantly lower value can lead
+ * to thread contention. But overestimates and underestimates within an order of magnitude do not
+ * usually have much noticeable impact. A value of one permits only one thread to modify the cache
+ * at a time, but since read operations and cache loading computations can proceed concurrently,
+ * this still yields higher concurrency than full synchronization.
+ *
+ * <p> Defaults to `4`. '''Note:''' The default may change in the future. If you care about this
+ * value, you should always choose it explicitly.
+ *
+ * <p>The current implementation uses the concurrency level to create a fixed number of hashtable
+ * segments, each governed by its own write lock. The segment lock is taken once for each explicit
+ * write, and twice for each cache loading computation (once prior to loading the new value,
+ * and once after loading completes). Much internal cache management is performed at the segment
+ * granularity. For example, access queues and write queues are kept per segment when they are
+ * required by the selected eviction algorithm. As such, when writing unit tests it is not
+ * uncommon to specify `concurrencyLevel(1)` in order to achieve more deterministic eviction
+ * behavior.
+ *
+ * <p>Note that future implementations may abandon segment locking in favor of more advanced
+ * concurrency controls.
+ *
+ * @throws IllegalArgumentException if {@code concurrencyLevel} is nonpositive
+ */
+ def concurrencyLevel(concurrencyLevel: Int): CacheBuilder[K, V] = {
+ checkNotNull(concurrencyLevel)
+ checkState(withConcurrencyLevel == None, "concurrency level was already set to %s", withConcurrencyLevel)
+ checkArgument(concurrencyLevel > 0)
+ copy(withConcurrencyLevel = Some(concurrencyLevel))
+ }
+
+ /** Specifies the maximum number of entries the cache may contain. Note that the cache <b>may evict
+ * an entry before this limit is exceeded</b>. As the cache size grows close to the maximum, the
+ * cache evicts entries that are less likely to be used again. For example, the cache may evict an
+ * entry because it hasn't been used recently or very often.
+ *
+ * <p>When `size` is zero, elements will be evicted immediately after being loaded into the
+ * cache. This can be useful in testing, or to disable caching temporarily without a code change.
+ *
+ * <p>This feature cannot be used in conjunction with `maximumWeight`.
+ *
+ * @param size the maximum size of the cache
+ * @throws IllegalArgumentException if `size` is negative
+ */
+ def maximumSize(size: Long): CacheBuilder[K, V] = {
+ checkNotNull(size)
+ checkState(withMaximumSize == None, "maximum size was already set to %s", withMaximumSize)
+ checkState(withMaximumWeight == None, "maximum weight was already set to %s", withMaximumWeight)
+ checkState(withWeigher == None, "maximum size can not be combined with weigher")
+ checkArgument(size >= 0, "maximum size must not be negative")
+ copy(withMaximumSize = Some(size))
+ }
+
+ /** Specifies the maximum weight of entries the cache may contain. Weight is determined using the
+ * `weigher` specified with `weigher((K1, V1) => Int)`, and use of this method requires a
+ * corresponding call to `weigher` prior to calling `build`.
+ *
+ * <p>Note that the cache <b>may evict an entry before this limit is exceeded</b>. As the cache
+ * size grows close to the maximum, the cache evicts entries that are less likely to be used
+ * again. For example, the cache may evict an entry because it hasn't been used recently or very
+ * often.
+ *
+ * <p>When {@code weight} is zero, elements will be evicted immediately after being loaded into
+ * cache. This can be useful in testing, or to disable caching temporarily without a code
+ * change.
+ *
+ * <p>Note that weight is only used to determine whether the cache is over capacity; it has no
+ * effect on selecting which entry should be evicted next.
+ *
+ * <p>This feature cannot be used in conjunction with `maximumSize`.
+ *
+ * @param weight the maximum total weight of entries the cache may contain
+ * @throws IllegalArgumentException if {@code weight} is negative
+ */
+ def maximumWeight(weight: Long): CacheBuilder[K, V] = {
+ checkNotNull(weight)
+ checkState(withMaximumWeight == None, "maximum weight was already set to %s", withMaximumWeight)
+ checkState(withMaximumSize == None, "maximum size was already set to %s", withMaximumSize)
+ checkArgument(weight >= 0, "maximum weight must not be negative")
+ copy(withMaximumWeight = Some(weight))
+ }
+
+ /** Specifies that each key (not value) stored in the cache should be wrapped in a
+ * `WeakReference` (by default, strong references are used).
+ *
+ * <p><b>Warning:</b> when this method is used, the resulting cache will use identity (`eq`)
+ * comparison to determine equality of keys.
+ *
+ * <p>Entries with keys that have been garbage collected may be counted in `Cache.size()`,
+ * but will never be visible to read or write operations; such entries are cleaned up as part of
+ * the routine maintenance described in the class doc.
+ *
+ * @throws IllegalStateException if the key strength was already set
+ */
+ def weakKeys(): CacheBuilder[K, V] = {
+ checkState(withWeakKeys == None, "Key strength was already set")
+ copy(withWeakKeys = Some(true))
+ }
+
+ /** Specifies that each value (not key) stored in the cache should be wrapped in a
+ * `WeakReference` (by default, strong references are used).
+ *
+ * <p>Weak values will be garbage collected once they are weakly reachable. This makes them a poor
+ * candidate for caching; consider {@link #softValues} instead.
+ *
+ * <p><b>Note:</b> when this method is used, the resulting cache will use identity (`eq`)
+ * comparison to determine equality of values.
+ *
+ * <p>Entries with values that have been garbage collected may be counted in {@link Cache#size},
+ * but will never be visible to read or write operations; such entries are cleaned up as part of
+ * the routine maintenance described in the class doc.
+ */
+ def weakValues(): CacheBuilder[K, V] = {
+ checkState(withWeakValues == None, "Value strength was already set")
+ checkState(withSoftValues == None, "Value strength was already set")
+ copy(withWeakValues = Some(true))
+ }
+
+ /** Specifies that each value (not key) stored in the cache should be wrapped in a
+ * `SoftReference` (by default, strong references are used). Softly-referenced objects will
+ * be garbage-collected in a <i>globally</i> least-recently-used manner, in response to memory
+ * demand.
+ *
+ * <p><b>Warning:</b> in most circumstances it is better to set a per-cache {@linkplain
+ * #maximumSize(long) maximum size} instead of using soft references. You should only use this
+ * method if you are well familiar with the practical consequences of soft references.
+ *
+ * <p><b>Note:</b> when this method is used, the resulting cache will use identity (`eq`)
+ * comparison to determine equality of values.
+ *
+ * <p>Entries with values that have been garbage collected may be counted in `Cache.size()`,
+ * but will never be visible to read or write operations; such entries are cleaned up as part of
+ * the routine maintenance described in the class doc.
+ */
+ def softValues(): CacheBuilder[K, V] = {
+ checkState(withWeakValues == None, "Value strength was already set")
+ checkState(withSoftValues == None, "Value strength was already set")
+ copy(withSoftValues = Some(true))
+ }
+
+ /** Specifies that each entry should be automatically removed from the cache once a fixed duration
+ * has elapsed after the entry's creation, or the most recent replacement of its value.
+ *
+ * <p>When `duration` is zero, this method hands off to
+ * `maximumSize(Long)` `(0)`, ignoring any otherwise-specificed maximum
+ * size or weight. This can be useful in testing, or to disable caching temporarily without a code
+ * change.
+ *
+ * <p>Expired entries may be counted in {@link Cache#size}, but will never be visible to read or
+ * write operations. Expired entries are cleaned up as part of the routine maintenance described
+ * in the class doc.
+ *
+ * @param duration the length of time after an entry is created that it should be automatically
+ * removed
+ * @param unit the unit that {@code duration} is expressed in
+ * @throws IllegalArgumentException if {@code duration} is negative
+ */
+ def expireAfterWrite(duration: Long, unit: TimeUnit): CacheBuilder[K, V] = {
+ checkNotNull(duration)
+ checkNotNull(unit)
+ checkState(withExpireAfterWrite == None, "expireAfterWrite was already set to %s", withExpireAfterWrite)
+ checkArgument(duration >= 0, "duration cannot be negative: %s %s", duration, unit)
+ copy(withExpireAfterWrite = Some((duration, unit)))
+ }
+
+ /** Specifies that each entry should be automatically removed from the cache once a fixed duration
+ * has elapsed after the entry's creation, the most recent replacement of its value, or its last
+ * access. Access time is reset by all cache read and write operations.
+ *
+ * <p>When `duration` is zero, this method hands off to
+ * `maximumSize(Long)` `(0)`, ignoring any otherwise-specificed maximum
+ * size or weight. This can be useful in testing, or to disable caching temporarily without a code
+ * change.
+ *
+ * <p>Expired entries may be counted in `Cache.size()`, but will never be visible to read or
+ * write operations. Expired entries are cleaned up as part of the routine maintenance described
+ * in the class doc.
+ *
+ * @param duration the length of time after an entry is last accessed that it should be
+ * automatically removed
+ * @param unit the unit that {@code duration} is expressed in
+ * @throws IllegalArgumentException if {@code duration} is negative
+ */
+ def expireAfterAccess(duration: Long, unit: TimeUnit): CacheBuilder[K, V] = {
+ checkNotNull(duration)
+ checkNotNull(unit)
+ checkState(withExpireAfterAccess == None, "expireAfterAccess was already set to %s ns", withExpireAfterAccess)
+ checkArgument(duration >= 0, "duration cannot be negative: %s %s", duration, unit)
+ copy(withExpireAfterAccess = Some((duration, unit)))
+ }
+
+ /** Specifies that active entries are eligible for automatic refresh once a fixed duration has
+ * elapsed after the entry's creation, or the most recent replacement of its value. The semantics
+ * of refreshes are specified in `LoadingCache.refresh()`, and are performed by calling
+ * `CacheLoader.reload()`.
+ *
+ * <p>As the default implementation of `CacheLoader.reload()` is synchronous, it is
+ * recommended that users of this method override `CacheLoader.reload()` with an asynchronous
+ * implementation; otherwise refreshes will be performed during unrelated cache read and write
+ * operations.
+ *
+ * <p>Currently automatic refreshes are performed when the first stale request for an entry
+ * occurs. The request triggering refresh will make a blocking call to `CacheLoader.reload()`
+ * and immediately return the new value if the returned future is complete, and the old value
+ * otherwise.
+ *
+ * <p><b>Note:</b> <i>all exceptions thrown during refresh will be logged and then swallowed</i>.
+ *
+ * @param duration the length of time after an entry is created that it should be considered
+ * stale, and thus eligible for refresh
+ * @param unit the unit that `duration` is expressed in
+ * @throws IllegalArgumentException if `duration` is negative
+ */
+ def refreshAfterWrite(duration: Long, unit: TimeUnit): CacheBuilder[K, V] = {
+ checkNotNull(duration)
+ checkNotNull(unit)
+ checkState(withRefreshAfterWrite == None, "refresh was already set to %s", withRefreshAfterWrite)
+ checkArgument(duration > 0, "duration must be positive: %s %s", duration, unit)
+ copy(withRefreshAfterWrite = Some((duration, unit)))
+ }
+
+ /** Specifies a nanosecond-precision time source for use in determining when entries should be
+ * expired. By default, `System#nanoTime` is used.
+ *
+ * <p>The primary intent of this method is to facilitate testing of caches which have been
+ * configured with `expireAfterWrite` or `expireAfterAccess`.
+ *
+ * @throws IllegalStateException if a ticker was already set
+ */
+ def ticker(ticker: Ticker): CacheBuilder[K, V] = {
+ checkNotNull(ticker)
+ checkState(withTicker == None)
+ copy(withTicker = Some(ticker))
+ }
+
+ /** Enable the accumulation of [[CacheStats]] during the operation of the cache. Without this
+ * `Cache.stats()` will return zero for all statistics. Note that recording stats requires
+ * bookkeeping to be performed with each operation, and thus imposes a performance penalty on
+ * cache operation.
+ */
+ def recordStats(): CacheBuilder[K, V] = copy(withRecordStats = Some(true))
+}
+
+/** Factory for [[CacheBuilder]] instances. */
+final object CacheBuilder {
+
+ /** Creates a new [[LoadingCache]] which caches the values from the function `f`
+ * with default settings, including strong keys, strong values,
+ * and no automatic eviction of any kind.
+ */
+ def cache[K, V](f: K => V) = newBuilder().build(f)
+
+ /** Constructs a new [[CacheBuilder]] instance with default settings, including strong keys,
+ * strong values, and no automatic eviction of any kind.
+ */
+ def apply() = newBuilder()
+
+ /** Constructs a new [[CacheBuilder]] instance with default settings, including strong keys,
+ * strong values, and no automatic eviction of any kind.
+ */
+ def newBuilder() = new CacheBuilder[Any, Any](None, None, None, None, None, None, None, None, None, None, None, None, None, None)
+
+ /** Creates a Guava CacheBuilder and sets the all properties from this
+ * CacheBuilder that are not `None`
+ *
+ * @return a Guava CacheBuilder with the properties mapped from this CachBuilder
+ */
+ private[mango] def createGuavaBuilder[K, V](builder: CacheBuilder[K, V]): GuavaCacheBuilder[K, V] = {