Merge branch 'subtree'
[fanfix.git] / src / be / nikiroo / utils / Progress.java
CommitLineData
b3aad1f9 1package be.nikiroo.utils;
86057589
NR
2
3import java.util.ArrayList;
4import java.util.EventListener;
5import java.util.HashMap;
6import java.util.List;
7import java.util.Map;
8import java.util.Map.Entry;
9
10/**
11 * Progress reporting system, possibly nested.
69155ad5
NR
12 * <p>
13 * A {@link Progress} can have a name, and that name will be reported through
14 * the event system (it will report the first non-null name in the stack from
15 * the {@link Progress} from which the event originated to the parent the event
16 * is listened on).
17 * <p>
18 * The {@link Progress} also has a table of keys/values shared amongst all the
19 * hierarchy (note that when adding a {@link Progress} to others, its values
20 * will be prioritized if some with the same keys were already present in the
21 * hierarchy).
86057589
NR
22 *
23 * @author niki
24 */
25public class Progress {
d827da2a
NR
26 /**
27 * This event listener is designed to report progress events from
28 * {@link Progress}.
29 *
30 * @author niki
31 */
86057589
NR
32 public interface ProgressListener extends EventListener {
33 /**
34 * A progression event.
35 *
36 * @param progress
da5bfa48
NR
37 * the {@link Progress} object that generated it, not
38 * necessarily the same as the one where the listener was
39 * attached (it could be a child {@link Progress} of this
40 * {@link Progress}).
86057589
NR
41 * @param name
42 * the first non-null name of the {@link Progress} step that
43 * generated this event
44 */
45 public void progress(Progress progress, String name);
46 }
47
69155ad5 48 private Map<Object, Object> map = new HashMap<Object, Object>();
0924c45d
NR
49 private Progress parent = null;
50 private Object lock = new Object();
86057589
NR
51 private String name;
52 private Map<Progress, Double> children;
53 private List<ProgressListener> listeners;
54 private int min;
55 private int max;
11f9e5f3
NR
56 private double relativeLocalProgress;
57 private double relativeProgress; // children included
86057589
NR
58
59 /**
60 * Create a new default unnamed {@link Progress}, from 0 to 100.
61 */
62 public Progress() {
63 this(null);
64 }
65
66 /**
67 * Create a new default {@link Progress}, from 0 to 100.
68 *
69 * @param name
70 * the name of this {@link Progress} step
71 */
72 public Progress(String name) {
73 this(name, 0, 100);
74 }
75
76 /**
77 * Create a new unnamed {@link Progress}, from min to max.
78 *
79 * @param min
80 * the minimum progress value (and starting value) -- must be
81 * non-negative
82 * @param max
83 * the maximum progress value
84 */
85 public Progress(int min, int max) {
86 this(null, min, max);
87 }
88
89 /**
90 * Create a new {@link Progress}, from min to max.
91 *
92 * @param name
93 * the name of this {@link Progress} step
94 * @param min
95 * the minimum progress value (and starting value) -- must be
96 * non-negative
97 * @param max
98 * the maximum progress value
99 */
100 public Progress(String name, int min, int max) {
101 this.name = name;
102 this.children = new HashMap<Progress, Double>();
103 this.listeners = new ArrayList<Progress.ProgressListener>();
104 setMinMax(min, max);
105 setProgress(min);
106 }
107
108 /**
109 * The name of this {@link Progress} step.
110 *
ba6cf425 111 * @return the name, can be NULL
86057589
NR
112 */
113 public String getName() {
114 return name;
115 }
116
2998b78a
NR
117 /**
118 * The name of this {@link Progress} step.
119 *
120 * @param name
121 * the new name
122 */
123 public void setName(String name) {
124 this.name = name;
11f9e5f3 125 changed(this, name);
2998b78a
NR
126 }
127
86057589
NR
128 /**
129 * The minimum progress value.
130 *
131 * @return the min
132 */
133 public int getMin() {
134 return min;
135 }
136
137 /**
138 * The minimum progress value.
139 *
140 * @param min
141 * the min to set
0924c45d
NR
142 *
143 *
d827da2a 144 * @throws RuntimeException
0924c45d 145 * if min &lt; 0 or if min &gt; max
86057589
NR
146 */
147 public void setMin(int min) {
148 if (min < 0) {
d827da2a 149 throw new RuntimeException("negative values not supported");
86057589
NR
150 }
151
96d4aebe 152 synchronized (lock) {
0924c45d 153 if (min > max) {
d827da2a 154 throw new RuntimeException(
0924c45d
NR
155 "The minimum progress value must be <= the maximum progress value");
156 }
86057589 157
0924c45d
NR
158 this.min = min;
159 }
86057589
NR
160 }
161
162 /**
163 * The maximum progress value.
164 *
165 * @return the max
166 */
167 public int getMax() {
168 return max;
169 }
170
171 /**
172 * The maximum progress value (must be >= the minimum progress value).
173 *
174 * @param max
175 * the max to set
0924c45d
NR
176 *
177 *
d827da2a 178 * @throws RuntimeException
0924c45d 179 * if max &lt; min
86057589
NR
180 */
181 public void setMax(int max) {
96d4aebe 182 synchronized (lock) {
0924c45d
NR
183 if (max < min) {
184 throw new Error(
185 "The maximum progress value must be >= the minimum progress value");
186 }
86057589 187
0924c45d
NR
188 this.max = max;
189 }
86057589
NR
190 }
191
192 /**
193 * Set both the minimum and maximum progress values.
194 *
195 * @param min
196 * the min
197 * @param max
198 * the max
0924c45d 199 *
d827da2a 200 * @throws RuntimeException
0924c45d 201 * if min &lt; 0 or if min &gt; max
86057589
NR
202 */
203 public void setMinMax(int min, int max) {
204 if (min < 0) {
d827da2a 205 throw new RuntimeException("negative values not supported");
86057589
NR
206 }
207
208 if (min > max) {
d827da2a 209 throw new RuntimeException(
86057589
NR
210 "The minimum progress value must be <= the maximum progress value");
211 }
212
96d4aebe 213 synchronized (lock) {
0924c45d
NR
214 this.min = min;
215 this.max = max;
216 }
86057589
NR
217 }
218
219 /**
220 * Get the total progress value (including the optional children
221 * {@link Progress}) on a {@link Progress#getMin()} to
222 * {@link Progress#getMax()} scale.
223 *
224 * @return the progress the value
225 */
226 public int getProgress() {
11f9e5f3 227 return (int) Math.round(relativeProgress * (max - min));
86057589
NR
228 }
229
230 /**
231 * Set the local progress value (not including the optional children
232 * {@link Progress}), on a {@link Progress#getMin()} to
233 * {@link Progress#getMax()} scale.
234 *
235 * @param progress
236 * the progress to set
237 */
238 public void setProgress(int progress) {
96d4aebe 239 synchronized (lock) {
11f9e5f3
NR
240 double childrenProgress = relativeProgress - relativeLocalProgress;
241
242 relativeLocalProgress = ((double) progress) / (max - min);
243
ba6cf425
NR
244 setRelativeProgress(this, name,
245 relativeLocalProgress + childrenProgress);
0924c45d 246 }
86057589
NR
247 }
248
11f9e5f3
NR
249 /**
250 * Get the total progress value (including the optional children
251 * {@link Progress}) on a 0.0 to 1.0 scale.
252 *
253 * @return the progress
254 */
255 public double getRelativeProgress() {
256 return relativeProgress;
257 }
258
ba6cf425
NR
259 /**
260 * Set the total progress value (including the optional children
261 * {@link Progress}), on a 0 to 1 scale.
262 * <p>
263 * Will generate a changed event from this very {@link Progress}.
264 *
265 * @param relativeProgress
266 * the progress to set
267 */
268 public void setRelativeProgress(double relativeProgress) {
269 setRelativeProgress(this, name, relativeProgress);
270 }
271
11f9e5f3
NR
272 /**
273 * Set the total progress value (including the optional children
274 * {@link Progress}), on a 0 to 1 scale.
275 *
276 * @param pg
ba6cf425
NR
277 * the {@link Progress} to report as the progression emitter (can
278 * be NULL, will then be considered the same as <tt>this</tt>)
11f9e5f3
NR
279 * @param name
280 * the current name (if it is NULL, the first non-null name in
281 * the hierarchy will overwrite it) of the {@link Progress} who
282 * emitted this change
283 * @param relativeProgress
284 * the progress to set
285 */
286 private void setRelativeProgress(Progress pg, String name,
287 double relativeProgress) {
96d4aebe 288 synchronized (lock) {
11f9e5f3
NR
289 relativeProgress = Math.max(0, relativeProgress);
290 relativeProgress = Math.min(1, relativeProgress);
291 this.relativeProgress = relativeProgress;
292
293 changed(pg, name);
294 }
295 }
296
297 /**
298 * Get the total progress value (including the optional children
299 * {@link Progress}) on a 0 to 1 scale.
300 *
301 * @return the progress the value
302 */
303 private int getLocalProgress() {
304 return (int) Math.round(relativeLocalProgress * (max - min));
305 }
306
cac67ebc
NR
307 /**
308 * Add some value to the current progression of this {@link Progress}.
309 *
310 * @param step
311 * the amount to add
312 */
313 public void add(int step) {
96d4aebe 314 synchronized (lock) {
11f9e5f3 315 setProgress(getLocalProgress() + step);
cac67ebc
NR
316 }
317 }
318
86057589
NR
319 /**
320 * Check if the action corresponding to this {@link Progress} is done (i.e.,
2a35af0b 321 * if its progress value == its max value).
86057589
NR
322 *
323 * @return TRUE if it is
324 */
325 public boolean isDone() {
356c0336 326 return getProgress() == max;
86057589
NR
327 }
328
cac67ebc
NR
329 /**
330 * Mark the {@link Progress} as done by setting its value to max.
331 */
332 public void done() {
96d4aebe 333 synchronized (lock) {
356c0336
NR
334 double childrenProgress = relativeProgress - relativeLocalProgress;
335 relativeLocalProgress = 1 - childrenProgress;
336 setRelativeProgress(this, name, 1d);
337 }
cac67ebc
NR
338 }
339
88b36f83
NR
340 /**
341 * Return the list of direct children of this {@link Progress}.
ba6cf425
NR
342 * <p>
343 * Can return an empty list, but never NULL.
88b36f83 344 *
2a35af0b 345 * @return the children (Who will think of the children??)
88b36f83 346 */
f4053377 347 public List<Progress> getChildren() {
96d4aebe 348 synchronized (lock) {
f4053377
NR
349 return new ArrayList<Progress>(children.keySet());
350 }
88b36f83
NR
351 }
352
ba6cf425
NR
353 /**
354 * The weight of this children if it is actually a child of this.
355 *
356 * @param child
357 * the child to get the weight of
358 *
359 * @return NULL if this is not a child of this
360 */
361 public Double getWeight(Progress child) {
362 synchronized (lock) {
363 return children.get(child);
364 }
365 }
366
86057589 367 /**
11f9e5f3 368 * Notify the listeners that this {@link Progress} changed value.
86057589 369 *
88b36f83 370 * @param pg
3766af24
NR
371 * the emmiter, that is, the (sub-){link Progress} that just
372 * reported some change, not always the same as <tt>this</tt>
86057589
NR
373 * @param name
374 * the current name (if it is NULL, the first non-null name in
88b36f83
NR
375 * the hierarchy will overwrite it) of the {@link Progress} who
376 * emitted this change
86057589 377 */
11f9e5f3 378 private void changed(Progress pg, String name) {
2a35af0b
NR
379 if (pg == null) {
380 pg = this;
381 }
382
11f9e5f3
NR
383 if (name == null) {
384 name = this.name;
385 }
386
96d4aebe 387 synchronized (lock) {
0924c45d
NR
388 for (ProgressListener l : listeners) {
389 l.progress(pg, name);
390 }
86057589
NR
391 }
392 }
393
394 /**
395 * Add a {@link ProgressListener} that will trigger on progress changes.
da5bfa48
NR
396 * <p>
397 * Note: the {@link Progress} that will be reported will be the active
398 * progress, not necessarily the same as the current one (it could be a
399 * child {@link Progress} of this {@link Progress}).
86057589
NR
400 *
401 * @param l
402 * the listener
403 */
404 public void addProgressListener(ProgressListener l) {
96d4aebe 405 synchronized (lock) {
f4053377
NR
406 this.listeners.add(l);
407 }
86057589
NR
408 }
409
2998b78a
NR
410 /**
411 * Remove a {@link ProgressListener} that would trigger on progress changes.
412 *
413 * @param l
414 * the listener
415 *
416 * @return TRUE if it was found (and removed)
417 */
418 public boolean removeProgressListener(ProgressListener l) {
96d4aebe 419 synchronized (lock) {
f4053377
NR
420 return this.listeners.remove(l);
421 }
2998b78a
NR
422 }
423
86057589
NR
424 /**
425 * Add a child {@link Progress} of the given weight.
426 *
427 * @param progress
428 * the child {@link Progress} to add
429 * @param weight
430 * the weight (on a {@link Progress#getMin()} to
431 * {@link Progress#getMax()} scale) of this child
432 * {@link Progress} in relation to its parent
0924c45d 433 *
d827da2a 434 * @throws RuntimeException
0924c45d
NR
435 * if weight exceed {@link Progress#getMax()} or if progress
436 * already has a parent
86057589
NR
437 */
438 public void addProgress(Progress progress, double weight) {
439 if (weight < min || weight > max) {
d827da2a 440 throw new RuntimeException(String.format(
0924c45d 441 "Progress object %s cannot have a weight of %f, "
0988831f 442 + "it is outside of its parent (%s) range (%d)",
0924c45d
NR
443 progress.name, weight, name, max));
444 }
445
446 if (progress.parent != null) {
d827da2a 447 throw new RuntimeException(String.format(
0924c45d 448 "Progress object %s cannot be added to %s, "
ba6cf425
NR
449 + "as it already has a parent (%s)",
450 progress.name, name, progress.parent.name));
86057589
NR
451 }
452
96d4aebe 453 ProgressListener progressListener = new ProgressListener() {
cd0c27d2 454 @Override
62c9ec78 455 public void progress(Progress pg, String name) {
96d4aebe 456 synchronized (lock) {
11f9e5f3 457 double total = relativeLocalProgress;
96d4aebe
NR
458 for (Entry<Progress, Double> entry : children.entrySet()) {
459 total += (entry.getValue() / (max - min))
460 * entry.getKey().getRelativeProgress();
0924c45d
NR
461 }
462
11f9e5f3 463 setRelativeProgress(pg, name, total);
86057589 464 }
86057589 465 }
96d4aebe 466 };
0924c45d 467
0924c45d 468 synchronized (lock) {
69155ad5
NR
469 // Should not happen but just in case
470 if (this.map != progress.map) {
471 this.map.putAll(progress.map);
472 }
473 progress.map = this.map;
96d4aebe
NR
474 progress.parent = this;
475 this.children.put(progress, weight);
476 progress.addProgressListener(progressListener);
0924c45d
NR
477 }
478 }
69155ad5 479
69155ad5
NR
480 /**
481 * Set the given value for the given key on this {@link Progress} and it's
482 * children.
483 *
484 * @param key
485 * the key
486 * @param value
487 * the value
488 */
489 public void put(Object key, Object value) {
490 map.put(key, value);
491 }
492
493 /**
8b2627ce
NR
494 * Return the value associated with this key as a {@link String} if any,
495 * NULL if not.
69155ad5
NR
496 * <p>
497 * If the value is not NULL but not a {@link String}, it will be converted
498 * via {@link Object#toString()}.
499 *
500 * @param key
501 * the key to check
502 *
503 * @return the value or NULL
504 */
8b2627ce 505 public String getString(Object key) {
69155ad5
NR
506 Object value = map.get(key);
507 if (value == null) {
508 return null;
509 }
510
511 return value.toString();
512 }
513
514 /**
515 * Return the value associated with this key if any, NULL if not.
516 *
517 * @param key
518 * the key to check
519 *
520 * @return the value or NULL
521 */
522 public Object get(Object key) {
523 return map.get(key);
524 }
ba6cf425
NR
525
526 @Override
527 public String toString() {
528 return "[Progress]" //
529 + (name == null || name.isEmpty() ? "" : " " + name) //
530 + ": " + getProgress() + " / " + getMax() //
531 + (children.isEmpty() ? ""
532 : " (with " + children.size() + " children)") //
533 ;
534 }
86057589 535}