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