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