summaryrefslogtreecommitdiff
path: root/src/interactive/scala/tools/nsc/interactive/Response.scala
blob: 3e84c83e55b32d45ca33550e93aca823eed1fe0a (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
/* NSC -- new Scala compiler
 * Copyright 2009-2013 Typesafe/Scala Solutions and LAMP/EPFL
 * @author Martin Odersky
 */
package scala.tools.nsc
package interactive

/** Typical interaction, given a predicate <user-input>, a function <display>,
 *  and an exception handler <handle>:
 *
 *  val TIMEOUT = 100 // (milliseconds) or something like that
 *  val r = new Response()
 *  while (!r.isComplete && !r.isCancelled) {
 *    if (<user-input>) r.cancel()
 *    else r.get(TIMEOUT) match {
 *      case Some(Left(data)) => <display>(data)
 *      case Some(Right(exc)) => <handle>(exc)
 *      case None =>
 *    }
 *  }
 */
class Response[T] {

  private var data: Option[Either[T, Throwable]] = None
  private var complete = false
  private var cancelled = false

  /** Set provisional data, more to come
   */
  def setProvisionally(x: T) = synchronized {
    data = Some(Left(x))
  }

  /** Set final data, and mark response as complete.
   */
  def set(x: T) = synchronized {
    data = Some(Left(x))
    complete = true
    notifyAll()
  }

  /** Store raised exception in data, and mark response as complete.
   */
  def raise(exc: Throwable) = synchronized {
    data = Some(Right(exc))
    complete = true
    notifyAll()
  }

  /** Get final data, wait as long as necessary.
   *  When interrupted will return with Right(InterruptedException)
   */
  def get: Either[T, Throwable] = synchronized {
    while (!complete) {
      try {
        wait()
      } catch {
        case exc: InterruptedException => {
          Thread.currentThread().interrupt()
          raise(exc)
        }
      }
    }
    data.get
  }

  /** Optionally get data within `timeout` milliseconds.
   *  When interrupted will return with Some(Right(InterruptedException))
   *  When timeout ends, will return last stored provisional result,
   *  or else None if no provisional result was stored.
   */
  def get(timeout: Long): Option[Either[T, Throwable]] = synchronized {
    val start = System.currentTimeMillis
    var current = start
    while (!complete && start + timeout > current) {
      try {
        wait(timeout - (current - start))
      } catch {
        case exc: InterruptedException => {
          Thread.currentThread().interrupt()
          raise(exc)
        }
      }
      current = System.currentTimeMillis
    }
    data
  }

  /** Final data set was stored
   */
  def isComplete = synchronized { complete }

  /** Cancel action computing this response (Only the
   *  party that calls get on a response may cancel).
   */
  def cancel() = synchronized { cancelled = true }

  /** A cancel request for this response has been issued
   */
  def isCancelled = synchronized { cancelled }

  def clear() = synchronized {
    data = None
    complete = false
    cancelled = false
  }
}