X-Git-Url: http://git.nikiroo.be/?a=blobdiff_plain;f=src%2Fbe%2Fnikiroo%2Futils%2FProgress.java;h=748d4a666c377123ff2eadd29c840ec4baf402e5;hb=9991e4e10cd1cb607210ecfda9ed48232574be21;hp=bea57845bcefaf4a8fd336d8e284750ae8488f8c;hpb=2998b78ae098aff12c0f8cfad2a6bc5303bb33e0;p=fanfix.git diff --git a/src/be/nikiroo/utils/Progress.java b/src/be/nikiroo/utils/Progress.java index bea5784..748d4a6 100644 --- a/src/be/nikiroo/utils/Progress.java +++ b/src/be/nikiroo/utils/Progress.java @@ -6,20 +6,38 @@ import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; -import java.util.Set; /** * Progress reporting system, possibly nested. + *

+ * A {@link Progress} can have a name, and that name will be reported through + * the event system (it will report the first non-null name in the stack from + * the {@link Progress} from which the event originated to the parent the event + * is listened on). + *

+ * The {@link Progress} also has a table of keys/values shared amongst all the + * hierarchy (note that when adding a {@link Progress} to others, its values + * will be prioritized if some with the same keys were already present in the + * hierarchy). * * @author niki */ public class Progress { + /** + * This event listener is designed to report progress events from + * {@link Progress}. + * + * @author niki + */ public interface ProgressListener extends EventListener { /** * 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 @@ -27,13 +45,16 @@ public class Progress { public void progress(Progress progress, String name); } + private Map map = new HashMap(); + private Progress parent = null; + private Object lock = new Object(); private String name; private Map children; private List listeners; private int min; private int max; - private int localProgress; - private int progress; // children included + private double relativeLocalProgress; + private double relativeProgress; // children included /** * Create a new default unnamed {@link Progress}, from 0 to 100. @@ -101,8 +122,7 @@ public class Progress { */ public void setName(String name) { this.name = name; - // will fire an action event: - setProgress(this.localProgress); + changed(this, name); } /** @@ -119,18 +139,24 @@ public class Progress { * * @param min * the min to set + * + * + * @throws RuntimeException + * if min < 0 or if min > max */ public void setMin(int min) { if (min < 0) { - throw new Error("negative values not supported"); + throw new RuntimeException("negative values not supported"); } - if (min > max) { - throw new Error( - "The minimum progress value must be <= the maximum progress value"); - } + synchronized (lock) { + if (min > max) { + throw new RuntimeException( + "The minimum progress value must be <= the maximum progress value"); + } - this.min = min; + this.min = min; + } } /** @@ -147,14 +173,20 @@ public class Progress { * * @param max * the max to set + * + * + * @throws RuntimeException + * 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 (lock) { + if (max < min) { + throw new Error( + "The maximum progress value must be >= the minimum progress value"); + } - this.max = max; + this.max = max; + } } /** @@ -164,19 +196,24 @@ public class Progress { * the min * @param max * the max + * + * @throws RuntimeException + * if min < 0 or if min > max */ public void setMinMax(int min, int max) { if (min < 0) { - throw new Error("negative values not supported"); + throw new RuntimeException("negative values not supported"); } if (min > max) { - throw new Error( + throw new RuntimeException( "The minimum progress value must be <= the maximum progress value"); } - this.min = min; - this.max = max; + synchronized (lock) { + this.min = min; + this.max = max; + } } /** @@ -187,7 +224,7 @@ public class Progress { * @return the progress the value */ public int getProgress() { - return progress; + return (int) Math.round(relativeProgress * (max - min)); } /** @@ -199,70 +236,145 @@ public class Progress { * the progress to set */ public void setProgress(int progress) { - int diff = this.progress - this.localProgress; - this.localProgress = progress; - setTotalProgress(this, name, progress + diff); + synchronized (lock) { + double childrenProgress = relativeProgress - relativeLocalProgress; + + relativeLocalProgress = ((double) progress) / (max - min); + + setRelativeProgress(this, name, relativeLocalProgress + + childrenProgress); + } + } + + /** + * 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() { + return relativeProgress; + } + + /** + * Set the total progress value (including the optional children + * {@link Progress}), on a 0 to 1 scale. + * + * @param pg + * the {@link Progress} to report as the progression emitter + * @param name + * the current name (if it is NULL, the first non-null name in + * the hierarchy will overwrite it) of the {@link Progress} who + * emitted this change + * @param relativeProgress + * the progress to set + */ + private void setRelativeProgress(Progress pg, String name, + double relativeProgress) { + synchronized (lock) { + relativeProgress = Math.max(0, relativeProgress); + relativeProgress = Math.min(1, relativeProgress); + this.relativeProgress = relativeProgress; + + changed(pg, name); + } + } + + /** + * Get the total progress value (including the optional children + * {@link Progress}) on a 0 to 1 scale. + * + * @return the progress the value + */ + private int getLocalProgress() { + return (int) Math.round(relativeLocalProgress * (max - min)); + } + + /** + * Add some value to the current progression of this {@link Progress}. + * + * @param step + * the amount to add + */ + public void add(int step) { + synchronized (lock) { + setProgress(getLocalProgress() + 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 */ public boolean isDone() { - return progress >= max; + return getProgress() == max; } /** - * Get the total progress value (including the optional children - * {@link Progress}) on a 0.0 to 1.0 scale. - * - * @return the progress + * Mark the {@link Progress} as done by setting its value to max. */ - public double getRelativeProgress() { - return (((double) progress) / (max - min)); + public void done() { + synchronized (lock) { + double childrenProgress = relativeProgress - relativeLocalProgress; + relativeLocalProgress = 1 - childrenProgress; + setRelativeProgress(this, name, 1d); + } } /** * 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 getChildren() { - return children.keySet(); + public List getChildren() { + synchronized (lock) { + return new ArrayList(children.keySet()); + } } /** - * Set the total progress value (including the optional children - * {@link Progress}), on a {@link Progress#getMin()} to - * {@link Progress#getMax()} scale. + * Notify the listeners that this {@link Progress} changed value. * * @param pg - * the {@link Progress} to report as the progression emitter + * the emmiter, that is, the (sub-){link Progress} that just + * reported some change, not always the same as this * @param name * the current name (if it is NULL, the first non-null name in * the hierarchy will overwrite it) of the {@link Progress} who * emitted this change - * @param progress - * the progress to set */ - private void setTotalProgress(Progress pg, String name, int progress) { - this.progress = progress; + private void changed(Progress pg, String name) { + if (pg == null) { + pg = this; + } + + if (name == null) { + name = this.name; + } - for (ProgressListener l : listeners) { - l.progress(pg, name); + synchronized (lock) { + for (ProgressListener l : listeners) { + l.progress(pg, name); + } } } /** * Add a {@link ProgressListener} that will trigger on progress changes. + *

+ * 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 */ public void addProgressListener(ProgressListener l) { - this.listeners.add(l); + synchronized (lock) { + this.listeners.add(l); + } } /** @@ -274,7 +386,9 @@ public class Progress { * @return TRUE if it was found (and removed) */ public boolean removeProgressListener(ProgressListener l) { - return this.listeners.remove(l); + synchronized (lock) { + return this.listeners.remove(l); + } } /** @@ -286,32 +400,96 @@ public class Progress { * the weight (on a {@link Progress#getMin()} to * {@link Progress#getMax()} scale) of this child * {@link Progress} in relation to its parent + * + * @throws RuntimeException + * 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 RuntimeException(String.format( + "Progress object %s cannot have a weight of %f, " + + "it is outside of its parent (%s) range (%d)", + progress.name, weight, name, max)); } - // Note: this is quite inefficient, especially with many children - // TODO: improve it? - progress.addProgressListener(new ProgressListener() { - public void progress(Progress progress, String name) { - double total = ((double) localProgress) / (max - min); - for (Entry entry : children.entrySet()) { - total += (entry.getValue() / (max - min)) - * entry.getKey().getRelativeProgress(); - } + if (progress.parent != null) { + throw new RuntimeException(String.format( + "Progress object %s cannot be added to %s, " + + "as it already has a parent (%s)", progress.name, + name, progress.parent.name)); + } - if (name == null) { - name = Progress.this.name; + ProgressListener progressListener = new ProgressListener() { + @Override + public void progress(Progress pg, String name) { + synchronized (lock) { + double total = relativeLocalProgress; + for (Entry entry : children.entrySet()) { + total += (entry.getValue() / (max - min)) + * entry.getKey().getRelativeProgress(); + } + + setRelativeProgress(pg, name, total); } + } + }; - setTotalProgress(progress, name, - (int) Math.round(total * (max - min))); + synchronized (lock) { + // Should not happen but just in case + if (this.map != progress.map) { + this.map.putAll(progress.map); } - }); + progress.map = this.map; + progress.parent = this; + this.children.put(progress, weight); + progress.addProgressListener(progressListener); + } + } + + /** + * Set the given value for the given key on this {@link Progress} and it's + * children. + * + * @param key + * the key + * @param value + * the value + */ + public void put(Object key, Object value) { + map.put(key, value); + } - this.children.put(progress, weight); + /** + * Return the value associated with this key as a {@link String} if any, + * NULL if not. + *

+ * If the value is not NULL but not a {@link String}, it will be converted + * via {@link Object#toString()}. + * + * @param key + * the key to check + * + * @return the value or NULL + */ + public String getString(Object key) { + Object value = map.get(key); + if (value == null) { + return null; + } + + return value.toString(); + } + + /** + * Return the value associated with this key if any, NULL if not. + * + * @param key + * the key to check + * + * @return the value or NULL + */ + public Object get(Object key) { + return map.get(key); } }