|
package org.iainhull.wrapped |
|
|
|
import scala.util.{ Try, Success, Failure } |
|
import scala.util.control.NoStackTrace |
|
|
|
/** |
|
* Utility trait for value classes (see http://docs.scala-lang.org/overviews/core/value-classes.html) |
|
* |
|
* Classes that mix in this trait must implement the `value` method and then are provided |
|
* with sensible versions of `toString`, `equals` and `hashCode` that delegate to the |
|
* `value`s implementations. |
|
* |
|
* Classes that use this trait should use a private constructor and use WrappedValue.Companion to |
|
* define their companion object. See PositiveInt for an example. |
|
*/ |
|
trait WrappedValue[T] extends Any { |
|
def value: T |
|
|
|
override def toString = this.getClass.getSimpleName + "(" + value.toString + ")" |
|
|
|
override def equals(other: Any): Boolean = { |
|
if (this.getClass.isInstance(other)) { |
|
value.equals(other.asInstanceOf[WrappedValue[T]].value) |
|
} else { |
|
false |
|
} |
|
} |
|
|
|
override def hashCode: Int = value.hashCode |
|
} |
|
|
|
object WrappedValue { |
|
abstract class Companion { |
|
type InnerType |
|
type WrappedType <: WrappedValue[InnerType] |
|
|
|
protected def construct(value: InnerType): WrappedType |
|
protected def validate(value: InnerType): Option[String] |
|
|
|
/** |
|
* Validate and construct the WrappedType |
|
* |
|
* @throws IllegalArgumentException is the value is not valid for WrappedType |
|
* |
|
* {{{ |
|
* val v = PositiveInt(42) |
|
* }}} |
|
*/ |
|
def apply(value: InnerType): WrappedType = { |
|
validate(value) foreach { message => require(false, message) } |
|
construct(value) |
|
} |
|
|
|
/** |
|
* Validate and construct the WrappedType (without throwing exceptions) |
|
* |
|
* {{{ |
|
* val tryV: Try[PositiveInt] = PositiveInt.from(42) |
|
* }}} |
|
*/ |
|
def from(value: InnerType): Try[WrappedType] = { |
|
validate(value) match { |
|
case Some(message) => Failure(new IllegalArgumentException(message) with NoStackTrace) |
|
case None => Success(construct(value)) |
|
} |
|
} |
|
|
|
/** |
|
* Support extracting the `value` from a wrapped type. |
|
* |
|
* {{{ |
|
* PositiveInt(5) match { |
|
* case PositiveInt(v) => println(v) // prints 5 |
|
* } |
|
* }}} |
|
*/ |
|
def unapply(wrapped: WrappedType): Option[InnerType] = Some(wrapped.value) |
|
|
|
/** |
|
* Provides an implicit ordering for the WrappedType iff InnerType supports an implicit ordering |
|
*/ |
|
implicit def ordering(implicit ord: Ordering[InnerType]): Ordering[WrappedType] = new WrappedOrdering(ord) |
|
|
|
class WrappedOrdering(ord: Ordering[InnerType]) extends Ordering[WrappedType] { |
|
override def compare(x: WrappedType, y: WrappedType): Int = ord.compare(x.value, y.value) |
|
} |
|
|
|
/** |
|
* Provide an implicit ConfigReader iff the InnerType supports an implicit ConfigReader |
|
*/ |
|
implicit def configReader(implicit reader: ConfigReader[InnerType]): ConfigReader[WrappedType] = new WrappedConfigReader(reader) |
|
|
|
class WrappedConfigReader(reader: ConfigReader[InnerType]) extends ConfigReader[WrappedType] { |
|
def tryParse(value: String): Try[WrappedType] = { |
|
reader.tryParse(value) flatMap (inner => Try(apply(inner))) |
|
} |
|
} |
|
} |
|
} |