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
007: * General Public License Version 2 only ("GPL") or the Common
008: * Development and Distribution License("CDDL") (collectively, the
009: * "License"). You may not use this file except in compliance with the
010: * License. You can obtain a copy of the License at
011: * http://www.netbeans.org/cddl-gplv2.html
012: * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
013: * specific language governing permissions and limitations under the
014: * License. When distributing the software, include this License Header
015: * Notice in each file and include the License file at
016: * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
017: * particular file as subject to the "Classpath" exception as provided
018: * by Sun in the GPL Version 2 section of the License file that
019: * accompanied this code. If applicable, add the following below the
020: * License Header, with the fields enclosed by brackets [] replaced by
021: * your own identifying information:
022: * "Portions Copyrighted [year] [name of copyright owner]"
023: *
024: * Contributor(s):
025: *
026: * The Original Software is NetBeans. The Initial Developer of the Original
027: * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
028: * Microsystems, Inc. All Rights Reserved.
029: *
030: * If you wish your version of this file to be governed by only the CDDL
031: * or only the GPL Version 2, indicate your decision by adding
032: * "[Contributor] elects to include this software in this distribution
033: * under the [CDDL or GPL Version 2] license." If you do not indicate a
034: * single choice of license, a recipient has the option to distribute
035: * your version of this file under either the CDDL, the GPL Version 2 or
036: * to extend the choice of license to its licensees as provided above.
037: * However, if you add GPL Version 2 code and therefore, elected the GPL
038: * Version 2 license, then the option applies only if the new code is
039: * made subject to such option by the copyright holder.
040: */
041:
042: package org.netbeans.modules.versioning.system.cvss;
043:
044: import org.openide.util.Cancellable;
045: import org.openide.util.Cancellable;
046: import org.openide.util.NbBundle;
047: import org.openide.ErrorManager;
048: import org.netbeans.api.progress.ProgressHandle;
049: import org.netbeans.api.progress.ProgressHandleFactory;
050:
051: import javax.swing.*;
052: import java.util.*;
053: import java.awt.event.ActionEvent;
054:
055: /**
056: * Support for actions that run multiple commands.
057: * Represents context that carry data shared by
058: * action commands executors. It can manage execution
059: * in multiple ClientRuntimes (threads).
060: *
061: * <p>Implements shared progress, logging support
062: * and cancelling.
063: *
064: * TODO add consolidated error reporting, nowadays, multiple errors from backgroud thread can popup
065: *
066: * @author Petr Kuzel
067: */
068: public final class ExecutorGroup extends AbstractAction implements
069: Cancellable {
070:
071: private final String name;
072: private final boolean abortOnExecutorFailure;
073: public boolean executed;
074: private boolean cancelled;
075: private List<Cancellable> cancellables = new ArrayList<Cancellable>(
076: 2);
077: private List executors = new ArrayList(2);
078: private List<ExecutorSupport> cleanups = new ArrayList<ExecutorSupport>(
079: 2);
080: /** ClientRuntime => CommandRunnale*/
081: private Map queues = new HashMap();
082: /** ClientRuntimes*/
083: private Set started = new HashSet();
084: /**
085: * Porgress handle is never created if the group is non-interactive.
086: */
087: private ProgressHandle progressHandle;
088: private long dataCounter;
089: private boolean hasBarrier;
090: private boolean failed;
091: private boolean executingCleanup;
092: private boolean nonInteractive;
093:
094: /**
095: * Creates new group.
096: *
097: * @param displayName
098: * Defines prefered display name - localized string that should highlight
099: * group purpose (i.e. in English use verb in gerund).
100: * E.g. <code>UpdateCommand</code> used to refresh statuses should
101: * be named "Refreshing Status" rather than "cvs -N update",
102: * "Updating" or "Status Refresh".
103: */
104: public ExecutorGroup(String displayName) {
105: this (displayName, true);
106: }
107:
108: public ExecutorGroup(String displayName,
109: boolean abortOnExecutorFailure) {
110: name = displayName;
111: this .abortOnExecutorFailure = abortOnExecutorFailure;
112: }
113:
114: /**
115: * Defines group display name.
116: */
117: public String getDisplayName() {
118: return name;
119: }
120:
121: /**
122: * Starts associated progress if not yet started. Allows to share
123: * progress with execution preparation phase (cache ops).
124: *
125: * @param details progress detail messag eor null
126: */
127: public synchronized void progress(String details) {
128: if (nonInteractive)
129: return;
130: if (progressHandle == null) {
131: progressHandle = ProgressHandleFactory.createHandle(
132: NbBundle.getMessage(ExecutorGroup.class, "BK2001",
133: name), this , this );
134: progressHandle.start();
135: }
136:
137: if (details != null) {
138: progressHandle.progress(details);
139: }
140: }
141:
142: /**
143: * Called by ExecutorSupport on enqueue.
144: * Pairs with finished.
145: *
146: * @param queue processign queue or null for all
147: * @param id identifier paired with {@link #finished}
148: */
149: synchronized void enqueued(ClientRuntime queue, Object id) {
150: progress(null);
151: if (progressHandle != null && started.size() == 0) {
152: progressHandle.setDisplayName(NbBundle.getMessage(
153: ExecutorGroup.class, "BK2005", name));
154: progressHandle.progress(NbBundle.getMessage(
155: ExecutorGroup.class, "BK1007"));
156: progressHandle.switchToDeterminate(100);
157: progressHandle.progress(1);
158: }
159:
160: Collection keys;
161: if (queue == null) {
162: keys = queues.keySet();
163: } else {
164: keys = Collections.singleton(queue);
165: }
166:
167: Iterator it = keys.iterator();
168: while (it.hasNext()) {
169: Object key = it.next();
170: Set commands = (Set) queues.get(key);
171: if (commands == null) {
172: commands = new HashSet();
173: }
174: commands.add(id);
175: queues.put(key, commands);
176: }
177: }
178:
179: /**
180: * Called by ExecutorSupport on start.
181: */
182: synchronized void started(ClientRuntime queue) {
183:
184: if (progressHandle != null) {
185: progressHandle.switchToIndeterminate();
186: progressHandle.setDisplayName(NbBundle.getMessage(
187: ExecutorGroup.class, "BK2001", name));
188: }
189:
190: if (!nonInteractive && started.add(queue)) {
191: String msg = NbBundle.getMessage(ExecutorGroup.class,
192: "BK1001", new Date(), getDisplayName());
193: String sep = NbBundle.getMessage(ExecutorGroup.class,
194: "BK1000");
195: String header = "\n" + sep + "\n" + msg + "\n"; // NOI18N
196: queue.log(header);
197: }
198: }
199:
200: /**
201: * Called by ExecutorSupport after processing.
202: *
203: * @param queue processign queue or null for all
204: * @param id identifier paired with {@link #enqueued(ClientRuntime, Object)}
205: */
206: synchronized void finished(ClientRuntime queue, Object id) {
207:
208: Collection keys;
209: if (queue == null) {
210: keys = new HashSet(queues.keySet());
211: } else {
212: keys = Collections.singleton(queue);
213: }
214:
215: boolean finished = executed; // TODO how to tip true for non-executed?
216: Iterator it = keys.iterator();
217: while (it.hasNext()) {
218: Object key = it.next();
219:
220: Set commands = (Set) queues.get(key);
221: commands.remove(id);
222: if (commands.isEmpty()) {
223: queues.remove(key);
224: if (executed && queues.isEmpty()
225: && progressHandle != null) {
226: progressHandle.finish();
227: progressHandle = null;
228: }
229: }
230: finished &= commands.isEmpty();
231: }
232:
233: if (finished) {
234: logFinished(queue);
235: }
236: }
237:
238: private void logFinished(ClientRuntime queue) {
239: if (nonInteractive)
240: return;
241: Collection consoles;
242: if (queue == null) {
243: consoles = started;
244: } else {
245: consoles = Collections.singleton(queue);
246: }
247:
248: String msg;
249: if (isCancelled()) {
250: msg = NbBundle.getMessage(ExecutorGroup.class, "BK1006",
251: new Date(), getDisplayName());
252: } else {
253: msg = NbBundle.getMessage(ExecutorGroup.class, "BK1002",
254: new Date(), getDisplayName());
255: }
256:
257: Iterator it2 = consoles.iterator();
258: while (it2.hasNext()) {
259: ClientRuntime console = (ClientRuntime) it2.next();
260: console.log(msg + "\n"); // NOI18N
261: console.flushLog();
262: }
263: }
264:
265: public boolean isCancelled() {
266: return cancelled;
267: }
268:
269: public boolean isFailed() {
270: return failed;
271: }
272:
273: /**
274: * User cancel comming from Progress UI.
275: * Must not be called by internals.
276: */
277: public boolean cancel() {
278: cancelled = true;
279: fail();
280: return true;
281: }
282:
283: /**
284: * A command in group failed. Stop all pending commands if abortOnExecutorFailure is set.
285: */
286: public void fail() {
287: if (!abortOnExecutorFailure)
288: return;
289: failed = true;
290: Iterator it;
291: synchronized (cancellables) {
292: it = new ArrayList<Cancellable>(cancellables).iterator();
293: }
294: while (it.hasNext()) {
295: try {
296: Cancellable cancellable = (Cancellable) it.next();
297: cancellable.cancel();
298: } catch (RuntimeException ex) {
299: ErrorManager.getDefault().notify(
300: ErrorManager.INFORMATIONAL, ex);
301: }
302: }
303: synchronized (executors) {
304: it = new ArrayList(executors).iterator();
305: }
306: while (it.hasNext()) {
307: try {
308: Object elem = it.next();
309: if (elem instanceof ExecutorSupport) {
310: ((ExecutorSupport) elem).getTask().cancel();
311: }
312: } catch (RuntimeException ex) {
313: ErrorManager.getDefault().notify(
314: ErrorManager.INFORMATIONAL, ex);
315: }
316: }
317: synchronized (this ) {
318: if (progressHandle != null) {
319: progressHandle.finish();
320: progressHandle = null;
321: }
322: }
323: }
324:
325: /**
326: * Add a cancelaable in chain of cancellable performers.
327: */
328: public void addCancellable(Cancellable cancellable) {
329: synchronized (cancellables) {
330: cancellables.add(cancellable);
331: }
332: }
333:
334: public void removeCancellable(Cancellable cancellable) {
335: synchronized (cancellables) {
336: cancellables.remove(cancellable);
337: }
338: }
339:
340: /**
341: * Add executor into this group.
342: */
343: public synchronized void addExecutor(ExecutorSupport executor) {
344: assert executed == false;
345: executor.joinGroup(this ); // XXX third party code executed under lock
346: executors.add(executor);
347: }
348:
349: /**
350: * Add executors into this group.
351: * @param executors groupable or <code>null</code>
352: */
353: public final synchronized void addExecutors(
354: ExecutorSupport[] executors) {
355: if (executors == null) {
356: return;
357: } else {
358: for (int i = 0; i < executors.length; i++) {
359: ExecutorSupport support = executors[i];
360: addExecutor(support);
361: }
362: }
363: }
364:
365: /**
366: * Group execution blocks on this barier until
367: * all previously added Groupable finishes (succesfuly or with fail).
368: *
369: * <p>Warning: Groups with barries have blocking {@link #execute},
370: * there is assert banning to execute such group from UI thread.
371: */
372: public synchronized void addBarrier(Runnable action) {
373: assert executed == false;
374: ExecutorGroupBar bar = new ExecutorGroupBar(executors, action);
375: bar.joinGroup(this );
376: executors.add(bar);
377: hasBarrier = true;
378: }
379:
380: /**
381: * Can be added only from barrier action!
382: */
383: public synchronized void addCleanups(ExecutorSupport[] executors) {
384: if (executors == null) {
385: return;
386: } else {
387: for (int i = 0; i < executors.length; i++) {
388: ExecutorSupport support = executors[i];
389: addCleanup(support);
390: }
391: }
392: }
393:
394: /**
395: * Can be added only from barrier action!
396: */
397: public synchronized void addCleanup(ExecutorSupport executor) {
398: assert executingCleanup == false;
399: executor.joinGroup(this );
400: cleanups.add(executor);
401: }
402:
403: /**
404: * Asynchronously executes all added executors. Executors
405: * are grouped according to CVSRoot and serialized in
406: * particular ClientRuntime (thread) queue. It maintains
407: *
408: * <p>Warning:
409: * <ul>
410: * <li>It becomes blocking if group contains barriers (there is UI thread assert).
411: * <li>Do not call {@link ExecutorSupport#execute} if you
412: * use grouping.
413: * </ul>
414: */
415: public void execute() {
416: assert (SwingUtilities.isEventDispatchThread() && hasBarrier) == false;
417:
418: synchronized (this ) {
419: executed = true;
420: }
421: Iterator it = executors.iterator();
422: int i = 0;
423: while (it.hasNext()) {
424: Groupable support = (Groupable) it.next();
425: try {
426: support.execute();
427: } catch (Error err) {
428: ErrorManager.getDefault().notify(err);
429: fail();
430: } catch (RuntimeException ex) {
431: ErrorManager.getDefault().notify(ex);
432: fail();
433: }
434: i++;
435: if (failed)
436: break;
437: }
438:
439: // cleanup actions
440:
441: synchronized (this ) {
442: executingCleanup = true;
443: }
444: it = cleanups.iterator();
445: while (it.hasNext()) {
446: Groupable support = (Groupable) it.next();
447: support.execute();
448: i++;
449: }
450:
451: synchronized (this ) {
452: if (i == 0 && progressHandle != null) { // kill progress provoked by progress()
453: progressHandle.finish();
454: progressHandle = null;
455: }
456: }
457: }
458:
459: /**
460: * Allows clients that execute grouped suppors
461: * synchronously communicate their assumtion that
462: * all supporst in group have been executed. It's
463: * time for progress cleanup and logging finished
464: * messages.
465: */
466: public synchronized void executed() {
467: if (executed == false) {
468: if (progressHandle != null) {
469: progressHandle.finish();
470: progressHandle = null;
471: }
472: logFinished(null);
473: }
474: }
475:
476: synchronized void increaseDataCounter(long bytes) {
477: dataCounter += bytes;
478: if (progressHandle != null) { // dangling event from zombie worker thread
479: progressHandle.progress(NbBundle.getMessage(
480: ExecutorGroup.class, "BK2002", name,
481: format(dataCounter)));
482: }
483: }
484:
485: private static String format(long counter) {
486: if (counter < 1024 * 16) {
487: return NbBundle.getMessage(ExecutorGroup.class, "BK2003",
488: new Long(counter));
489: }
490: counter /= 1024;
491: return NbBundle.getMessage(ExecutorGroup.class, "BK2004",
492: new Long(counter));
493:
494: // do not go to megabytes as user want to see CHANGING number
495: // it can be solved by average speed in last 5sec, as it drops to zero
496: // something is wrong
497: }
498:
499: /**
500: * Link action. Take random output, inmost coses one anyway.
501: */
502: public void actionPerformed(ActionEvent e) {
503: if (queues != null) {
504: Set keys = queues.keySet();
505: if (keys.isEmpty() == false) {
506: ClientRuntime queue = (ClientRuntime) keys.iterator()
507: .next();
508: queue.focusLog();
509: }
510: }
511: }
512:
513: public void setNonInteractive(boolean nonInteractive) {
514: this .nonInteractive = nonInteractive;
515: }
516:
517: public static interface Groupable {
518:
519: /**
520: * Notifies the Groupable that it is a part of
521: * given execution chain.
522: *
523: * <p> Must be called before {@link #execute}
524: */
525: void joinGroup(ExecutorGroup group);
526:
527: /** Execute custom code. */
528: void execute();
529: }
530: }
|