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 *
111 * @return the name
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
244 setRelativeProgress(this, name, relativeLocalProgress
245 + 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
259 /**
260 * Set the total progress value (including the optional children
261 * {@link Progress}), on a 0 to 1 scale.
262 *
263 * @param pg
264 * the {@link Progress} to report as the progression emitter
265 * @param name
266 * the current name (if it is NULL, the first non-null name in
267 * the hierarchy will overwrite it) of the {@link Progress} who
268 * emitted this change
269 * @param relativeProgress
270 * the progress to set
271 */
272 private void setRelativeProgress(Progress pg, String name,
273 double relativeProgress) {
96d4aebe 274 synchronized (lock) {
11f9e5f3
NR
275 relativeProgress = Math.max(0, relativeProgress);
276 relativeProgress = Math.min(1, relativeProgress);
277 this.relativeProgress = relativeProgress;
278
279 changed(pg, name);
280 }
281 }
282
283 /**
284 * Get the total progress value (including the optional children
285 * {@link Progress}) on a 0 to 1 scale.
286 *
287 * @return the progress the value
288 */
289 private int getLocalProgress() {
290 return (int) Math.round(relativeLocalProgress * (max - min));
291 }
292
cac67ebc
NR
293 /**
294 * Add some value to the current progression of this {@link Progress}.
295 *
296 * @param step
297 * the amount to add
298 */
299 public void add(int step) {
96d4aebe 300 synchronized (lock) {
11f9e5f3 301 setProgress(getLocalProgress() + step);
cac67ebc
NR
302 }
303 }
304
86057589
NR
305 /**
306 * Check if the action corresponding to this {@link Progress} is done (i.e.,
2a35af0b 307 * if its progress value == its max value).
86057589
NR
308 *
309 * @return TRUE if it is
310 */
311 public boolean isDone() {
356c0336 312 return getProgress() == max;
86057589
NR
313 }
314
cac67ebc
NR
315 /**
316 * Mark the {@link Progress} as done by setting its value to max.
317 */
318 public void done() {
96d4aebe 319 synchronized (lock) {
356c0336
NR
320 double childrenProgress = relativeProgress - relativeLocalProgress;
321 relativeLocalProgress = 1 - childrenProgress;
322 setRelativeProgress(this, name, 1d);
323 }
cac67ebc
NR
324 }
325
88b36f83
NR
326 /**
327 * Return the list of direct children of this {@link Progress}.
328 *
2a35af0b 329 * @return the children (Who will think of the children??)
88b36f83 330 */
f4053377 331 public List<Progress> getChildren() {
96d4aebe 332 synchronized (lock) {
f4053377
NR
333 return new ArrayList<Progress>(children.keySet());
334 }
88b36f83
NR
335 }
336
86057589 337 /**
11f9e5f3 338 * Notify the listeners that this {@link Progress} changed value.
86057589 339 *
88b36f83 340 * @param pg
3766af24
NR
341 * the emmiter, that is, the (sub-){link Progress} that just
342 * reported some change, not always the same as <tt>this</tt>
86057589
NR
343 * @param name
344 * the current name (if it is NULL, the first non-null name in
88b36f83
NR
345 * the hierarchy will overwrite it) of the {@link Progress} who
346 * emitted this change
86057589 347 */
11f9e5f3 348 private void changed(Progress pg, String name) {
2a35af0b
NR
349 if (pg == null) {
350 pg = this;
351 }
352
11f9e5f3
NR
353 if (name == null) {
354 name = this.name;
355 }
356
96d4aebe 357 synchronized (lock) {
0924c45d
NR
358 for (ProgressListener l : listeners) {
359 l.progress(pg, name);
360 }
86057589
NR
361 }
362 }
363
364 /**
365 * Add a {@link ProgressListener} that will trigger on progress changes.
da5bfa48
NR
366 * <p>
367 * Note: the {@link Progress} that will be reported will be the active
368 * progress, not necessarily the same as the current one (it could be a
369 * child {@link Progress} of this {@link Progress}).
86057589
NR
370 *
371 * @param l
372 * the listener
373 */
374 public void addProgressListener(ProgressListener l) {
96d4aebe 375 synchronized (lock) {
f4053377
NR
376 this.listeners.add(l);
377 }
86057589
NR
378 }
379
2998b78a
NR
380 /**
381 * Remove a {@link ProgressListener} that would trigger on progress changes.
382 *
383 * @param l
384 * the listener
385 *
386 * @return TRUE if it was found (and removed)
387 */
388 public boolean removeProgressListener(ProgressListener l) {
96d4aebe 389 synchronized (lock) {
f4053377
NR
390 return this.listeners.remove(l);
391 }
2998b78a
NR
392 }
393
86057589
NR
394 /**
395 * Add a child {@link Progress} of the given weight.
396 *
397 * @param progress
398 * the child {@link Progress} to add
399 * @param weight
400 * the weight (on a {@link Progress#getMin()} to
401 * {@link Progress#getMax()} scale) of this child
402 * {@link Progress} in relation to its parent
0924c45d 403 *
d827da2a 404 * @throws RuntimeException
0924c45d
NR
405 * if weight exceed {@link Progress#getMax()} or if progress
406 * already has a parent
86057589
NR
407 */
408 public void addProgress(Progress progress, double weight) {
409 if (weight < min || weight > max) {
d827da2a 410 throw new RuntimeException(String.format(
0924c45d 411 "Progress object %s cannot have a weight of %f, "
0988831f 412 + "it is outside of its parent (%s) range (%d)",
0924c45d
NR
413 progress.name, weight, name, max));
414 }
415
416 if (progress.parent != null) {
d827da2a 417 throw new RuntimeException(String.format(
0924c45d
NR
418 "Progress object %s cannot be added to %s, "
419 + "as it already has a parent (%s)", progress.name,
420 name, progress.parent.name));
86057589
NR
421 }
422
96d4aebe 423 ProgressListener progressListener = new ProgressListener() {
cd0c27d2 424 @Override
62c9ec78 425 public void progress(Progress pg, String name) {
96d4aebe 426 synchronized (lock) {
11f9e5f3 427 double total = relativeLocalProgress;
96d4aebe
NR
428 for (Entry<Progress, Double> entry : children.entrySet()) {
429 total += (entry.getValue() / (max - min))
430 * entry.getKey().getRelativeProgress();
0924c45d
NR
431 }
432
11f9e5f3 433 setRelativeProgress(pg, name, total);
86057589 434 }
86057589 435 }
96d4aebe 436 };
0924c45d 437
0924c45d 438 synchronized (lock) {
69155ad5
NR
439 // Should not happen but just in case
440 if (this.map != progress.map) {
441 this.map.putAll(progress.map);
442 }
443 progress.map = this.map;
96d4aebe
NR
444 progress.parent = this;
445 this.children.put(progress, weight);
446 progress.addProgressListener(progressListener);
0924c45d
NR
447 }
448 }
69155ad5 449
69155ad5
NR
450 /**
451 * Set the given value for the given key on this {@link Progress} and it's
452 * children.
453 *
454 * @param key
455 * the key
456 * @param value
457 * the value
458 */
459 public void put(Object key, Object value) {
460 map.put(key, value);
461 }
462
463 /**
8b2627ce
NR
464 * Return the value associated with this key as a {@link String} if any,
465 * NULL if not.
69155ad5
NR
466 * <p>
467 * If the value is not NULL but not a {@link String}, it will be converted
468 * via {@link Object#toString()}.
469 *
470 * @param key
471 * the key to check
472 *
473 * @return the value or NULL
474 */
8b2627ce 475 public String getString(Object key) {
69155ad5
NR
476 Object value = map.get(key);
477 if (value == null) {
478 return null;
479 }
480
481 return value.toString();
482 }
483
484 /**
485 * Return the value associated with this key if any, NULL if not.
486 *
487 * @param key
488 * the key to check
489 *
490 * @return the value or NULL
491 */
492 public Object get(Object key) {
493 return map.get(key);
494 }
86057589 495}