62e46874c3e0918ff49877f74b4d7314bae0be83
[nikiroo-utils.git] / src / be / nikiroo / utils / Progress.java
1 package be.nikiroo.utils;
2
3 import java.util.ArrayList;
4 import java.util.EventListener;
5 import java.util.HashMap;
6 import java.util.List;
7 import java.util.Map;
8 import java.util.Map.Entry;
9 import java.util.Set;
10
11 /**
12 * Progress reporting system, possibly nested.
13 *
14 * @author niki
15 */
16 public class Progress {
17 public interface ProgressListener extends EventListener {
18 /**
19 * A progression event.
20 *
21 * @param progress
22 * the {@link Progress} object that generated it, not
23 * necessarily the same as the one where the listener was
24 * attached (it could be a child {@link Progress} of this
25 * {@link Progress}).
26 * @param name
27 * the first non-null name of the {@link Progress} step that
28 * generated this event
29 */
30 public void progress(Progress progress, String name);
31 }
32
33 private String name;
34 private Map<Progress, Double> children;
35 private List<ProgressListener> listeners;
36 private int min;
37 private int max;
38 private int localProgress;
39 private int progress; // children included
40
41 /**
42 * Create a new default unnamed {@link Progress}, from 0 to 100.
43 */
44 public Progress() {
45 this(null);
46 }
47
48 /**
49 * Create a new default {@link Progress}, from 0 to 100.
50 *
51 * @param name
52 * the name of this {@link Progress} step
53 */
54 public Progress(String name) {
55 this(name, 0, 100);
56 }
57
58 /**
59 * Create a new unnamed {@link Progress}, from min to max.
60 *
61 * @param min
62 * the minimum progress value (and starting value) -- must be
63 * non-negative
64 * @param max
65 * the maximum progress value
66 */
67 public Progress(int min, int max) {
68 this(null, min, max);
69 }
70
71 /**
72 * Create a new {@link Progress}, from min to max.
73 *
74 * @param name
75 * the name of this {@link Progress} step
76 * @param min
77 * the minimum progress value (and starting value) -- must be
78 * non-negative
79 * @param max
80 * the maximum progress value
81 */
82 public Progress(String name, int min, int max) {
83 this.name = name;
84 this.children = new HashMap<Progress, Double>();
85 this.listeners = new ArrayList<Progress.ProgressListener>();
86 setMinMax(min, max);
87 setProgress(min);
88 }
89
90 /**
91 * The name of this {@link Progress} step.
92 *
93 * @return the name
94 */
95 public String getName() {
96 return name;
97 }
98
99 /**
100 * The name of this {@link Progress} step.
101 *
102 * @param name
103 * the new name
104 */
105 public void setName(String name) {
106 this.name = name;
107 // will fire an action event:
108 setProgress(this.localProgress);
109 }
110
111 /**
112 * The minimum progress value.
113 *
114 * @return the min
115 */
116 public int getMin() {
117 return min;
118 }
119
120 /**
121 * The minimum progress value.
122 *
123 * @param min
124 * the min to set
125 */
126 public void setMin(int min) {
127 if (min < 0) {
128 throw new Error("negative values not supported");
129 }
130
131 if (min > max) {
132 throw new Error(
133 "The minimum progress value must be <= the maximum progress value");
134 }
135
136 this.min = min;
137 }
138
139 /**
140 * The maximum progress value.
141 *
142 * @return the max
143 */
144 public int getMax() {
145 return max;
146 }
147
148 /**
149 * The maximum progress value (must be >= the minimum progress value).
150 *
151 * @param max
152 * the max to set
153 */
154 public void setMax(int max) {
155 if (max < min) {
156 throw new Error(
157 "The maximum progress value must be >= the minimum progress value");
158 }
159
160 this.max = max;
161 }
162
163 /**
164 * Set both the minimum and maximum progress values.
165 *
166 * @param min
167 * the min
168 * @param max
169 * the max
170 */
171 public void setMinMax(int min, int max) {
172 if (min < 0) {
173 throw new Error("negative values not supported");
174 }
175
176 if (min > max) {
177 throw new Error(
178 "The minimum progress value must be <= the maximum progress value");
179 }
180
181 this.min = min;
182 this.max = max;
183 }
184
185 /**
186 * Get the total progress value (including the optional children
187 * {@link Progress}) on a {@link Progress#getMin()} to
188 * {@link Progress#getMax()} scale.
189 *
190 * @return the progress the value
191 */
192 public int getProgress() {
193 return progress;
194 }
195
196 /**
197 * Set the local progress value (not including the optional children
198 * {@link Progress}), on a {@link Progress#getMin()} to
199 * {@link Progress#getMax()} scale.
200 *
201 * @param progress
202 * the progress to set
203 */
204 public void setProgress(int progress) {
205 int diff = this.progress - this.localProgress;
206 this.localProgress = progress;
207 setTotalProgress(this, name, progress + diff);
208 }
209
210 /**
211 * Check if the action corresponding to this {@link Progress} is done (i.e.,
212 * if its progress value is >= its max value).
213 *
214 * @return TRUE if it is
215 */
216 public boolean isDone() {
217 return progress >= max;
218 }
219
220 /**
221 * Get the total progress value (including the optional children
222 * {@link Progress}) on a 0.0 to 1.0 scale.
223 *
224 * @return the progress
225 */
226 public double getRelativeProgress() {
227 return (((double) progress) / (max - min));
228 }
229
230 /**
231 * Return the list of direct children of this {@link Progress}.
232 *
233 * @return the children (who will think of them??)
234 */
235 public Set<Progress> getChildren() {
236 return children.keySet();
237 }
238
239 /**
240 * Set the total progress value (including the optional children
241 * {@link Progress}), on a {@link Progress#getMin()} to
242 * {@link Progress#getMax()} scale.
243 *
244 * @param pg
245 * the {@link Progress} to report as the progression emitter
246 * @param name
247 * the current name (if it is NULL, the first non-null name in
248 * the hierarchy will overwrite it) of the {@link Progress} who
249 * emitted this change
250 * @param progress
251 * the progress to set
252 */
253 private void setTotalProgress(Progress pg, String name, int progress) {
254 this.progress = progress;
255
256 for (ProgressListener l : listeners) {
257 l.progress(pg, name);
258 }
259 }
260
261 /**
262 * Add a {@link ProgressListener} that will trigger on progress changes.
263 * <p>
264 * Note: the {@link Progress} that will be reported will be the active
265 * progress, not necessarily the same as the current one (it could be a
266 * child {@link Progress} of this {@link Progress}).
267 *
268 * @param l
269 * the listener
270 */
271 public void addProgressListener(ProgressListener l) {
272 this.listeners.add(l);
273 }
274
275 /**
276 * Remove a {@link ProgressListener} that would trigger on progress changes.
277 *
278 * @param l
279 * the listener
280 *
281 * @return TRUE if it was found (and removed)
282 */
283 public boolean removeProgressListener(ProgressListener l) {
284 return this.listeners.remove(l);
285 }
286
287 /**
288 * Add a child {@link Progress} of the given weight.
289 *
290 * @param progress
291 * the child {@link Progress} to add
292 * @param weight
293 * the weight (on a {@link Progress#getMin()} to
294 * {@link Progress#getMax()} scale) of this child
295 * {@link Progress} in relation to its parent
296 */
297 public void addProgress(Progress progress, double weight) {
298 if (weight < min || weight > max) {
299 throw new Error(
300 "A Progress object cannot have a weight outside its parent range");
301 }
302
303 // Note: this is quite inefficient, especially with many children
304 // TODO: improve it?
305 progress.addProgressListener(new ProgressListener() {
306 public void progress(Progress pg, String name) {
307 double total = ((double) localProgress) / (max - min);
308 for (Entry<Progress, Double> entry : children.entrySet()) {
309 total += (entry.getValue() / (max - min))
310 * entry.getKey().getRelativeProgress();
311 }
312
313 if (name == null) {
314 name = Progress.this.name;
315 }
316
317 setTotalProgress(pg, name,
318 (int) Math.round(total * (max - min)));
319 }
320 });
321
322 this.children.put(progress, weight);
323 }
324 }