Merge commit '087a6e8e7f1b0e63633831948e99ae110b92ae45'
[fanfix.git] / src / be / nikiroo / utils / Progress.java
1 package be.nikiroo.utils;
2
3 import java.util.ArrayList;
4 import java.util.EventListener;
5 import java.util.HashMap;
6 import java.util.List;
7 import java.util.Map;
8 import java.util.Map.Entry;
9
10 /**
11 * Progress reporting system, possibly nested.
12 *
13 * @author niki
14 */
15 public class Progress {
16 /**
17 * This event listener is designed to report progress events from
18 * {@link Progress}.
19 *
20 * @author niki
21 */
22 public interface ProgressListener extends EventListener {
23 /**
24 * A progression event.
25 *
26 * @param progress
27 * the {@link Progress} object that generated it, not
28 * necessarily the same as the one where the listener was
29 * attached (it could be a child {@link Progress} of this
30 * {@link Progress}).
31 * @param name
32 * the first non-null name of the {@link Progress} step that
33 * generated this event
34 */
35 public void progress(Progress progress, String name);
36 }
37
38 private Progress parent = null;
39 private Object lock = new Object();
40 private String name;
41 private Map<Progress, Double> children;
42 private List<ProgressListener> listeners;
43 private int min;
44 private int max;
45 private double relativeLocalProgress;
46 private double relativeProgress; // children included
47
48 /**
49 * Create a new default unnamed {@link Progress}, from 0 to 100.
50 */
51 public Progress() {
52 this(null);
53 }
54
55 /**
56 * Create a new default {@link Progress}, from 0 to 100.
57 *
58 * @param name
59 * the name of this {@link Progress} step
60 */
61 public Progress(String name) {
62 this(name, 0, 100);
63 }
64
65 /**
66 * Create a new unnamed {@link Progress}, from min to max.
67 *
68 * @param min
69 * the minimum progress value (and starting value) -- must be
70 * non-negative
71 * @param max
72 * the maximum progress value
73 */
74 public Progress(int min, int max) {
75 this(null, min, max);
76 }
77
78 /**
79 * Create a new {@link Progress}, from min to max.
80 *
81 * @param name
82 * the name of this {@link Progress} step
83 * @param min
84 * the minimum progress value (and starting value) -- must be
85 * non-negative
86 * @param max
87 * the maximum progress value
88 */
89 public Progress(String name, int min, int max) {
90 this.name = name;
91 this.children = new HashMap<Progress, Double>();
92 this.listeners = new ArrayList<Progress.ProgressListener>();
93 setMinMax(min, max);
94 setProgress(min);
95 }
96
97 /**
98 * The name of this {@link Progress} step.
99 *
100 * @return the name
101 */
102 public String getName() {
103 return name;
104 }
105
106 /**
107 * The name of this {@link Progress} step.
108 *
109 * @param name
110 * the new name
111 */
112 public void setName(String name) {
113 this.name = name;
114 changed(this, name);
115 }
116
117 /**
118 * The minimum progress value.
119 *
120 * @return the min
121 */
122 public int getMin() {
123 return min;
124 }
125
126 /**
127 * The minimum progress value.
128 *
129 * @param min
130 * the min to set
131 *
132 *
133 * @throws RuntimeException
134 * if min &lt; 0 or if min &gt; max
135 */
136 public void setMin(int min) {
137 if (min < 0) {
138 throw new RuntimeException("negative values not supported");
139 }
140
141 synchronized (lock) {
142 if (min > max) {
143 throw new RuntimeException(
144 "The minimum progress value must be <= the maximum progress value");
145 }
146
147 this.min = min;
148 }
149 }
150
151 /**
152 * The maximum progress value.
153 *
154 * @return the max
155 */
156 public int getMax() {
157 return max;
158 }
159
160 /**
161 * The maximum progress value (must be >= the minimum progress value).
162 *
163 * @param max
164 * the max to set
165 *
166 *
167 * @throws RuntimeException
168 * if max &lt; min
169 */
170 public void setMax(int max) {
171 synchronized (lock) {
172 if (max < min) {
173 throw new Error(
174 "The maximum progress value must be >= the minimum progress value");
175 }
176
177 this.max = max;
178 }
179 }
180
181 /**
182 * Set both the minimum and maximum progress values.
183 *
184 * @param min
185 * the min
186 * @param max
187 * the max
188 *
189 * @throws RuntimeException
190 * if min &lt; 0 or if min &gt; max
191 */
192 public void setMinMax(int min, int max) {
193 if (min < 0) {
194 throw new RuntimeException("negative values not supported");
195 }
196
197 if (min > max) {
198 throw new RuntimeException(
199 "The minimum progress value must be <= the maximum progress value");
200 }
201
202 synchronized (lock) {
203 this.min = min;
204 this.max = max;
205 }
206 }
207
208 /**
209 * Get the total progress value (including the optional children
210 * {@link Progress}) on a {@link Progress#getMin()} to
211 * {@link Progress#getMax()} scale.
212 *
213 * @return the progress the value
214 */
215 public int getProgress() {
216 return (int) Math.round(relativeProgress * (max - min));
217 }
218
219 /**
220 * Set the local progress value (not including the optional children
221 * {@link Progress}), on a {@link Progress#getMin()} to
222 * {@link Progress#getMax()} scale.
223 *
224 * @param progress
225 * the progress to set
226 */
227 public void setProgress(int progress) {
228 synchronized (lock) {
229 double childrenProgress = relativeProgress - relativeLocalProgress;
230
231 relativeLocalProgress = ((double) progress) / (max - min);
232
233 setRelativeProgress(this, name, relativeLocalProgress
234 + childrenProgress);
235 }
236 }
237
238 /**
239 * Get the total progress value (including the optional children
240 * {@link Progress}) on a 0.0 to 1.0 scale.
241 *
242 * @return the progress
243 */
244 public double getRelativeProgress() {
245 return relativeProgress;
246 }
247
248 /**
249 * Set the total progress value (including the optional children
250 * {@link Progress}), on a 0 to 1 scale.
251 *
252 * @param pg
253 * the {@link Progress} to report as the progression emitter
254 * @param name
255 * the current name (if it is NULL, the first non-null name in
256 * the hierarchy will overwrite it) of the {@link Progress} who
257 * emitted this change
258 * @param relativeProgress
259 * the progress to set
260 */
261 private void setRelativeProgress(Progress pg, String name,
262 double relativeProgress) {
263 synchronized (lock) {
264 relativeProgress = Math.max(0, relativeProgress);
265 relativeProgress = Math.min(1, relativeProgress);
266 this.relativeProgress = relativeProgress;
267
268 changed(pg, name);
269 }
270 }
271
272 /**
273 * Get the total progress value (including the optional children
274 * {@link Progress}) on a 0 to 1 scale.
275 *
276 * @return the progress the value
277 */
278 private int getLocalProgress() {
279 return (int) Math.round(relativeLocalProgress * (max - min));
280 }
281
282 /**
283 * Add some value to the current progression of this {@link Progress}.
284 *
285 * @param step
286 * the amount to add
287 */
288 public void add(int step) {
289 synchronized (lock) {
290 setProgress(getLocalProgress() + step);
291 }
292 }
293
294 /**
295 * Check if the action corresponding to this {@link Progress} is done (i.e.,
296 * if its progress value == its max value).
297 *
298 * @return TRUE if it is
299 */
300 public boolean isDone() {
301 return getProgress() == max;
302 }
303
304 /**
305 * Mark the {@link Progress} as done by setting its value to max.
306 */
307 public void done() {
308 synchronized (lock) {
309 double childrenProgress = relativeProgress - relativeLocalProgress;
310 relativeLocalProgress = 1 - childrenProgress;
311 setRelativeProgress(this, name, 1d);
312 }
313 }
314
315 /**
316 * Return the list of direct children of this {@link Progress}.
317 *
318 * @return the children (Who will think of the children??)
319 */
320 public List<Progress> getChildren() {
321 synchronized (lock) {
322 return new ArrayList<Progress>(children.keySet());
323 }
324 }
325
326 /**
327 * Notify the listeners that this {@link Progress} changed value.
328 *
329 * @param pg
330 * the emmiter, that is, the (sub-){link Progress} that just
331 * reported some change, not always the same as <tt>this</tt>
332 * @param name
333 * the current name (if it is NULL, the first non-null name in
334 * the hierarchy will overwrite it) of the {@link Progress} who
335 * emitted this change
336 */
337 private void changed(Progress pg, String name) {
338 if (pg == null) {
339 pg = this;
340 }
341
342 if (name == null) {
343 name = this.name;
344 }
345
346 synchronized (lock) {
347 for (ProgressListener l : listeners) {
348 l.progress(pg, name);
349 }
350 }
351 }
352
353 /**
354 * Add a {@link ProgressListener} that will trigger on progress changes.
355 * <p>
356 * Note: the {@link Progress} that will be reported will be the active
357 * progress, not necessarily the same as the current one (it could be a
358 * child {@link Progress} of this {@link Progress}).
359 *
360 * @param l
361 * the listener
362 */
363 public void addProgressListener(ProgressListener l) {
364 synchronized (lock) {
365 this.listeners.add(l);
366 }
367 }
368
369 /**
370 * Remove a {@link ProgressListener} that would trigger on progress changes.
371 *
372 * @param l
373 * the listener
374 *
375 * @return TRUE if it was found (and removed)
376 */
377 public boolean removeProgressListener(ProgressListener l) {
378 synchronized (lock) {
379 return this.listeners.remove(l);
380 }
381 }
382
383 /**
384 * Add a child {@link Progress} of the given weight.
385 *
386 * @param progress
387 * the child {@link Progress} to add
388 * @param weight
389 * the weight (on a {@link Progress#getMin()} to
390 * {@link Progress#getMax()} scale) of this child
391 * {@link Progress} in relation to its parent
392 *
393 * @throws RuntimeException
394 * if weight exceed {@link Progress#getMax()} or if progress
395 * already has a parent
396 */
397 public void addProgress(Progress progress, double weight) {
398 if (weight < min || weight > max) {
399 throw new RuntimeException(String.format(
400 "Progress object %s cannot have a weight of %f, "
401 + "it is outside of its parent (%s) range (%d)",
402 progress.name, weight, name, max));
403 }
404
405 if (progress.parent != null) {
406 throw new RuntimeException(String.format(
407 "Progress object %s cannot be added to %s, "
408 + "as it already has a parent (%s)", progress.name,
409 name, progress.parent.name));
410 }
411
412 ProgressListener progressListener = new ProgressListener() {
413 @Override
414 public void progress(Progress pg, String name) {
415 synchronized (lock) {
416 double total = relativeLocalProgress;
417 for (Entry<Progress, Double> entry : children.entrySet()) {
418 total += (entry.getValue() / (max - min))
419 * entry.getKey().getRelativeProgress();
420 }
421
422 setRelativeProgress(pg, name, total);
423 }
424 }
425 };
426
427 synchronized (lock) {
428 progress.parent = this;
429 this.children.put(progress, weight);
430 progress.addProgressListener(progressListener);
431 }
432 }
433 }