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.
13 * A {@link Progress} can have a name, and that name will be reported through
14 * the event system (it will report the first non-null name in the stack from
15 * the {@link Progress} from which the event originated to the parent the event
18 * The {@link Progress} also has a table of keys/values shared amongst all the
19 * hierarchy (note that when adding a {@link Progress} to others, its values
20 * will be prioritized if some with the same keys were already present in the
25 public class Progress
{
27 * This event listener is designed to report progress events from
32 public interface ProgressListener
extends EventListener
{
34 * A progression event.
37 * the {@link Progress} object that generated it, not
38 * necessarily the same as the one where the listener was
39 * attached (it could be a child {@link Progress} of this
42 * the first non-null name of the {@link Progress} step that
43 * generated this event
45 public void progress(Progress progress
, String name
);
48 private Map
<Object
, Object
> map
= new HashMap
<Object
, Object
>();
49 private Progress parent
= null;
50 private Object lock
= new Object();
52 private Map
<Progress
, Double
> children
;
53 private List
<ProgressListener
> listeners
;
56 private double relativeLocalProgress
;
57 private double relativeProgress
; // children included
60 * Create a new default unnamed {@link Progress}, from 0 to 100.
67 * Create a new default {@link Progress}, from 0 to 100.
70 * the name of this {@link Progress} step
72 public Progress(String name
) {
77 * Create a new unnamed {@link Progress}, from min to max.
80 * the minimum progress value (and starting value) -- must be
83 * the maximum progress value
85 public Progress(int min
, int max
) {
90 * Create a new {@link Progress}, from min to max.
93 * the name of this {@link Progress} step
95 * the minimum progress value (and starting value) -- must be
98 * the maximum progress value
100 public Progress(String name
, int min
, int max
) {
102 this.children
= new HashMap
<Progress
, Double
>();
103 this.listeners
= new ArrayList
<Progress
.ProgressListener
>();
109 * The name of this {@link Progress} step.
111 * @return the name, can be NULL
113 public String
getName() {
118 * The name of this {@link Progress} step.
123 public void setName(String name
) {
129 * The minimum progress value.
133 public int getMin() {
138 * The minimum progress value.
144 * @throws RuntimeException
145 * if min < 0 or if min > max
147 public void setMin(int min
) {
149 throw new RuntimeException("negative values not supported");
152 synchronized (lock
) {
154 throw new RuntimeException(
155 "The minimum progress value must be <= the maximum progress value");
163 * The maximum progress value.
167 public int getMax() {
172 * The maximum progress value (must be >= the minimum progress value).
178 * @throws RuntimeException
181 public void setMax(int max
) {
182 synchronized (lock
) {
185 "The maximum progress value must be >= the minimum progress value");
193 * Set both the minimum and maximum progress values.
200 * @throws RuntimeException
201 * if min < 0 or if min > max
203 public void setMinMax(int min
, int max
) {
205 throw new RuntimeException("negative values not supported");
209 throw new RuntimeException(
210 "The minimum progress value must be <= the maximum progress value");
213 synchronized (lock
) {
220 * Get the total progress value (including the optional children
221 * {@link Progress}) on a {@link Progress#getMin()} to
222 * {@link Progress#getMax()} scale.
224 * @return the progress the value
226 public int getProgress() {
227 return (int) Math
.round(relativeProgress
* (max
- min
));
231 * Set the local progress value (not including the optional children
232 * {@link Progress}), on a {@link Progress#getMin()} to
233 * {@link Progress#getMax()} scale.
236 * the progress to set
238 public void setProgress(int progress
) {
239 synchronized (lock
) {
240 double childrenProgress
= relativeProgress
- relativeLocalProgress
;
242 relativeLocalProgress
= ((double) progress
) / (max
- min
);
244 setRelativeProgress(this, name
,
245 relativeLocalProgress
+ childrenProgress
);
250 * Get the total progress value (including the optional children
251 * {@link Progress}) on a 0.0 to 1.0 scale.
253 * @return the progress
255 public double getRelativeProgress() {
256 return relativeProgress
;
260 * Set the total progress value (including the optional children
261 * {@link Progress}), on a 0 to 1 scale.
263 * Will generate a changed event from this very {@link Progress}.
265 * @param relativeProgress
266 * the progress to set
268 public void setRelativeProgress(double relativeProgress
) {
269 setRelativeProgress(this, name
, relativeProgress
);
273 * Set the total progress value (including the optional children
274 * {@link Progress}), on a 0 to 1 scale.
277 * the {@link Progress} to report as the progression emitter (can
278 * be NULL, will then be considered the same as <tt>this</tt>)
280 * the current name (if it is NULL, the first non-null name in
281 * the hierarchy will overwrite it) of the {@link Progress} who
282 * emitted this change
283 * @param relativeProgress
284 * the progress to set
286 private void setRelativeProgress(Progress pg
, String name
,
287 double relativeProgress
) {
288 synchronized (lock
) {
289 relativeProgress
= Math
.max(0, relativeProgress
);
290 relativeProgress
= Math
.min(1, relativeProgress
);
291 this.relativeProgress
= relativeProgress
;
298 * Get the total progress value (including the optional children
299 * {@link Progress}) on a 0 to 1 scale.
301 * @return the progress the value
303 private int getLocalProgress() {
304 return (int) Math
.round(relativeLocalProgress
* (max
- min
));
308 * Add some value to the current progression of this {@link Progress}.
313 public void add(int step
) {
314 synchronized (lock
) {
315 setProgress(getLocalProgress() + step
);
320 * Check if the action corresponding to this {@link Progress} is done (i.e.,
321 * if its progress value == its max value).
323 * @return TRUE if it is
325 public boolean isDone() {
326 return getProgress() == max
;
330 * Mark the {@link Progress} as done by setting its value to max.
333 synchronized (lock
) {
334 double childrenProgress
= relativeProgress
- relativeLocalProgress
;
335 relativeLocalProgress
= 1 - childrenProgress
;
336 setRelativeProgress(this, name
, 1d
);
341 * Return the list of direct children of this {@link Progress}.
343 * Can return an empty list, but never NULL.
345 * @return the children (Who will think of the children??)
347 public List
<Progress
> getChildren() {
348 synchronized (lock
) {
349 return new ArrayList
<Progress
>(children
.keySet());
354 * The weight of this children if it is actually a child of this.
357 * the child to get the weight of
359 * @return NULL if this is not a child of this
361 public Double
getWeight(Progress child
) {
362 synchronized (lock
) {
363 return children
.get(child
);
368 * Notify the listeners that this {@link Progress} changed value.
371 * the emmiter, that is, the (sub-){link Progress} that just
372 * reported some change, not always the same as <tt>this</tt>
374 * the current name (if it is NULL, the first non-null name in
375 * the hierarchy will overwrite it) of the {@link Progress} who
376 * emitted this change
378 private void changed(Progress pg
, String name
) {
387 synchronized (lock
) {
388 for (ProgressListener l
: listeners
) {
389 l
.progress(pg
, name
);
395 * Add a {@link ProgressListener} that will trigger on progress changes.
397 * Note: the {@link Progress} that will be reported will be the active
398 * progress, not necessarily the same as the current one (it could be a
399 * child {@link Progress} of this {@link Progress}).
404 public void addProgressListener(ProgressListener l
) {
405 synchronized (lock
) {
406 this.listeners
.add(l
);
411 * Remove a {@link ProgressListener} that would trigger on progress changes.
416 * @return TRUE if it was found (and removed)
418 public boolean removeProgressListener(ProgressListener l
) {
419 synchronized (lock
) {
420 return this.listeners
.remove(l
);
425 * Add a child {@link Progress} of the given weight.
428 * the child {@link Progress} to add
430 * the weight (on a {@link Progress#getMin()} to
431 * {@link Progress#getMax()} scale) of this child
432 * {@link Progress} in relation to its parent
434 * @throws RuntimeException
435 * if weight exceed {@link Progress#getMax()} or if progress
436 * already has a parent
438 public void addProgress(Progress progress
, double weight
) {
439 if (weight
< min
|| weight
> max
) {
440 throw new RuntimeException(String
.format(
441 "Progress object %s cannot have a weight of %f, "
442 + "it is outside of its parent (%s) range (%d)",
443 progress
.name
, weight
, name
, max
));
446 if (progress
.parent
!= null) {
447 throw new RuntimeException(String
.format(
448 "Progress object %s cannot be added to %s, "
449 + "as it already has a parent (%s)",
450 progress
.name
, name
, progress
.parent
.name
));
453 ProgressListener progressListener
= new ProgressListener() {
455 public void progress(Progress pg
, String name
) {
456 synchronized (lock
) {
457 double total
= relativeLocalProgress
;
458 for (Entry
<Progress
, Double
> entry
: children
.entrySet()) {
459 total
+= (entry
.getValue() / (max
- min
))
460 * entry
.getKey().getRelativeProgress();
463 setRelativeProgress(pg
, name
, total
);
468 synchronized (lock
) {
469 // Should not happen but just in case
470 if (this.map
!= progress
.map
) {
471 this.map
.putAll(progress
.map
);
473 progress
.map
= this.map
;
474 progress
.parent
= this;
475 this.children
.put(progress
, weight
);
476 progress
.addProgressListener(progressListener
);
481 * Set the given value for the given key on this {@link Progress} and it's
489 public void put(Object key
, Object value
) {
494 * Return the value associated with this key as a {@link String} if any,
497 * If the value is not NULL but not a {@link String}, it will be converted
498 * via {@link Object#toString()}.
503 * @return the value or NULL
505 public String
getString(Object key
) {
506 Object value
= map
.get(key
);
511 return value
.toString();
515 * Return the value associated with this key if any, NULL if not.
520 * @return the value or NULL
522 public Object
get(Object key
) {
527 public String
toString() {
528 return "[Progress]" //
529 + (name
== null || name
.isEmpty() ?
"" : " " + name
) //
530 + ": " + getProgress() + " / " + getMax() //
531 + (children
.isEmpty() ?
""
532 : " (with " + children
.size() + " children)") //