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
;
12 * Progress reporting system, possibly nested.
16 public class Progress
{
18 * This event listener is designed to report progress events from
23 public interface ProgressListener
extends EventListener
{
25 * A progression event.
28 * the {@link Progress} object that generated it, not
29 * necessarily the same as the one where the listener was
30 * attached (it could be a child {@link Progress} of this
33 * the first non-null name of the {@link Progress} step that
34 * generated this event
36 public void progress(Progress progress
, String name
);
39 private Progress parent
= null;
40 private Object lock
= new Object();
42 private Map
<Progress
, Double
> children
;
43 private List
<ProgressListener
> listeners
;
46 private double relativeLocalProgress
;
47 private double relativeProgress
; // children included
50 * Create a new default unnamed {@link Progress}, from 0 to 100.
57 * Create a new default {@link Progress}, from 0 to 100.
60 * the name of this {@link Progress} step
62 public Progress(String name
) {
67 * Create a new unnamed {@link Progress}, from min to max.
70 * the minimum progress value (and starting value) -- must be
73 * the maximum progress value
75 public Progress(int min
, int max
) {
80 * Create a new {@link Progress}, from min to max.
83 * the name of this {@link Progress} step
85 * the minimum progress value (and starting value) -- must be
88 * the maximum progress value
90 public Progress(String name
, int min
, int max
) {
92 this.children
= new HashMap
<Progress
, Double
>();
93 this.listeners
= new ArrayList
<Progress
.ProgressListener
>();
99 * The name of this {@link Progress} step.
103 public String
getName() {
108 * The name of this {@link Progress} step.
113 public void setName(String name
) {
119 * The minimum progress value.
123 public int getMin() {
128 * The minimum progress value.
134 * @throws RuntimeException
135 * if min < 0 or if min > max
137 public void setMin(int min
) {
139 throw new RuntimeException("negative values not supported");
142 synchronized (getLock()) {
144 throw new RuntimeException(
145 "The minimum progress value must be <= the maximum progress value");
153 * The maximum progress value.
157 public int getMax() {
162 * The maximum progress value (must be >= the minimum progress value).
168 * @throws RuntimeException
171 public void setMax(int max
) {
172 synchronized (getLock()) {
175 "The maximum progress value must be >= the minimum progress value");
183 * Set both the minimum and maximum progress values.
190 * @throws RuntimeException
191 * if min < 0 or if min > max
193 public void setMinMax(int min
, int max
) {
195 throw new RuntimeException("negative values not supported");
199 throw new RuntimeException(
200 "The minimum progress value must be <= the maximum progress value");
203 synchronized (getLock()) {
210 * Get the total progress value (including the optional children
211 * {@link Progress}) on a {@link Progress#getMin()} to
212 * {@link Progress#getMax()} scale.
214 * @return the progress the value
216 public int getProgress() {
217 return (int) Math
.round(relativeProgress
* (max
- min
));
221 * Set the local progress value (not including the optional children
222 * {@link Progress}), on a {@link Progress#getMin()} to
223 * {@link Progress#getMax()} scale.
226 * the progress to set
228 public void setProgress(int progress
) {
229 synchronized (getLock()) {
230 double childrenProgress
= relativeProgress
- relativeLocalProgress
;
232 relativeLocalProgress
= ((double) progress
) / (max
- min
);
234 setRelativeProgress(this, name
, relativeLocalProgress
240 * Get the total progress value (including the optional children
241 * {@link Progress}) on a 0.0 to 1.0 scale.
243 * @return the progress
245 public double getRelativeProgress() {
246 return relativeProgress
;
250 * Set the total progress value (including the optional children
251 * {@link Progress}), on a 0 to 1 scale.
254 * the {@link Progress} to report as the progression emitter
256 * the current name (if it is NULL, the first non-null name in
257 * the hierarchy will overwrite it) of the {@link Progress} who
258 * emitted this change
259 * @param relativeProgress
260 * the progress to set
262 private void setRelativeProgress(Progress pg
, String name
,
263 double relativeProgress
) {
264 synchronized (getLock()) {
265 relativeProgress
= Math
.max(0, relativeProgress
);
266 relativeProgress
= Math
.min(1, relativeProgress
);
267 this.relativeProgress
= relativeProgress
;
274 * Get the total progress value (including the optional children
275 * {@link Progress}) on a 0 to 1 scale.
277 * @return the progress the value
279 private int getLocalProgress() {
280 return (int) Math
.round(relativeLocalProgress
* (max
- min
));
284 * Add some value to the current progression of this {@link Progress}.
289 public void add(int step
) {
290 synchronized (getLock()) {
291 setProgress(getLocalProgress() + step
);
296 * Check if the action corresponding to this {@link Progress} is done (i.e.,
297 * if its progress value == its max value).
299 * @return TRUE if it is
301 public boolean isDone() {
302 return getProgress() == max
;
306 * Mark the {@link Progress} as done by setting its value to max.
309 synchronized (getLock()) {
310 double childrenProgress
= relativeProgress
- relativeLocalProgress
;
311 relativeLocalProgress
= 1 - childrenProgress
;
312 setRelativeProgress(this, name
, 1d
);
317 * Return the list of direct children of this {@link Progress}.
319 * @return the children (Who will think of the children??)
321 public Set
<Progress
> getChildren() {
322 return children
.keySet();
326 * Notify the listeners that this {@link Progress} changed value.
331 * the current name (if it is NULL, the first non-null name in
332 * the hierarchy will overwrite it) of the {@link Progress} who
333 * emitted this change
335 private void changed(Progress pg
, String name
) {
344 synchronized (getLock()) {
345 for (ProgressListener l
: listeners
) {
346 l
.progress(pg
, name
);
352 * Add a {@link ProgressListener} that will trigger on progress changes.
354 * Note: the {@link Progress} that will be reported will be the active
355 * progress, not necessarily the same as the current one (it could be a
356 * child {@link Progress} of this {@link Progress}).
361 public void addProgressListener(ProgressListener l
) {
362 this.listeners
.add(l
);
366 * Remove a {@link ProgressListener} that would trigger on progress changes.
371 * @return TRUE if it was found (and removed)
373 public boolean removeProgressListener(ProgressListener l
) {
374 return this.listeners
.remove(l
);
378 * Add a child {@link Progress} of the given weight.
381 * the child {@link Progress} to add
383 * the weight (on a {@link Progress#getMin()} to
384 * {@link Progress#getMax()} scale) of this child
385 * {@link Progress} in relation to its parent
387 * @throws RuntimeException
388 * if weight exceed {@link Progress#getMax()} or if progress
389 * already has a parent
391 public void addProgress(Progress progress
, double weight
) {
392 if (weight
< min
|| weight
> max
) {
393 throw new RuntimeException(String
.format(
394 "Progress object %s cannot have a weight of %f, "
395 + "it is outside of its parent (%s) range (%f)",
396 progress
.name
, weight
, name
, max
));
399 if (progress
.parent
!= null) {
400 throw new RuntimeException(String
.format(
401 "Progress object %s cannot be added to %s, "
402 + "as it already has a parent (%s)", progress
.name
,
403 name
, progress
.parent
.name
));
406 progress
.addProgressListener(new ProgressListener() {
408 public void progress(Progress pg
, String name
) {
409 synchronized (getLock()) {
410 double total
= relativeLocalProgress
;
411 for (Entry
<Progress
, Double
> entry
: children
.entrySet()) {
412 total
+= (entry
.getValue() / (max
- min
))
413 * entry
.getKey().getRelativeProgress();
416 setRelativeProgress(pg
, name
, total
);
421 this.children
.put(progress
, weight
);
425 * The lock object to use (this one or the recursively-parent one).
427 * @return the lock object to use
429 private Object
getLock() {
430 synchronized (lock
) {
431 if (parent
!= null) {
432 return parent
.getLock();