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.netbeans.modules.versioning.util.CommandReport;
045: import org.netbeans.lib.cvsclient.CVSRoot;
046: import org.netbeans.lib.cvsclient.event.*;
047: import org.netbeans.lib.cvsclient.command.*;
048: import org.netbeans.modules.versioning.system.cvss.util.Utils;
049: import org.netbeans.modules.versioning.system.cvss.util.CommandDuplicator;
050: import org.netbeans.modules.versioning.system.cvss.ui.wizards.RootWizard;
051: import org.netbeans.modules.versioning.system.cvss.ui.UIUtils;
052: import org.openide.ErrorManager;
053: import org.openide.DialogDescriptor;
054: import org.openide.DialogDisplayer;
055: import org.openide.NotifyDescriptor;
056: import org.openide.filesystems.FileUtil;
057: import org.openide.util.RequestProcessor;
058: import org.openide.util.NbBundle;
059: import org.openide.util.TaskListener;
060:
061: import javax.swing.*;
062: import javax.swing.event.ChangeListener;
063: import javax.swing.event.ChangeEvent;
064: import java.util.*;
065: import java.util.List;
066: import java.io.IOException;
067: import java.io.File;
068: import java.io.StringWriter;
069: import java.io.PrintWriter;
070: import java.awt.*;
071: import java.awt.event.ActionEvent;
072: import java.awt.event.ActionListener;
073: import java.text.MessageFormat;
074: import org.openide.xml.XMLUtil;
075:
076: /**
077: * Support class for command executors:
078: * <ul>
079: * <li>asynchronously executes command using
080: * one thread per repository thread pool
081: * <li>logs server output to console
082: * <li>supports execution retry on I/O or authentification errors
083: * <li>reliably detects command termination
084: * </ul>
085: *
086: * <p>Static method {@link #prepareBasicCommand} splits command
087: * operating over files in multiple repositories as necessary.
088: *
089: * @author Maros Sandor
090: */
091: public abstract class ExecutorSupport implements CVSListener,
092: ExecutorGroup.Groupable {
093:
094: /**
095: * CVS server messages that start with one of these patterns won't be displayed in Output.
096: * Library needs these messages to prune empty directories, hence this workaround.
097: */
098: private static final String[] ignoredMessagePrefixes = {
099: "cvs server: Updating", "cvs server: New directory" }; // NOI18N
100:
101: protected final FileStatusCache cache;
102:
103: /**
104: * List of {@link org.netbeans.lib.cvsclient.command.FileInfoContainer} objects that were collected during
105: * command execution. This list is meant to be processed by subclasses in the
106: * {@link #commandFinished(org.netbeans.modules.versioning.system.cvss.ClientRuntime.Result)} method.
107: * It is never cleared after command successfuly finishes.
108: */
109: protected List toRefresh = new ArrayList(10);
110:
111: protected final CvsVersioningSystem cvs;
112: protected final Command cmd;
113: private final GlobalOptions options;
114: private RequestProcessor.Task task;
115: private List taskListeners = new ArrayList(2);
116: private Throwable internalError;
117: private boolean terminated;
118: private boolean commandFailed;
119:
120: private boolean finishedExecution;
121: private boolean executed;
122: private CommandRunnable commandRunnable;
123:
124: private StringBuffer message = new StringBuffer();
125: private ClientRuntime clientRuntime;
126: private List errorMessages = new ArrayList();
127: private List warningMessages = new ArrayList();
128:
129: private ExecutorGroup group;
130:
131: /** t9y */
132: boolean t9yRetryFlag;
133:
134: /**
135: * Be non-interactive.
136: */
137: private boolean nonInteractive;
138:
139: /**
140: * Creates execution environment for given command.
141: * @param cvs
142: * @param cmd that has undergone {@link #prepareBasicCommand} splitting.
143: * @param options
144: */
145: protected ExecutorSupport(CvsVersioningSystem cvs, Command cmd,
146: GlobalOptions options) {
147: this .cvs = cvs;
148: this .cmd = cmd;
149: this .options = options;
150: cache = cvs.getStatusCache();
151: }
152:
153: protected void setNonInteractive(boolean nonInteractive) {
154: this .nonInteractive = nonInteractive;
155: }
156:
157: /**
158: * Async execution.
159: * Returns after enqueing into execution queue i.e.
160: * after {@link #commandEnqueued} call.
161: */
162: public void execute() {
163: assert executed == false;
164: executed = true;
165: if (group == null) {
166: group = new ExecutorGroup(getDisplayName());
167: group.setNonInteractive(nonInteractive);
168: }
169:
170: setup();
171: executeImpl();
172: }
173:
174: private void executeImpl() {
175: try {
176: task = cvs.post(cmd, options, this );
177: } catch (Throwable e) {
178: internalError = e;
179: group.fail();
180:
181: String msg = NbBundle.getMessage(ExecutorSupport.class,
182: "BK1003", new Date(), getDisplayName());
183: if (clientRuntime != null) { // it is null if command did not start
184: clientRuntime.log(msg + "\n"); // NOI18N
185: clientRuntime.logError(e);
186: }
187: ErrorManager.getDefault().notify(
188: ErrorManager.INFORMATIONAL, e);
189: synchronized (this ) {
190: finishedExecution = true;
191: notifyAll();
192: }
193: cleanup();
194: }
195: }
196:
197: /**
198: * Called once, just before the command is sent to CVS for execution.
199: */
200: protected void setup() {
201: }
202:
203: /**
204: * Called once, after the command finishes execution.
205: */
206: protected void cleanup() {
207: }
208:
209: /**
210: * Default implementation takes first non-null name:
211: * <ul>
212: * <li>group display name
213: * <li>command display name
214: * <li>plain command syntax
215: * </ul>
216: */
217: protected String getDisplayName() {
218: String commandName;
219: if (group != null) {
220: commandName = group.getDisplayName();
221: } else {
222: commandName = cmd.getDisplayName();
223: if (commandName == null) {
224: commandName = cmd.getCVSCommand();
225: }
226: }
227: return commandName;
228: }
229:
230: /**
231: * Controls command textual messages logging
232: * into output window. By default everything is logged.
233: */
234: protected boolean logCommandOutput() {
235: return true;
236: }
237:
238: public void joinGroup(ExecutorGroup group) {
239: assert executed == false;
240: this .group = group;
241: }
242:
243: public ExecutorGroup getGroup() {
244: return group;
245: }
246:
247: /**
248: * Return internal errors.
249: * @see #isSuccessful
250: */
251: public Throwable getFailure() {
252: return internalError;
253: }
254:
255: /**
256: * Was the execution cancelled by user?
257: */
258: public boolean isCancelled() {
259: return group.isCancelled();
260: }
261:
262: /**
263: * @return true on no internal error, user cancel nor command fail ("server erroe:")
264: */
265: public boolean isSuccessful() {
266: return internalError == null && group.isCancelled() == false
267: && commandFailed == false;
268: }
269:
270: /** @return task instance actually used (can change on retry) or null. */
271: public RequestProcessor.Task getTask() {
272: return task;
273: }
274:
275: public void messageSent(MessageEvent e) {
276: if (e.isError()) {
277: String msg = e.getMessage();
278: if (msg == null) {
279: // null is not too descriptive, pass it's source
280: RuntimeException rex = new RuntimeException(
281: "Received null MessageEvent from:"); // NOI18N
282: StringWriter sw = new StringWriter();
283: PrintWriter pw = new PrintWriter(sw);
284: rex.printStackTrace(pw);
285: pw.close();
286: msg = sw.getBuffer().toString();
287: }
288: errorMessages.add(msg);
289: } else if (e.getMessage().startsWith("W ")) { // NOI18N
290: warningMessages.add(e.getMessage().substring(2));
291: }
292: if (e.isTagged()) {
293: String s = MessageEvent.parseTaggedMessage(message, e
294: .getMessage());
295: if (s != null) {
296: clientRuntime.log(s + "\n"); // NOI18N
297: message.setLength(0);
298: }
299: } else {
300: // If waiting for lock command execution looks deadlocked, always propagate
301: // E cvs server: [09:38:43] waiting for httpd's lock in /shared/data/ccvs/
302: boolean locked = e.getMessage().indexOf("waiting for") != -1; // NOI18N
303: locked &= e.getMessage().indexOf("lock in") != -1; // NOI18N
304: if (locked || logCommandOutput()) {
305: if (e.getMessage().length() > 0) { // filter out bogus newlines
306: if (shouldBeDisplayed(e.getMessage())) {
307: clientRuntime.log(e.getMessage() + "\n"); // NOI18N
308: }
309: }
310: }
311: }
312: }
313:
314: private boolean shouldBeDisplayed(String message) {
315: for (int i = 0; i < ignoredMessagePrefixes.length; i++) {
316: if (message.startsWith(ignoredMessagePrefixes[i]))
317: return false;
318: }
319: return true;
320: }
321:
322: public void messageSent(BinaryMessageEvent e) {
323: }
324:
325: public void fileAdded(FileAddedEvent e) {
326: }
327:
328: public void fileRemoved(FileRemovedEvent e) {
329: }
330:
331: public void fileUpdated(FileUpdatedEvent e) {
332: }
333:
334: public void fileToRemove(FileToRemoveEvent e) {
335: }
336:
337: public void fileInfoGenerated(FileInfoEvent e) {
338: assert !terminated;
339: FileInfoContainer fic = e.getInfoContainer();
340: if (fic.getFile() == null) {
341: // this probably indicates a bug in the library but is usually harmless, log it just for reference
342: ErrorManager.getDefault().log(
343: ErrorManager.WARNING,
344: org.netbeans.modules.versioning.util.Utils
345: .getStackTrace());
346: return;
347: }
348: if (fic instanceof DefaultFileInfoContainer) {
349: DefaultFileInfoContainer dfic = ((DefaultFileInfoContainer) fic);
350: dfic.setFile(FileUtil.normalizeFile(dfic.getFile()));
351: // filter out duplicate events, see org.netbeans.lib.cvsclient.response.UpdatedResponse.process()
352: // ? file.txt, U file.txt and C file.txt can all be fired for a single file in any order
353: for (Iterator i = toRefresh.iterator(); i.hasNext();) {
354: FileInfoContainer existing = (FileInfoContainer) i
355: .next();
356: if (existing.getFile().equals(fic.getFile())) {
357: String existingType = ((DefaultFileInfoContainer) existing)
358: .getType();
359: String newType = dfic.getType();
360: if (importance(newType) <= importance(existingType))
361: return;
362: i.remove();
363: break;
364: }
365: }
366: }
367: toRefresh.add(fic);
368: }
369:
370: private int importance(String type) {
371: return "UC".indexOf(type); // NOI18N
372: }
373:
374: /**
375: * Associates this executor with actualy enqueued runnable
376: * (ClientRunnable created by ClientRuntime) performing the command.
377: *
378: * <p>Adds the runnable into group cancelable chain.
379: */
380: public void commandEnqueued(CommandRunnable commandRunnable) {
381: this .commandRunnable = commandRunnable;
382: group.enqueued(cvs.getClientRuntime(cmd, options), this );
383: group.addCancellable(commandRunnable);
384: }
385:
386: /**
387: * It (re)runs...
388: */
389: public void commandStarted(CommandRunnable commandRunnable) {
390: clientRuntime = cvs.getClientRuntime(cmd, options);
391: group.started(clientRuntime);
392: }
393:
394: public void commandTerminated(TerminationEvent e) {
395: try {
396: if (e.getSource() instanceof ClientRuntime.Result) {
397: assert !terminated;
398: terminated = true;
399: ClientRuntime.Result result = (ClientRuntime.Result) e
400: .getSource();
401: Throwable error = result.getError();
402: if (result.isAborted()
403: || Thread.currentThread().isInterrupted()) {
404: toRefresh.clear();
405: return;
406: } else if (error != null) {
407: toRefresh.clear();
408: if (error instanceof CommandException) {
409: // TODO internalError = result.getError();?
410: // TODO group.fail();?
411: internalError = error;
412: ErrorManager.getDefault().notify(
413: ErrorManager.INFORMATIONAL, error);
414: report(NbBundle.getMessage(
415: ExecutorSupport.class,
416: "MSG_CommandFailed_Title"), NbBundle
417: .getMessage(ExecutorSupport.class,
418: "MSG_CommandFailed_Prompt"),
419: Arrays.asList(new String[] { error
420: .getMessage() }),
421: NotifyDescriptor.ERROR_MESSAGE);
422: } else if (!nonInteractive
423: && retryConnection(error)) {
424: terminated = false;
425: String msg = NbBundle.getMessage(
426: ExecutorSupport.class, "BK1004",
427: new Date(), getDisplayName());
428: clientRuntime = cvs.getClientRuntime(cmd,
429: options);
430: clientRuntime.log(msg + "\n"); // NOI18N
431: executeImpl();
432: } else {
433: if (!nonInteractive) {
434: String msg = NbBundle.getMessage(
435: ExecutorSupport.class, "BK1005",
436: new Date(), getDisplayName());
437: clientRuntime.log(msg + "\n"); // NOI18N
438: }
439: internalError = result.getError();
440: group.fail();
441: ErrorManager.getDefault().notify(
442: ErrorManager.INFORMATIONAL,
443: internalError);
444: // TODO ErrorManager.getDefault().notify(ErrorManager.USER, internalError);?
445: }
446: } else { // error == null
447: commandFinished((ClientRuntime.Result) e
448: .getSource());
449: if (cmd.hasFailed()) {
450: commandFailed = true;
451: group.fail();
452: report(NbBundle.getMessage(
453: ExecutorSupport.class,
454: "MSG_CommandFailed_Title"), NbBundle
455: .getMessage(ExecutorSupport.class,
456: "MSG_CommandFailed_Prompt"),
457: errorMessages,
458: NotifyDescriptor.ERROR_MESSAGE);
459: }
460: if (warningMessages.size() > 0) {
461: report(NbBundle.getMessage(
462: ExecutorSupport.class,
463: "MSG_CommandWarning_Title"), NbBundle
464: .getMessage(ExecutorSupport.class,
465: "MSG_CommandWarning_Prompt"),
466: warningMessages,
467: NotifyDescriptor.WARNING_MESSAGE);
468: }
469: }
470: }
471: } finally {
472: if (terminated) {
473: cleanup();
474: synchronized (this ) {
475: finishedExecution = true;
476: notifyAll();
477: }
478:
479: Iterator it;
480: synchronized (taskListeners) {
481: it = new ArrayList(taskListeners).iterator();
482: }
483: while (it.hasNext()) {
484: try {
485: TaskListener listener = (TaskListener) it
486: .next();
487: listener.taskFinished(task);
488: } catch (RuntimeException ex) {
489: ErrorManager.getDefault().notify(ex);
490: }
491: }
492:
493: group.finished(clientRuntime, this );
494: }
495: }
496: }
497:
498: protected void report(String title, String prompt,
499: List<String> messages, int type) {
500: if (nonInteractive)
501: return;
502: boolean emptyReport = true;
503: for (String message : messages) {
504: if (message != null && message.length() > 0) {
505: emptyReport = false;
506: break;
507: }
508: }
509: if (emptyReport)
510: return;
511: CommandReport report = new CommandReport(prompt, messages);
512: JButton ok = new JButton(NbBundle.getMessage(
513: ExecutorSupport.class, "MSG_CommandReport_OK"));
514: NotifyDescriptor descriptor = new NotifyDescriptor(report,
515: title, NotifyDescriptor.DEFAULT_OPTION, type,
516: new Object[] { ok }, ok);
517: DialogDisplayer.getDefault().notify(descriptor);
518: }
519:
520: /** Retry aware task events source*/
521: public void addTaskListener(TaskListener l) {
522: synchronized (taskListeners) {
523: taskListeners.add(l);
524: }
525: }
526:
527: public void removeTaskListener(TaskListener l) {
528: synchronized (taskListeners) {
529: taskListeners.remove(l);
530: }
531: }
532:
533: /**
534: * I/O exception occured give user chance to fix it.
535: * It shows dialog allowing to rewise proxy settings.
536: */
537: private boolean retryConnection(Throwable cause) {
538:
539: Throwable initialCause = cause;
540: String cvsRoot = getCvsRoot();
541: if (cvsRoot == null)
542: return false;
543:
544: final CVSRoot root;
545: try {
546: root = CVSRoot.parse(cvsRoot);
547: } catch (IllegalArgumentException ex) {
548: return false;
549: }
550:
551: final RootWizard rootWizard = RootWizard.configureRoot(root
552: .toString());
553: JPanel panel = new JPanel();
554: panel.setLayout(new BorderLayout());
555: panel.setBorder(BorderFactory.createEmptyBorder(0, 6, 6, 6));
556: StringBuffer reason = new StringBuffer("<ul>"); // NOI18N
557: while (cause != null) {
558: try {
559: String msg = cause.getLocalizedMessage();
560: if (msg == null) {
561: msg = cause.getClass().getName();
562: } else {
563: msg = XMLUtil.toElementContent(msg);
564: }
565: reason.append("<li>" + msg + "</li>"); // NOI18N
566: } catch (IOException ex) {
567: ErrorManager.getDefault().notify(ex);
568: }
569: cause = cause.getCause();
570: }
571: reason.append("</ul>"); // NOI18N
572: String msg = NbBundle.getMessage(ExecutorSupport.class,
573: "BK0001", reason.toString(), cvsRoot);
574: JLabel label = new JLabel(msg);
575: int ex = Math.max((int) (cvsRoot.length() * 1.1), 50);
576: UIUtils.computePreferredSize(label, ex);
577: panel.add(label, BorderLayout.NORTH);
578: panel.add(rootWizard.getPanel(), BorderLayout.CENTER);
579:
580: String okMsg = NbBundle.getMessage(ExecutorSupport.class,
581: "CTL_Password_Action_Ok");
582: final JButton ok = new JButton(okMsg);
583: ok.setEnabled(rootWizard.isValid());
584: ok.getAccessibleContext().setAccessibleDescription(okMsg);
585: String cancelMsg = NbBundle.getMessage(ExecutorSupport.class,
586: "CTL_Password_Action_Cancel");
587: final JButton cancel = new JButton(cancelMsg);
588: cancel.getAccessibleContext().setAccessibleDescription(
589: cancelMsg);
590: DialogDescriptor descriptor = new DialogDescriptor(panel,
591: NbBundle.getMessage(ExecutorSupport.class, "BK0004",
592: getDisplayName()), true, new Object[] { ok,
593: cancel }, ok, DialogDescriptor.BOTTOM_ALIGN,
594: null, new ActionListener() {
595: public void actionPerformed(ActionEvent e) {
596: }
597: });
598: descriptor.setMessageType(DialogDescriptor.WARNING_MESSAGE);
599: descriptor.setClosingOptions(null);
600: rootWizard.addChangeListener(new ChangeListener() {
601: public void stateChanged(ChangeEvent e) {
602: ok.setEnabled(rootWizard.isValid());
603: }
604: });
605:
606: ErrorManager.getDefault().notify(ErrorManager.INFORMATIONAL,
607: initialCause);
608: Dialog dialog = DialogDisplayer.getDefault().createDialog(
609: descriptor);
610: dialog.getAccessibleContext().setAccessibleDescription(
611: NbBundle.getMessage(ExecutorSupport.class, "BK0005"));
612: dialog.setVisible(true);
613:
614: boolean retry = false;
615: if (descriptor.getValue() == ok) {
616: rootWizard.commit(false);
617: retry = true;
618: }
619: return retry;
620: }
621:
622: private String getCvsRoot() {
623: if (cmd.getGlobalOptions() != null
624: && cmd.getGlobalOptions().getCVSRoot() != null)
625: return cmd.getGlobalOptions().getCVSRoot();
626: if (options != null && options.getCVSRoot() != null)
627: return options.getCVSRoot();
628: try {
629: return cvs.detectCvsRoot(cmd);
630: } catch (NotVersionedException e) {
631: }
632: return null;
633: }
634:
635: protected abstract void commandFinished(ClientRuntime.Result result);
636:
637: public void moduleExpanded(ModuleExpansionEvent e) {
638: }
639:
640: /**
641: * Prepares the command for execution by splitting it into one or more separate commands.
642: * The split is necessary if the original command acts on files that are from different repositories or
643: * they lie under different filesystem roots.
644: *
645: * @param cmd original command to be executed
646: * @return array of commands where each command contains only files that have a common parent and are stored under
647: * the same CVS root
648: *
649: * @see ExecutorGroup
650: */
651: protected static BasicCommand[] prepareBasicCommand(BasicCommand cmd)
652: throws IOException {
653: String format = cmd.getDisplayName();
654: File[] files = cmd.getFiles();
655: if (files == null || files.length < 2) {
656: if (format != null)
657: cmd.setDisplayName(MessageFormat.format(format,
658: new Object[] { files == null ? "" : files[0]
659: .getName() })); // NOI18N
660: return new BasicCommand[] { cmd };
661: }
662: File[][] fileSets = splitFiles(files);
663: if (fileSets.length == 1) {
664: String nfiles = NbBundle.getMessage(ExecutorSupport.class,
665: "MSG_ExecutorSupport_CommandFiles", Integer
666: .toString(fileSets[0].length));
667: if (format != null)
668: cmd.setDisplayName(MessageFormat.format(format,
669: new Object[] { nfiles }));
670: return new BasicCommand[] { cmd };
671: }
672: BasicCommand[] commands = new BasicCommand[fileSets.length];
673: CommandDuplicator cloner = CommandDuplicator.getDuplicator(cmd);
674: for (int i = 0; i < fileSets.length; i++) {
675: BasicCommand bc = (BasicCommand) cloner.duplicate();
676: bc.setFiles(fileSets[i]);
677: commands[i] = bc;
678: String nfiles = NbBundle.getMessage(ExecutorSupport.class,
679: "MSG_ExecutorSupport_CommandFiles", Integer
680: .toString(fileSets[i].length));
681: if (format != null)
682: commands[i].setDisplayName(MessageFormat.format(format,
683: new Object[] { nfiles }));
684: }
685: return commands;
686: }
687:
688: /**
689: * Splits input files to groups with common CVS root and common local filesystem parent. Files in each group
690: * are guaranteed to belong to the same CVS root and lie under one local directory (it may be '/').
691: *
692: * @param files files to examine
693: * @return File[][] groups of files
694: * @throws IOException if a CVS/Root file is unreadable
695: */
696: protected static File[][] splitFiles(File[] files)
697: throws IOException {
698: List ret = new ArrayList();
699: File[][] aset = splitByCvsRoot(files);
700: for (int i = 0; i < aset.length; i++) {
701: File[] fileSet = aset[i];
702: File[][] splitSet = splitByCommonParent(fileSet);
703: for (int j = 0; j < splitSet.length; j++) {
704: ret.add(splitSet[j]);
705: }
706: }
707: return (File[][]) ret.toArray(new File[ret.size()][]);
708: }
709:
710: // XXX actually masks error in cvsclient library
711: // command-line cvs works smoothly over multi-cvsrooted
712: // workdirs opening new connections as necessary
713: protected static File[][] splitByCvsRoot(File[] files)
714: throws IOException {
715: Map fileBuckets = new HashMap();
716: for (int i = 0; i < files.length; i++) {
717: File file = files[i];
718: String root = Utils.getCVSRootFor(file);
719: Set bucket = (Set) fileBuckets.get(root);
720: if (bucket == null) {
721: bucket = new HashSet();
722: fileBuckets.put(root, bucket);
723: }
724: bucket.add(file);
725: }
726: File[][] sets = new File[fileBuckets.size()][];
727: int idx = 0;
728: for (Iterator i = fileBuckets.values().iterator(); i.hasNext();) {
729: Set bucket = (Set) i.next();
730: sets[idx++] = (File[]) bucket.toArray(new File[bucket
731: .size()]);
732: }
733: return sets;
734: }
735:
736: private static File[][] splitByCommonParent(File[] files) {
737: Map fileBuckets = new HashMap();
738: for (int i = 0; i < files.length; i++) {
739: File file = files[i];
740: File parent;
741: if (file.isDirectory()) {
742: parent = file;
743: } else {
744: parent = file.getParentFile();
745: }
746:
747: Set fileset = null;
748: File commonParent = null;
749: for (Iterator j = fileBuckets.keySet().iterator(); j
750: .hasNext();) {
751: File key = (File) j.next();
752: commonParent = org.netbeans.modules.versioning.util.Utils
753: .getCommonParent(parent, key);
754: if (commonParent != null) {
755: fileset = (Set) fileBuckets.get(key);
756: j.remove();
757: break;
758: }
759: }
760:
761: if (commonParent == null) {
762: fileset = new HashSet(1);
763: commonParent = parent;
764: }
765: fileset.add(file);
766: fileBuckets.put(commonParent, fileset);
767: }
768:
769: File[][] sets = new File[fileBuckets.size()][];
770: int idx = 0;
771: for (Iterator i = fileBuckets.values().iterator(); i.hasNext();) {
772: Set bucket = (Set) i.next();
773: sets[idx++] = (File[]) bucket.toArray(new File[bucket
774: .size()]);
775: }
776: return sets;
777: }
778:
779: /**
780: * Waits until all executors finish.
781: *
782: * @param executors array of executors to check
783: * @return true if all executors finished successfuly, false otherwise
784: */
785: public static boolean wait(ExecutorSupport[] executors) {
786: boolean success = true;
787: for (int i = 0; i < executors.length; i++) {
788: ExecutorSupport executor = executors[i];
789: synchronized (executor) {
790: while (!executor.finishedExecution) {
791: try {
792: executor.wait();
793: } catch (InterruptedException e) {
794: // forward interrupt
795: executor.getGroup().cancel();
796: }
797: }
798: }
799: if (executor.isSuccessful() == false) {
800: success = false;
801: }
802: }
803: return success;
804: }
805:
806: /**
807: * Notify progress in terms of transmitted/received bytes.
808: */
809: public void increaseDataCounter(long bytes) {
810: group.increaseDataCounter(bytes);
811: }
812:
813: }
|