001: /*
002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
003: *
004: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
005: *
006: * The contents of this file are subject to the terms of either the GNU General
007: * Public License Version 2 only ("GPL") or the Common Development and Distribution
008: * License("CDDL") (collectively, the "License"). You may not use this file except in
009: * compliance with the License. You can obtain a copy of the License at
010: * http://www.netbeans.org/cddl-gplv2.html or nbbuild/licenses/CDDL-GPL-2-CP. See the
011: * License for the specific language governing permissions and limitations under the
012: * License. When distributing the software, include this License Header Notice in
013: * each file and include the License file at nbbuild/licenses/CDDL-GPL-2-CP. Sun
014: * designates this particular file as subject to the "Classpath" exception as
015: * provided by Sun in the GPL Version 2 section of the License file that
016: * accompanied this code. If applicable, add the following below the License Header,
017: * with the fields enclosed by brackets [] replaced by your own identifying
018: * information: "Portions Copyrighted [year] [name of copyright owner]"
019: *
020: * Contributor(s):
021: *
022: * The Original Software is NetBeans. The Initial Developer of the Original Software
023: * is Sun Microsystems, Inc. Portions Copyright 1997-2007 Sun Microsystems, Inc. All
024: * Rights Reserved.
025: *
026: * If you wish your version of this file to be governed by only the CDDL or only the
027: * GPL Version 2, indicate your decision by adding "[Contributor] elects to include
028: * this software in this distribution under the [CDDL or GPL Version 2] license." If
029: * you do not indicate a single choice of license, a recipient has the option to
030: * distribute your version of this file under either the CDDL, the GPL Version 2 or
031: * to extend the choice of license to its licensees as provided above. However, if
032: * you add GPL Version 2 code and therefore, elected the GPL Version 2 license, then
033: * the option applies only if the new code is made subject to such option by the
034: * copyright holder.
035: */
036:
037: package org.netbeans.installer.utils.progress;
038:
039: import java.util.ArrayList;
040: import java.util.List;
041: import org.netbeans.installer.utils.ResourceUtils;
042: import org.netbeans.installer.utils.StringUtils;
043:
044: /**
045: * This class encapsulates data that describes the course of an operation, such as
046: * the operation description, detailed status string and completion percentage.
047: *
048: * <p>
049: * A typical usecase would be for a client to costruct an instance of this object
050: * and pass it to the operation handler, which will update the progress as the
051: * operation goes on. The client can register itself as a listener for the progress
052: * object; this way it will receive notifications on the progress updates.
053: *
054: * <p>
055: * The client can also use the built-in cancellation facility to inform the
056: * operation handler of the necessity to break the operation and return. Whether
057: * the operation will be stopped, however, solely depends on the operation handler's
058: * politeness.
059: *
060: * <p>
061: * In some cases the opreation handler is not capable of working directly with the
062: * progress that was passed to it. In this case it should construct a specialized
063: * {@link Progress} instance and use the synchronization facility to keep these two
064: * in sync.
065: *
066: * @see #addProgressListener(ProgressListener)
067: * @see #setCanceled(boolean)
068: * @see #synchronizeFrom(Progress)
069: * @see CompositeProgress
070: *
071: * @author Kirill Sorokin
072: *
073: * @since 1.0
074: */
075: public class Progress {
076: /////////////////////////////////////////////////////////////////////////////////
077: // Instance
078: /**
079: * The title (i.e. human readable description) of the progress. The title is
080: * expected not to change often during the course of the progress.
081: */
082: protected String title;
083:
084: /**
085: * The detailed status of the progress. As opposed to the title, the detail
086: * is expected to change with every couple changes in the procentage.
087: */
088: protected String detail;
089:
090: /**
091: * The current state of the progress expressed in percents. Obviously the most
092: * often changed property of the progress.
093: */
094: protected int percentage;
095:
096: /**
097: * A flag which indicates whether or not the progress has been canceled. It is
098: * expected that the updater of the progress will check the value of this
099: * flag and act accordingly. It should be used to establish some basic
100: * communication between the invoker of a lengthy operation and the code which
101: * actually performs the operation.
102: */
103: protected boolean canceled;
104:
105: /**
106: * The listener which controls the synchronization between two progresses.
107: */
108: private ProgressListener synchronizer;
109:
110: /**
111: * The source of the synchronization data.
112: */
113: private Progress source;
114:
115: /**
116: * The list of registered progress listeners.
117: */
118: private List<ProgressListener> listeners;
119:
120: // constructors /////////////////////////////////////////////////////////////////
121: /**
122: * Creates a new {@link Progress} instance. The new instance has its
123: * <code>title</code> and <code>detail</code> set to empty strings,
124: * <code>percentage</code> to {@link #START} and <code>canceled</code> to
125: * <code>false</code>.
126: */
127: public Progress() {
128: title = StringUtils.EMPTY_STRING;
129: detail = StringUtils.EMPTY_STRING;
130: percentage = START;
131:
132: canceled = false;
133:
134: synchronizer = null;
135: source = null;
136:
137: listeners = new ArrayList<ProgressListener>();
138: }
139:
140: /**
141: * Creates a new {@link Progress} instance and registers the supplied listener.
142: *
143: * <p>
144: * It's important to remember that listeners in progress are not implemented via
145: * weak references and hence should be handled with care. The most common case
146: * would be to dump the complete progresses tree together with all listeners.
147: * However if it's not the case, be sure to remove the listener with the
148: * {@link #removeProgressListener(ProgressListener)} method.
149: *
150: * @param initialListener A progress listener to register upon progress
151: * creation.
152: *
153: * @see #Progress()
154: * @see #removeProgressListener(ProgressListener)
155: */
156: public Progress(final ProgressListener initialListener) {
157: this ();
158:
159: addProgressListener(initialListener);
160: }
161:
162: // getters/setters //////////////////////////////////////////////////////////////
163: /**
164: * Returns the value of the <code>title</code> property.
165: *
166: * @return The value of the <code>title</code> property.
167: */
168: public String getTitle() {
169: return title;
170: }
171:
172: /**
173: * Sets the value of the <code>title</code> property. The value is updated only
174: * if the supplied title is different from the current. Also the progress
175: * listeners are notified only if the update actually took place.
176: *
177: * @param title The new value for the <code>title</code> property.
178: */
179: public void setTitle(final String title) {
180: if (!this .title.equals(title)) {
181: this .title = title;
182:
183: notifyListeners();
184: }
185: }
186:
187: /**
188: * Returns the value of the <code>detail</code> property.
189: *
190: * @return The value of the <code>detail</code> property.
191: */
192: public String getDetail() {
193: return detail;
194: }
195:
196: /**
197: * Sets the value of the <code>detail</code> property. The value is updated only
198: * if the supplied detail is different from the current. Also the progress
199: * listeners are notified only if the update actually took place.
200: *
201: * @param detail The new value for the <code>detail</code> property.
202: */
203: public void setDetail(final String detail) {
204: if (!this .detail.equals(detail)) {
205: this .detail = detail;
206:
207: notifyListeners();
208: }
209: }
210:
211: /**
212: * Returns the value of the <code>percentage</code> property.
213: *
214: * @return The value of the <code>percentage</code> property.
215: */
216: public int getPercentage() {
217: return percentage;
218: }
219:
220: /**
221: * Sets the value of the <code>percentage</code> property. The value is updated
222: * only if the supplied percentage is different from the current. Also the
223: * progress listeners are notified only if the update actually took place.
224: *
225: * @param percentage The new value for the <code>percentage</code> property.
226: *
227: * @throws {@link IllegalArgumentException} if the supplied percentage cannot be
228: * set.
229: */
230: public void setPercentage(final int percentage) {
231: if (this .percentage != percentage) {
232: if ((percentage < START) || (percentage > COMPLETE)) {
233: throw new IllegalArgumentException(StringUtils.format(
234: ERROR_WRONG_PERCENTAGE, percentage, START,
235: COMPLETE));
236: }
237:
238: this .percentage = percentage;
239:
240: notifyListeners();
241: }
242: }
243:
244: /**
245: * Sets the value of the <code>percentage</code> property. This method is the
246: * same as the {@link #setPercentage(int)} one, with the only difference that
247: * it accepts a <code>long</code> parameter. The parameter is converted to an
248: * <code>int</code> and the corresponding method is called.
249: *
250: * @param percentage The new value for the <code>percentage</code> property.
251: *
252: * @throws {@link IllegalArgumentException} if the supplied percentage cannot be
253: * set.
254: */
255: public void setPercentage(final long percentage) {
256: setPercentage((int) percentage);
257: }
258:
259: /**
260: * Adds the specified amount to the <code>percentage</code> property. The added
261: * amount can be either positive or negative. Teh percentage value will be
262: * updated only if the result of the addition is different from the current
263: * percentage. Also the listeners are notified only is the update actually took
264: * place.
265: *
266: * @param addition The amount to add to the <code>percentage</code> property.
267: *
268: * @throws {@link IllegalArgumentException} if the supplied percentage cannot be
269: * added.
270: */
271: public void addPercentage(final int addition) {
272: final int result = percentage + addition;
273:
274: if (this .percentage != result) {
275: if ((result < START) || (result > COMPLETE)) {
276: throw new IllegalArgumentException(StringUtils
277: .format(ERROR_WRONG_PERCENTAGE, result, START,
278: COMPLETE));
279: }
280:
281: this .percentage = result;
282:
283: notifyListeners();
284: }
285: }
286:
287: /**
288: * Returns the value of the <code>canceled</code> property.
289: *
290: * @return The value of the <code>canceled</code> property.
291: */
292: public boolean isCanceled() {
293: return canceled;
294: }
295:
296: /**
297: * Sets the value of the <code>canceled</code> property. The value is updated
298: * only if the supplied value is different from the current. Also the progress
299: * listeners are notified only if the update actually took place.
300: *
301: * <p>
302: * If the progress is in being synchronized from another progress. The cancelled
303: * status will be propagated to the synchronization source.
304: *
305: * @param canceled The new value for the <code>canceled</code> property.
306: */
307: public void setCanceled(final boolean canceled) {
308: if (this .canceled != canceled) {
309: this .canceled = canceled;
310:
311: // propagate the cancel status to the source
312: if (source != null) {
313: source.setCanceled(canceled);
314: }
315:
316: notifyListeners();
317: }
318: }
319:
320: // synchronization //////////////////////////////////////////////////////////////
321: /**
322: * Sets up the synchronization of this progress object from the specified
323: * source. If there was a synchronization link already set up, it is removed.
324: *
325: * <p>
326: * Once the synchronization is established, the methods that set the core
327: * progress properties do not have any effect - the properties will be correctly
328: * set, but will be overwritten with the next source update.
329: *
330: * <p>
331: * In order to remove the synchronization link on this progress, just supply
332: * <code>null</code> as the parameter.
333: *
334: * @param progress A progress object from which to set up the synchronization,
335: * or <code>null</code> to cancel the synchronization.
336: */
337: public void synchronizeFrom(final Progress progress) {
338: if (source != null) {
339: source.removeProgressListener(synchronizer);
340: }
341:
342: if (progress != null) {
343: synchronizer = new ProgressListener() {
344: public void progressUpdated(Progress progress) {
345: setTitle(progress.getTitle());
346: setDetail(progress.getDetail());
347: setPercentage(progress.getPercentage());
348: }
349: };
350:
351: source = progress;
352: source.addProgressListener(synchronizer);
353: }
354: }
355:
356: /**
357: * Sets up the reverse synchronization of this progress object from the
358: * specified source.
359: *
360: * <p>
361: * This method is very similar to the {@link #synchronizeFrom(Progress)} method.
362: * The only difference is in that instead of setting the percentage of this
363: * progress object to the percentage of the source, it is set to the difference
364: * between {@link #COMPLETE} and the source's percentage.
365: *
366: * <p>
367: * In order to remove the synchronization link on this progress, just supply
368: * <code>null</code> as the parameter.
369: *
370: * @param progress A progress object from which to set up the synchronization,
371: * or <code>null</code> to cancel the synchronization.
372: *
373: * @see #synchronizeFrom(Progress)
374: */
375: public void reverseSynchronizeFrom(final Progress progress) {
376: // clear the current source
377: if (source != null) {
378: source.removeProgressListener(synchronizer);
379: }
380:
381: if (progress != null) {
382: synchronizer = new ProgressListener() {
383: public void progressUpdated(Progress progress) {
384: setTitle(progress.getTitle());
385: setDetail(progress.getDetail());
386: setPercentage(Progress.COMPLETE
387: - progress.getPercentage());
388: }
389: };
390:
391: source = progress;
392: source.addProgressListener(synchronizer);
393: }
394: }
395:
396: /**
397: * Sets up the synchronization of this progress object to the specified target.
398: * This method just calls {@link #synchronizeFrom(Progress)} on the target
399: * progress, supplying itself as the parameter.
400: *
401: * @param progress A progress object to which to set up the synchronization.
402: *
403: * @see #synchronizeFrom(Progress)
404: */
405: public void synchronizeTo(final Progress progress) {
406: progress.synchronizeFrom(this );
407: }
408:
409: /**
410: * Sets up the reverse synchronization of this progress object to the specified
411: * target. This method just calls {@link #reverseSynchronizeFrom(Progress)} on
412: * the target progress, supplying itself as the parameter.
413: *
414: * @param progress A progress object to which to set up the synchronization.
415: *
416: * @see #reverseSynchronizeFrom(Progress)
417: */
418: public void reverseSynchronizeTo(final Progress progress) {
419: progress.reverseSynchronizeFrom(this );
420: }
421:
422: // listeners ////////////////////////////////////////////////////////////////////
423: /**
424: * Adds (registers) a progress listener to this progrss object. If the argument
425: * is null, no action will be performed.
426: *
427: * <p>
428: * In most cases a single {@link Progress} instance will have only one listener,
429: * so it is feasible to use the specialized constructor overlod instead of this
430: * method.
431: *
432: * @param listener A progress listener to add.
433: *
434: * @see ProgressListener
435: * @see #Progress(ProgressListener)
436: */
437: public void addProgressListener(final ProgressListener listener) {
438: if (listener == null)
439: return;
440:
441: synchronized (listeners) {
442: listeners.add(listener);
443: }
444: }
445:
446: /**
447: * Removes (unregisters) a progress listener from this progress object. If the
448: * argument is null or is not present in the listeners list, no action will
449: * be performed.
450: *
451: * @param listener A progress listener to remove.
452: *
453: * @see ProgressListener
454: */
455: public void removeProgressListener(final ProgressListener listener) {
456: if (listener == null)
457: return;
458:
459: synchronized (listeners) {
460: listeners.remove(listener);
461: }
462: }
463:
464: /**
465: * Notifies the registered listeners that one or more of the progress'
466: * properties has changed.
467: *
468: * @see ProgressListener
469: */
470: protected void notifyListeners() {
471: final ProgressListener[] clone;
472:
473: synchronized (listeners) {
474: clone = listeners.toArray(new ProgressListener[listeners
475: .size()]);
476: }
477:
478: for (ProgressListener listener : clone) {
479: listener.progressUpdated(this );
480: }
481: }
482:
483: /////////////////////////////////////////////////////////////////////////////////
484: // Constants
485: /**
486: * The initial (minimum) value of a progress' percentage.
487: */
488: public static final int START = 0;
489:
490: /**
491: * The final (maximum) value of a progress' percentage.
492: */
493: public static final int COMPLETE = 100;
494:
495: /**
496: * The error message which will be displayed when a user tries to set an invalid
497: * percentage.
498: */
499: public static final String ERROR_WRONG_PERCENTAGE = ResourceUtils
500: .getString(Progress.class, "P.error.percentage"); // NOI18N
501: }
|