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