Commit | Line | Data |
---|---|---|
b3aad1f9 | 1 | package be.nikiroo.utils; |
86057589 NR |
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. | |
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 | */ | |
25 | public 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 < 0 or if min > 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 < 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 < 0 or if min > 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 | } |