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.LinkedList;
040: import java.util.List;
041: import org.netbeans.installer.utils.ResourceUtils;
042: import org.netbeans.installer.utils.StringUtils;
043:
044: /**
045: * A specialized subclass of {@link Progress} which is capable having child
046: * progresses.
047: *
048: * <p>
049: * Each registered child has an assosiated percentage number. It is called "relative
050: * percentage" and basically means which part of the composite's total percentage
051: * does this child "own". Additionally the composite has its "own percentage", which
052: * is independent of the children.
053: *
054: * <p>
055: * The composite registers itself as a listener on each added child and updates its
056: * total percentage according to the children's states. It also propagates the
057: * change information to its own listeners.
058: *
059: * <p>
060: * The synchronization behavior of a composite progress is not completely defined,
061: * if the composite serves as the target of a synchronization link. It will behave
062: * exactly as a regular progress if it has no children, but in the other case,
063: * errors may arise if the synchronization routine attempts to set an illegal
064: * percentage, i.e. if this "attempted" percentage will conflict with the children.
065: *
066: * <p>
067: * An additional capability of a composite progress is its ability to fetch
068: * children's detail status whenever a child is updated. This bahaior is controlled
069: * by the <code>synchronizeDetails</code> flag.
070: *
071: * @author Kirill Sorokin
072: *
073: * @since 1.0
074: */
075: public final class CompositeProgress extends Progress implements
076: ProgressListener {
077: /////////////////////////////////////////////////////////////////////////////////
078: // Instance
079: /**
080: * The list of child progresses.
081: */
082: private List<Progress> progresses;
083:
084: /**
085: * The list of childrens' relative percentages.
086: */
087: private List<Integer> percentages;
088:
089: /**
090: * The flag which controls whether the composite will adopt its children's
091: * detail values. If it's set to true, then whenever a child's state changes,
092: * the composite will update its detail with the detail of the just updated
093: * child.
094: */
095: private boolean synchronizeDetails;
096:
097: // constructors /////////////////////////////////////////////////////////////////
098: /**
099: * Creates a new {@link CompositeProgress} instance.
100: */
101: public CompositeProgress() {
102: super ();
103:
104: progresses = new LinkedList<Progress>();
105: percentages = new LinkedList<Integer>();
106:
107: synchronizeDetails = false;
108: }
109:
110: /**
111: * Creates a new {@link CompositeProgress} instance and registers the supplied
112: * listener.
113: *
114: * @param initialListener A progress listener to register upon progress
115: * creation.
116: *
117: * @see #CompositeProgress()
118: */
119: public CompositeProgress(final ProgressListener initialListener) {
120: this ();
121:
122: addProgressListener(initialListener);
123: }
124:
125: // overrides ////////////////////////////////////////////////////////////////////
126: /**
127: * Returns the total percentage of the composite, i.e. its own percentage and
128: * sum of percentages of its child progresses (each multiplied by the relative
129: * percentage of that child).
130: *
131: * @return The total percentage of the composite.
132: */
133: @Override
134: public int getPercentage() {
135: int total = 0;
136: for (int i = 0; i < progresses.size(); i++) {
137: total += progresses.get(i).getPercentage()
138: * percentages.get(i);
139: }
140:
141: total = (total / COMPLETE) + percentage;
142:
143: return total;
144: }
145:
146: /**
147: * Sets the core composite's percentage. The value is updated only if the
148: * supplied percentage is different from the current. Also the progress
149: * listeners are notified only if the update actually took place.
150: *
151: * @param percentage The new value for the core composite's percentage.
152: *
153: * @throws {@link IllegalArgumentException} if the supplied percentage cannot be
154: * set.
155: */
156: @Override
157: public void setPercentage(final int percentage) {
158: if (this .percentage != percentage) {
159: if (!evaluatePercentage(percentage)) {
160: throw new IllegalArgumentException(StringUtils.format(
161: ERROR_WRONG_PERCENTAGE, percentage, START,
162: COMPLETE));
163: }
164:
165: this .percentage = percentage;
166:
167: notifyListeners();
168: }
169: }
170:
171: /**
172: * Adds the specified amount to the core composite's percentage. The added
173: * amount can be either positive or negative. The percentage value will be
174: * updated only if the result of the addition is different from the current core
175: * percentage. Also the listeners are notified only is the update actually took
176: * place.
177: *
178: * @param addition The amount to add to the core composite's percentage.
179: *
180: * @throws {@IllegalArgumentException} if the supplied percentage cannot be
181: * added.
182: */
183: @Override
184: public void addPercentage(final int addition) {
185: final int result = this .percentage + addition;
186:
187: if (this .percentage != result) {
188: if (!evaluatePercentage(result)) {
189: throw new IllegalArgumentException(StringUtils.format(
190: ERROR_WRONG_PERCENTAGE, addition, START,
191: COMPLETE));
192: }
193:
194: this .percentage = result;
195:
196: notifyListeners();
197: }
198: }
199:
200: /**
201: * Sets the value of the <code>canceled</code> property. The value is updated
202: * only if the supplied value is different from the current. Also the progress
203: * listeners are notified only if the update actually took place.
204: *
205: * <p>
206: * If the progress is in being synchronized from another progress. The cancelled
207: * status will be propagated to the synchronization source.
208: *
209: * <p>
210: * Additionally this method propagates the <code>canceled</code> value to the
211: * child progresses.
212: *
213: * @param canceled The new value for the <code>canceled</code> property.
214: */
215: @Override
216: public void setCanceled(final boolean canceled) {
217: super .setCanceled(canceled);
218:
219: for (Progress child : progresses) {
220: child.setCanceled(canceled);
221: }
222: }
223:
224: // composite-specific methods ///////////////////////////////////////////////////
225: /**
226: * Add a new child progress to the composite.
227: *
228: * @param progress The child progress to add to the composite.
229: * @param percentage The child's relative percentage within the composite.
230: *
231: * @throws {@link IllegalArgumentException} if the supplied percentage cannot be
232: * added.
233: */
234: public void addChild(final Progress progress, final int percentage) {
235: // check wehther we can add a new child with the given percentage
236: if (!evaluatePercentage(percentage)) {
237: throw new IllegalArgumentException(StringUtils
238: .format(ERROR_WRONG_PERCENTAGE, percentage, START,
239: COMPLETE));
240: }
241:
242: progresses.add(progress);
243: percentages.add(percentage);
244:
245: progress.addProgressListener(this );
246:
247: notifyListeners();
248: }
249:
250: /**
251: * Remove a child progress from the composite.
252: *
253: * @param progress The child progress to remove from the composite.
254: *
255: * @throws {@link IllegalArgumentException} if the supplied progress is not in
256: * composite.
257: */
258: public void removeChild(final Progress progress) {
259: final int index = progresses.indexOf(progress);
260: if (index != -1) {
261: percentages.remove(index);
262: progresses.remove(index);
263: } else {
264: throw new IllegalArgumentException(StringUtils
265: .format(ERROR_WRONG_PROGRESS));
266: }
267: progress.removeProgressListener(this );
268: }
269:
270: /**
271: * Sets the value of the <code>synchronizeDetails</code> property.
272: *
273: * @param synchronizeDetails The new value for the
274: * <code>synchronizeDetails</code> property.
275: */
276: public void synchronizeDetails(final boolean synchronizeDetails) {
277: this .synchronizeDetails = synchronizeDetails;
278: }
279:
280: // progress listener implementation /////////////////////////////////////////////
281: /**
282: * This method will get called by the child progresses as they change state -
283: * the composite automatically registers itself as a listener on each of the
284: * children.
285: *
286: * @param progress The child progress whose state has changed.
287: */
288: public void progressUpdated(final Progress progress) {
289: if (synchronizeDetails) {
290: setDetail(progress.getDetail());
291: }
292:
293: notifyListeners();
294: }
295:
296: // private //////////////////////////////////////////////////////////////////////
297: /**
298: * This methods evaluates the given percentage in terms of whether it could be
299: * added to the composite's total percentage. It sums the composite's own
300: * percentage, the declared percentages of all the children and the percentage
301: * being evaluated. The return value depends on whether the sum fits into the
302: * allowed percentage range or not.
303: *
304: * @param percentage The percentage to evaluate.
305: * @return <code>true</code> if the specified percentage can be added to the
306: * composite, <code>false</code> otherwise.
307: */
308: private boolean evaluatePercentage(final int percentage) {
309: int total = percentage;
310: for (Integer value : percentages) {
311: total += value;
312: }
313:
314: return (total >= START) && (total <= COMPLETE);
315: }
316:
317: /////////////////////////////////////////////////////////////////////////////////
318: // Constants
319: /**
320: * The error message which will be displayed when a user tries to set an invalid
321: * percentage either directly or via a child.
322: */
323: public static final String ERROR_WRONG_PERCENTAGE = ResourceUtils
324: .getString(CompositeProgress.class, "CP.error.percentage"); // NOI18N
325: /**
326: * The error message which will be displayed when a user tries to remove
327: * a non-existent child.
328: */
329: public static final String ERROR_WRONG_PROGRESS = ResourceUtils
330: .getString(CompositeProgress.class, "CP.error.progress"); // NOI18N
331: }
|