View Javadoc

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 -&gt; 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 }