com.twitter.finagle.Stack.scala Maven / Gradle / Ivy
The newest version!
package com.twitter.finagle
import scala.collection.mutable
import scala.collection.immutable
/**
* Stacks represent stackable elements of type T. It is assumed that
* T-typed elements can be stacked in some meaningful way; examples
* are functions (function composition) [[com.twitter.finagle.Filter
* Filters]] (chaining), and [[com.twitter.finagle.ServiceFactory
* ServiceFactories]] (through transformers). T-typed values are also
* meant to compose: the stack itself materializes into a T-typed
* value.
*
* Stacks are persistent, allowing for nondestructive
* transformations; they are designed to represent 'template' stacks
* which can be configured in various ways before materializing the
* stack itself.
*/
private[finagle]
sealed trait Stack[T] {
import Stack._
/**
* The head field of the Stack composes all associated metadata
* of the topmost element of the stack.
* @note `head` does not give access to the value `T`, use `make` instead
* @see [[com.twitter.finagle.Stack.Head]]
*/
val head: Stack.Head
/**
* Materialize the current stack with the given parameters,
* producing a `T`-typed value representing the current
* configuration.
*/
def make(params: Params): T
/**
* Transform one stack to another by applying `fn` on each element;
* the map traverses on the element produced by `fn`, not the
* original stack.
*/
def transform(fn: Stack[T] => Stack[T]): Stack[T] =
fn(this) match {
case Node(head, mk, next) => Node(head, mk, next.transform(fn))
case leaf@Leaf(_, _) => leaf
}
/**
* Remove all nodes in the stack that match the `target` role.
* Leaf nodes are not removable.
*/
def remove(target: Role): Stack[T] =
this match {
case Node(head, mk, next) =>
if (head.role == target) next.remove(target)
else Node(head, mk, next.remove(target))
case leaf@Leaf(_, _) => leaf
}
/**
* Replace any stack elements matching the argument role with a given
* [[com.twitter.finagle.Stackable Stackable]]. If no elements match the
* role, then an unmodified stack is returned.
*/
def replace(target: Role, replacement: Stackable[T]): Stack[T] = transform {
case n@Node(head, _, next) if head.role == target =>
replacement +: next
case stk => stk
}
/**
* Replace any stack elements matching the argument role with a given
* [[com.twitter.finagle.Stackable]]. If no elements match the
* role, then an unmodified stack is returned. `replacement` must conform to
* typeclass [[com.twitter.finagle.CanStackFrom]].
*/
def replace[U](target: Role, replacement: U)(implicit csf: CanStackFrom[U, T]): Stack[T] =
replace(target, csf.toStackable(target, replacement))
/**
* Traverse the stack, invoking `fn` on each element.
*/
def foreach(fn: Stack[T] => Unit) {
fn(this)
this match {
case Node(_, _, next) => next.foreach(fn)
case Leaf(_, _) =>
}
}
/**
* Enumerate each well-formed stack contained within this stack.
*/
def tails: Iterator[Stack[T]] = {
val buf = new mutable.ArrayBuffer[Stack[T]]
foreach { buf += _ }
buf.toIterator
}
/**
* Produce a new stack representing the concatenation of `this`
* with `right`. Note that this replaces the terminating element of
* `this`.
*/
def ++(right: Stack[T]): Stack[T] = this match {
case Node(head, mk, left) => Node(head, mk, left++right)
case Leaf(_, _) => right
}
/**
* A copy of this Stack with `stk` prepended.
*/
def +:(stk: Stackable[T]): Stack[T] =
stk.toStack(this)
override def toString = {
val elems = tails map {
case Node(head, mk, _) => "Node(role = %s, description = %s)".format(head.role, head.description)
case Leaf(head, t) => "Leaf(role = %s, description = %s)".format(head.role, head.description)
}
elems mkString "\n"
}
}
private[finagle] object Stack {
/**
* Base trait for Stack roles. A stack's role is indicative of its
* functionality. Roles provide a way to group similarly-purposed stacks and
* slot stack elements into specific usages.
*
* TODO: CSL-869
*/
case class Role(val name: String) {
// Override `toString` to return the flat, lowercase object name for use in stats.
private[this] lazy val _toString = name.toLowerCase
override def toString = _toString
}
/**
* Trait encompassing all associated metadata of a stack element.
* [[com.twitter.finagle.Stackable Stackables]] extend this trait.
*/
trait Head {
/**
* The [[com.twitter.finagle.Stack.Role Role]] that the element can serve
*/
val role: Stack.Role
/**
* The description of the functionality of the element
*/
val description: String
/**
* The [[com.twitter.finagle.Stack.Param Params]] used to configure the element
*/
def params: Map[String, String]
}
/**
* Nodes materialize by transforming the underlying stack in
* some way.
*/
case class Node[T](head: Stack.Head, mk: (Params, Stack[T]) => Stack[T], next: Stack[T])
extends Stack[T]
{
def make(params: Params) = mk(params, next).make(params)
}
object Node {
/**
* A constructor for a 'simple' Node.
*/
def apply[T](head: Stack.Head, mk: T => T, next: Stack[T]): Node[T] =
Node(head, (p, stk) => Leaf(head, mk(stk.make(p))), next)
}
/**
* A static stack element; necessarily the last.
*/
case class Leaf[T](head: Stack.Head, t: T) extends Stack[T] {
def make(params: Params) = t
}
object Leaf {
/**
* If only a role is given when constructing a leaf, then the head
* is created automatically
*/
def apply[T](_role: Stack.Role, t: T): Leaf[T] = {
val head = new Stack.Head {
val role = _role
val description = _role.toString
val params = Map.empty[String, String]
}
Leaf(head, t)
}
}
/**
* A typeclass representing P-typed elements, eligible as
* parameters for stack configuration. Note that the typeclass
* instance itself is used as the key in parameter maps; thus
* typeclasses should be persistent:
*
* {{{
* case class Multiplier(i: Int)
* implicit object Multiplier extends Stack.Param[Multiplier] {
* val default = Multiplier(123)
* }
* }}}
*/
trait Param[P] {
def default: P
}
/**
* A parameter map.
*/
trait Params {
/**
* Get the current value of the P-typed parameter.
*/
def apply[P: Param]: P
/**
* Returns true if there is a non-default value for
* the P-typed parameter.
*/
def contains[P: Param]: Boolean
/**
* Produce a new parameter map, overriding any previous
* `P`-typed value.
*/
def +[P: Param](p: P): Params
}
object Params {
private case class Prms(map: Map[Param[_], Any]) extends Params {
def apply[P](implicit param: Param[P]): P =
map.get(param) match {
case Some(v) => v.asInstanceOf[P]
case None => param.default
}
def contains[P](implicit param: Param[P]): Boolean =
map.contains(param)
def +[P](p: P)(implicit param: Param[P]): Params =
copy(map + (param -> p))
}
/**
* The empty parameter map.
*/
val empty: Params = Prms(Map.empty)
}
/**
* A convenient class to construct stackable modules. This variant
* operates over stack values. Useful for building stack elements:
*
* {{{
* def myNode = new Simple[Int=>Int]("myelem") {
* def make(params: Params, next: Int=>Int): (Int=>Int) = {
* val Multiplied(m) = params[Multiplier]
* i => next(i*m)
* }
* }
* }}}
*/
abstract class Simple[T] extends Stackable[T] {
type Params = Stack.Params
def make(next: T)(implicit params: Params): T
def toStack(next: Stack[T]) = {
Node(this, (params, next) => Leaf(this, make(next.make(params))(params)), next)
}
}
/**
* A convenience class to construct stackable modules. This variant
* operates over stacks. Useful for building stack elements:
*
* {{{
* def myNode = new Module[Int=>Int]("myelem") {
* def make(params: Params, next: Stack[Int=>Int]): Stack[Int=>Int] = {
* val Multiplier(m) = params[Multiplier]
* if (m == 1) next // It's a no-op, skip it.
* else Stack.Leaf("multiply", i => next.make(params)(i)*m)
* }
* }
* }}}
*/
abstract class Module[T] extends Stackable[T] {
type Params = Stack.Params
def make(next: Stack[T])(implicit params: Params): Stack[T]
def toStack(next: Stack[T]) = {
Node(this, (params, next) => make(next)(params), next)
}
}
}
/**
* Produce a stack from a `T`-typed element.
*/
trait Stackable[T] extends Stack.Head {
private val _params = mutable.Map.empty[String, String]
def params: immutable.Map[String, String] = _params.toMap
def toStack(next: Stack[T]): Stack[T]
// Record the parameter names and values
private def register(paramVal: Product): Unit = {
// zip two lists, and pair any unmatched values with `padding`
def zipWithPadding[T](l1: List[T], l2: List[T], padding: T): List[(T, T)] =
(l1.length - l2.length) match {
case 0 => l1 zip l2
case x if x > 0 => l1 zip (l2 ++ List.fill(x)(padding))
case x if x < 0 => (l1 ++ List.fill(-x)(padding)) zip l2
}
val paramValues = paramVal.productIterator.toList.map(_.toString)
val paramNames = paramVal.getClass.getDeclaredFields().map(_.getName()).toList
zipWithPadding(paramNames, paramValues, "").map { case (k, v) => _params.put(k, v) }
}
// Using get[] when accessing parameters in Stackables causes
// the parameter name and value to be recorded in the params map of the
// Stackable.head. Per-module recorded parameters are shown by
// [[com.twitter.server.TwitterServer TwitterServer]] at the admin endpoint
// "/admin/clients/")
// TODO: Replace signature with equivalent:
// def get[P <: Product : Stack.Param](implicit params: Stack.Params): P
// Once upgraded to Scala 2.10 (2.9 does not support having both implicit
// parameters and context bounds)
protected def get[P <: Product](implicit param: Stack.Param[P], params: Stack.Params): P = {
val paramVal = params[P]
register(paramVal)
paramVal
}
}
/**
* A typeclass for "stackable" items. This is used by the
* [[com.twitter.finagle.StackBuilder StackBuilder]] to provide a
* convenient interface for constructing Stacks.
*/
@scala.annotation.implicitNotFound("${From} is not Stackable to ${To}")
trait CanStackFrom[-From, To] {
def toStackable(role: Stack.Role, el: From): Stackable[To]
}
object CanStackFrom {
implicit def fromFun[T]: CanStackFrom[T=>T, T] =
new CanStackFrom[T=>T, T] {
def toStackable(role: Stack.Role, fn: T => T): Stackable[T] = {
val test = role
new Stack.Simple[T] {
val role = test
val description = role.name
def make(next: T)(implicit params: Stack.Params) = fn(next)
}
}
}
}
/**
* StackBuilders are imperative-style builders for Stacks. It
* maintains a stack onto which new elements can be pushed (defining
* a new stack).
*/
class StackBuilder[T](init: Stack[T]) {
def this(role: Stack.Role, end: T) = this(Stack.Leaf(role, end))
private[this] var stack = init
/**
* Push the stack element `el` onto the stack; el must conform to
* typeclass [[com.twitter.finagle.CanStackFrom CanStackFrom]].
*/
def push[U](role: Stack.Role, el: U)(implicit csf: CanStackFrom[U, T]): this.type = {
stack = csf.toStackable(role, el) +: stack
this
}
/**
* Push a [[com.twitter.finagle.Stackable Stackable]] module onto
* the stack.
*/
def push(module: Stackable[T]): this.type = {
stack = module +: stack
this
}
/**
* Get the current stack as defined by the builder.
*/
def result: Stack[T] = stack
/**
* Materialize the current stack: equivalent to
* `result.make()`.
*/
def make(params: Stack.Params): T = result.make(params)
override def toString = "Builder(%s)".format(stack)
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy