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