From 0f0fb2e69f3be99a8cca9c7399383f1c151ee920 Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Sun, 14 Dec 2014 20:15:10 -0500 Subject: [PATCH 01/14] Add ability to link to other Scaladocs in ours. We do two things: turn on autoAPIMappings, which automatically lets us link to Scaladocs for modules that set their public URIs, and explicitly add the mapping for the scala-xml module. The addition of the scala-xml module is also reasonably generic, so we can add more module-specific URIs fairly easily in the future as needed. --- project/Build.scala | 44 ++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 42 insertions(+), 2 deletions(-) diff --git a/project/Build.scala b/project/Build.scala index 1d4f98b360..17afdcfd3d 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -19,9 +19,35 @@ import Keys._ import net.liftweb.sbt.LiftBuildPlugin._ import Dependencies._ +/** + * Pattern-matches an attributed file, extracting its module organization, + * name, and revision if available in its attributes. + */ +object MatchingModule { + def unapply(file: Attributed[File]): Option[(String,String,String)] = { + file.get(moduleID.key).map { moduleInfo => + (moduleInfo.organization, moduleInfo.name, moduleInfo.revision) + } + } +} object BuildDef extends Build { + /** + * A helper that returns the revision and JAR file for a given dependency. + * Useful when trying to attach API doc URI information. + */ + def findManagedDependency(classpath: Seq[Attributed[File]], + organization: String, + name: String): Option[(String,File)] = { + classpath.collectFirst { + case entry @ MatchingModule(moduleOrganization, moduleName, revision) + if moduleOrganization == organization && + moduleName.startsWith(name) => + (revision, entry.data) + } + } + lazy val liftProjects = core ++ web ++ persistence lazy val framework = @@ -179,6 +205,20 @@ object BuildDef extends Build { liftProject(id = if (module.startsWith(prefix)) module else prefix + module, base = file(base) / module.stripPrefix(prefix)) - def liftProject(id: String, base: File): Project = - Project(id, base).settings(liftBuildSettings: _*).settings(scalacOptions ++= List("-feature", "-language:implicitConversions")) + def liftProject(id: String, base: File): Project = { + Project(id, base) + .settings(liftBuildSettings: _*) + .settings(scalacOptions ++= List("-feature", "-language:implicitConversions")) + .settings( + autoAPIMappings := true, + apiMappings ++= { + val cp: Seq[Attributed[File]] = (fullClasspath in Compile).value + + findManagedDependency(cp, "org.scala-lang.modules", "scala-xml").map { + case (revision, file) => + (file -> url("http://www.scala-lang.org/api/" + version)) + }.toMap + } + ) + } } From c307b50f3b35551392f72a777ae10ee837f6d343 Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Sun, 14 Dec 2014 20:22:00 -0500 Subject: [PATCH 02/14] Clean up/improve lift-common Scaladocs. Some places get more detailed Scaladocs, others get some facelifts and get converted to wiki syntax instead of HTML. Examples added throughout. --- .../main/scala/net/liftweb/common/Box.scala | 838 +++++++++++------- .../net/liftweb/common/CombinableBox.scala | 15 +- .../net/liftweb/common/Conversions.scala | 58 +- .../net/liftweb/common/FuncJBridge.scala | 6 +- .../main/scala/net/liftweb/common/HList.scala | 91 +- .../main/scala/net/liftweb/common/LRU.scala | 60 +- .../net/liftweb/common/LoanWrapper.scala | 30 +- .../scala/net/liftweb/common/Logging.scala | 134 ++- .../net/liftweb/common/SimpleActor.scala | 46 +- .../scala/net/liftweb/common/SimpleList.scala | 51 +- 10 files changed, 859 insertions(+), 470 deletions(-) diff --git a/core/common/src/main/scala/net/liftweb/common/Box.scala b/core/common/src/main/scala/net/liftweb/common/Box.scala index 2cd805c553..d85c565999 100644 --- a/core/common/src/main/scala/net/liftweb/common/Box.scala +++ b/core/common/src/main/scala/net/liftweb/common/Box.scala @@ -24,7 +24,12 @@ import scala.reflect.Manifest import java.util.{Iterator => JavaIterator, ArrayList => JavaArrayList} /** - * The bridge from Java to Scala Box + * A bridge to make using Lift `[[Box]]` from Java easier. + * + * In particular, provides access to the `Box` companion object so that + * functions like `[[Box$.legacyNullTest legacyNullTest]]` can be used easily + * from Java, as well as access to the `[[Empty]]` singleton so that empty + * values can be created easily from Java. */ class BoxJBridge { /** @@ -33,25 +38,25 @@ class BoxJBridge { def box: BoxTrait = Box /** - * Get the None singleton + * Get the `[[Empty]]` singleton. */ def empty: EmptyBox = Empty } /** * The Box companion object provides methods to create a Box from: - * + * - an `[[scala.Option Option]]` + * - a `[[scala.collection.immutable.List List]]` + * - any `[[scala.AnyRef AnyRef]]` object, converting `null` to `[[Empty]]` and + * anything else to a `[[Full]]` with the given object * - * It also provides implicit methods to transform Option to Box, Box to Iterable, and Box to Option + * It also provides implicit methods to transform `Option` to `Box`, `Box` to + * `[[scala.collection.Iterable Iterable]]`, and `Box` to `Option`. */ object Box extends BoxTrait { /** - * Helper class to provide an easy way for converting Lists of Boxes[T] into - * a Box of List[T]. + * Helper class to provide an easy way for converting a `List[Box[T]]` into + * a `Box[List[T]]`. **/ implicit class ListOfBoxes[T](val theListOfBoxes: List[Box[T]]) extends AnyVal { /** @@ -87,17 +92,10 @@ object Box extends BoxTrait { } /** - * The Box companion object provides methods to create a Box from: - * - * - * It also provides implicit methods to transform Option to Box, Box to Iterable, and Box to Option + * Implementation for the `[[Box$]]` singleton. */ sealed trait BoxTrait { - val primativeMap: Map[Class[_], Class[_]] = Map( + val primitiveMap: Map[Class[_], Class[_]] = Map( java.lang.Boolean.TYPE -> classOf[java.lang.Boolean], java.lang.Character.TYPE -> classOf[java.lang.Character], java.lang.Byte.TYPE -> classOf[java.lang.Byte], @@ -107,9 +105,14 @@ sealed trait BoxTrait { java.lang.Long.TYPE -> classOf[java.lang.Long], java.lang.Short.TYPE -> classOf[java.lang.Short]) + @deprecated("Use the correctly-spelled primitiveMap instead.","3.0") + val primativeMap = primitiveMap + /** - * Create a Box from the specified Option. - * @return a Box created from an Option. Full(x) if the Option is Some(x) and Empty otherwise + * Create a `Box` from the specified `Option`. + * + * @return `Full` with the contents if the `Option` is `Some` + * and `Empty` otherwise. */ def apply[T](in: Option[T]) = in match { case Some(x) => Full(x) @@ -117,9 +120,9 @@ sealed trait BoxTrait { } /** - * Create a Box from the specified Box, checking for null. - * @return Full(x) if in is Full(x) and x is not null - * Empty otherwise + * Create a `Box` from the specified `Box`, checking for `null`. + * + * @return `Full(in)` if `in` is non-null, `Empty` otherwise. */ def apply[T](in: Box[T]) = in match { case Full(x) => legacyNullTest(x) @@ -128,8 +131,12 @@ sealed trait BoxTrait { } /** - * Transform a List with zero or one elements to a Box. - * @return a Box object containing the head of a List. Full(x) if the List contains at least one element and Empty otherwise. + * Transform a `List` with zero or one elements to a `Box`. + * + * Note that any elements past the head of the list are lost! + * + * @return `Full(x)` with the head of the list if it contains at least one + * element and `Empty` otherwise. */ def apply[T](in: List[T]) = in match { case x :: _ => Full(x) @@ -137,47 +144,65 @@ sealed trait BoxTrait { } /** - * Apply the specified PartialFunction to the specified value and return the result - * in a Full Box; if the pf is undefined at that point return Empty. - * @param pf the partial function to use to transform the value - * @param value the value to transform - * @return a Full box containing the transformed value if pf.isDefinedAt(value); Empty otherwise + * Apply the specified `PartialFunction` to the specified `value` and return the result + * in a `Full`; if the `pf`` is not defined at that point return `Empty`. + * + * @param pf The partial function to use to transform the value. + * @param value The value to transform. + * + * @return A `Full` containing the transformed value if + * `pf.isDefinedAt(value)` and `Empty` otherwise. */ def apply[InType, OutType](pf: PartialFunction[InType, OutType])(value: InType): Box[OutType] = if (pf.isDefinedAt(value)) Full(pf(value)) else Empty /** - * Apply the specified PartialFunction to the specified value and return the result - * in a Full Box; if the pf is undefined at that point return Empty. - * @param pf the partial function to use to transform the value - * @param value the value to transform - * @return a Full box containing the transformed value if pf.isDefinedAt(value); Empty otherwise + * Apply the specified `PartialFunction` to the specified `value` and return + * the result in a `Full`; if the `pf`` is not defined at that point return + * `Empty`. + * + * @param pf The partial function to use to transform the value. + * @param value The value to transform. + * @return A `Full` containing the transformed value if + * `pf.isDefinedAt(value)` and `Empty` otherwise. */ def apply[InType, OutType](value: InType)(pf: PartialFunction[InType, OutType]): Box[OutType] = if (pf.isDefinedAt(value)) Full(pf(value)) else Empty /** - * This implicit transformation allows one to use a Box as an Iterable - * @return List(in) if this Box is Full(in); Nil otherwise + * This implicit transformation allows one to use a `Box` as an `Iterable` of + * zero or one elements. + * + * @return A single-element `List` with the contents if the box is `Full` + * and `[[scala.collection.immutable.Nil Nil]]` otherwise. */ implicit def box2Iterable[T](in: Box[T]): Iterable[T] = in.toList /** - * This implicit transformation allows one to use an Option as a Box. - * @return a Box object from an Option. Full(in) if the Option is Some(in); Empty otherwise + * This implicit transformation allows one to use an `Option` as a `Box`. + * + * @return `Full` with the contents if the `Option` is `Some` and `Empty` + * otherwise. */ implicit def option2Box[T](in: Option[T]): Box[T] = Box(in) /** - * This implicit transformation allows one to use a Box as an Option. - * @return Some(in) if this Box is Full(in); None otherwise + * This implicit transformation allows one to use a `Box` as an `Option`. + * + * Note that `Box` implements `get` specifically to avoid usage of `.get` on + * `Box` instances. Boxes should be opened using `openOrThrowException` and + * their contents compared using `== Full(expectedValue)`. + * + * @return `Some` with the contents if the box is `Full` and `[[scala.None None]]` + * otherwise. */ implicit def box2Option[T](in: Box[T]): Option[T] = in.toOption /** - * This method allows one to encapsulate any object in a Box in a null-safe manner, - * treating null values to Empty - * @return Full(in) if in is not null; Empty otherwise + * This method allows one to encapsulate any object in a Box in a null-safe + * manner, converting `null` values to `Empty`. + * + * @return `Full` if `in` is not null and `Empty` otherwise. */ def legacyNullTest[T](in: T): Box[T] = in match { case null => Empty @@ -185,19 +210,29 @@ sealed trait BoxTrait { } /** - * Alias for legacyNullTest. - * This method allows one to encapsulate any object in a Box in a null-safe manner, - * returning Empty if the specified value is null. - * @return Full(in) if in is not null Empty otherwise + * Alias for `[[legacyNullTest]]`. */ def !![T](in: T): Box[T] = legacyNullTest(in) /** - * Create a Full box containing the specified value if "in" is an instance - * of the specified class, or Empty otherwise. + * Create a `Full` box containing the specified value if `in` is an instance of + * the specified class `clz` and `Empty` otherwise. + * + * This is basically a Java-friendly version of `[[asA]]`, which you should + * prefer when using Scala. + * + * For example: + * {{{ + * scala> Box.isA("boom", classOf[Int]) + * res0: net.liftweb.common.Box[Int] = Empty + * + * scala> Box.isA(5, classOf[Int]) + * res1: net.liftweb.common.Box[Int] = Full(5) + * }}} */ - def isA[A, B](in: A, clz: Class[B]): Box[B] = - (Box !! in).isA(clz) + def isA[A, B](in: A, clz: Class[B]): Box[B] = { + (Box !! in).isA(clz) + } // NOTE: We use an existential type here so that you can invoke asA with // just one type parameter. To wit, this lets you do: @@ -210,108 +245,213 @@ sealed trait BoxTrait { // // Uglier, and generally not as nice. /** - * Create a Full box containing the specified value if in is of - * type B; Empty otherwise. + * Create a `Full` box containing the specified value if `in` is of type + * `B` and `Empty` otherwise. + * + * For example: + * {{{ + * scala> Box.asA[Int]("boom") + * res0: net.liftweb.common.Box[Int] = Empty + * + * scala> Box.asA[Int](5) + * res1: net.liftweb.common.Box[Int] = Full(5) + * }}} */ - def asA[B](in: T forSome { type T })(implicit m: Manifest[B]): Box[B] = - (Box !! in).asA[B] + def asA[B](in: T forSome { type T })(implicit m: Manifest[B]): Box[B] = { + (Box !! in).asA[B] + } } /** - * The Box class is a container which is able to declare if it is Full (containing a single non-null value) or EmptyBox. An EmptyBox, or empty, can be the Empty singleton, Failure or ParamFailure. - * Failure and ParamFailure contain information about why the Box is empty including - * exception information, chained Failures and a String. - * It serves a similar purpose to the Option class from Scala standard library but adds several features: - * + * Used as a return type for certain methods that should not be called. One + * example is the `get` method on a Lift `Box`. It exists to prevent client + * code from using `.get` as an easy way to open a `Box`, so it needs a return + * type that will match no valid client return types. + */ +final class DoNotCallThisMethod + +/** + * The `Box` class is a container which is able to declare if it is `Full` + * (containing a single non-null value) or `EmptyBox`. An EmptyBox, or empty, + * can be the `Empty` singleton, `Failure` or `ParamFailure`. `Failure` and + * `ParamFailure` contain information about why the `Box` is empty including + * exception information, possibly chained `Failures` and a `String` message. + * + * This serves a similar purpose to the `[[scala.Option Option]]` class from + * Scala standard library but adds several features: + * - You can transform it to a `Failure` object if it is `Empty` (with the + * `[[?~]]` or `[[failMsg]]` method). + * - You can chain failure messages on `Failure`s (with the `?~!` or + * `[[compoundFailMsg]]` method). + * - You "run" a function on a `Box`, with a default to return if the box is + * `Empty`: + * {{{ + * val littleTeddyBears: Box[Int] = Full(10) + * littleTeddyBears.run("and then there were none") { (default: String, teddyBears: Int) => + * s"\$teddyBears little teddy bears" + * } // => 10 little teddy bears + * + * val updatedTeddyBears: Box[Int] = Empty + * littleTeddyBears.run("and then there were none") { (default: String, teddyBears: Int) => + * s"\$teddyBears little teddy bears" + * } // => and then there were none + * }}} + * - You can "pass" a `Box` to a function for side effects: + * {{{ + * val littleTeddyBears: Box[Int] = Full(10) + * + * doSomething( + * littleTeddyBears $ { teddyBears: Box[Int] => + * println("Are there any?") + * println(teddyBears openOr 0) + * } + * ) // doSomething gets a Box[Int] as well + * }}} + * + * If you grew up on Java, you're used to `Exceptions` as part of your program + * logic. The Scala philosophy and the Lift philosophy is that exceptions are + * for exceptional conditions such as failure of an external resource (e.g., + * your database goes offline) rather than simply indicating that a parameter + * wasn't supplied or couldn't be parsed. + * + * Lift's `Box` and Scala's `Option` provide mechanisms for being explicit + * about a value existing or not existing rather than relying on a reference + * being not-null. However, extracting a value from a `Box` should be done + * correctly. Available options are: + * - Using a `for` comprehension, especially for multiple boxes: + * {{{ + * val loggedInUser: Box[User] = + * for { + * username <- possibleUsername + * password <- possiblePassword + * user <- User.find("username" -> username) + * if User.checkPassword(password, user.password) + * } yield { + * user + * } + * }}} + * - Using `map`, `flatMap`, `filter`, and `foreach` (`for` comprehensions + * use these under the covers): + * {{{ + * val fullName: Box[String] = + * loggedInUser.map { user => + * user.name + " (" + user.nickname + ")" + * } + * val bestFriend: Box[User] = + * loggedInUser.flatMap { user => + * UserFriends.find(user.bestFriend.id) + * } + * val allowedUser: Box[User] = + * loggedInUser.filter(_.canAccess_?(currentPage)) + * + * fullName.foreach { name => + * logger.info(s"User \$name is in the building.") + * } + * }}} + * - Using pattern-matching (a good way to deal with `Failure`s): + * {{{ + * val loginMessage: String = + * loggedInUser match { + * case Full(user) => + * "Login successful!" + * case Failure(message, _, _) => + * s"Login failed: \$message" + * case Empty => + * s"Unknown failure logging in." + * } + * }}} + * - For comparisons (e.g., in tests), use `==` and `===`: + * {{{ + * loggedInUser must_== Full(mockUser) + * (loggedInUser === mockUser) must beTrue + * }}} */ sealed abstract class Box[+A] extends Product with Serializable{ self => /** - * Returns true if this Box contains no value (is Empty or Failure or ParamFailure) - * @return true if this Box contains no value + * Returns `true` if this `Box` contains no value (i.e., it is `Empty` or + * `Failure` or `ParamFailure`). */ def isEmpty: Boolean /** * Returns true if the box contains a value. - * @return true if this Box contains a value */ def isDefined: Boolean = !isEmpty /** - * If you grew up on Java, you're used to Exceptions as part of your program logic. - * The Scala philosophy and the Lift philosophy is that exceptions are for exceptional - * conditions such as failure of an external resource (e.g., your database goes offline) - * rather than simply indicating that a parameter wasn't supplied or couldn't be parsed. - * - * Lift's Box and Scala's Option provide a mechanism for being explicit about a value - * existing or not existing rather than relying on a reference being not-null. However, - * extracting a value from a Box should be done correctly. Correctly can be (in order of use - * in David Pollak's code): a for comprehension; using map, flatMap or foreach; or using pattern matching. + * The only time when you should be using this method is if the value is + * guaranteed to be available based on a guard outside of the method. In these + * cases, please provide that information in the justification `String`. + * For example, User.currentUser.openOrThrowException("This snippet is only + * used on pages where the user is logged in"). For tests, use `[[==]]` or + * `[[===]]` instead. See the class documentation for more information. * - * The only times when you should be using this method are: the value is guaranteed to be available based - * on a guard outside of the method using the Box or in tests. For example, - * User.currentUser.openOrThrowException("This snippet is used on pages where the user is logged in") + * A valid justification for using this method should not be "I want my code + * to fail fast when I call it." Using exceptions in the core logic of your + * application should be strongly discouraged. * - * A valid justification for using this method should not be "I want my code to fail fast when I call it." - * Using exceptions in the core logic of your application should be strongly discouraged. + * @param justification Justify why calling this method is okay and why it + * will not result in an exception being thrown. This serves both as + * mandatory documentation and as a very clear indication of what + * unexpected thing happened in the event you were wrong about the + * guard. * - * This method replaces open_! because people used open_! and generally ignored the reason for the "!", - * so we're making it more explicit that this method should not commonly be used and should be justified - * when used. - * - * @param justification Justify why calling this method is okay and why it will not result in an Exception - * - * @return The contents of the Box if it has one or an exception if not + * @return The contents of the `Box` if it is `Full`. + * @throws NullPointerException If you attempt to call it on an `EmptyBox`, + * with a message that includes the provided `justification`. */ def openOrThrowException(justification: String): A /** - * Exists to avoid the implicit conversion from Box to Option. Opening a Box - * unsafely should be done using openOrThrowException. + * Exists to avoid the implicit conversion from `Box` to `Option`. Opening a + * `Box` unsafely should be done using `openOrThrowException`. + * + * This method *always* throws an exception. */ - final def get: Nothing = { + final def get: DoNotCallThisMethod = { throw new Exception("Attempted to open a Box incorrectly. Please use openOrThrowException.") } /** - * Return the value contained in this Box if it is full; otherwise return the specified default - * @return the value contained in this Box if it is full; otherwise return the specified default + * Return the value contained in this `Box` if it is full; otherwise return + * the specified default. Equivalent to `Option`'s `[[scala.Option.getOrElse getOrElse]]`. */ def openOr[B >: A](default: => B): B = default /** - * Apply a function to the value contained in this Box if it exists and return - * a new Box containing the result, or empty otherwise. - * @return the modified Box or empty + * Apply a function to the value contained in this `Box` if it exists and return + * a `Full` containing the result. If this `Box` is not already `Full`, return + * the unchanged box. + * + * @note This means that using `map` with a `Failure` will preserve the + * `Failure.` */ def map[B](f: A => B): Box[B] = Empty /** - * Apply a function returning a Box to the value contained in this Box if it exists - * and return the result, or empty otherwise. - * @return the modified Box or empty + * Apply a function returning a `Box` to the value contained in this `Box` if + * it exists and return the resulting `Box`. If this `Box` is not already + * `Full`, return the unchanged box. + * + * @note This means that using `map` with a `Failure` will preserve the + * `Failure.` */ def flatMap[B](f: A => Box[B]): Box[B] = Empty /** - * Return this Box if it contains a value satisfying the specified predicate; Empty otherwise - * @return this Box if it contains a value satisfying the specified predicate; Empty otherwise + * If this `Box` contains a value and it satisfies the specified `predicate`, + * return the `Box` unchanged. Otherwise, return an `Empty`. */ def filter(p: A => Boolean): Box[A] = this /** - * Makes Box play better with Scala 2.8 for comprehensions + * Makes `Box` play better with Scala `for` comprehensions. */ def withFilter(p: A => Boolean): WithFilter = new WithFilter(p) /** - * Play NiceLike with the Scala 2.8 for comprehension + * Makes `Box` play better with Scala `for` comprehensions. */ class WithFilter(p: A => Boolean) { def map[B](f: A => B): Box[B] = self.filter(p).map(f) @@ -322,55 +462,81 @@ sealed abstract class Box[+A] extends Product with Serializable{ } /** - * Determine whether this Box contains a value which satisfies the specified predicate + * If this `Box` contains a value and it satisfies the specified `predicate`, + * return `true`. Otherwise, return `false`. + * * @return true if this Box does contain a value and it satisfies the predicate */ def exists(func: A => Boolean): Boolean = false /** - * Determine whether all Box values satisfy the predicate - * @return true if the Box is empty, or if Box's value satisfies the predicate + * If this `Box` contains a value and it does not satisfy the specified + * `predicate`, return `false`. Otherwise, return `true`. + * + * @return true If the `Box` is empty, or if its value satisfies the + * predicate. */ def forall(func: A => Boolean): Boolean = true /** - * Creates a Box if the current Box is Full and the value does not satisfy the predicate, f. - * - * @param f the predicate used to test value. - * - * @return a Box + * + * If this `Box` contains a value and it does *not* satisfy the specified + * `predicate`, return the `Box` unchanged. Otherwise, return an `Empty`. */ def filterNot(f: A => Boolean): Box[A] = filter(a => !f(a)) /** - * Perform a side effect by calling the specified function - * with the value contained in this box. + * Perform a side effect by calling the specified function with the value + * contained in this box. The function does not run if this `Box` is empty. */ def foreach[U](f: A => U): Unit = {} /** - * Return a Full[B] if the contents of this Box is an instance of the specified class, - * otherwise return Empty + * Create a `Full` box containing the specified value if `in` is an instance of + * the specified class `clz` and `Empty` otherwise. + * + * This is basically a Java-friendly version of `[[asA]]`, which you should + * prefer when using Scala. + * + * For example: + * {{{ + * scala> Full("boom").isA(classOf[Int]) + * res0: net.liftweb.common.Box[Int] = Empty + * + * scala> Full(5).isA(classOf[Int]) + * res1: net.liftweb.common.Box[Int] = Full(5) + * }}} */ def isA[B](cls: Class[B]): Box[B] = Empty /** - * Return a Full[B] if the contents of this Box is of type B, otherwise return Empty + * Create a `Full` box containing the specified value if `in` is of type + * `B` and `Empty` otherwise. + * + * For example: + * {{{ + * scala> Full("boom").asA[Int] + * res0: net.liftweb.common.Box[Int] = Empty + * + * scala> Full(5).asA[Int] + * res1: net.liftweb.common.Box[Int] = Full(5) + * }}} */ def asA[B](implicit m: Manifest[B]): Box[B] = Empty /** - * Return this Box if Full, or the specified alternative if this is empty + * Return this Box if `Full`, or the specified alternative if it is empty. */ def or[B >: A](alternative: => Box[B]): Box[B] = alternative /** - * Returns an Iterator over the value contained in this Box + * Returns an `[[scala.collection.Iterator Iterator]]` over the value + * contained in this `Box`, if any. */ def elements: Iterator[A] = Iterator.empty /** - * Get a Java Iterator from the Box + * Get a `java.util.Iterator` from the Box. */ def javaIterator[B >: A]: JavaIterator[B] = { val ar = new JavaArrayList[B]() @@ -379,96 +545,136 @@ sealed abstract class Box[+A] extends Product with Serializable{ } /** - * Returns an Iterator over the value contained in this Box + * Returns an `[[scala.collection.Iterator Iterator]]` over the value + * contained in this `Box`, if any. + * + * Synonym for `[[elements]]`. */ def iterator: Iterator[A] = this.elements /** - * Returns a List of one element if this is Full, or an empty list if empty. + * Returns a `[[scala.collection.immutable.List List]]` of one element if this + * is Full, or an empty list if empty. */ def toList: List[A] = Nil /** - * Returns the contents of this box in an Option if this is Full, or - * None if this is a empty (Empty, Failure or ParamFailure) + * Returns the contents of this box wrapped in `Some` if this is Full, or + * `None` if this is a empty (meaning an `Empty`, `Failure` or + * `ParamFailure`). */ def toOption: Option[A] = None /** - * Transform an Empty to a Failure with the specified message. - * @param msg the failure message - * @return a Failure with the message if this Box is Empty + * Transform an Empty to a Failure with the specified message. Otherwise + * leaves returns the same box. + * + * @note This means a `Failure` will also remain unchanged; see `?~!` to + * change these. + * + * @return A Failure with the message if this `Box` is `Empty`, this box + * otherwise. */ def ?~(msg: => String): Box[A] = this - - /** - * Transform an Empty to a ParamFailure with the specified typesafe - * parameter. - * @param errorCode a value indicating the error - * @return a ParamFailure with the specified value + * Transform an `Empty` or `Failure` to a `ParamFailure` with the specified + * type-safe parameter. + * + * @param errorCode A value indicating the error. + * @return A `ParamFailure` with the specified value, unless this is already a + * `ParamFailure` or a `Full`. If this is a `Failure`, the + * `ParamFailure` will preserve the message of the `Failure`. */ def ~>[T](errorCode: => T): Box[A] = this /** - * Alias for ?~ + * Alias for `[[?~]]`. */ def failMsg(msg: => String): Box[A] = ?~(msg) /** - * Transform an EmptyBox to a Failure with the specified message and chain - * the new Failure to any previous Failure represented by this Box. - * @param msg the failure message - * @return a Failure with the message if this Box is an Empty Box. Chain the messages if it is already a Failure + * Chain the given `msg` as a `Failure` ahead of any failures this `Box` may + * represent. + * + * If this is an `Empty`, this method behaves like `[[?~]]`. If it is a `Failure`, + * however, this method returns a new `Failure` with the given `msg` and with its + * `[[Failure.chain chain]]` set to this `Failure`. + * + * As with `[[?~]]`, if this is a `Full`, we return it unchanged. + * + * @return A Failure with the message if this Box is an Empty Box. Chain this + * box to the new `Failure` if this is a `Failure`. The unchanged box + * if it is a `Full`. */ def ?~!(msg: => String): Box[A] = ?~(msg) /** - * Alias for ?~! + * Alias for `?~!`. */ def compoundFailMsg(msg: => String): Box[A] = ?~!(msg) /** - * Filter this box on the specified predicate, returning a Failure with the specified - * message if the predicate is not satisfied. - * @param msg the failure message - * @param p a predicate - * @return a Failure with the message if the predicate is not satisfied by the value contained in this Box + * If this `Box` contains a value and it satisfies the specified `predicate`, + * return the `Box` unchanged. Otherwise, return a `Failure` with the given + * `msg`. + * + * @see [[filter]] + * + * @return A `Failure` with the message if the box is empty or the predicate + * is not satisfied by the value contained in this Box. */ def filterMsg(msg: String)(p: A => Boolean): Box[A] = filter(p) ?~ msg /** - * This method calls the specified function with the value contained in this Box - * @return the result of the function or a default value + * This method calls the specified function with the specified `in` value and + * the value contained in this `Box`. If this box is empty, returns the `in` + * value directly. + * + * @return The result of the function or the `in` value. */ def run[T](in: => T)(f: (T, A) => T) = in /** - * Perform a side effect by passing this Box to the specified function - * and return this Box unmodified. - * @return this Box + * Perform a side effect by passing this Box to the specified function and + * return this Box unmodified. Similar to `foreach`, except that `foreach` + * returns `Unit`, while this method allows chained use of the `Box`. + * + * @return This box. */ def pass(f: Box[A] => Unit): Box[A] = {f(this) ; this} /** - * Alias for pass + * Alias for `[[pass]]`. */ def $(f: Box[A] => Unit): Box[A] = pass(f) /** - * Determines equality based upon the contents of this Box instead of the box itself. - * As a result, it is not symmetric. Which means that for + * For `Full` and `Empty`, this has the expected behavior. Equality in terms + * of Failure checks for equivalence of failure causes: + * {{{ + * Failure("boom") == Failure("boom") + * Failure("bam") != Failure("boom") + * Failure("boom", Full(someException), Empty) != Failure("boom") + * }}} * - *
-   *     val foo = "foo"
-   *     val boxedFoo = Full(foo)
-   *     foo == boxedFoo //is false
-   *     boxedFoo == foo //is true
-   * 
+ * For other values, determines equality based upon the contents of this `Box` + * instead of the box itself. As a result, it is not symmetric. As an example: + * {{{ + * val foo = "foo" + * val boxedFoo = Full(foo) + * foo == boxedFoo //is false + * boxedFoo == foo //is true + * }}} * - * For Full and Empty, this has the expected behavior. Equality in terms of Failure - * checks for equivalence of failure causes. + * It is safest to use `===` explicitly when you're looking for this behavior, + * and use `==` only for box-to-box comparisons: + * {{{ + * Full("magic") == Full("magic") + * Full("magic") != Full("another") + * Full("magic") != Empty + * Full("magic") != Failure("something's gone wrong") + * }}} */ override def equals(other: Any): Boolean = (this, other) match { case (Full(x), Full(y)) => x == y @@ -478,8 +684,7 @@ sealed abstract class Box[+A] extends Product with Serializable{ } /** - * Apply the function f1 to the contents of this Box if available; if this - * is empty return the specified alternative. + * Equivalent to `flatMap(f1).or(alternative)`. */ def choice[B](f1: A => Box[B])(alternative: => Box[B]): Box[B] = this match { case Full(x) => f1(x) @@ -487,46 +692,59 @@ sealed abstract class Box[+A] extends Product with Serializable{ } /** - * Returns true if the value contained in this box is equal to the specified value. + * Returns true if the value contained in this box is equal to the specified + * value. This is the same thing that `==` does when it's handed a value that + * isn't a `Box`, but using this is recommended because it's clearer that the + * behavior will be different than the usual expectation. */ def ===[B >: A](to: B): Boolean = false /** - * Equivalent to map(f).openOr(Full(dflt)) + * Equivalent to `map(f).openOr(Full(dflt))`. */ def dmap[B](dflt: => B)(f: A => B): B = dflt /** - * If the Box is Full, apply the transform function on the - * value, otherwise just return the value untransformed + * If the `Box` is `Full`, apply the transform function `f` on the value `v`; + * otherwise, just return the value untransformed. + * + * The transform function is expected to be a function that will take the + * value `v` and produce a function from the value in the box to a new value + * of the same type as `v`. * - * @param v the value - * @param f the transformation function - * @tparam T the type of the value - * @return the value or the transformed value is the Box is Full + * For example: + * {{{ + * val myBox = Full(10) + * myBox.fullXForm("No teddy bears left.")({ message => + * { teddyBears: Int => + * s"\$message Oh wait, there are \$teddyBears left!" + * } + * }) + * }}} + * + * @tparam T The type of the initial value, default value, and transformed + * value. + * @return If the `Box` is `Full`, the value once transformed by the function + * returned by `f`. Otherwise, the initial value `v`. */ def fullXform[T](v: T)(f: T => A => T): T = v /** - * An Either that is a Left with the given argument - * left if this is empty, or a Right if this - * Full with the Box's value. + * An `Either` that is a `Left` with the given argument `left` if this is + * empty, or a `Right` with the boxed value if this is `Full`. */ def toRight[B](left: => B): Either[B, A] = Left(left) /** - * An Either that is a Right with the given - * argument - * right if this is empty, or a Left if this is - * Fill with the Box's value + * An `Either` that is a `Right` with the given argument `right` if this is + * empty, or a `Left` with the boxed value if this is `Full`. */ def toLeft[B](right: => B): Either[A, B] = Right(right) - /** - * If the partial function is defined at the current Box's value - * apply the partial function. + * If the partial function is defined at the current Box's value, apply the + * partial function. */ final def collect[B](pf: PartialFunction[A, B]): Box[B] = { flatMap(value => @@ -534,46 +752,25 @@ sealed abstract class Box[+A] extends Product with Serializable{ else Empty) } + /** + * An alias for `collect`. + * + * Although this function is different for true collections, because `Box` is + * really a collection of 1, the two functions are identical. + */ + final def collectFirst[B](pf: PartialFunction[A, B]): Box[B] = { + collect(pf) + } } /** - * Full is a Box containing a value. + * `Full` is a `[[Box]]` that contains a value. */ -final case class Full[+A](value: A) extends Box[A]{ - +final case class Full[+A](value: A) extends Box[A] { def isEmpty: Boolean = false - - - /** - * If you grew up on Java, you're used to Exceptions as part of your program logic. - * The Scala philosophy and the Lift philosophy is that exceptions are for exceptional - * conditions such as failure of an external resource (e.g., your database goes offline) - * rather than simply indicating that a parameter wasn't supplied or couldn't be parsed. - * - * Lift's Box and Scala's Option provide a mechanism for being explicit about a value - * existing or not existing rather than relying on a reference being not-null. However, - * extracting a value from a Box should be done correctly. Correctly can be (in order of use - * in David Pollak's code): a for comprehension; using map, flatMap or foreach; or using pattern matching. - * - * The only times when you should be using this method are: the value is guaranteed to be available based - * on a guard outside of the method using the Box or in tests. For example, - * User.currentUser.openOrThrowException("This snippet is used on pages where the user is logged in") - * - * A valid justification for using this method should not be "I want my code to fail fast when I call it." - * Using exceptions in the core logic of your application should be strongly discouraged. - * - * This method replaces open_! because people used open_! and generally ignored the reason for the "!", - * so we're making it more explicit that this method should not commonly be used and should be justified - * when used. - * - * @param justification Justify why calling this method is okay and why it will not result in an Exception - * - * @return The contents of the Box if it has one or an exception if not - */ def openOrThrowException(justification: String): A = value - override def openOr[B >: A](default: => B): B = value override def or[B >: A](alternative: => Box[B]): Box[B] = this @@ -598,37 +795,17 @@ final case class Full[+A](value: A) extends Box[A]{ override def run[T](in: => T)(f: (T, A) => T) = f(in, value) - /** - * If the Box is Full, apply the transform function on the - * value, otherwise just return the value untransformed - * - * @param v the value - * @param f the transformation function - * @tparam T the type of the value - * @return the value or the transformed value is the Box is Full - */ override def fullXform[T](v: T)(f: T => A => T): T = f(v)(value) - /** - * An Either that is a Left with the given argument - * left if this is empty, or a Right if this - * Full with the Box's value. - */ override def toRight[B](left: => B): Either[B, A] = Right(value) - /** - * An Either that is a Right with the given - * argument - * right if this is empty, or a Left if this is - * Fill with the Box's value - */ override def toLeft[B](right: => B): Either[A, B] = Left(value) override def isA[B](clsOrg: Class[B]): Box[B] = value match { case value: AnyRef => - val cls = Box.primativeMap.get(clsOrg) match { + val cls = Box.primitiveMap.get(clsOrg) match { case Some(c) => c case _ => clsOrg } @@ -646,49 +823,22 @@ final case class Full[+A](value: A) extends Box[A]{ } /** - * Singleton object representing an Empty Box + * Singleton object representing a completely empty `Box` with no value or + * failure information. */ case object Empty extends EmptyBox /** - * The EmptyBox is a Box containing no value. + * An `EmptyBox` is a `Box` containing no value. It can sometimes carry + * additional failure information, as in `[[Failure]]` and `[[ParamFailure]]`. */ sealed abstract class EmptyBox extends Box[Nothing] with Serializable { def isEmpty: Boolean = true - - /** - * If you grew up on Java, you're used to Exceptions as part of your program logic. - * The Scala philosophy and the Lift philosophy is that exceptions are for exceptional - * conditions such as failure of an external resource (e.g., your database goes offline) - * rather than simply indicating that a parameter wasn't supplied or couldn't be parsed. - * - * Lift's Box and Scala's Option provide a mechanism for being explicit about a value - * existing or not existing rather than relying on a reference being not-null. However, - * extracting a value from a Box should be done correctly. Correctly can be (in order of use - * in David Pollak's code): a for comprehension; using map, flatMap or foreach; or using pattern matching. - * - * The only times when you should be using this method are: the value is guaranteed to be available based - * on a guard outside of the method using the Box or in tests. For example, - * User.currentUser.openOrThrowException("This snippet is used on pages where the user is logged in") - * - * A valid justification for using this method should not be "I want my code to fail fast when I call it." - * Using exceptions in the core logic of your application should be strongly discouraged. - * - * This method replaces open_! because people used open_! and generally ignored the reason for the "!", - * so we're making it more explicit that this method should not commonly be used and should be justified - * when used. - * - * @param justification Justify why calling this method is okay and why it will not result in an Exception - * - * @return The contents of the Box if it has one or an exception if not - */ def openOrThrowException(justification: String) = throw new NullPointerException("An Empty Box was opened. The justification for allowing the openOrThrowException was "+justification) - - override def openOr[B >: Nothing](default: => B): B = default override def or[B >: Nothing](alternative: => Box[B]): Box[B] = alternative @@ -699,51 +849,26 @@ sealed abstract class EmptyBox extends Box[Nothing] with Serializable { override def ?~!(msg: => String): Failure = Failure(msg, Empty, Empty) - override def ~>[T](errorCode: => T): ParamFailure[T] = - ParamFailure("", Empty, Empty, errorCode) + override def ~>[T](errorCode: => T): ParamFailure[T] = ParamFailure("", Empty, Empty, errorCode) } /** - * Companion object used to simplify the creation of a simple Failure. + * Companion object used to simplify the creation of a simple `Failure` with + * just a message. */ object Failure { def apply(msg: String) = new Failure(msg, Empty, Empty) } /** - * A Failure is an EmptyBox with an additional failure message explaining the reason for its being empty. - * It can also optionally provide an exception or a chain of causes represented as a list of other Failure objects + * A `Failure` is an `[[EmptyBox]]` with an additional failure message + * explaining the reason for its being empty. It can also optionally provide an + * exception and/or a chain of previous `Failure`s that may have caused this + * one. */ -sealed case class Failure(msg: String, exception: Box[Throwable], chain: Box[Failure]) extends EmptyBox{ +sealed case class Failure(msg: String, exception: Box[Throwable], chain: Box[Failure]) extends EmptyBox { type A = Nothing - - /** - * If you grew up on Java, you're used to Exceptions as part of your program logic. - * The Scala philosophy and the Lift philosophy is that exceptions are for exceptional - * conditions such as failure of an external resource (e.g., your database goes offline) - * rather than simply indicating that a parameter wasn't supplied or couldn't be parsed. - * - * Lift's Box and Scala's Option provide a mechanism for being explicit about a value - * existing or not existing rather than relying on a reference being not-null. However, - * extracting a value from a Box should be done correctly. Correctly can be (in order of use - * in David Pollak's code): a for comprehension; using map, flatMap or foreach; or using pattern matching. - * - * The only times when you should be using this method are: the value is guaranteed to be available based - * on a guard outside of the method using the Box or in tests. For example, - * User.currentUser.openOrThrowException("This snippet is used on pages where the user is logged in") - * - * A valid justification for using this method should not be "I want my code to fail fast when I call it." - * Using exceptions in the core logic of your application should be strongly discouraged. - * - * This method replaces open_! because people used open_! and generally ignored the reason for the "!", - * so we're making it more explicit that this method should not commonly be used and should be justified - * when used. - * - * @param justification Justify why calling this method is okay and why it will not result in an Exception - * - * @return The contents of the Box if it has one or an exception if not - */ override def openOrThrowException(justification: String) = throw new NullPointerException("An Failure Box was opened. Failure Message: "+msg+ ". The justification for allowing the openOrThrowException was "+justification) { @@ -764,8 +889,13 @@ sealed case class Failure(msg: String, exception: Box[Throwable], chain: Box[Fai } /** - * Get the exception chain along with the exception chain of any - * chained failures + * Return a list of the exceptions that led to this `Failure`. First, unflattens + * the list of causes of this `Failure`'s `exception`. Then, if this `Failure` + * has a `chain`, walks down it and concatenates their `exceptionChain` to the + * end of this one's. + * + * @return A single list of `Throwable`s from the most direct cause to the + * least direct cause of this `Failure`. */ def exceptionChain: List[Throwable] = { import scala.collection.mutable.ListBuffer @@ -782,19 +912,34 @@ sealed case class Failure(msg: String, exception: Box[Throwable], chain: Box[Fai } /** - * Gets the deepest exception cause + * Gets the deepest exception cause, if any, which is ostensibly the root + * cause of this `Failure`. */ def rootExceptionCause: Box[Throwable] = { exceptionChain.lastOption } /** - * Flatten the Failure chain to a List where this - * Failure is at the head + * Flatten the `Failure` chain to a List where this Failure is at the head. */ def failureChain: List[Failure] = this :: chain.toList.flatMap(_.failureChain) + /** + * Reduce this `Failure`'s message and the messages of all chained failures a + * to a single `String`. The resulting string links each step in the failure + * chain with <-, and this `Failure`'s message is last. + * + * For example: + * {{{ + * scala> Failure("It's all gone wrong.") ?~! "Something's gone wrong." ?~! "It's all sideways" + * res0: net.liftweb.common.Failure = Failure(It's all sideways,Empty, + * Full(Failure(Something's gone wrong.,Empty, + * Full(Failure(It's all gone wrong.,Empty,Empty))))) + * scala> res0.messageChain + * res1: String = It's all sideways <- Something's gone wrong. <- It's all gone wrong. + * }}} + */ def messageChain: String = (this :: chainList).map(_.msg).mkString(" <- ") override def equals(other: Any): Boolean = (this, other) match { @@ -811,8 +956,48 @@ sealed case class Failure(msg: String, exception: Box[Throwable], chain: Box[Fai } /** - * A ParamFailure is a Failure with an additional typesafe parameter that can - * allow an application to store other information related to the failure. + * Companion object used to simplify the creation of simple `ParamFailure`s, as + * well as allow pattern-matching on the `ParamFailure`. + */ +object ParamFailure { + def apply[T](msg: String, exception: Box[Throwable], chain: Box[Failure], param: T) = + new ParamFailure(msg, exception, chain, param) + + def apply[T](msg: String, param: T) = new ParamFailure(msg, Empty, Empty, param) + + def unapply(in: Box[_]): Option[(String, Box[Throwable], Box[Failure], Any)] = in match { + case pf: ParamFailure[_] => Some((pf.msg, pf.exception, pf.chain, pf.param)) + case _ => None + } +} + +/** + * A `ParamFailure` is a `Failure` with an additional type-safe parameter that + * can allow an application to store other information related to the failure. + * + * For example: + * {{{ + * val loggedInUser = + * for { + * username ?~ "Missing username" ~> "error.missingUser" + * password ?~! "Missing password" ~> "error.missingPassword" + * user <- User.find("username" -> username) + * if User.checkPassword(password, user.password) + * } yield { + * user + * } + * + * loggedInUser match { + * case ParamFailure(message, _, _, i18nKey: String) => + * tellUser(i18n(i18nKey)) + * case Failure(message, _, _) => + * tellUser(failureMessage) + * case Empty => + * tellUser("Unknown login failure.") + * case _ => + * tellUser("You're in!") + * } + * }}} */ final class ParamFailure[T](override val msg: String, override val exception: Box[Throwable], @@ -834,71 +1019,48 @@ final class ParamFailure[T](override val msg: String, } /** - * A trait that a class can mix into itself to convert itself into a Box + * A trait that a class can mix into itself to indicate that it can convert + * itself into a `Box`. */ trait Boxable[T] { def asBox: Box[T] } -object ParamFailure { - def apply[T](msg: String, exception: Box[Throwable], chain: Box[Failure], param: T) = - new ParamFailure(msg, exception, chain, param) - - def apply[T](msg: String, param: T) = new ParamFailure(msg, Empty, Empty, param) - - def unapply(in: Box[_]): Option[(String, Box[Throwable], Box[Failure], Any)] = in match { - case pf: ParamFailure[_] => Some((pf.msg, pf.exception, pf.chain, pf.param)) - case _ => None - } -} - /** - * Sometimes it's convenient to access either a Box[T] - * or a T. If you specify BoxOrRaw[T], the - * either a T or a Box[T] can be passed and the "right thing" - * will happen + * Sometimes it's convenient to access either a Box[T] or a T. If you specify + * BoxOrRaw[T], the either a T or a Box[T] can be passed and the "right thing" + * will happen, including nulls being treated as `Empty`. */ sealed trait BoxOrRaw[T] { def box: Box[T] } /** - * The companion object that has helpful conversions + * Companion object with implicit conversions to allow `BoxOrRaw[T]` to + * masquerade as the appropriate types. */ object BoxOrRaw { - /** - * Convert a T to a BoxOrRaw[T] - */ implicit def rawToBoxOrRaw[T, Q <: T](r: Q): BoxOrRaw[T] = RawBoxOrRaw(r: T) - /** - * Convert a Box[T] to a BoxOrRaw[T] - */ implicit def boxToBoxOrRaw[T, Q <% T](r: Box[Q]): BoxOrRaw[T] = { BoxedBoxOrRaw(r.map(v => v: T)) } - /** - * Convert an Option[T] to a BoxOrRaw[T] - */ implicit def optionToBoxOrRaw[T, Q <% T](r: Option[Q]): BoxOrRaw[T] = { BoxedBoxOrRaw(r.map(v => v: T)) } - /** - * Convert a BoxOrRaw[T] to a Box[T] - */ implicit def borToBox[T](in: BoxOrRaw[T]): Box[T] = in.box } /** - * The Boxed up BoxOrRaw + * The BoxOrRaw that represents a boxed value. */ final case class BoxedBoxOrRaw[T](box: Box[T]) extends BoxOrRaw[T] /** - * The raw version of BoxOrRaw + * The BoxOrRaw that represents a raw value. */ final case class RawBoxOrRaw[T](raw: T) extends BoxOrRaw[T] { def box: Box[T] = diff --git a/core/common/src/main/scala/net/liftweb/common/CombinableBox.scala b/core/common/src/main/scala/net/liftweb/common/CombinableBox.scala index a763b2d0fb..38e80c4a6b 100644 --- a/core/common/src/main/scala/net/liftweb/common/CombinableBox.scala +++ b/core/common/src/main/scala/net/liftweb/common/CombinableBox.scala @@ -20,12 +20,8 @@ package common import scala.language.implicitConversions /** - *

- * Via an HList containing a Collection of Box[things], either generate an - * HList of the things or a List[Failure] - *

- * - * + * Via an `[[HLists.HList HList]]` containing a collection of `[[Box]]`, either generates an + * `HList` of the things (unboxed) or a `List[Failure]`. */ object CombinableBox { import HLists._ @@ -59,14 +55,13 @@ object CombinableBox { } /** - * If the Failure is going to be condensed, generate a FailureList + * If the `[[Failure]]` is going to be condensed, a `FailureList` is generated + * to allow type-safe pattern matches without worrying about erasure. */ final case class FailureList(failures: List[Failure]) - - /** - * The place where the results are accumulated + * The place where the results are accumulated. */ final case class CombinableBox[B, C <: HList](rhs: Result[B :+: C]) { def :&: [A](lhs: Boxable[A]): Result[A :+: B :+: C] = this.:&:(lhs.asBox) diff --git a/core/common/src/main/scala/net/liftweb/common/Conversions.scala b/core/common/src/main/scala/net/liftweb/common/Conversions.scala index 2046d4bb48..f6a308dbbd 100644 --- a/core/common/src/main/scala/net/liftweb/common/Conversions.scala +++ b/core/common/src/main/scala/net/liftweb/common/Conversions.scala @@ -26,17 +26,26 @@ import scala.xml.NodeSeq */ /** - * A helpful trait that will accept either a String or a NodeSeq via - * an implicit conversion. So, all you need to do is put in a String or - * a NodeSeq and the right thing will happen. + * This trait is used to unify `String`s and `[[scala.xml.NodeSeq NodeSeq]]`s + * into one type. It is used in conjuction with the implicit conversions defined + * in its companion object. */ sealed trait StringOrNodeSeq { def nodeSeq: scala.xml.NodeSeq } /** - * The companion object that has helpful - * implicit conversions from String and NodeSeq + * Provides implicit conversions to the `StringOrNodeSeq` trait, which can in + * turn be implicitly converted to `[[scala.xml.NodeSeq NodeSeq]]`. This allows + * using a `String` as a natural part of `NodeSeq` APIs without having to + * explicitly wrap it in `scala.xml.Text` or having to write overloads for all + * methods that should accept both. + * + * This is used in certain Lift APIs, for example, to accept either a `String` + * or more complex content. For example, a `button` can have either a simple + * label or complex HTML content. HTML APIs that can do this can accept a + * parameter of type `StringOrNodeSeq` to allow the user to pass either in as + * their needs dictate. */ object StringOrNodeSeq { import scala.xml._ @@ -50,7 +59,8 @@ object StringOrNodeSeq { } /** - * Convert a NodeSeq (well, a Seq[Node]) to a StringOrNodeSeq + * This is written in terms of a `Seq[Node]` to make sure Scala converts + * everything it should to a `StringOrNodeSeq`. `NodeSeq` is a `Seq[Node]`.` */ implicit def nsTo(ns: Seq[Node]): StringOrNodeSeq = new StringOrNodeSeq { @@ -64,18 +74,24 @@ object StringOrNodeSeq { } /** - * Sometimes you want a function that returns a String as a parameter, - * but many times, you'll just want to pass a String constant. In - * those cases, this trait and it's implicit conversions come in really - * handy. Basically, a String constant or a String function can be passed and - * either will be implicitly converted into a StringFunc. + * This trait is used to unify `()=>String` and `String` into one type. It is + * used in conjunction with the implicit conversions defined in its companion + * object. */ sealed trait StringFunc { def func: () => String } /** - * The companion object to StringFunc with helpful implicit conversions + * Provides implicit conversions to the `StringFunc` trait. This allows using a + * `String` as a natural part of APIs that want to allow the flexibility of a + * `()=>String` without having to write overloads for all methods that should + * accept both. + * + * Lift's Menu API, for example, allows CSS classes to be defined either as + * a `String` or a `()=>String`. The latter could use the current request and + * session state to do more interesting things than a hard-coded `String` would, + * while the former is simpler to use. */ object StringFunc { /** @@ -106,18 +122,20 @@ final case class ConstStringFunc(str: String) extends StringFunc { } /** - * Sometimes you want a function that returns a NodeSeq as a parameter, - * but many times, you'll just want to pass a NodeSeq constant. In - * those cases, this trait and it's implicit conversions come in really - * handy. Basically, a NodeSeq constant or a NodeSeq function can be passed and - * either will be implicitly converted into a NodeSeqFunc. + * This trait is used to unify `()=>[[scala.xml.NodeSeq NodeSeq]]` and + * `[[scala.xml.NodeSeq NodeSeq]]` into one type. It is used in conjunction + * with the implicit conversions defined in its [[NodeSeqFunc$ companion + * object]]. */ sealed trait NodeSeqFunc { def func: () => NodeSeq } /** - * The companion object to NodeSeqFunc with helpful implicit conversions + * Provides implicit conversions to the `NodeSeqFunc` trait. This allows using a + * `[[scala.xml.NodeSeq NodeSeq]]` as a natural part of APIs that want to allow + * the flexibility of a `()=>[[scala.xml.NodeSeq NodeSeq]]` without having to + * write overloads for all methods that should accept both. */ object NodeSeqFunc { /** @@ -136,12 +154,12 @@ object NodeSeqFunc { } /** - * The case class that holds a NodeSeq function. + * The case class that holds a `[[scala.xml.NodeSeq NodeSeq]]` function. */ final case class RealNodeSeqFunc(func: () => NodeSeq) extends NodeSeqFunc /** - * The case class that holds the NodeSeq constant. + * The case class that holds the `[[scala.xml.NodeSeq NodeSeq]]` constant. */ final case class ConstNodeSeqFunc(ns: NodeSeq) extends NodeSeqFunc { lazy val func = () => ns diff --git a/core/common/src/main/scala/net/liftweb/common/FuncJBridge.scala b/core/common/src/main/scala/net/liftweb/common/FuncJBridge.scala index 23790371ef..ba9d21354c 100644 --- a/core/common/src/main/scala/net/liftweb/common/FuncJBridge.scala +++ b/core/common/src/main/scala/net/liftweb/common/FuncJBridge.scala @@ -22,7 +22,11 @@ import scala.language.implicitConversions object FuncJBridge extends FuncJBridge /** - * Bridge from Java functions to Scala functions + * Bridges from Java functions to Scala functions. + * + * The implicits defined here allow Scala code to interact seamlessly between + * the Java function-like interfaces and the Scala function interfaces for + * various function arities. */ class FuncJBridge { /** diff --git a/core/common/src/main/scala/net/liftweb/common/HList.scala b/core/common/src/main/scala/net/liftweb/common/HList.scala index 15825c67c9..a199577c4d 100644 --- a/core/common/src/main/scala/net/liftweb/common/HList.scala +++ b/core/common/src/main/scala/net/liftweb/common/HList.scala @@ -18,13 +18,56 @@ package net.liftweb package common /** - * Support for heterogenious lists, aka HLists + * Basic support for heterogeneous lists, aka + * [[http://apocalisp.wordpress.com/2010/07/06/type-level-programming-in-scala-part-6a-heterogeneous-list%C2%A0basics/ HLists]]. * + * An `HList` can be constructed like so: + * + * {{{ + * import net.liftweb.common.HLists._ + * + * trait Base + * case class Type1(value: String) extends Base + * case class Type2(otherValue: String) extends Base + * + * val myHList = Type1("Value") :+: Type2("Other Value") :+: HNil + * myHList match { + * case firstThing :+: secondThing :+: HNil => + * println(firstThing.value) + * println(secondThing.otherValue) + * } + * }}} + * + * Above, we see that the `HList` preserved the value of the types of its + * members, otherwise we wouldn't have been able to fetch `value` and + * `otherValue`, respectively. + * + * Trying the same thing with a list won't work: + * + * {{{ + * val myList = Type1("Value") :: Type2("Other Value") :: Nil + * myList match { + * case firstThing :: secondThing :: Nil => + * // error: value value is not a member of Product with Serializable with Base + * println(firstThing.value) + * } + * }}} + * + * This is because `value` is not defined in `Base`. The inferred type of the + * `List` has to be a common ancestor class or trait of `Type1` and `Type2`, and + * no such type has a `value` method. */ object HLists { /** - * The trait that defines HLists + * The base trait for `HList`s. Functions that take `HList`s will need a type + * parameter subtype of `HList`: + * + * {{{ + * def myHListFunction[T <: HList](list: HList) = { + * println(s"This HList has \${list.length} items!") + * } + * }}} */ sealed trait HList { type Head @@ -37,14 +80,22 @@ object HLists { } /** - * The last element of an HList + * The last element of an `HList`. This is the starting point for an `HList`, + * and carries a `:+:` method to start one: + * + * {{{ + * scala> Type1("Value") :+: HNil + * res0: net.liftweb.common.HLists.HCons[Type1,net.liftweb.common.HLists.HNil] = Type1(Value) :+: HNil + * }}} */ final class HNil extends HList { type Head = Nothing type Tail = HNil /** - * Create a new HList based on this node + * Chains the given value to the front of an `HList`. + * + * Produces a `T :+: HNil`. */ def :+:[T](v: T) = HCons(v, this) @@ -62,7 +113,23 @@ object HLists { val HNil = new HNil() /** - * The HList cons cell + * The `HList` cons cell, which represents one part of an `HList` in linked + * list style. + * + * Carries the information about the type of this element, plus the `HList` + * type of the rest of the list. + * + * You can use `:+:` to make this `HList` longer: + * + * {{{ + * scala> val first = Type1("Value") :+: HNil + * first: net.liftweb.common.HLists.HCons[Type1,net.liftweb.common.HLists.HNil] = Type1(Value) :+: HNil + * scala> Type2("Other Value") :+: first + * res0: net.liftweb.common.HLists.HCons[Type2, + * net.liftweb.common.HLists.HCons[Type1, + * net.liftweb.common.HLists.HNil]] = + * Type2(Other Value) :+: Type1(Value) :+: HNil + * }}} */ final case class HCons[H, T <: HList](head: H, tail: T) extends HList { type This = HCons[H, T] @@ -70,7 +137,7 @@ object HLists { type Tail = T /** - * Create a new HList based on this node + * Chains the given value to the front of this `HList`. */ def :+:[T](v: T) = HCons(v, this) @@ -84,21 +151,25 @@ object HLists { type :+:[H, T <: HList] = HCons[H, T] + /** + * Provides the support needed to be able to pattern-match an `HList`. + */ object :+: { def unapply[H, T <: HList](in: HCons[H, T]): Option[(H, T)] = Some(in.head, in.tail) } } -// Some useful type system stuff from Miles Sabin /** - * Encoding for "A is not a subtype of B" + * Encoding for "A is not a subtype of B". */ sealed trait ExcludeThisType[A, B] /** - * The companion object to <:!<. This allows one of specify - * that a type is not a subtype of another type + * The companion object to `ExcludeThisType`. This allows one of specify that a + * type is not a subtype of another type. + * + * Based on work by Miles Sabin. */ object ExcludeThisType { def unexpected: Nothing = sys.error("Unexpected invocation") diff --git a/core/common/src/main/scala/net/liftweb/common/LRU.scala b/core/common/src/main/scala/net/liftweb/common/LRU.scala index b4da7bedde..06bcd64a2a 100644 --- a/core/common/src/main/scala/net/liftweb/common/LRU.scala +++ b/core/common/src/main/scala/net/liftweb/common/LRU.scala @@ -47,7 +47,18 @@ private[common] trait LinkedListElem[T1, T2] { /** - * Implements an LRU Hashmap + * Implements an LRU Hashmap. Given a size, this map will evict the least + * recently used item(s) when new items are added. + * + * Note that `LRUMap` is *not* thread-safe. + * + * @param initmaxSize The initial max size. This can be updated using + * `[[updateMaxSize]]`. + * @param loadFactor If non-`Empty`, specifies the load factor for the + * backing `java.util.HashMap`. + * @param expiredFunc When a key-value pair is removed, the last thing that + * happens is that these functions are invoked. Note that this happens + * after `[[expired]]` is invoked. */ class LRUMap[K, V](initMaxSize: Int, loadFactor: Box[Float], expiredFunc: ((K, V) => Unit)*) extends LinkedListElem[K, V] { import java.util.HashMap @@ -58,6 +69,10 @@ class LRUMap[K, V](initMaxSize: Int, loadFactor: Box[Float], expiredFunc: ((K, V def maxSize = _maxSize + /** + * Updates the `LRUMap`'s current max size to `newMaxSize`, evicting the + * oldest entries if the size has shrunk. + */ def updateMaxSize(newMaxSize: Int) { val oldMaxSize = _maxSize _maxSize = newMaxSize @@ -75,6 +90,13 @@ class LRUMap[K, V](initMaxSize: Int, loadFactor: Box[Float], expiredFunc: ((K, V private[this] val localMap = new HashMap[K, LinkedListElem[K, V]](maxSize / 4, loadFactor openOr 0.75f) + /** + * Fetches the given key, returning `[[Empty]]` if the key does not exist in + * the map. A key may not be in the map either if it was never added or if it + * has been expired. + * + * Accessing a key this way will mark its value as most-recently-used. + */ def get(key: K): Box[V] = localMap.get(key) match { case null => Empty case v => @@ -83,12 +105,29 @@ class LRUMap[K, V](initMaxSize: Int, loadFactor: Box[Float], expiredFunc: ((K, V Full(v.value2) } + /** + * Unsafe version of `[[get]]`. + * + * @throws NullPointerException If the key does not exist in the map. Use `get` + * instead to get a safe `[[Box]]` result that can be checked for + * existence, or use `[[contains]]` before calling this. + */ def apply(key: K) = get(key).openOrThrowException("Simulating what happens with a regular Map, use contains(key) to check if it is present or not.") + /** + * Check if the given `key` exists in the map. A key may not be in the map + * either if it was never added or if it has been expired. + */ def contains(key: K): Boolean = localMap.containsKey(key) + /** + * Remove the given `key` and its associated value from the map. + */ def -(key: K) = remove(key) + /** + * Alias for `[[-]]`. + */ def remove(key: K) { localMap.get(key) match { case null => @@ -98,6 +137,13 @@ class LRUMap[K, V](initMaxSize: Int, loadFactor: Box[Float], expiredFunc: ((K, V } } + /** + * Set the `value` for the given `key` in the map. + * + * Marks the given `value` as the most recently used, and, if this `key` is + * new in the map and the map has grown beyond the specifiex `[[maxSize]]`, + * evicts the least-recently-used entries. + */ def update(key: K, value: V) { localMap.get(key) match { case null => @@ -117,16 +163,15 @@ class LRUMap[K, V](initMaxSize: Int, loadFactor: Box[Float], expiredFunc: ((K, V /** * Override this method to implement a test to see if a particular - * element can be expired from the cache + * element can be expired from the cache. */ protected def canExpire(k: K, v: V): Boolean = { true } /** - * A mechanism for expiring elements from cache. This method - * can devolve into O(n ^ 2) if lots of elements can't be - * expired + * A mechanism for expiring elements from cache. This method can devolve into + * O(n ^ 2) if lots of elements can't be expired. */ private def doRemoveIfTooMany() { while (localMap.size > maxSize) { @@ -143,7 +188,9 @@ class LRUMap[K, V](initMaxSize: Int, loadFactor: Box[Float], expiredFunc: ((K, V } /** - * Called when a key/value pair is removed + * Called when a key/value pair is removed, before the `expiredFunc`. + * + * Does nothing by default, override for custom functionality. */ protected def expired(key: K, value: V) { @@ -163,6 +210,5 @@ class LRUMap[K, V](initMaxSize: Int, loadFactor: Box[Float], expiredFunc: ((K, V } def size: Int = localMap.size - } diff --git a/core/common/src/main/scala/net/liftweb/common/LoanWrapper.scala b/core/common/src/main/scala/net/liftweb/common/LoanWrapper.scala index 110f57795b..7f5e421445 100644 --- a/core/common/src/main/scala/net/liftweb/common/LoanWrapper.scala +++ b/core/common/src/main/scala/net/liftweb/common/LoanWrapper.scala @@ -18,15 +18,21 @@ package net.liftweb package common /** - * This trait defines the principle contract for function objects that - * wrap the processing of HTTP requests by Lift while utilizing the preestablished - * request-local scope. + * A component that takes action around some other functionality. It may choose + * to execute or not execute that functionality, but should not interpret or + * change the returned value; instead, it should perform orthogonal actions that + * need to occur around the given functionality. A canonical example is wrapping + * an SQL transaction around some piece of code. + * + * As an example, this trait defines the principal contract for function objects + * that wrap the processing of HTTP requests in Lift. */ trait CommonLoanWrapper { /** * Implementations of this method may either call f to continue processing * the wrapped call as normal, or may ignore f to entirely replace the - * wrapped call with a custom implementation + * wrapped call with a custom implementation. + * * @param f the delegate which provides processing by the underlying framework */ def apply[T](f: => T): T @@ -34,7 +40,21 @@ trait CommonLoanWrapper { object CommonLoanWrapper { /** - * If you have a List of LoanWrappers, apply them and then the functions + * If you have a List of LoanWrappers, apply them and then the functions. For + * example: + * + * {{{ + * val firstWrapper = new TimerWrapper() + * val secondWrapper = new TransactionWrapper() + * + * CommonLoanWrapper(firstWrapper :: secondWrapper :: Nil) { + * // do some things + * }) + * }}} + * + * The inner code will be wrapped first in the timer and then in the + * transaction, so that the timer will time the results of running the code + * inside a transaction. */ def apply[T, LWT <: CommonLoanWrapper](lst: List[LWT])(f: => T): T = lst match { case Nil => f diff --git a/core/common/src/main/scala/net/liftweb/common/Logging.scala b/core/common/src/main/scala/net/liftweb/common/Logging.scala index 546b0f63bb..4e5e778d4a 100644 --- a/core/common/src/main/scala/net/liftweb/common/Logging.scala +++ b/core/common/src/main/scala/net/liftweb/common/Logging.scala @@ -19,6 +19,38 @@ package common import org.slf4j.{MDC => SLF4JMDC, Marker, Logger => SLF4JLogger, LoggerFactory} +/** + * Provides some helpers to easily create `[[Logger]]` instances. + * + * For example: + * {{{ + * class MyClass { + * val logger = Logger(classOf[MyClass]) + * } + * }}} + * + * It can also be used to provide global setup for loggers created this way: + * + * {{{ + * Logger.setup = Full(Logback.withFile(new URL("file:///path/to/config.xml"))) + * + * class MyClass { + * val logger = Loger(classOf[MyClass]) + * + * logger.debug("Hello") // uses the above configuration + * } + * }}} + * + * Last but not least, you can wrap chunks of code with particular + * [[http://www.slf4j.org/manual.html#mdc Mapped Diagnostic Context]] values: + * + * {{{ + * Logger.logWith("mykey" -> "value") { + * logger.debug("stuff") // has mykey set to value in the MDC + * } + * logger.debug("more stuff") // mykey is set to its previous value + * }}} + */ object Logger { private[common] lazy val checkConfig: Boolean = { setup.foreach {_()}; @@ -26,17 +58,21 @@ object Logger { } /** - * This function, if set, will be called before any loggers are created + * This function, if set, will be called before any loggers are created. * - * Useful for initializing the logging backend with a non-default configuration + * Useful for initializing the logging backend with a non-default configuration. * - * Helpers exists for log4j & logback: + * Helpers exists for [[Log4j log4j]] and [[Logback logback]]: * - * Logger.setup = Full(Log4j.withFile(url) + * {{{ + * Logger.setup = Full(Log4j.withFile(url) + * }}} * * or - * - * Logger.setup = Full(Logback.withFile(url)) + * + * {{{ + * Logger.setup = Full(Logback.withFile(url)) + * }}} * */ var setup: Box[() => Unit] = Empty @@ -53,11 +89,11 @@ object Logger { def apply(name: String): Logger = if (checkConfig) new WrappedLogger(LoggerFactory.getLogger(name)) else null /** - * Set the Mapped Diagnostic Context for the thread and execute - * the function f + * Set the [[http://www.slf4j.org/manual.html#mdc Mapped Diagnostic Context]] + * for the thread and execute the block `f`. * - * Upon return, the MDC is cleared of the values passed (any - * MDC values that existed prior to this call remains) + * Upon return, the MDC is cleared of the values passed (any MDC values that + * existed prior to this call remains). */ def logWith[F](mdcValues: (String,Any)*)(f: => F): F = { val old = SLF4JMDC.getCopyOfContextMap @@ -76,8 +112,8 @@ object Logger { /** * The Mapped Diagnostics Context can hold values per thread and output them * with each logged output. - * - * The logging backend needs to be configured to log these values + * + * The logging backend needs to be configured to log these values. */ object MDC { /** @@ -101,20 +137,20 @@ object MDC { } /** - * Logger is a thin wrapper on top of an SLF4J Logger + * `Logger` is a thin wrapper on top of an SLF4J Logger. * - * The main purpose is to utilize Scala features for logging + * The main purpose is to utilize Scala features for logging. * - * Note that the dynamic type of "this" is used when this trait is - * mixed in. + * Note that the dynamic type of "this" is used when this trait is mixed in. * - * This may not always be what you want. If you need the static type, you have to declare your - * own Logger: + * This may not always be what you want. If you need the static type, you have + * to declare your own `Logger`: * + * {{{ * class MyClass { * val logger = Logger(classOf[MyClass]) * } - * + * }}} */ trait Logger { private lazy val logger: SLF4JLogger = _logger // removed @transient 'cause there's no reason for transient on val @@ -132,9 +168,9 @@ trait Logger { } /** - * Trace a Failure. If the log level is trace and the Box is - * a Failure, trace the message concatenated with the Failure's message. - * If the Failure contains an Exception, trace that as well. + * Trace a `[[Failure]]`. If the log level is trace and the `[[Box]]` is a + * `Failure`, trace the message concatenated with the `Failure`'s message. If + * the `Failure` contains an `Exception`, trace that as well. */ def trace(msg: => AnyRef, box: Box[_]): Unit = { if (logger.isTraceEnabled) { @@ -155,9 +191,9 @@ trait Logger { def isTraceEnabled = logger.isTraceEnabled /** - * Debug a Failure. If the log level is debug and the Box is - * a Failure, debug the message concatenated with the Failure's message. - * If the Failure contains an Exception, debug that as well. + * Debug a `Failure`. If the log level is debug and the `Box` is a + * `Failure`, debug the message concatenated with the `Failure`'s message. If + * the `Failure` contains an `Exception`, debug that as well. */ def debug(msg: => AnyRef, box: Box[_]): Unit = { if (logger.isDebugEnabled) { @@ -178,9 +214,9 @@ trait Logger { def isDebugEnabled = logger.isDebugEnabled /** - * Info a Failure. If the log level is info and the Box is - * a Failure, info the message concatenated with the Failure's message. - * If the Failure contains an Exception, info that as well. + * Info a `Failure`. If the log level is info and the `Box` is a `Failure`, + * info the message concatenated with the `Failure`'s message. If the + * `Failure` contains an `Exception`, info that as well. */ def info(msg: => AnyRef, box: Box[_]): Unit = { if (logger.isInfoEnabled) { @@ -199,9 +235,9 @@ trait Logger { def isInfoEnabled = logger.isInfoEnabled /** - * Warn a Failure. If the log level is warn and the Box is - * a Failure, warn the message concatenated with the Failure's message. - * If the Failure contains an Exception, warn that as well. + * Warn a `Failure`. If the log level is warn and the `Box` is a `Failure`, + * warn the message concatenated with the `Failure`'s message. If the + * `Failure` contains an `Exception`, warn that as well. */ def warn(msg: => AnyRef, box: Box[_]): Unit = { if (logger.isWarnEnabled) { @@ -220,9 +256,9 @@ trait Logger { def isWarnEnabled = logger.isWarnEnabled /** - * Error a Failure. If the log level is error and the Box is - * a Failure, error the message concatenated with the Failure's message. - * If the Failure contains an Exception, error that as well. + * Error a `Failure`. If the log level is error and the `Box` is a `Failure`, + * error the message concatenated with the `Failure`'s message. If the + * `Failure` contains an `Exception`, error that as well. */ def error(msg: => AnyRef, box: Box[_]): Unit = { if (logger.isErrorEnabled) { @@ -243,33 +279,43 @@ trait Logger { } +/** + * Represents a `[[Logger]]` backed by an SLF4J `Logger`. + */ class WrappedLogger(l: SLF4JLogger) extends Logger { override def _logger = l } /** - * Mixin with a nested Logger + * If you mix this into your class, you will get a protected `logger` instance + * `val` that will be a `[[Logger]]` instance. */ trait Loggable { @transient protected val logger = Logger(this.getClass) } /** - * Mixin with a nested lazy Logger + * If you mix this into your class, you will get a protected `logger` instance + * `lazy val` that will be a `[[Logger]]` instance. * - * Useful for mixin to objects that are created before Lift has booted (and thus Logging is not yet configured) + * Useful for mixing into objects that are created before Lift has booted (and + * thus Logging is not yet configured). */ trait LazyLoggable { @transient protected lazy val logger = Logger(this.getClass) } /** - * Configuration helpers for the log4j logging backend + * Configuration helpers for the log4j logging backend. */ object Log4j { import org.apache.log4j.{LogManager,PropertyConfigurator} import org.apache.log4j.xml.DOMConfigurator + /** + * Default configuration for log4j backend. Appends to the console with a + * simple layout at `INFO` level. + */ val defaultProps = """ @@ -285,7 +331,8 @@ object Log4j { """ /** - * Configure with contents of the specified filed (either .xml or .properties) + * Configure with the contents of the file at the specified `url` (either + * `.xml` or `.properties`). */ def withFile(url: java.net.URL)() = { if (url.getPath.endsWith(".xml")) { @@ -295,7 +342,8 @@ object Log4j { PropertyConfigurator.configure(url) } /** - * Configure with the specified configuration. config must contain a valid XML document + * Configure with the specified configuration. `config` must contain a valid + * XML document. */ def withConfig(config: String)() = { val domConf = new DOMConfigurator @@ -304,13 +352,13 @@ object Log4j { } /** - * Configure with simple defaults + * Configure with simple defaults. See [[defaultProps]]. */ def withDefault() = withConfig(defaultProps) } /** - * Configuration helpers for the Logback logging backend + * Configuration helpers for the Logback logging backend. */ object Logback { import ch.qos.logback.classic.LoggerContext; @@ -318,7 +366,7 @@ object Logback { import ch.qos.logback.classic.joran.JoranConfigurator; /** - * Configure with contents of the specified XML filed + * Configure with the contents of the XML file at the specified `url`. */ def withFile(url: java.net.URL)() = { val lc = LoggerFactory.getILoggerFactory().asInstanceOf[LoggerContext]; diff --git a/core/common/src/main/scala/net/liftweb/common/SimpleActor.scala b/core/common/src/main/scala/net/liftweb/common/SimpleActor.scala index 09c387ddce..792194d0fe 100644 --- a/core/common/src/main/scala/net/liftweb/common/SimpleActor.scala +++ b/core/common/src/main/scala/net/liftweb/common/SimpleActor.scala @@ -18,8 +18,8 @@ package net.liftweb package common /** - * The simple definition of an actor. Something that - * can receive a message of type T. + * The simple definition of an actor. Something that can be sent a message of + * type `T`. */ trait SimpleActor[-T] { /** @@ -31,52 +31,62 @@ trait SimpleActor[-T] { } /** - * An Actor that can receive a message of any type + * An Actor that can receive a message of any type. */ trait SimplestActor extends SimpleActor[Any] /** - * An Actor that can receive messsages of type T and - * return responses of type R. + * An Actor that can receive messsages of type `T` and return responses of type + * `R`. */ trait TypedActor[-T, +R] extends SimpleActor[T] { - def !?(param: T): R + /** + * Compatible with Scala actors' `!?` method, sends the given `message` to + * this actor and waits infinitely for a reply. + */ + def !?(message: T): R /** - * Compatible with Scala Actors' !? method + * Compatible with Scala actors' `!?` method, sends the given `message` to + * this actor and waits up to `timeout` for a reply, returning `[[Empty]]` or + * `[[Failure]]` if no reply is received by then. */ def !?(timeout: Long, message: Any): Box[R] /** - * Asynchronous message send. Send-and-receive eventually. Waits on a Future for the reply message. - * If recevied within the Actor default timeout interval then it returns Some(result) and if a timeout - * has occured None. + * Asynchronous message send. Send-and-receive eventually. Waits on a Future + * for the reply message. If recevied within the Actor default timeout + * interval then it returns `Full(result)` and if a timeout has occured + * `[[Empty]]` or `[[Failure]]`. */ def !!(message: T): Box[R] /** - * Asynchronous message send. Send-and-receive eventually. Waits on a Future for the reply message. - * If recevied within timout interval that is specified then it returns Some(result) and if a timeout - * has occured None. + * Asynchronous message send. Send-and-receive eventually. Waits on a Future + * for the reply message. If recevied within timout interval that is + * specified then it returns `Full(result)` and if a timeout has occured + * `[[Empty]]` or `[[Failure]]`. */ def !!(message: T, timeout: Long): Box[R] - } /** - * Generic Actor interface. Can receive any type of message. - * Can return (via !! and !?) messages of type R. + * Generic Actor interface. Can receive any type of message. Can return (via + * `!!` and `!?`) messages of type `R`. */ trait GenericActor[+R] extends TypedActor[Any, R] /** * Generic Actor interface. Can receive any type of message. - * Can return (via !! and !?) messages of any type. + * Can return (via `!!` and `!?`) messages of any type. */ trait SimplestGenericActor extends GenericActor[Any] - +/** + * Interface for an actor that can internally forward received messages to other + * actors. + */ trait ForwardableActor[From, To] { self: TypedActor[From, To] => diff --git a/core/common/src/main/scala/net/liftweb/common/SimpleList.scala b/core/common/src/main/scala/net/liftweb/common/SimpleList.scala index fdc67372b9..d9536faeab 100644 --- a/core/common/src/main/scala/net/liftweb/common/SimpleList.scala +++ b/core/common/src/main/scala/net/liftweb/common/SimpleList.scala @@ -21,26 +21,32 @@ import java.util.{List => JavaList, Iterator => JavaIterator, ArrayList, ListIterator, Collection => JavaCollection} /** - * An immutable singly linked list that uses the Scala List class - * as backing store, but is Java-friendly. + * An immutable singly linked list that uses the Scala List class as backing + * store, but is Java-friendly as a `java.util.List`. Note however that since it + * is immutable, you have to capture the results of addition/removal operations. + * + * The typical mutating methods like `add`, `set`, `clear`, and `remove` are all + * unsupported, as are mutating methods on its iterators, since this collection + * is immutable. */ final case class SimpleList[T](underlying: List[T]) extends JavaList[T] { /** - * Construct an empty List + * Construct an empty list. */ def this() = this(Nil) def this(jl: JavaList[T]) = this(jl.toArray().toList.asInstanceOf[List[T]]) /** - * Append an item to this list. An O(n) operation where n is the - * number of items in the underlying List. + * Append an item to this list. This operation is O(n) where `n` is the number + * of items in the underlying `List`, and returns the updated list. */ def append(item: T): SimpleList[T] = SimpleList(underlying :+ item) /** - * Prepends an item to this list. O(1) + * Prepends an item to this list. This operation is O(1) and returns the + * updated list. */ def prepend(item: T): SimpleList[T] = SimpleList(item :: underlying) @@ -67,9 +73,9 @@ final case class SimpleList[T](underlying: List[T]) extends JavaList[T] { def isEmpty(): Boolean = underlying.isEmpty /** - * Does the List contain the element + * Returns true if this list contains the given `obj`. */ - def contains(x: Object): Boolean = underlying.contains(x) + def contains(obj: Object): Boolean = underlying.contains(obj) def iterator(): JavaIterator[T] = { val it = underlying.iterator @@ -170,26 +176,36 @@ final case class SimpleList[T](underlying: List[T]) extends JavaList[T] { } /** - * An immutable singly linked list that uses the Scala List class - * as backing store, but is Java-friendly. + * An immutable vector that uses the Scala `[[scala.collection.immutable.Vector Vector]]` + * class as backing store, but is Java-friendly as a `java.util.List`. Note however that + * since it is immutable, you have to capture the results of addition/removal + * operations. + * + * The typical mutating methods like `add`, `set`, `clear`, and `remove` are all + * unsupported, as are mutating methods on its iterators, since this collection + * is immutable. + * + * @see [[http://docs.scala-lang.org/overviews/collections/concrete-immutable-collection-classes.html#vectors "Scala's Collection Library overview"]] + * section on Vectors for more information. */ final case class SimpleVector[T](underlying: Vector[T]) extends JavaList[T] { /** - * Construct an empty List + * Construct an empty vector. */ def this() = this(Vector()) def this(jl: JavaList[T]) = this(Vector(jl.toArray().toList.asInstanceOf[List[T]] :_*)) /** - * Append an item to this list. An O(n) operation where n is the - * number of items in the underlying List. + * Append an item to this vector. This operation is effectively O(1) and + * returns the updated vector. */ def append(item: T): SimpleVector[T] = SimpleVector(underlying :+ item) /** - * Prepends an item to this list. O(1) + * Prepends an item to this vector. This operation is effectively O(1) and + * returns the updated vector. */ def prepend(item: T): SimpleVector[T] = SimpleVector(item +: underlying) @@ -216,9 +232,9 @@ final case class SimpleVector[T](underlying: Vector[T]) extends JavaList[T] { def isEmpty(): Boolean = underlying.isEmpty /** - * Does the List contain the element + * Returns `true` if this vector contains the given `obj`. */ - def contains(x: Object): Boolean = underlying.contains(x) + def contains(obj: Object): Boolean = underlying.contains(obj) def iterator(): JavaIterator[T] = { val it = underlying.iterator @@ -270,8 +286,7 @@ final case class SimpleVector[T](underlying: Vector[T]) extends JavaList[T] { } ret - } - + } def toArray[X](in: Array[X with Object]): Array[X with Object] = { val clz = in.getClass.getComponentType() From b017a28023443b7bc2e98978a08322257f86d8d5 Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Sun, 14 Dec 2014 20:23:02 -0500 Subject: [PATCH 03/14] Move an import up to the head of Conversions.scala. --- .../common/src/main/scala/net/liftweb/common/Conversions.scala | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/core/common/src/main/scala/net/liftweb/common/Conversions.scala b/core/common/src/main/scala/net/liftweb/common/Conversions.scala index f6a308dbbd..cb27936513 100644 --- a/core/common/src/main/scala/net/liftweb/common/Conversions.scala +++ b/core/common/src/main/scala/net/liftweb/common/Conversions.scala @@ -18,7 +18,7 @@ package net.liftweb package common import scala.language.implicitConversions -import scala.xml.NodeSeq +import scala.xml._ /* * This file contains common conversions and other utilities to make @@ -48,7 +48,6 @@ sealed trait StringOrNodeSeq { * their needs dictate. */ object StringOrNodeSeq { - import scala.xml._ /** * Convert a String to a StringOrNodeSeq From 55fa831d9b848c5d37d376f56fa2a4ec82f2d8e7 Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Sun, 14 Dec 2014 20:23:44 -0500 Subject: [PATCH 04/14] Clean out documentation that adds nothing. Some of the scaladocs were extremely redundant with respect to the functions they were describing. This commit drops those Scaladocs. --- .../net/liftweb/common/Conversions.scala | 21 ------------------- .../net/liftweb/common/FuncJBridge.scala | 21 ------------------- .../main/scala/net/liftweb/common/HList.scala | 6 ------ .../scala/net/liftweb/common/SimpleList.scala | 12 ----------- 4 files changed, 60 deletions(-) diff --git a/core/common/src/main/scala/net/liftweb/common/Conversions.scala b/core/common/src/main/scala/net/liftweb/common/Conversions.scala index cb27936513..c1119312e0 100644 --- a/core/common/src/main/scala/net/liftweb/common/Conversions.scala +++ b/core/common/src/main/scala/net/liftweb/common/Conversions.scala @@ -48,10 +48,6 @@ sealed trait StringOrNodeSeq { * their needs dictate. */ object StringOrNodeSeq { - - /** - * Convert a String to a StringOrNodeSeq - */ implicit def strTo[T <% String](str: T): StringOrNodeSeq = new StringOrNodeSeq { def nodeSeq: NodeSeq = Text(str) @@ -66,9 +62,6 @@ object StringOrNodeSeq { def nodeSeq: NodeSeq = ns } - /** - * Convert a StringOrNodeSeq into a NodeSeq - */ implicit def toNodeSeq(sns: StringOrNodeSeq): NodeSeq = sns.nodeSeq } @@ -93,29 +86,15 @@ sealed trait StringFunc { * while the former is simpler to use. */ object StringFunc { - /** - * If you've got something that can be converted into a String (a constant) - * but want a StringFunc, this implicit will do the conversion. - */ implicit def strToStringFunc[T](str: T)(implicit f: T => String): StringFunc = ConstStringFunc(f(str)) - /** - * If you've got something that can be converted into a String Function - * but want a StringFunc, this implicit will do the conversion. - */ implicit def funcToStringFunc[T](func: () => T)(implicit f: T => String): StringFunc = RealStringFunc(() => f(func())) } -/** - * The case class that holds a String function. - */ final case class RealStringFunc(func: () => String) extends StringFunc -/** - * The case class that holds the String constant. - */ final case class ConstStringFunc(str: String) extends StringFunc { lazy val func = () => str } diff --git a/core/common/src/main/scala/net/liftweb/common/FuncJBridge.scala b/core/common/src/main/scala/net/liftweb/common/FuncJBridge.scala index ba9d21354c..975af6d082 100644 --- a/core/common/src/main/scala/net/liftweb/common/FuncJBridge.scala +++ b/core/common/src/main/scala/net/liftweb/common/FuncJBridge.scala @@ -29,51 +29,30 @@ object FuncJBridge extends FuncJBridge * various function arities. */ class FuncJBridge { - /** - * Lift the Java Func0 to a Scala Function0 - */ implicit def lift[Z](f: Func0[Z]): Function0[Z] = new Function0[Z] { def apply(): Z = f.apply() } - /** - * Drop from Scala function to Java function - */ implicit def drop[Z](f: Function0[Z]): Func0[Z] = new Func0[Z] { def apply(): Z = f.apply() } - /** - * Lift the Java Func1 to a Scala Function1 - */ implicit def lift[A, Z](f: Func1[A, Z]): Function1[A, Z] = new Function1[A, Z] { def apply(a: A): Z = f.apply(a) } - /** - * Lift the Java Func2 to a Scala Function2 - */ implicit def lift[A, B, Z](f: Func2[A, B, Z]): Function2[A, B, Z] = new Function2[A, B, Z] { def apply(a: A, b: B): Z = f.apply(a, b) } - /** - * Lift the Java Func3 to a Scala Function3 - */ implicit def lift[A, B, C, Z](f: Func3[A, B, C, Z]): Function3[A, B, C, Z] = new Function3[A, B, C, Z] { def apply(a: A, b: B, c: C): Z = f.apply(a, b, c) } - /** - * Lift the Java Func4 to a Scala Function4 - */ implicit def lift[A, B, C, D, Z](f: Func4[A, B, C, D, Z]): Function4[A, B, C, D, Z] = new Function4[A, B, C, D, Z] { def apply(a: A, b: B, c: C, d: D): Z = f.apply(a, b, c, d) } - /** - * Lift the Java Callable to a Scala Function0 - */ implicit def lift[Z](f: java.util.concurrent.Callable[Z]): Function0[Z] = new Function0[Z] { def apply(): Z = f.call() } diff --git a/core/common/src/main/scala/net/liftweb/common/HList.scala b/core/common/src/main/scala/net/liftweb/common/HList.scala index a199577c4d..b7775305b7 100644 --- a/core/common/src/main/scala/net/liftweb/common/HList.scala +++ b/core/common/src/main/scala/net/liftweb/common/HList.scala @@ -101,9 +101,6 @@ object HLists { override def toString = "HNil" - /** - * The length of the HList - */ def length = 0 } @@ -143,9 +140,6 @@ object HLists { override def toString = head + " :+: " + tail - /** - * The length of the HList - */ def length = 1 + tail.length } diff --git a/core/common/src/main/scala/net/liftweb/common/SimpleList.scala b/core/common/src/main/scala/net/liftweb/common/SimpleList.scala index d9536faeab..76f599462f 100644 --- a/core/common/src/main/scala/net/liftweb/common/SimpleList.scala +++ b/core/common/src/main/scala/net/liftweb/common/SimpleList.scala @@ -62,14 +62,8 @@ final case class SimpleList[T](underlying: List[T]) extends JavaList[T] { def tail(): SimpleList[T] = SimpleList(underlying.tail) - /** - * The size of the List - */ def size(): Int = underlying.length - /** - * Is the List Empty - */ def isEmpty(): Boolean = underlying.isEmpty /** @@ -221,14 +215,8 @@ final case class SimpleVector[T](underlying: Vector[T]) extends JavaList[T] { def tail(): SimpleVector[T] = SimpleVector(underlying.tail) - /** - * The size of the List - */ def size(): Int = underlying.length - /** - * Is the List Empty - */ def isEmpty(): Boolean = underlying.isEmpty /** From 0f8ce86d31753f260cdadeb1f5cfd4890fa6f747 Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Sun, 14 Dec 2014 20:24:25 -0500 Subject: [PATCH 05/14] Deprecate NodeSeqFunc. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit It doesn’t seem to be used anywhere and we generally lean on transformation functions rather than generation functions for NodeSeq. This is also something we want to continue to encourage, so making using NodeSeqs directly a little more painful is okay. --- .../src/main/scala/net/liftweb/common/Conversions.scala | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/core/common/src/main/scala/net/liftweb/common/Conversions.scala b/core/common/src/main/scala/net/liftweb/common/Conversions.scala index c1119312e0..1097ad52a3 100644 --- a/core/common/src/main/scala/net/liftweb/common/Conversions.scala +++ b/core/common/src/main/scala/net/liftweb/common/Conversions.scala @@ -105,6 +105,8 @@ final case class ConstStringFunc(str: String) extends StringFunc { * with the implicit conversions defined in its [[NodeSeqFunc$ companion * object]]. */ +@deprecated("""Lift now mostly uses NodeSeq=>NodeSeq transformations rather than +NodeSeq constants; consider doing the same.""","3.0") sealed trait NodeSeqFunc { def func: () => NodeSeq } @@ -115,6 +117,8 @@ sealed trait NodeSeqFunc { * the flexibility of a `()=>[[scala.xml.NodeSeq NodeSeq]]` without having to * write overloads for all methods that should accept both. */ +@deprecated("""Lift now mostly uses NodeSeq=>NodeSeq transformations rather than +NodeSeq constants; consider doing the same.""","3.0") object NodeSeqFunc { /** * If you've got something that can be converted into a NodeSeq (a constant) @@ -134,11 +138,15 @@ object NodeSeqFunc { /** * The case class that holds a `[[scala.xml.NodeSeq NodeSeq]]` function. */ +@deprecated("""Lift now mostly uses NodeSeq=>NodeSeq transformations rather than +NodeSeq constants; consider doing the same.""","3.0") final case class RealNodeSeqFunc(func: () => NodeSeq) extends NodeSeqFunc /** * The case class that holds the `[[scala.xml.NodeSeq NodeSeq]]` constant. */ +@deprecated("""Lift now mostly uses NodeSeq=>NodeSeq transformations rather than +NodeSeq constants; consider doing the same.""","3.0") final case class ConstNodeSeqFunc(ns: NodeSeq) extends NodeSeqFunc { lazy val func = () => ns } From 22ee9e4fef4fa070186ef01181dfbd957bc74755 Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Sun, 14 Dec 2014 20:24:52 -0500 Subject: [PATCH 06/14] Rename a variable in Logger. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit checkConfig was used to make sure setup was being run. It’s now named ranSetup, since that’s more reflective of what it means. --- .../main/scala/net/liftweb/common/Logging.scala | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/core/common/src/main/scala/net/liftweb/common/Logging.scala b/core/common/src/main/scala/net/liftweb/common/Logging.scala index 4e5e778d4a..19a2b91e8d 100644 --- a/core/common/src/main/scala/net/liftweb/common/Logging.scala +++ b/core/common/src/main/scala/net/liftweb/common/Logging.scala @@ -52,8 +52,8 @@ import org.slf4j.{MDC => SLF4JMDC, Marker, Logger => SLF4JLogger, LoggerFactory} * }}} */ object Logger { - private[common] lazy val checkConfig: Boolean = { - setup.foreach {_()}; + private[common] lazy val ranSetup: Boolean = { + setup.foreach { _() } true } @@ -85,8 +85,8 @@ object Logger { className } - def apply(cls: Class[_]): Logger = if (checkConfig) new WrappedLogger(LoggerFactory.getLogger(loggerNameFor(cls))) else null - def apply(name: String): Logger = if (checkConfig) new WrappedLogger(LoggerFactory.getLogger(name)) else null + def apply(cls: Class[_]): Logger = if (ranSetup) new WrappedLogger(LoggerFactory.getLogger(loggerNameFor(cls))) else null + def apply(name: String): Logger = if (ranSetup) new WrappedLogger(LoggerFactory.getLogger(name)) else null /** * Set the [[http://www.slf4j.org/manual.html#mdc Mapped Diagnostic Context]] @@ -153,9 +153,9 @@ object MDC { * }}} */ trait Logger { - private lazy val logger: SLF4JLogger = _logger // removed @transient 'cause there's no reason for transient on val - // changed to lazy val so it only gets initialized on use rather than on instantiation - protected def _logger = if (Logger.checkConfig) LoggerFactory.getLogger(Logger.loggerNameFor(this.getClass)) else null + private lazy val logger: SLF4JLogger = _logger + + protected def _logger = if (Logger.ranSetup) LoggerFactory.getLogger(Logger.loggerNameFor(this.getClass)) else null def assertLog(assertion: Boolean, msg: => String) = if (assertion) info(msg) From 6b2917bb64c1dff37346450e7f5dee12d7fc76ff Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Sun, 14 Dec 2014 20:25:57 -0500 Subject: [PATCH 07/14] Use UnsupportedOperationException where reasonable. SimpleList and SimpleVector have a bunch of unsupported operations with respect to regular Java collections; these were through Exception instances. We switch them to throw UnsupportedOperationException as per the Java collection contract. --- .../scala/net/liftweb/common/SimpleList.scala | 44 +++++++++---------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/core/common/src/main/scala/net/liftweb/common/SimpleList.scala b/core/common/src/main/scala/net/liftweb/common/SimpleList.scala index 76f599462f..4063d5806a 100644 --- a/core/common/src/main/scala/net/liftweb/common/SimpleList.scala +++ b/core/common/src/main/scala/net/liftweb/common/SimpleList.scala @@ -76,7 +76,7 @@ final case class SimpleList[T](underlying: List[T]) extends JavaList[T] { new JavaIterator[T] { def hasNext() = it.hasNext def next(): T = it.next() - def remove() = throw new Exception("Does not support") + def remove() = throw new UnsupportedOperationException() } } @@ -95,17 +95,17 @@ final case class SimpleList[T](underlying: List[T]) extends JavaList[T] { def lastIndexOf(obj: Object): Int = underlying.lastIndexOf(obj) - def add(x: T): Boolean = throw new Exception("Does not support") + def add(x: T): Boolean = throw new UnsupportedOperationException() - def add(after: Int, x: T): Unit = throw new Exception("Does not support") + def add(after: Int, x: T): Unit = throw new UnsupportedOperationException() - def set(after: Int, x: T): T = throw new Exception("Does not support") + def set(after: Int, x: T): T = throw new UnsupportedOperationException() - def clear(): Unit = throw new Exception("Does not support") + def clear(): Unit = throw new UnsupportedOperationException() - def remove(pos: Int): T = throw new Exception("Does not support") + def remove(pos: Int): T = throw new UnsupportedOperationException() - def remove(obj: Object): Boolean = throw new Exception("Does not support") + def remove(obj: Object): Boolean = throw new UnsupportedOperationException() def get(pos: Int): T = underlying(pos) @@ -142,13 +142,13 @@ final case class SimpleList[T](underlying: List[T]) extends JavaList[T] { ret } - def retainAll(jc: JavaCollection[_]): Boolean = throw new Exception("Does not support") + def retainAll(jc: JavaCollection[_]): Boolean = throw new UnsupportedOperationException() - def removeAll(jc: JavaCollection[_]): Boolean = throw new Exception("Does not support") + def removeAll(jc: JavaCollection[_]): Boolean = throw new UnsupportedOperationException() - def addAll(jc: JavaCollection[_ <: T]): Boolean = throw new Exception("Does not support") + def addAll(jc: JavaCollection[_ <: T]): Boolean = throw new UnsupportedOperationException() - def addAll(index: Int, jc: JavaCollection[_ <: T]): Boolean = throw new Exception("Does not support") + def addAll(index: Int, jc: JavaCollection[_ <: T]): Boolean = throw new UnsupportedOperationException() def containsAll(jc: JavaCollection[_]): Boolean = { val it = jc.iterator() @@ -229,7 +229,7 @@ final case class SimpleVector[T](underlying: Vector[T]) extends JavaList[T] { new JavaIterator[T] { def hasNext() = it.hasNext def next(): T = it.next() - def remove() = throw new Exception("Does not support") + def remove() = throw new UnsupportedOperationException() } } @@ -248,17 +248,17 @@ final case class SimpleVector[T](underlying: Vector[T]) extends JavaList[T] { def lastIndexOf(obj: Object): Int = underlying.lastIndexOf(obj) - def add(x: T): Boolean = throw new Exception("Does not support") + def add(x: T): Boolean = throw new UnsupportedOperationException() - def add(after: Int, x: T): Unit = throw new Exception("Does not support") + def add(after: Int, x: T): Unit = throw new UnsupportedOperationException() - def set(after: Int, x: T): T = throw new Exception("Does not support") + def set(after: Int, x: T): T = throw new UnsupportedOperationException() - def clear(): Unit = throw new Exception("Does not support") + def clear(): Unit = throw new UnsupportedOperationException() - def remove(pos: Int): T = throw new Exception("Does not support") + def remove(pos: Int): T = throw new UnsupportedOperationException() - def remove(obj: Object): Boolean = throw new Exception("Does not support") + def remove(obj: Object): Boolean = throw new UnsupportedOperationException() def get(pos: Int): T = underlying(pos) @@ -290,13 +290,13 @@ final case class SimpleVector[T](underlying: Vector[T]) extends JavaList[T] { ret } - def retainAll(jc: JavaCollection[_]): Boolean = throw new Exception("Does not support") + def retainAll(jc: JavaCollection[_]): Boolean = throw new UnsupportedOperationException() - def removeAll(jc: JavaCollection[_]): Boolean = throw new Exception("Does not support") + def removeAll(jc: JavaCollection[_]): Boolean = throw new UnsupportedOperationException() - def addAll(jc: JavaCollection[_ <: T]): Boolean = throw new Exception("Does not support") + def addAll(jc: JavaCollection[_ <: T]): Boolean = throw new UnsupportedOperationException() - def addAll(index: Int, jc: JavaCollection[_ <: T]): Boolean = throw new Exception("Does not support") + def addAll(index: Int, jc: JavaCollection[_ <: T]): Boolean = throw new UnsupportedOperationException() def containsAll(jc: JavaCollection[_]): Boolean = { val it = jc.iterator() From 00ac6d2ee4ae1dca70b66b4b08e4d7f7539351f2 Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Sun, 21 Dec 2014 16:48:17 -0500 Subject: [PATCH 08/14] Fix incorrect emphasis scaladoc markers. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Apparently * doesn’t emphasize, ''' does. Seems legit… --- core/common/src/main/scala/net/liftweb/common/Box.scala | 4 ++-- core/common/src/main/scala/net/liftweb/common/LRU.scala | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/core/common/src/main/scala/net/liftweb/common/Box.scala b/core/common/src/main/scala/net/liftweb/common/Box.scala index d85c565999..f844f75adc 100644 --- a/core/common/src/main/scala/net/liftweb/common/Box.scala +++ b/core/common/src/main/scala/net/liftweb/common/Box.scala @@ -407,7 +407,7 @@ sealed abstract class Box[+A] extends Product with Serializable{ * Exists to avoid the implicit conversion from `Box` to `Option`. Opening a * `Box` unsafely should be done using `openOrThrowException`. * - * This method *always* throws an exception. + * This method '''always''' throws an exception. */ final def get: DoNotCallThisMethod = { throw new Exception("Attempted to open a Box incorrectly. Please use openOrThrowException.") @@ -480,7 +480,7 @@ sealed abstract class Box[+A] extends Product with Serializable{ /** * - * If this `Box` contains a value and it does *not* satisfy the specified + * If this `Box` contains a value and it does '''not''' satisfy the specified * `predicate`, return the `Box` unchanged. Otherwise, return an `Empty`. */ def filterNot(f: A => Boolean): Box[A] = filter(a => !f(a)) diff --git a/core/common/src/main/scala/net/liftweb/common/LRU.scala b/core/common/src/main/scala/net/liftweb/common/LRU.scala index 06bcd64a2a..b619c5d369 100644 --- a/core/common/src/main/scala/net/liftweb/common/LRU.scala +++ b/core/common/src/main/scala/net/liftweb/common/LRU.scala @@ -50,7 +50,7 @@ private[common] trait LinkedListElem[T1, T2] { * Implements an LRU Hashmap. Given a size, this map will evict the least * recently used item(s) when new items are added. * - * Note that `LRUMap` is *not* thread-safe. + * Note that `LRUMap` is '''not''' thread-safe. * * @param initmaxSize The initial max size. This can be updated using * `[[updateMaxSize]]`. From 082536357ae7262584c7e195aeadad939cb0aade Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Sun, 21 Dec 2014 18:02:25 -0500 Subject: [PATCH 09/14] Fix a typo in the Logger docs. --- core/common/src/main/scala/net/liftweb/common/Logging.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/common/src/main/scala/net/liftweb/common/Logging.scala b/core/common/src/main/scala/net/liftweb/common/Logging.scala index 19a2b91e8d..5f60b7f405 100644 --- a/core/common/src/main/scala/net/liftweb/common/Logging.scala +++ b/core/common/src/main/scala/net/liftweb/common/Logging.scala @@ -35,7 +35,7 @@ import org.slf4j.{MDC => SLF4JMDC, Marker, Logger => SLF4JLogger, LoggerFactory} * Logger.setup = Full(Logback.withFile(new URL("file:///path/to/config.xml"))) * * class MyClass { - * val logger = Loger(classOf[MyClass]) + * val logger = Logger(classOf[MyClass]) * * logger.debug("Hello") // uses the above configuration * } From 15b7e816076de5da2f40fee6caccd90c8d0ba394 Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Sun, 21 Dec 2014 18:03:55 -0500 Subject: [PATCH 10/14] Clarify and improve various scaladocs. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Mostly adding `` around classes and parameters in docs, with a couple of tweaks to docs that weren’t clear enough. --- .../main/scala/net/liftweb/common/Box.scala | 103 ++++++++++-------- .../net/liftweb/common/Conversions.scala | 36 +++--- .../net/liftweb/common/LoanWrapper.scala | 8 +- 3 files changed, 80 insertions(+), 67 deletions(-) diff --git a/core/common/src/main/scala/net/liftweb/common/Box.scala b/core/common/src/main/scala/net/liftweb/common/Box.scala index f844f75adc..cef6b27aea 100644 --- a/core/common/src/main/scala/net/liftweb/common/Box.scala +++ b/core/common/src/main/scala/net/liftweb/common/Box.scala @@ -92,7 +92,7 @@ object Box extends BoxTrait { } /** - * Implementation for the `[[Box$]]` singleton. + * Implementation for the `[[Box$ Box]]` singleton. */ sealed trait BoxTrait { val primitiveMap: Map[Class[_], Class[_]] = Map( @@ -122,7 +122,8 @@ sealed trait BoxTrait { /** * Create a `Box` from the specified `Box`, checking for `null`. * - * @return `Full(in)` if `in` is non-null, `Empty` otherwise. + * @return `Full(in)` if `in` is a `Full` box and its value is non-null, + * `Empty` otherwise. */ def apply[T](in: Box[T]) = in match { case Full(x) => legacyNullTest(x) @@ -131,9 +132,8 @@ sealed trait BoxTrait { } /** - * Transform a `List` with zero or one elements to a `Box`. - * - * Note that any elements past the head of the list are lost! + * Transform a `List` with zero or more elements to a `Box`, losing all but + * the first element if there are more than one. * * @return `Full(x)` with the head of the list if it contains at least one * element and `Empty` otherwise. @@ -272,10 +272,11 @@ final class DoNotCallThisMethod /** * The `Box` class is a container which is able to declare if it is `Full` - * (containing a single non-null value) or `EmptyBox`. An EmptyBox, or empty, - * can be the `Empty` singleton, `Failure` or `ParamFailure`. `Failure` and - * `ParamFailure` contain information about why the `Box` is empty including - * exception information, possibly chained `Failures` and a `String` message. + * (containing a single non-null value) or `[[EmptyBox]]`. An `EmptyBox`, + * or empty, can be the `[[Empty]]` singleton, `[[Failure]]` or + * `[[ParamFailure]]`. `Failure` and `ParamFailure` contain information about + * why the `Box` is empty including exception information, possibly chained + * `Failure`s and a `String` message. * * This serves a similar purpose to the `[[scala.Option Option]]` class from * Scala standard library but adds several features: @@ -283,8 +284,8 @@ final class DoNotCallThisMethod * `[[?~]]` or `[[failMsg]]` method). * - You can chain failure messages on `Failure`s (with the `?~!` or * `[[compoundFailMsg]]` method). - * - You "run" a function on a `Box`, with a default to return if the box is - * `Empty`: + * - You can "run" a function on a `Box`, with a default to return if the box + * is `Empty`: * {{{ * val littleTeddyBears: Box[Int] = Full(10) * littleTeddyBears.run("and then there were none") { (default: String, teddyBears: Int) => @@ -308,7 +309,9 @@ final class DoNotCallThisMethod * ) // doSomething gets a Box[Int] as well * }}} * - * If you grew up on Java, you're used to `Exceptions` as part of your program + * === Exceptions and Empty Box Handling === + * + * If you grew up on Java, you're used to `Exception`s as part of your program * logic. The Scala philosophy and the Lift philosophy is that exceptions are * for exceptional conditions such as failure of an external resource (e.g., * your database goes offline) rather than simply indicating that a parameter @@ -383,8 +386,8 @@ sealed abstract class Box[+A] extends Product with Serializable{ * The only time when you should be using this method is if the value is * guaranteed to be available based on a guard outside of the method. In these * cases, please provide that information in the justification `String`. - * For example, User.currentUser.openOrThrowException("This snippet is only - * used on pages where the user is logged in"). For tests, use `[[==]]` or + * For example, `User.currentUser.openOrThrowException("This snippet is only + * used on pages where the user is logged in")`. For tests, use `[[==]]` or * `[[===]]` instead. See the class documentation for more information. * * A valid justification for using this method should not be "I want my code @@ -432,9 +435,9 @@ sealed abstract class Box[+A] extends Product with Serializable{ /** * Apply a function returning a `Box` to the value contained in this `Box` if * it exists and return the resulting `Box`. If this `Box` is not already - * `Full`, return the unchanged box. + * `Full`, return this box unchanged. * - * @note This means that using `map` with a `Failure` will preserve the + * @note This means that using `flatMap` with a `Failure` will preserve the * `Failure.` */ def flatMap[B](f: A => Box[B]): Box[B] = Empty @@ -462,16 +465,17 @@ sealed abstract class Box[+A] extends Product with Serializable{ } /** - * If this `Box` contains a value and it satisfies the specified `predicate`, + * If this `Box` contains a value and it satisfies the specified `func`, * return `true`. Otherwise, return `false`. * - * @return true if this Box does contain a value and it satisfies the predicate + * @return `true` if this Box does contain a value and it satisfies the + * predicate. */ def exists(func: A => Boolean): Boolean = false /** * If this `Box` contains a value and it does not satisfy the specified - * `predicate`, return `false`. Otherwise, return `true`. + * `func`, return `false`. Otherwise, return `true`. * * @return true If the `Box` is empty, or if its value satisfies the * predicate. @@ -481,7 +485,7 @@ sealed abstract class Box[+A] extends Product with Serializable{ /** * * If this `Box` contains a value and it does '''not''' satisfy the specified - * `predicate`, return the `Box` unchanged. Otherwise, return an `Empty`. + * `f`, return the `Box` unchanged. Otherwise, return an `Empty`. */ def filterNot(f: A => Boolean): Box[A] = filter(a => !f(a)) @@ -492,8 +496,8 @@ sealed abstract class Box[+A] extends Product with Serializable{ def foreach[U](f: A => U): Unit = {} /** - * Create a `Full` box containing the specified value if `in` is an instance of - * the specified class `clz` and `Empty` otherwise. + * If this box is `Full` and contains an object of type `B`, returns a `Full` + * of type `Box[B]`. Otherwise, returns `Empty`. * * This is basically a Java-friendly version of `[[asA]]`, which you should * prefer when using Scala. @@ -510,8 +514,8 @@ sealed abstract class Box[+A] extends Product with Serializable{ def isA[B](cls: Class[B]): Box[B] = Empty /** - * Create a `Full` box containing the specified value if `in` is of type - * `B` and `Empty` otherwise. + * Create a `Full` box containing the specified value if this box's value is + * of type `B` and `Empty` otherwise. * * For example: * {{{ @@ -525,7 +529,8 @@ sealed abstract class Box[+A] extends Product with Serializable{ def asA[B](implicit m: Manifest[B]): Box[B] = Empty /** - * Return this Box if `Full`, or the specified alternative if it is empty. + * Return this Box if `Full`, or the specified alternative if it is + * empty. Equivalent to `Option`'s `[[scala.Option.orElse orElse]]`. */ def or[B >: A](alternative: => Box[B]): Box[B] = alternative @@ -559,20 +564,19 @@ sealed abstract class Box[+A] extends Product with Serializable{ def toList: List[A] = Nil /** - * Returns the contents of this box wrapped in `Some` if this is Full, or - * `None` if this is a empty (meaning an `Empty`, `Failure` or - * `ParamFailure`). + * Returns the contents of this box wrapped in `Some` if this is `Full`, or + * `None` if this is empty (meaning an `Empty`, `Failure` or ParamFailure`). */ def toOption: Option[A] = None /** - * Transform an Empty to a Failure with the specified message. Otherwise - * leaves returns the same box. + * Transform an `Empty` to a `Failure` with the specified message. Otherwise + * returns the same box. * * @note This means a `Failure` will also remain unchanged; see `?~!` to * change these. * - * @return A Failure with the message if this `Box` is `Empty`, this box + * @return A `Failure` with the message if this `Box` is `Empty`, this box * otherwise. */ def ?~(msg: => String): Box[A] = this @@ -603,9 +607,9 @@ sealed abstract class Box[+A] extends Product with Serializable{ * * As with `[[?~]]`, if this is a `Full`, we return it unchanged. * - * @return A Failure with the message if this Box is an Empty Box. Chain this - * box to the new `Failure` if this is a `Failure`. The unchanged box - * if it is a `Full`. + * @return A `Failure` with the message if this `Box` is an `Empty` box. Chain + * this box to the new `Failure` if this is a `Failure`. The unchanged + * box if it is a `Full`. */ def ?~!(msg: => String): Box[A] = ?~(msg) @@ -636,8 +640,8 @@ sealed abstract class Box[+A] extends Product with Serializable{ def run[T](in: => T)(f: (T, A) => T) = in /** - * Perform a side effect by passing this Box to the specified function and - * return this Box unmodified. Similar to `foreach`, except that `foreach` + * Perform a side effect by passing this `Box` to the specified function and + * return this `Box` unmodified. Similar to `foreach`, except that `foreach` * returns `Unit`, while this method allows chained use of the `Box`. * * @return This box. @@ -700,7 +704,7 @@ sealed abstract class Box[+A] extends Product with Serializable{ def ===[B >: A](to: B): Boolean = false /** - * Equivalent to `map(f).openOr(Full(dflt))`. + * Equivalent to `map(f).openOr(dflt)`. */ def dmap[B](dflt: => B)(f: A => B): B = dflt @@ -731,14 +735,16 @@ sealed abstract class Box[+A] extends Product with Serializable{ def fullXform[T](v: T)(f: T => A => T): T = v /** - * An `Either` that is a `Left` with the given argument `left` if this is - * empty, or a `Right` with the boxed value if this is `Full`. + * An `[[scala.util.Either Either]]` that is a `Left` with the given argument + * `left` if this is empty, or a `Right` with the boxed value if this is + * `Full`. */ def toRight[B](left: => B): Either[B, A] = Left(left) /** - * An `Either` that is a `Right` with the given argument `right` if this is - * empty, or a `Left` with the boxed value if this is `Full`. + * An `[[scala.util.Either Either]]` that is a `Right` with the given argument + * `right` if this is empty, or a `Left` with the boxed value if this is + * `Full`. */ def toLeft[B](right: => B): Either[A, B] = Right(right) @@ -972,8 +978,9 @@ object ParamFailure { } /** - * A `ParamFailure` is a `Failure` with an additional type-safe parameter that - * can allow an application to store other information related to the failure. + * A `ParamFailure` is a `[[Failure]]` with an additional type-safe parameter + * that can allow an application to store other information related to the + * failure. * * For example: * {{{ @@ -1027,9 +1034,9 @@ trait Boxable[T] { } /** - * Sometimes it's convenient to access either a Box[T] or a T. If you specify - * BoxOrRaw[T], the either a T or a Box[T] can be passed and the "right thing" - * will happen, including nulls being treated as `Empty`. + * Sometimes it's convenient to access either a `[[Box]][T]` or a `T`. If you + * specify `BoxOrRaw[T]`, either a `T` or a `Box[T]` can be passed and the + * "right thing" will happen, including `null`s being treated as `Empty`. */ sealed trait BoxOrRaw[T] { def box: Box[T] @@ -1055,12 +1062,12 @@ object BoxOrRaw { } /** - * The BoxOrRaw that represents a boxed value. + * The `[[BoxOrRaw]]` that represents a boxed value. */ final case class BoxedBoxOrRaw[T](box: Box[T]) extends BoxOrRaw[T] /** - * The BoxOrRaw that represents a raw value. + * The `[[BoxOrRaw]]` that represents a raw value. */ final case class RawBoxOrRaw[T](raw: T) extends BoxOrRaw[T] { def box: Box[T] = diff --git a/core/common/src/main/scala/net/liftweb/common/Conversions.scala b/core/common/src/main/scala/net/liftweb/common/Conversions.scala index 1097ad52a3..bf156e4fe3 100644 --- a/core/common/src/main/scala/net/liftweb/common/Conversions.scala +++ b/core/common/src/main/scala/net/liftweb/common/Conversions.scala @@ -28,7 +28,7 @@ import scala.xml._ /** * This trait is used to unify `String`s and `[[scala.xml.NodeSeq NodeSeq]]`s * into one type. It is used in conjuction with the implicit conversions defined - * in its companion object. + * in its [[StringOrNodeSeq$ companion object]]. */ sealed trait StringOrNodeSeq { def nodeSeq: scala.xml.NodeSeq @@ -67,8 +67,8 @@ object StringOrNodeSeq { /** * This trait is used to unify `()=>String` and `String` into one type. It is - * used in conjunction with the implicit conversions defined in its companion - * object. + * used in conjunction with the implicit conversions defined in its [[StringFunc$ + * companion object]]. */ sealed trait StringFunc { def func: () => String @@ -93,8 +93,14 @@ object StringFunc { RealStringFunc(() => f(func())) } +/** + * See `[[StringFunc]]`. + */ final case class RealStringFunc(func: () => String) extends StringFunc +/** + * See `[[StringFunc]]`. + */ final case class ConstStringFunc(str: String) extends StringFunc { lazy val func = () => str } @@ -105,8 +111,8 @@ final case class ConstStringFunc(str: String) extends StringFunc { * with the implicit conversions defined in its [[NodeSeqFunc$ companion * object]]. */ -@deprecated("""Lift now mostly uses NodeSeq=>NodeSeq transformations rather than -NodeSeq constants; consider doing the same.""","3.0") +@deprecated("""Lift now mostly uses `NodeSeq=>NodeSeq` transformations rather +than `NodeSeq` constants; consider doing the same.""","3.0") sealed trait NodeSeqFunc { def func: () => NodeSeq } @@ -117,19 +123,19 @@ sealed trait NodeSeqFunc { * the flexibility of a `()=>[[scala.xml.NodeSeq NodeSeq]]` without having to * write overloads for all methods that should accept both. */ -@deprecated("""Lift now mostly uses NodeSeq=>NodeSeq transformations rather than -NodeSeq constants; consider doing the same.""","3.0") +@deprecated("""Lift now mostly uses `NodeSeq=>NodeSeq` transformations rather +than `NodeSeq` constants; consider doing the same.""","3.0") object NodeSeqFunc { /** - * If you've got something that can be converted into a NodeSeq (a constant) - * but want a NodeSeqFunc, this implicit will do the conversion. + * If you've got something that can be converted into a `NodeSeq` (a constant) + * but want a `NodeSeqFunc`, this implicit will do the conversion. */ implicit def nsToNodeSeqFunc[T](ns: T)(implicit f: T => NodeSeq): NodeSeqFunc = ConstNodeSeqFunc(f(ns)) /** - * If you've got something that can be converted into a String Function - * but want a StringFunc, this implicit will do the conversion. + * If you've got something that can be converted into a `NodeSeq` function but + * want a `NodeSeqFunc`, this implicit will do the conversion. */ implicit def funcToNodeSeqFunc[T](func: () => T)(implicit f: T => NodeSeq): NodeSeqFunc = RealNodeSeqFunc(() => f(func())) @@ -138,15 +144,15 @@ object NodeSeqFunc { /** * The case class that holds a `[[scala.xml.NodeSeq NodeSeq]]` function. */ -@deprecated("""Lift now mostly uses NodeSeq=>NodeSeq transformations rather than -NodeSeq constants; consider doing the same.""","3.0") +@deprecated("""Lift now mostly uses `NodeSeq=>NodeSeq` transformations rather +than `NodeSeq` constants; consider doing the same.""","3.0") final case class RealNodeSeqFunc(func: () => NodeSeq) extends NodeSeqFunc /** * The case class that holds the `[[scala.xml.NodeSeq NodeSeq]]` constant. */ -@deprecated("""Lift now mostly uses NodeSeq=>NodeSeq transformations rather than -NodeSeq constants; consider doing the same.""","3.0") +@deprecated("""Lift now mostly uses `NodeSeq=>NodeSeq` transformations rather +than `NodeSeq` constants; consider doing the same.""","3.0") final case class ConstNodeSeqFunc(ns: NodeSeq) extends NodeSeqFunc { lazy val func = () => ns } diff --git a/core/common/src/main/scala/net/liftweb/common/LoanWrapper.scala b/core/common/src/main/scala/net/liftweb/common/LoanWrapper.scala index 7f5e421445..7388162659 100644 --- a/core/common/src/main/scala/net/liftweb/common/LoanWrapper.scala +++ b/core/common/src/main/scala/net/liftweb/common/LoanWrapper.scala @@ -29,8 +29,8 @@ package common */ trait CommonLoanWrapper { /** - * Implementations of this method may either call f to continue processing - * the wrapped call as normal, or may ignore f to entirely replace the + * Implementations of this method may either call `f` to continue processing + * the wrapped call as normal, or may ignore `f` to entirely replace the * wrapped call with a custom implementation. * * @param f the delegate which provides processing by the underlying framework @@ -40,8 +40,8 @@ trait CommonLoanWrapper { object CommonLoanWrapper { /** - * If you have a List of LoanWrappers, apply them and then the functions. For - * example: + * If you have a `List` of `LoanWrapper`s, apply them and then the + * functions. For example: * * {{{ * val firstWrapper = new TimerWrapper() From 2b1d749b0155635a07c5daf6d41090d0710defb2 Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Sun, 21 Dec 2014 18:04:15 -0500 Subject: [PATCH 11/14] Bump to Scala 2.11.3. The specific motivation is that scaladoc in 2.11.3 fixes an issue with picking up indentation in code blocks properly. --- build.sbt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build.sbt b/build.sbt index b664165368..f907f03950 100644 --- a/build.sbt +++ b/build.sbt @@ -12,9 +12,9 @@ startYear in ThisBuild := Some(2006) organizationName in ThisBuild := "WorldWide Conferencing, LLC" -scalaVersion in ThisBuild := "2.11.2" +scalaVersion in ThisBuild := "2.11.3" -crossScalaVersions in ThisBuild := Seq("2.11.2") +crossScalaVersions in ThisBuild := Seq("2.11.3") libraryDependencies in ThisBuild <++= scalaVersion {sv => Seq(specs2, scalacheck, scalatest) } From a9fcf2d62961f7da6daa120ab435fbd006a20941 Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Sun, 21 Dec 2014 18:07:10 -0500 Subject: [PATCH 12/14] Restore some implicit conversion scaladocs. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit These are implicits that take implicits as parameters, and the docs make it a little clearer what that’s for. --- .../scala/net/liftweb/common/Conversions.scala | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/core/common/src/main/scala/net/liftweb/common/Conversions.scala b/core/common/src/main/scala/net/liftweb/common/Conversions.scala index bf156e4fe3..56240d7e0c 100644 --- a/core/common/src/main/scala/net/liftweb/common/Conversions.scala +++ b/core/common/src/main/scala/net/liftweb/common/Conversions.scala @@ -86,9 +86,23 @@ sealed trait StringFunc { * while the former is simpler to use. */ object StringFunc { + /** + * Implicit conversion from any type that in turn has an implicit conversion + * to a `String`, to a `StringFunc`. In particular, this means that if a given + * method takes a `StringFunc` as a parameter, it can accept either a `String` + * and any type that has an implicit conversion to `String` in scope. + */ implicit def strToStringFunc[T](str: T)(implicit f: T => String): StringFunc = ConstStringFunc(f(str)) + /** + * Implicit conversion from any function that produces a type that in turn has + * an implicit conversion to a `String`, to a `StringFunc`. In particular, + * this means that if a given method takes a `StringFunc` as a parameter, it + * can accept either a function that returns a `String` and a function that + * returns any other type that has an implicit conversion to `String` in + * scope. + */ implicit def funcToStringFunc[T](func: () => T)(implicit f: T => String): StringFunc = RealStringFunc(() => f(func())) } From dd822a48cfe6f30c2c668c6d02b55367a2ac3e20 Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Tue, 23 Dec 2014 21:19:29 -0500 Subject: [PATCH 13/14] Bump to Scala 2.11.4 because Scala. Evidently 2.11.3 had binary compatibility issues, so 2.11.4 it is! --- build.sbt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build.sbt b/build.sbt index f907f03950..a8f90023b9 100644 --- a/build.sbt +++ b/build.sbt @@ -12,9 +12,9 @@ startYear in ThisBuild := Some(2006) organizationName in ThisBuild := "WorldWide Conferencing, LLC" -scalaVersion in ThisBuild := "2.11.3" +scalaVersion in ThisBuild := "2.11.4" -crossScalaVersions in ThisBuild := Seq("2.11.3") +crossScalaVersions in ThisBuild := Seq("2.11.4") libraryDependencies in ThisBuild <++= scalaVersion {sv => Seq(specs2, scalacheck, scalatest) } From d57119b117d8a8b00017d9a4d445ee7b2737190e Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Sun, 21 Dec 2014 18:10:01 -0500 Subject: [PATCH 14/14] Take a stab at clarifying the FuncJBridge class comment. --- .../src/main/scala/net/liftweb/common/FuncJBridge.scala | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/core/common/src/main/scala/net/liftweb/common/FuncJBridge.scala b/core/common/src/main/scala/net/liftweb/common/FuncJBridge.scala index 975af6d082..4943b59c32 100644 --- a/core/common/src/main/scala/net/liftweb/common/FuncJBridge.scala +++ b/core/common/src/main/scala/net/liftweb/common/FuncJBridge.scala @@ -27,6 +27,11 @@ object FuncJBridge extends FuncJBridge * The implicits defined here allow Scala code to interact seamlessly between * the Java function-like interfaces and the Scala function interfaces for * various function arities. + * + * In particular, there is a pair of implicits for each arity of function from 0 + * to 4. There is one implicit (called `lift`) from the Java function type to + * the corresponding Scala function type and one (called `drop`) from the Scala + * function type to the corresponding Java function type. */ class FuncJBridge { implicit def lift[Z](f: Func0[Z]): Function0[Z] = new Function0[Z] {