1 package cz.cuni.amis.utils.future;
2
3 import java.util.HashMap;
4 import java.util.Map;
5 import java.util.concurrent.CountDownLatch;
6 import java.util.concurrent.TimeUnit;
7
8 import cz.cuni.amis.utils.exception.PogamutInterruptedException;
9 import cz.cuni.amis.utils.flag.Flag;
10 import cz.cuni.amis.utils.flag.FlagListener;
11
12
13 /**
14 * Future implementation that is listening on a flag and when it's terminal state
15 * is set on the flag the future completes itself with result specified in the map
16 * (under the key of the flag value).
17 * <p><p>
18 * Thread-safe implementation, not as easy as it seems ;-)
19 *
20 * @author Jimmy
21 *
22 * @param <Result>
23 * @param <FlagType>
24 */
25 public class FlagFuture<Result, FlagType> implements IFuture<Result>, FlagListener<FlagType>{
26
27 /**
28 * Mapping flag value -> results, if a value of the flag is not listed here then the
29 * object ignores that flag value.
30 */
31 private Map<FlagType, Result> terminalMap;
32
33 /**
34 * Where to listen for values.
35 */
36 private Flag<FlagType> waitFlag;
37
38 /**
39 * Latch where the users of this object is waiting for the future to complete.
40 */
41 private final CountDownLatch latch = new CountDownLatch(1);
42
43 /**
44 * Whether the future is done - the future is done when the flag reaches one of
45 * the terminal states from the map terminalMap.
46 */
47 private boolean done = false;
48
49 /**
50 * Result of the future - when the flag reaches one of the terminal value (as defined
51 * in terminalMap) according result (from the map) is written here and 'done' is set to true.
52 */
53 private Result result = null;
54
55 /**
56 * In constructor you have to specify a flag where the future should listen at + terminal
57 * states for the future (terminalMap).
58 * <p><p>
59 * Possible flag values and the behavior of the future:
60 * <ol>
61 * <li>flag value is in the keys of terminalMap - the future completes with the result under the key from the flag</li>
62 * <li>flag value <b>is not</b> in the keys of terminalMap - the future ignores that value and waits for next flag change</li>
63 * </ol>
64 * <p>
65 * Note that the flag value is examined during the construction of the object and if the
66 * flag has value that is in the terminalMap the future is completes itself.
67 *
68 * @param waitFlag
69 * @param terminalMap
70 */
71 public FlagFuture(Flag<FlagType> waitFlag, Map<FlagType, Result> terminalMap) {
72 this.terminalMap = new HashMap<FlagType, Result>(terminalMap);
73 this.waitFlag = waitFlag;
74 init();
75 }
76
77 /**
78 * Initializing future to wait for 'terminalFlagValue' at 'waitFlag', when that happens complete
79 * itself with result 'resultValue'.
80 * @param waitFlag
81 * @param terminalFlagValue
82 * @param resultValue
83 */
84 public FlagFuture(Flag<FlagType> waitFlag, FlagType terminalFlagValue, Result resultValue) {
85 terminalMap = new HashMap<FlagType, Result>();
86 terminalMap.put(terminalFlagValue, resultValue);
87 this.waitFlag = waitFlag;
88 init();
89 }
90
91 /**
92 * Initialize the listener to a flag + checking the current value of the flag.
93 * <p><p>
94 * Called from constructor.
95 */
96 private void init() {
97 waitFlag.addListener(this);
98 synchronized(latch) {
99 if (!done) {
100 FlagType value = waitFlag.getFlag();
101 if (terminalMap.containsKey(value)) {
102 result = terminalMap.get(value);
103 done = true;
104 latch.countDown();
105 }
106 }
107 }
108 if (done) waitFlag.removeListener(this);
109 }
110
111 /**
112 * Stops the future (not the task it represents!). It raise the latch
113 * unblocking all threads waiting for the future to complete. Sets the result
114 * to desired value.
115 * <p><p>
116 * Note that this happened IFF !isDone(), e.g. if the future is already done this
117 * won't do anything (as latch is already raised and the result value has been
118 * determined).
119 * <p><p>
120 * Note that this is different behavior that cancel() should implement, therefore
121 * it's a different method.
122 */
123 public void stop(Result result) {
124 synchronized(latch) {
125 if (done) return;
126 done = true;
127 this.result = result;
128 }
129 waitFlag.removeListener(this);
130 latch.countDown();
131 }
132
133 @Override
134 public boolean cancel(boolean mayInterruptIfRunning) {
135 // can't cancel
136 return false;
137 }
138
139 @Override
140 public Result get() {
141 try {
142 latch.await();
143 } catch (InterruptedException e) {
144 throw new PogamutInterruptedException(e.getMessage(), e, this);
145 }
146 return result;
147 }
148
149 @Override
150 public Result get(long timeout, TimeUnit unit) {
151 try {
152 latch.await(timeout, unit);
153 } catch (InterruptedException e) {
154 throw new PogamutInterruptedException(e.getMessage(), e, this);
155 }
156 return result;
157 }
158
159 @Override
160 public boolean isCancelled() {
161 return false;
162 }
163
164 @Override
165 public boolean isDone() {
166 return done;
167 }
168
169 @Override
170 public void flagChanged(FlagType changedValue) {
171 synchronized(latch) {
172 if (done) {
173 waitFlag.removeListener(this);
174 return;
175 }
176 if (terminalMap.containsKey(changedValue)) {
177 result = terminalMap.get(changedValue);
178 done = true;
179 waitFlag.removeListener(this);
180 latch.countDown();
181 }
182 }
183 }
184 }