* A progression event.
*
* @param progress
- * the {@link Progress} object that generated it
+ * the {@link Progress} object that generated it, not
+ * necessarily the same as the one where the listener was
+ * attached (it could be a child {@link Progress} of this
+ * {@link Progress}).
* @param name
* the first non-null name of the {@link Progress} step that
* generated this event
public void progress(Progress progress, String name);
}
+ private Progress parent = null;
+ private Object lock = new Object();
private String name;
private Map<Progress, Double> children;
private List<ProgressListener> listeners;
*/
public void setName(String name) {
this.name = name;
- // will fire an action event:
- setProgress(this.localProgress);
+ changed(this);
}
/**
*
* @param min
* the min to set
+ *
+ *
+ * @throws Error
+ * if min < 0 or if min > max
*/
public void setMin(int min) {
if (min < 0) {
throw new Error("negative values not supported");
}
- if (min > max) {
- throw new Error(
- "The minimum progress value must be <= the maximum progress value");
- }
+ synchronized (getLock()) {
+ if (min > max) {
+ throw new Error(
+ "The minimum progress value must be <= the maximum progress value");
+ }
- this.min = min;
+ this.min = min;
+ }
}
/**
*
* @param max
* the max to set
+ *
+ *
+ * @throws Error
+ * if max < min
*/
public void setMax(int max) {
- if (max < min) {
- throw new Error(
- "The maximum progress value must be >= the minimum progress value");
- }
+ synchronized (getLock()) {
+ if (max < min) {
+ throw new Error(
+ "The maximum progress value must be >= the minimum progress value");
+ }
- this.max = max;
+ this.max = max;
+ }
}
/**
* the min
* @param max
* the max
+ *
+ * @throws Error
+ * if min < 0 or if min > max
*/
public void setMinMax(int min, int max) {
if (min < 0) {
"The minimum progress value must be <= the maximum progress value");
}
- this.min = min;
- this.max = max;
+ synchronized (getLock()) {
+ this.min = min;
+ this.max = max;
+ }
}
/**
* the progress to set
*/
public void setProgress(int progress) {
- int diff = this.progress - this.localProgress;
- this.localProgress = progress;
- setTotalProgress(this, name, progress + diff);
+ synchronized (getLock()) {
+ int diff = this.progress - this.localProgress;
+ this.localProgress = progress;
+ setTotalProgress(this, name, progress + diff);
+ }
+ }
+
+ /**
+ * Add some value to the current progression of this {@link Progress}.
+ *
+ * @param step
+ * the amount to add
+ */
+ public void add(int step) {
+ synchronized (getLock()) {
+ setProgress(localProgress + step);
+ }
}
/**
* Check if the action corresponding to this {@link Progress} is done (i.e.,
- * if its progress value is >= its max value).
+ * if its progress value == its max value).
*
* @return TRUE if it is
*/
return progress >= max;
}
+ /**
+ * Mark the {@link Progress} as done by setting its value to max.
+ */
+ public void done() {
+ setProgress(getMax());
+ }
+
/**
* Get the total progress value (including the optional children
* {@link Progress}) on a 0.0 to 1.0 scale.
* @return the progress
*/
public double getRelativeProgress() {
+ if (max == min) {
+ return 1;
+ }
+
return (((double) progress) / (max - min));
}
/**
* Return the list of direct children of this {@link Progress}.
*
- * @return the children (who will think of them??)
+ * @return the children (Who will think of the children??)
*/
public Set<Progress> getChildren() {
return children.keySet();
* the progress to set
*/
private void setTotalProgress(Progress pg, String name, int progress) {
- this.progress = progress;
+ synchronized (getLock()) {
+ progress = Math.max(min, progress);
+ progress = Math.min(max, progress);
+
+ if (progress != this.progress) {
+ this.progress = progress;
+ changed(pg);
+ }
+ }
+ }
+
+ /**
+ * Notify the listeners that this {@link Progress} changed value.
+ *
+ * @param pg
+ * the emmiter
+ */
+ private void changed(Progress pg) {
+ if (pg == null) {
+ pg = this;
+ }
- for (ProgressListener l : listeners) {
- l.progress(pg, name);
+ synchronized (getLock()) {
+ for (ProgressListener l : listeners) {
+ l.progress(pg, name);
+ }
}
}
/**
* Add a {@link ProgressListener} that will trigger on progress changes.
+ * <p>
+ * Note: the {@link Progress} that will be reported will be the active
+ * progress, not necessarily the same as the current one (it could be a
+ * child {@link Progress} of this {@link Progress}).
*
* @param l
* the listener
* the weight (on a {@link Progress#getMin()} to
* {@link Progress#getMax()} scale) of this child
* {@link Progress} in relation to its parent
+ *
+ * @throws Error
+ * if weight exceed {@link Progress#getMax()} or if progress
+ * already has a parent
*/
public void addProgress(Progress progress, double weight) {
if (weight < min || weight > max) {
- throw new Error(
- "A Progress object cannot have a weight outside its parent range");
+ throw new Error(String.format(
+ "Progress object %s cannot have a weight of %f, "
+ + "it is outside of its parent (%s) range (%f)",
+ progress.name, weight, name, max));
+ }
+
+ if (progress.parent != null) {
+ throw new Error(String.format(
+ "Progress object %s cannot be added to %s, "
+ + "as it already has a parent (%s)", progress.name,
+ name, progress.parent.name));
}
- // Note: this is quite inefficient, especially with many children
- // TODO: improve it?
progress.addProgressListener(new ProgressListener() {
public void progress(Progress pg, String name) {
- double total = ((double) localProgress) / (max - min);
- for (Entry<Progress, Double> entry : children.entrySet()) {
- total += (entry.getValue() / (max - min))
- * entry.getKey().getRelativeProgress();
- }
-
- if (name == null) {
- name = Progress.this.name;
+ synchronized (getLock()) {
+ double total = ((double) localProgress) / (max - min);
+ for (Entry<Progress, Double> entry : children.entrySet()) {
+ total += (entry.getValue() / (max - min))
+ * entry.getKey().getRelativeProgress();
+ }
+
+ if (name == null) {
+ name = Progress.this.name;
+ }
+
+ setTotalProgress(pg, name,
+ (int) Math.round(total * (max - min)));
}
-
- setTotalProgress(pg, name,
- (int) Math.round(total * (max - min)));
}
});
this.children.put(progress, weight);
}
+
+ /**
+ * The lock object to use (this one or the recursively-parent one).
+ *
+ * @return the lock object to use
+ */
+ private Object getLock() {
+ synchronized (lock) {
+ if (parent != null) {
+ return parent.getLock();
+ }
+
+ return lock;
+ }
+ }
}