Version 4.5.1
[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
10 /**
11 * Progress reporting system, possibly nested.
12 *
13 * @author niki
14 */
15 public class Progress {
16 /**
17 * This event listener is designed to report progress events from
18 * {@link Progress}.
19 *
20 * @author niki
21 */
22 public interface ProgressListener extends EventListener {
23 /**
24 * A progression event.
25 *
26 * @param progress
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}).
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
38 private Progress parent = null;
39 private Object lock = new Object();
40 private String name;
41 private Map<Progress, Double> children;
42 private List<ProgressListener> listeners;
43 private int min;
44 private int max;
45 private double relativeLocalProgress;
46 private double relativeProgress; // children included
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
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;
114 changed(this, name);
115 }
116
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
131 *
132 *
133 * @throws RuntimeException
134 * if min &lt; 0 or if min &gt; max
135 */
136 public void setMin(int min) {
137 if (min < 0) {
138 throw new RuntimeException("negative values not supported");
139 }
140
141 synchronized (lock) {
142 if (min > max) {
143 throw new RuntimeException(
144 "The minimum progress value must be <= the maximum progress value");
145 }
146
147 this.min = min;
148 }
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
165 *
166 *
167 * @throws RuntimeException
168 * if max &lt; min
169 */
170 public void setMax(int max) {
171 synchronized (lock) {
172 if (max < min) {
173 throw new Error(
174 "The maximum progress value must be >= the minimum progress value");
175 }
176
177 this.max = max;
178 }
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
188 *
189 * @throws RuntimeException
190 * if min &lt; 0 or if min &gt; max
191 */
192 public void setMinMax(int min, int max) {
193 if (min < 0) {
194 throw new RuntimeException("negative values not supported");
195 }
196
197 if (min > max) {
198 throw new RuntimeException(
199 "The minimum progress value must be <= the maximum progress value");
200 }
201
202 synchronized (lock) {
203 this.min = min;
204 this.max = max;
205 }
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() {
216 return (int) Math.round(relativeProgress * (max - min));
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) {
228 synchronized (lock) {
229 double childrenProgress = relativeProgress - relativeLocalProgress;
230
231 relativeLocalProgress = ((double) progress) / (max - min);
232
233 setRelativeProgress(this, name, relativeLocalProgress
234 + childrenProgress);
235 }
236 }
237
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) {
263 synchronized (lock) {
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
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) {
289 synchronized (lock) {
290 setProgress(getLocalProgress() + step);
291 }
292 }
293
294 /**
295 * Check if the action corresponding to this {@link Progress} is done (i.e.,
296 * if its progress value == its max value).
297 *
298 * @return TRUE if it is
299 */
300 public boolean isDone() {
301 return getProgress() == max;
302 }
303
304 /**
305 * Mark the {@link Progress} as done by setting its value to max.
306 */
307 public void done() {
308 synchronized (lock) {
309 double childrenProgress = relativeProgress - relativeLocalProgress;
310 relativeLocalProgress = 1 - childrenProgress;
311 setRelativeProgress(this, name, 1d);
312 }
313 }
314
315 /**
316 * Return the list of direct children of this {@link Progress}.
317 *
318 * @return the children (Who will think of the children??)
319 */
320 public List<Progress> getChildren() {
321 synchronized (lock) {
322 return new ArrayList<Progress>(children.keySet());
323 }
324 }
325
326 /**
327 * Notify the listeners that this {@link Progress} changed value.
328 *
329 * @param pg
330 * the emmiter
331 * @param name
332 * the current name (if it is NULL, the first non-null name in
333 * the hierarchy will overwrite it) of the {@link Progress} who
334 * emitted this change
335 */
336 private void changed(Progress pg, String name) {
337 if (pg == null) {
338 pg = this;
339 }
340
341 if (name == null) {
342 name = this.name;
343 }
344
345 synchronized (lock) {
346 for (ProgressListener l : listeners) {
347 l.progress(pg, name);
348 }
349 }
350 }
351
352 /**
353 * Add a {@link ProgressListener} that will trigger on progress changes.
354 * <p>
355 * Note: the {@link Progress} that will be reported will be the active
356 * progress, not necessarily the same as the current one (it could be a
357 * child {@link Progress} of this {@link Progress}).
358 *
359 * @param l
360 * the listener
361 */
362 public void addProgressListener(ProgressListener l) {
363 synchronized (lock) {
364 this.listeners.add(l);
365 }
366 }
367
368 /**
369 * Remove a {@link ProgressListener} that would trigger on progress changes.
370 *
371 * @param l
372 * the listener
373 *
374 * @return TRUE if it was found (and removed)
375 */
376 public boolean removeProgressListener(ProgressListener l) {
377 synchronized (lock) {
378 return this.listeners.remove(l);
379 }
380 }
381
382 /**
383 * Add a child {@link Progress} of the given weight.
384 *
385 * @param progress
386 * the child {@link Progress} to add
387 * @param weight
388 * the weight (on a {@link Progress#getMin()} to
389 * {@link Progress#getMax()} scale) of this child
390 * {@link Progress} in relation to its parent
391 *
392 * @throws RuntimeException
393 * if weight exceed {@link Progress#getMax()} or if progress
394 * already has a parent
395 */
396 public void addProgress(Progress progress, double weight) {
397 if (weight < min || weight > max) {
398 throw new RuntimeException(String.format(
399 "Progress object %s cannot have a weight of %f, "
400 + "it is outside of its parent (%s) range (%d)",
401 progress.name, weight, name, max));
402 }
403
404 if (progress.parent != null) {
405 throw new RuntimeException(String.format(
406 "Progress object %s cannot be added to %s, "
407 + "as it already has a parent (%s)", progress.name,
408 name, progress.parent.name));
409 }
410
411 ProgressListener progressListener = new ProgressListener() {
412 @Override
413 public void progress(Progress pg, String name) {
414 synchronized (lock) {
415 double total = relativeLocalProgress;
416 for (Entry<Progress, Double> entry : children.entrySet()) {
417 total += (entry.getValue() / (max - min))
418 * entry.getKey().getRelativeProgress();
419 }
420
421 setRelativeProgress(pg, name, total);
422 }
423 }
424 };
425
426 synchronized (lock) {
427 progress.parent = this;
428 this.children.put(progress, weight);
429 progress.addProgressListener(progressListener);
430 }
431 }
432 }