All Downloads are FREE. Search and download functionalities are using the official Maven repository.

net.liftweb.actor.LAFuture.scala Maven / Gradle / Ivy

The newest version!
/*
 * Copyright 2009-2011 WorldWide Conferencing, LLC
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package net.liftweb
package actor

import scala.collection.mutable.ArrayBuffer

import common._


/**
 * A container that contains a calculated value
 * or may contain one in the future
 */
class LAFuture[T](val scheduler: LAScheduler = LAScheduler, context: Box[LAFuture.Context] = Empty) {
  private var item: T = _
  private var failure: Box[Nothing] = Empty
  private var satisfied = false
  private var aborted = false
  private var toDo: List[T => Unit] = Nil
  private var onFailure: List[Box[Nothing] => Unit] = Nil
  private var onComplete: List[Box[T] => Unit] = Nil

  LAFuture.notifyObservers(this)

  /**
   * Satify the future... perform the calculation
   * the results in setting a value for the future
   */
  def satisfy(value: T): Unit = {
    val funcs = synchronized {
      try {
        if (!satisfied && !aborted) {
          item = value
          satisfied = true
          val result = toDo
          toDo = Nil
          onFailure = Nil
          onComplete.foreach(f => LAFuture.executeWithObservers(scheduler, () => f(Full(value))))
          onComplete = Nil
          result
        } else Nil
      } finally {
        notifyAll()
      }
    }
    funcs.foreach(f => LAFuture.executeWithObservers(scheduler, () => f(value)))
  }

  /**
   * Complete the Future... with a Box... useful from Helpers.tryo
   * @param value
   */
  def complete(value: Box[T]): Unit = {
      value match {
        case Full(v) => satisfy(v)
        case x: EmptyBox => fail(x)
      }
  }

  /**
   * Get the future value
   */
  @scala.annotation.tailrec
  final def get: T = synchronized {
    if (satisfied) item
    else if (aborted) throw new AbortedFutureException(failure)
    else {
      this.wait()
      if (satisfied) item
      else if (aborted) throw new AbortedFutureException(failure)
      else get
    }
  }

  /**
   * Execute the function with the value. If the
   * value has not been satisfied, execute the function
   * when the value is satified
   */
  def foreach(f: T => Unit) {
    onSuccess(f)
  }

  /**
   * Map the future over a function
   * @param f the function to apply to the future
   * @tparam A the type that the function returns
   * @return a Future that represents the function applied to the value of the future
   */
  def map[A](f: T => A): LAFuture[A] = {
    val result = new LAFuture[A](scheduler, context)
    val contextFn = LAFuture.inContext(f, context)
    onComplete(v => result.complete(v.flatMap(n => Box.tryo(contextFn(n)))))
    result
  }

  def flatMap[A](f: T => LAFuture[A]): LAFuture[A] = {
    val result = new LAFuture[A](scheduler, context)
    val contextFn = LAFuture.inContext(f, context)
    onComplete(v => v match {
      case Full(v) =>
        Box.tryo(contextFn(v)) match {
          case Full(successfullyComputedFuture) =>
            successfullyComputedFuture.onComplete(v2 => result.complete(v2))
          case e: EmptyBox =>
            result.complete(e)
        }
      case e: EmptyBox =>
        result.complete(e)
    })
    result
  }

  def filter(f: T => Boolean): LAFuture[T] = {
    val result = new LAFuture[T](scheduler, context)
    onComplete(v => result.complete(v.filter(f)))
    result
  }

  def withFilter(f: T => Boolean): LAFuture[T] = filter(f)

  /**
   * Get the future value or if the value is not
   * satisfied after the timeout period, return an
   * Empty
   */
  def get(timeout: Long): Box[T] = synchronized {
    if (satisfied) Full(item)
    else if (aborted) failure
    else {
      try {
        wait(timeout)
        if (satisfied) Full(item)
        else if (aborted) failure
        else Empty
      } catch {
        case _: InterruptedException => Empty
      }
    }
  }

  /**
   * Java-friendly alias for satisfied_?.
   */
  def isSatisfied: Boolean = satisfied_?

  /**
   * Has the future been satisfied
   */
  def satisfied_? = synchronized {satisfied}
  /**
   * Java-friendly alias for aborted_?.
   */
  def isAborted: Boolean = aborted_?

  /**
   * Has the future been aborted
   */
  def aborted_? = synchronized {aborted}

  /**
   * Java-friendly alias for completed_?.
   */
  def isCompleted: Boolean = completed_?
  /**
   * Has the future completed?
   */
  def completed_? : Boolean = synchronized(satisfied || aborted)

  @deprecated("Please use completed_? instead.", "3.1.0")
  def complete_? : Boolean = completed_?

  /**
   * Abort the future.  It can never be satified
   */
  def abort() {
    fail(Empty)
  }

  /**
   * Execute the function on success of the future
   *
   * @param f the function to execute on success.
   */
  def onSuccess(f: T => Unit) {
    val contextFn = LAFuture.inContext(f, context)
    synchronized {
      if (satisfied) {LAFuture.executeWithObservers(scheduler, () => contextFn(item))} else
      if (!aborted) {
        toDo ::= contextFn
      }
    }
  }

  /**
   * Execute a function on failure
   *
   * @param f the function to execute. Will receive a Box[Nothing] which may be a Failure if there's exception data
   */
  def onFail(f: Box[Nothing] => Unit) {
    val contextFn = LAFuture.inContext(f, context)
    synchronized {
      if (aborted) LAFuture.executeWithObservers(scheduler, () => contextFn(failure)) else
      if (!satisfied) {
        onFailure ::= contextFn
      }
    }
  }

  /**
   * A function to execute on completion of the Future, success or failure
   *
   * @param f the function to execute on completion of the Future
   */
  def onComplete(f: Box[T] => Unit) {
    val contextFn = LAFuture.inContext(f, context)
    synchronized {
      if (satisfied) {LAFuture.executeWithObservers(scheduler, () => contextFn(Full(item)))} else
      if (aborted) {LAFuture.executeWithObservers(scheduler, () => contextFn(failure))} else
      onComplete ::= contextFn
    }
  }

  /**
   * If the execution fails, do this
   * @param e
   */
  def fail(e: Exception) {
    fail(Failure(e.getMessage, Full(e), Empty))
  }

  /**
   * If the execution fails as a Box[Nothing], do this
   * @param e
   */
  def fail(e: Box[Nothing]) {
    synchronized {
      if (!satisfied && !aborted) {
        aborted = true
        failure = e
        onFailure.foreach(f => LAFuture.executeWithObservers(scheduler, () => f(e)))
        onComplete.foreach(f => LAFuture.executeWithObservers(scheduler, () => f(e)))
        onComplete = Nil
        onFailure = Nil
        toDo = Nil

        notifyAll()
      }
    }
  }
}

/**
 * Thrown if an LAFuture is aborted during a get
 */
final class AbortedFutureException(why: Box[Nothing]) extends Exception("Aborted Future")

object LAFuture {
  /**
   * Create an LAFuture from a function that
   * will be applied on a separate thread. The LAFuture
   * is returned immediately and the value may be obtained
   * by calling `get`
   *
   * @param f the function that computes the value of the future
   * @tparam T the type
   * @return an LAFuture that will yield its value when the value has been computed
   */
  def apply[T](f: () => T, scheduler: LAScheduler = LAScheduler, context: Box[Context] = Empty): LAFuture[T] = {
    val result = new LAFuture[T](scheduler, context)
    val contextFn = inContext(f, context)
    scheduler.execute(() => {
      try {
        result.satisfy(contextFn())
      } catch {
        case e: Exception => result.fail(e)
      }
    })
    result
  }

  /**
   * Build a new future with a call-by-name value that returns a type T
   * @param f the call-by-name code the defines the future
   * @tparam T the type that
   * @return
   */
  def build[T](f: => T, scheduler: LAScheduler = LAScheduler, context: Box[Context] = Empty): LAFuture[T] = {
    this.apply(() => f, scheduler, context)
  }

  private val threadInfo = new ThreadLocal[List[LAFuture[_] => Unit]]

  /**
   * Notify all the observers that we created a future.
   *
   * @param future
   */
  private def notifyObservers(future: LAFuture[_]) {
    val observers = threadInfo.get()
    if (null eq observers) {} else {
      observers.foreach(_(future))
    }
  }

  private def executeWithObservers(scheduler: LAScheduler, f: () => Unit) {
    val cur = threadInfo.get()
    scheduler.execute(() => {
      val old = threadInfo.get()
      threadInfo.set(cur)
      try {
        f()
      } finally {
        threadInfo.set(old)
      }
    })
  }

  /**
   * Do something when a future is created on this thread. This can be used
   * to see if there's any Future activity on a thread and if there is,
   * we can do smart things on an observing thread.
   *
   * @param observation the function to execute on Future creation
   * @param toDo the action call-by-name code to execute whi
   * @tparam T the type of the value returned by toDo
   * @return the value computed by toDo
   */
  def observeCreation[T](observation: LAFuture[_] => Unit)(toDo: => T): T = {
    val old = threadInfo.get()
    threadInfo.set(if (null eq old) List(observation) else observation :: old)
    try {
      toDo
    } finally {
      threadInfo.set(old)
    }
  }

  /**
   * Given handlers for a value's success and failure and a set of futures, runs
   * the futures simultaneously and invokes either success or failure callbacks
   * as each future completes. When all futures are complete, if the handlers
   * have not either satisfied or failed the overall result, `onAllFuturesCompleted`
   * is called to complete it. If it *still* isn't complete, the overall result
   * is failed with an error.
   *
   * Note that the success and failure functions are guaranteed to be run in a
   * thread-safe manner. Each is passed the value, the result future, the
   * accumulating `ArrayBuffer`, and the index of the future that has been
   * completed. For the failure handler, the value is the `Box` of the failure.
   */
  def collect[T, A](
    onFutureSucceeded: (T, LAFuture[A], ArrayBuffer[Box[T]], Int)=>Unit,
    onFutureFailed: (Box[Nothing], LAFuture[A], ArrayBuffer[Box[T]], Int)=>Unit,
    onAllFuturesCompleted: (LAFuture[A], ArrayBuffer[Box[T]])=>Unit,
    futures: LAFuture[T]*
  ): LAFuture[A] = {
    val result = new LAFuture[A]

    if (futures.isEmpty) {
      onAllFuturesCompleted(result, new ArrayBuffer[Box[T]](0))
    } else {
      val sync = new Object
      val len = futures.length
      val accumulator = new ArrayBuffer[Box[T]](len)
       // pad array so inserts at random places are possible
      for (i <- 0 to len) { accumulator.insert(i, Empty) }
      var gotCnt = 0

      futures.toList.zipWithIndex.foreach {
        case (future, index) =>
          future.onSuccess {
            value => sync.synchronized {
              gotCnt += 1
              onFutureSucceeded(value, result, accumulator, index)

              if (gotCnt >= len && ! result.completed_?) {
                onAllFuturesCompleted(result, accumulator)

                if (! result.completed_?) {
                  result.fail(Failure("collect invoker did not complete result"))
                }
              }
            }
          }
          future.onFail {
            failureBox => sync.synchronized {
              gotCnt += 1
              onFutureFailed(failureBox, result, accumulator, index)

              if (gotCnt >= len && ! result.completed_?) {
                onAllFuturesCompleted(result, accumulator)

                if (! result.completed_?) {
                  result.fail(Failure("collect invoker did not complete result"))
                }
              }
            }
          }
      }
    }

    result
  }


  /**
   * Collect all the future values into the aggregate future
   * The returned future will be satisfied when all the
   * collected futures are satisfied
   */
  def collect[T](future: LAFuture[T]*): LAFuture[List[T]] = {
    collect[T, List[T]](
      onFutureSucceeded = { (value, result, values, index) =>
        values.insert(index, Full(value))
      },
      onFutureFailed = { (valueBox, result, values, index) => result.fail(valueBox) },
      onAllFuturesCompleted = { (result, values) => result.satisfy(values.toList.flatten) },
      future: _*
    )
  }

  /**
   * Collect all the future values into the aggregate future
   * The returned future will be satisfied when all the
   * collected futures are satisfied or if any of the
   * futures is Empty, then immediately satisfy the
   * returned future with an Empty
   */
  def collectAll[T](future: LAFuture[Box[T]]*): LAFuture[Box[List[T]]] = {
    collect[Box[T], Box[List[T]]](
      onFutureSucceeded = { (value, result, values, index) =>
        value match {
          case Full(realValue) =>
            values.insert(index, Full(Full(realValue)))
          case other: EmptyBox =>
            result.satisfy(other)
        }
      },
      onFutureFailed = { (valueBox, result, values, index) => result.fail(valueBox) },
      onAllFuturesCompleted = { (result: LAFuture[Box[List[T]]], values: ArrayBuffer[Box[Box[T]]]) =>
        result.satisfy(Full(values.toList.flatten.flatten))
      },
      future: _*
    )
  }

  private def inContext[T](f: () => T, context: Box[LAFuture.Context]): () => T = {
    context.map(_.around(f)) openOr f
  }

  private def inContext[A, T](f: (A) => T, context: Box[LAFuture.Context]): (A) => T = {
    context.map(_.around(f)) openOr f
  }

  /**
    * Allows to wrap function in another function providing some additional 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. Typical example is setting up DB
    * transaction.
    *
    * This is similar to [[net.liftweb.common.CommonLoanWrapper]], however, it decorates the
    * function eagerly. This way, you can access current thread's state which is essential
    * to do things like set up a HTTP session wrapper
    */
  trait Context {
    def around[T](fn: () => T): () => T
    def around[A, T](fn: (A) => T): (A) => T
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy