Fix Cache (URL to File could fail if no parent)
[nikiroo-utils.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 import java.util.Set;
10
11 /**
12 * Progress reporting system, possibly nested.
13 *
14 * @author niki
15 */
16 public class Progress {
17 /**
18 * This event listener is designed to report progress events from
19 * {@link Progress}.
20 *
21 * @author niki
22 */
23 public interface ProgressListener extends EventListener {
24 /**
25 * A progression event.
26 *
27 * @param progress
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}).
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
39 private Progress parent = null;
40 private Object lock = new Object();
41 private String name;
42 private Map<Progress, Double> children;
43 private List<ProgressListener> listeners;
44 private int min;
45 private int max;
46 private double relativeLocalProgress;
47 private double relativeProgress; // children included
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
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;
115 changed(this, name);
116 }
117
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
132 *
133 *
134 * @throws RuntimeException
135 * if min &lt; 0 or if min &gt; max
136 */
137 public void setMin(int min) {
138 if (min < 0) {
139 throw new RuntimeException("negative values not supported");
140 }
141
142 synchronized (getLock()) {
143 if (min > max) {
144 throw new RuntimeException(
145 "The minimum progress value must be <= the maximum progress value");
146 }
147
148 this.min = min;
149 }
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
166 *
167 *
168 * @throws RuntimeException
169 * if max &lt; min
170 */
171 public void setMax(int max) {
172 synchronized (getLock()) {
173 if (max < min) {
174 throw new Error(
175 "The maximum progress value must be >= the minimum progress value");
176 }
177
178 this.max = max;
179 }
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
189 *
190 * @throws RuntimeException
191 * if min &lt; 0 or if min &gt; max
192 */
193 public void setMinMax(int min, int max) {
194 if (min < 0) {
195 throw new RuntimeException("negative values not supported");
196 }
197
198 if (min > max) {
199 throw new RuntimeException(
200 "The minimum progress value must be <= the maximum progress value");
201 }
202
203 synchronized (getLock()) {
204 this.min = min;
205 this.max = max;
206 }
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() {
217 return (int) Math.round(relativeProgress * (max - min));
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) {
229 synchronized (getLock()) {
230 double childrenProgress = relativeProgress - relativeLocalProgress;
231
232 relativeLocalProgress = ((double) progress) / (max - min);
233
234 setRelativeProgress(this, name, relativeLocalProgress
235 + childrenProgress);
236 }
237 }
238
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
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()) {
291 setProgress(getLocalProgress() + step);
292 }
293 }
294
295 /**
296 * Check if the action corresponding to this {@link Progress} is done (i.e.,
297 * if its progress value == its max value).
298 *
299 * @return TRUE if it is
300 */
301 public boolean isDone() {
302 return getProgress() == max;
303 }
304
305 /**
306 * Mark the {@link Progress} as done by setting its value to max.
307 */
308 public void done() {
309 synchronized (getLock()) {
310 double childrenProgress = relativeProgress - relativeLocalProgress;
311 relativeLocalProgress = 1 - childrenProgress;
312 setRelativeProgress(this, name, 1d);
313 }
314 }
315
316 /**
317 * Return the list of direct children of this {@link Progress}.
318 *
319 * @return the children (Who will think of the children??)
320 */
321 public Set<Progress> getChildren() {
322 return children.keySet();
323 }
324
325 /**
326 * Notify the listeners that this {@link Progress} changed value.
327 *
328 * @param pg
329 * the emmiter
330 * @param name
331 * the current name (if it is NULL, the first non-null name in
332 * the hierarchy will overwrite it) of the {@link Progress} who
333 * emitted this change
334 */
335 private void changed(Progress pg, String name) {
336 if (pg == null) {
337 pg = this;
338 }
339
340 if (name == null) {
341 name = this.name;
342 }
343
344 synchronized (getLock()) {
345 for (ProgressListener l : listeners) {
346 l.progress(pg, name);
347 }
348 }
349 }
350
351 /**
352 * Add a {@link ProgressListener} that will trigger on progress changes.
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}).
357 *
358 * @param l
359 * the listener
360 */
361 public void addProgressListener(ProgressListener l) {
362 this.listeners.add(l);
363 }
364
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
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
386 *
387 * @throws RuntimeException
388 * if weight exceed {@link Progress#getMax()} or if progress
389 * already has a parent
390 */
391 public void addProgress(Progress progress, double weight) {
392 if (weight < min || weight > max) {
393 throw new RuntimeException(String.format(
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) {
400 throw new RuntimeException(String.format(
401 "Progress object %s cannot be added to %s, "
402 + "as it already has a parent (%s)", progress.name,
403 name, progress.parent.name));
404 }
405
406 progress.addProgressListener(new ProgressListener() {
407 @Override
408 public void progress(Progress pg, String name) {
409 synchronized (getLock()) {
410 double total = relativeLocalProgress;
411 for (Entry<Progress, Double> entry : children.entrySet()) {
412 total += (entry.getValue() / (max - min))
413 * entry.getKey().getRelativeProgress();
414 }
415
416 setRelativeProgress(pg, name, total);
417 }
418 }
419 });
420
421 this.children.put(progress, weight);
422 }
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 }
438 }