001/*
002 * Copyright (c) 2012-2017 Institut National des Sciences Appliquées de Lyon (INSA-Lyon)
003 *
004 * All rights reserved. This program and the accompanying materials
005 * are made available under the terms of the Eclipse Public License v1.0
006 * which accompanies this distribution, and is available at
007 * http://www.eclipse.org/legal/epl-v10.html
008 */
009
010package gololang.concurrent.async;
011
012import java.util.HashSet;
013
014/**
015 * A promise object is used to abstract over possibly asynchronous computations.
016 *
017 * You should consult the "golodoc" of the {@code gololang.Async} module.
018 *
019 * @see gololang.concurrent.async.Future
020 * @see gololang.concurrent.async.AssignedFuture
021 */
022public final class Promise {
023
024  private volatile boolean resolved = false;
025  private volatile Object value;
026
027  private final Object lock = new Object();
028  private final HashSet<Future.Observer> setObservers = new HashSet<>();
029  private final HashSet<Future.Observer> failObservers = new HashSet<>();
030
031  /**
032   * Checks whether the promise has been resolved.
033   *
034   * @return {@code true} if it has been resolved, {@code false} otherwise.
035   */
036  public boolean isResolved() {
037    return resolved;
038  }
039
040  /**
041   * Checks whether the promise has failed.
042   *
043   * @return {@code true} if it has been resolved and failed, {@code false} otherwise.
044   */
045  public boolean isFailed() {
046    return value instanceof Throwable;
047  }
048
049  /**
050   * Non-blocking get.
051   *
052   * @return the promise value, which may be {@code null} if it has not been resolved yet.
053   */
054  public Object get() {
055    return value;
056  }
057
058  /**
059   * Blocking get, waiting until the promise is resolved.
060   *
061   * @return the promise value.
062   * @throws InterruptedException if the current thread gets interrupted.
063   */
064  public Object blockingGet() throws InterruptedException {
065    synchronized (lock) {
066      while (!resolved) {
067        lock.wait();
068      }
069      return value;
070    }
071  }
072
073  /**
074   * Sets the promise value. This has no effect if the promise has already been resolved.
075   *
076   * @param value the value.
077   * @return this promise.
078   */
079  public Promise set(Object value) {
080    if (resolved) {
081      return this;
082    }
083    synchronized (lock) {
084      if (!resolved) {
085        this.value = value;
086        this.resolved = true;
087        lock.notifyAll();
088      }
089    }
090    HashSet<Future.Observer> observers = isFailed() ? failObservers : setObservers;
091    for (Future.Observer observer : observers) {
092      observer.apply(value);
093    }
094    return this;
095  }
096
097  /**
098   * Fails the promise. This has no effect if the promise has already been resolved.
099   *
100   * @param throwable the failure.
101   * @return this promise.
102   */
103  public Promise fail(Throwable throwable) {
104    return set(throwable);
105  }
106
107  /**
108   * Creates a new future to observe the eventual resolution of this promise.
109   *
110   * @return a new future object.
111   */
112  public Future future() {
113    return new Future() {
114      @Override
115      public Object get() {
116        return Promise.this.get();
117      }
118
119      @Override
120      public Object blockingGet() throws InterruptedException {
121        return Promise.this.blockingGet();
122      }
123
124      @Override
125      public boolean isResolved() {
126        return Promise.this.isResolved();
127      }
128
129      @Override
130      public boolean isFailed() {
131        return Promise.this.isFailed();
132      }
133
134      @Override
135      public Future onSet(Observer observer) {
136        synchronized (lock) {
137          if (resolved && !Promise.this.isFailed()) {
138            observer.apply(value);
139          } else {
140            setObservers.add(observer);
141          }
142        }
143        return this;
144      }
145
146      @Override
147      public Future onFail(Observer observer) {
148        synchronized (lock) {
149          if (resolved && Promise.this.isFailed()) {
150            observer.apply(value);
151          } else {
152            failObservers.add(observer);
153          }
154        }
155        return this;
156      }
157    };
158  }
159
160  @Override
161  public String toString() {
162    return "Promise{" +
163        "resolved=" + resolved +
164        ", value=" + value +
165        '}';
166  }
167}