1 package be
.nikiroo
.utils
;
3 import java
.util
.ArrayList
;
4 import java
.util
.EventListener
;
5 import java
.util
.HashMap
;
8 import java
.util
.Map
.Entry
;
11 * Progress reporting system, possibly nested.
15 public class Progress
{
17 * This event listener is designed to report progress events from
22 public interface ProgressListener
extends EventListener
{
24 * A progression event.
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
32 * the first non-null name of the {@link Progress} step that
33 * generated this event
35 public void progress(Progress progress
, String name
);
38 private Progress parent
= null;
39 private Object lock
= new Object();
41 private Map
<Progress
, Double
> children
;
42 private List
<ProgressListener
> listeners
;
45 private double relativeLocalProgress
;
46 private double relativeProgress
; // children included
49 * Create a new default unnamed {@link Progress}, from 0 to 100.
56 * Create a new default {@link Progress}, from 0 to 100.
59 * the name of this {@link Progress} step
61 public Progress(String name
) {
66 * Create a new unnamed {@link Progress}, from min to max.
69 * the minimum progress value (and starting value) -- must be
72 * the maximum progress value
74 public Progress(int min
, int max
) {
79 * Create a new {@link Progress}, from min to max.
82 * the name of this {@link Progress} step
84 * the minimum progress value (and starting value) -- must be
87 * the maximum progress value
89 public Progress(String name
, int min
, int max
) {
91 this.children
= new HashMap
<Progress
, Double
>();
92 this.listeners
= new ArrayList
<Progress
.ProgressListener
>();
98 * The name of this {@link Progress} step.
102 public String
getName() {
107 * The name of this {@link Progress} step.
112 public void setName(String name
) {
118 * The minimum progress value.
122 public int getMin() {
127 * The minimum progress value.
133 * @throws RuntimeException
134 * if min < 0 or if min > max
136 public void setMin(int min
) {
138 throw new RuntimeException("negative values not supported");
141 synchronized (getLock()) {
143 throw new RuntimeException(
144 "The minimum progress value must be <= the maximum progress value");
152 * The maximum progress value.
156 public int getMax() {
161 * The maximum progress value (must be >= the minimum progress value).
167 * @throws RuntimeException
170 public void setMax(int max
) {
171 synchronized (getLock()) {
174 "The maximum progress value must be >= the minimum progress value");
182 * Set both the minimum and maximum progress values.
189 * @throws RuntimeException
190 * if min < 0 or if min > max
192 public void setMinMax(int min
, int max
) {
194 throw new RuntimeException("negative values not supported");
198 throw new RuntimeException(
199 "The minimum progress value must be <= the maximum progress value");
202 synchronized (getLock()) {
209 * Get the total progress value (including the optional children
210 * {@link Progress}) on a {@link Progress#getMin()} to
211 * {@link Progress#getMax()} scale.
213 * @return the progress the value
215 public int getProgress() {
216 return (int) Math
.round(relativeProgress
* (max
- min
));
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.
225 * the progress to set
227 public void setProgress(int progress
) {
228 synchronized (getLock()) {
229 double childrenProgress
= relativeProgress
- relativeLocalProgress
;
231 relativeLocalProgress
= ((double) progress
) / (max
- min
);
233 setRelativeProgress(this, name
, relativeLocalProgress
239 * Get the total progress value (including the optional children
240 * {@link Progress}) on a 0.0 to 1.0 scale.
242 * @return the progress
244 public double getRelativeProgress() {
245 return relativeProgress
;
249 * Set the total progress value (including the optional children
250 * {@link Progress}), on a 0 to 1 scale.
253 * the {@link Progress} to report as the progression emitter
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
261 private void setRelativeProgress(Progress pg
, String name
,
262 double relativeProgress
) {
263 synchronized (getLock()) {
264 relativeProgress
= Math
.max(0, relativeProgress
);
265 relativeProgress
= Math
.min(1, relativeProgress
);
266 this.relativeProgress
= relativeProgress
;
273 * Get the total progress value (including the optional children
274 * {@link Progress}) on a 0 to 1 scale.
276 * @return the progress the value
278 private int getLocalProgress() {
279 return (int) Math
.round(relativeLocalProgress
* (max
- min
));
283 * Add some value to the current progression of this {@link Progress}.
288 public void add(int step
) {
289 synchronized (getLock()) {
290 setProgress(getLocalProgress() + step
);
295 * Check if the action corresponding to this {@link Progress} is done (i.e.,
296 * if its progress value == its max value).
298 * @return TRUE if it is
300 public boolean isDone() {
301 return getProgress() == max
;
305 * Mark the {@link Progress} as done by setting its value to max.
308 synchronized (getLock()) {
309 double childrenProgress
= relativeProgress
- relativeLocalProgress
;
310 relativeLocalProgress
= 1 - childrenProgress
;
311 setRelativeProgress(this, name
, 1d
);
316 * Return the list of direct children of this {@link Progress}.
318 * @return the children (Who will think of the children??)
320 public List
<Progress
> getChildren() {
321 synchronized (getLock()) {
322 return new ArrayList
<Progress
>(children
.keySet());
327 * Notify the listeners that this {@link Progress} changed value.
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
336 private void changed(Progress pg
, String name
) {
345 synchronized (getLock()) {
346 for (ProgressListener l
: listeners
) {
347 l
.progress(pg
, name
);
353 * Add a {@link ProgressListener} that will trigger on progress changes.
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}).
362 public void addProgressListener(ProgressListener l
) {
363 synchronized (getLock()) {
364 this.listeners
.add(l
);
369 * Remove a {@link ProgressListener} that would trigger on progress changes.
374 * @return TRUE if it was found (and removed)
376 public boolean removeProgressListener(ProgressListener l
) {
377 synchronized (getLock()) {
378 return this.listeners
.remove(l
);
383 * Add a child {@link Progress} of the given weight.
386 * the child {@link Progress} to add
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
392 * @throws RuntimeException
393 * if weight exceed {@link Progress#getMax()} or if progress
394 * already has a parent
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
));
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
));
411 progress
.parent
= this;
413 progress
.addProgressListener(new ProgressListener() {
415 public void progress(Progress pg
, String name
) {
416 synchronized (getLock()) {
417 double total
= relativeLocalProgress
;
418 synchronized (getLock()) {
419 for (Entry
<Progress
, Double
> entry
: children
421 total
+= (entry
.getValue() / (max
- min
))
422 * entry
.getKey().getRelativeProgress();
426 setRelativeProgress(pg
, name
, total
);
431 synchronized (getLock()) {
432 this.children
.put(progress
, weight
);
437 * The lock object to use (this one or the recursively-parent one).
439 * @return the lock object to use
441 private Object
getLock() {
442 synchronized (lock
) {
443 if (parent
!= null) {
444 return parent
.getLock();