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

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

There is a newer version: 3.5.0
Show 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 common._


/**
 * A container that contains a calculated value
 * or may contain one in the future
 */
class LAFuture[T](val scheduler: LAScheduler) {
  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

  def this() {
    this(LAScheduler)
  }

  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 ret = toDo
          toDo = Nil
          onFailure = Nil
          onComplete.foreach(f => LAFuture.executeWithObservers(scheduler, () => f(Full(value))))
          onComplete = Nil
          ret
        } 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 ret = new LAFuture[A](scheduler)
    onComplete(v => ret.complete(v.flatMap(n => Box.tryo(f(n)))))
    ret
  }

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

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

  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
      }
    }
  }

  /**
   * Has the future been satisfied
   */
  def isSatisfied: Boolean = synchronized {satisfied}

  /**
   * Has the future been aborted
   */
  def isAborted: Boolean = synchronized {aborted}

  /**
   * 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) {
    synchronized {
      if (satisfied) {LAFuture.executeWithObservers(scheduler, () => f(item))} else
      if (!aborted) {
        toDo ::= f
      }
    }
  }

  /**
   * 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) {
    synchronized {
      if (aborted) LAFuture.executeWithObservers(scheduler, () => f(failure)) else
      if (!satisfied) {
        onFailure ::= f
      }
    }
  }

  /**
   * 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) {
    synchronized {
      if (satisfied) {LAFuture.executeWithObservers(scheduler, () => f(Full(item)))} else
      if (aborted) {LAFuture.executeWithObservers(scheduler, () => f(failure))} else
      onComplete ::= f
    }
  }

  /**
   * 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()
      }
    }
  }

  /**
   * Has the future completed?
   */
  def complete_? : Boolean = synchronized(satisfied || aborted)
}

/**
 * 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): LAFuture[T] = {
    val ret = new LAFuture[T](scheduler)
    scheduler.execute(() => {
      try {
      ret.satisfy(f())
      } catch {
        case e: Exception => ret.fail(e)
      }
    })
    ret
  }

  /**
   * 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): LAFuture[T] = {
    this.apply(() => f, scheduler)
  }

  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)
    }
  }


  /**
   * 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]] = {
    val ret = new LAFuture[List[T]]
    if (future.isEmpty) {
      ret.satisfy(Nil)
    } else {
      val sync = new Object
      val len = future.length
      val vals = new collection.mutable.ArrayBuffer[Box[T]](len)
       // pad array so inserts at random places are possible
      for (i <- 0 to len) { vals.insert(i, Empty) }
      var gotCnt = 0

      future.toList.zipWithIndex.foreach {
        case (f, idx) =>
          f.foreach {
            v => sync.synchronized {
              vals.insert(idx, Full(v))
              gotCnt += 1
              if (gotCnt >= len) {
                ret.satisfy(vals.toList.flatten)
              }
            }
          }
      }
    }

    ret
  }

  /**
   * 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]]] = {
    val ret = new LAFuture[Box[List[T]]]
    if (future.isEmpty) {
      ret.satisfy(Full(Nil))
    } else {
      val sync = new Object
      val len = future.length
      val vals = new collection.mutable.ArrayBuffer[Box[T]](len)
      // pad array so inserts at random places are possible
      for (i <- 0 to len) { vals.insert(i, Empty) }
      var gotCnt = 0

      future.toList.zipWithIndex.foreach {
        case (f, idx) =>
          f.foreach {
            vb => sync.synchronized {
              vb match {
                case Full(v) => {
                  vals.insert(idx, Full(v))
                  gotCnt += 1
                  if (gotCnt >= len) {
                    ret.satisfy(Full(vals.toList.flatten))
                  }
                }

                case eb: EmptyBox => {
                  ret.satisfy(eb)
                }
              }
            }
          }
      }
    }

    ret
  }
}





© 2015 - 2025 Weber Informatics LLC | Privacy Policy