/* * Copyright 2000-2009 JetBrains s.r.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.community.intellij.plugins.communitycase.commands; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.progress.ProgressIndicator; import com.intellij.openapi.progress.ProgressManager; import com.intellij.openapi.progress.Task; import com.intellij.openapi.util.Key; import com.intellij.openapi.vcs.VcsException; import com.intellij.openapi.vfs.VirtualFile; import org.community.intellij.plugins.communitycase.Vcs; import org.community.intellij.plugins.communitycase.i18n.Bundle; import org.community.intellij.plugins.communitycase.ui.UiUtil; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.awt.*; import java.util.Collection; import java.util.concurrent.LinkedBlockingQueue; /** * Handler utilities that allow running handlers with progress indicators */ public class HandlerUtil { /** * The logger instance */ private static final Logger LOG = Logger.getInstance("#"+HandlerUtil.class.getName()); /** * a private constructor for utility class */ private HandlerUtil() { } /** * Execute simple process synchronously with progress * * @param handler a handler * @param operationTitle an operation title shown in progress dialog * @param operationName an operation name shown in failure dialog * @return A stdout content or null if there was error (exit code != 0 or exception during start). */ @Nullable public static String doSynchronously(final SimpleHandler handler, String operationTitle, @NonNls final String operationName) { handler.addListener(new HandlerListenerBase(handler, operationName) { protected String getErrorText() { String text = handler.getStderr(); if (text.length() == 0) { text = handler.getStdout(); } return text; } }); runHandlerSynchronously(handler, operationTitle, ProgressManager.getInstance(), true); if (!handler.isStarted() || handler.getExitCode() != 0) { return null; } return handler.getStdout(); } /** * Execute simple process synchronously with progress * * @param handler a handler * @param operationTitle an operation title shown in progress dialog * @param operationName an operation name shown in failure dialog * @return An exit code */ public static int doSynchronously(final LineHandler handler, String operationTitle, @NonNls final String operationName) { return doSynchronously(handler, operationTitle, operationName, true); } /** * Execute simple process synchronously with progress * * @param handler a handler * @param operationTitle an operation title shown in progress dialog * @param operationName an operation name shown in failure dialog * @param showErrors if true, the errors are shown when process is terminated * @return An exit code */ public static int doSynchronously(final LineHandler handler, String operationTitle, @NonNls final String operationName, boolean showErrors) { return doSynchronously(handler, operationTitle, operationName, showErrors, true); } /** * Execute simple process synchronously with progress * * @param handler a handler * @param operationTitle an operation title shown in progress dialog * @param operationName an operation name shown in failure dialog * @param showErrors if true, the errors are shown when process is terminated * @param setIndeterminateFlag a flag indicating that progress should be configured as indeterminate * @return An exit code */ public static int doSynchronously(final LineHandler handler, final String operationTitle, @NonNls final String operationName, final boolean showErrors, final boolean setIndeterminateFlag) { final ProgressManager manager = ProgressManager.getInstance(); manager.run(new Task.Modal(handler.project(), operationTitle, false) { public void run(@NotNull final ProgressIndicator indicator) { handler.addLineListener(new LineHandlerListenerProgress(indicator, handler, operationName, showErrors)); runInCurrentThread(handler, indicator, setIndeterminateFlag, operationTitle); } }); if (!handler.isStarted()) { return -1; } return handler.getExitCode(); } /** * Run handler synchronously. The method assumes that all listeners are set up. * * @param handler a handler to run * @param operationTitle operation title * @param manager a progress manager * @param setIndeterminateFlag if true handler is configured as indeterminate */ private static void runHandlerSynchronously(final Handler handler, final String operationTitle, final ProgressManager manager, final boolean setIndeterminateFlag) { manager.runProcessWithProgressSynchronously(new Runnable() { public void run() { runInCurrentThread(handler, manager.getProgressIndicator(), setIndeterminateFlag, operationTitle); } }, operationTitle, false, handler.project()); } /** * Run handler in the current thread * * @param handler a handler to run * @param indicator a progress manager * @param setIndeterminateFlag if true handler is configured as indeterminate * @param operationName */ public static void runInCurrentThread(final Handler handler, final ProgressIndicator indicator, final boolean setIndeterminateFlag, @Nullable final String operationName) { runInCurrentThread(handler, new Runnable() { public void run() { if (indicator != null) { indicator.setText(operationName == null ? Bundle.message("running", handler.printableCommandLine()) : operationName); indicator.setText2(""); if (setIndeterminateFlag) { indicator.setIndeterminate(true); } } } }); } /** * Run handler in the current thread * * @param handler a handler to run * @param postStartAction an action that is executed */ static void runInCurrentThread(final Handler handler, @Nullable final Runnable postStartAction) { final Vcs vcs = Vcs.getInstance(handler.myProject); if (vcs == null) { return; } boolean suspendable = false; switch (handler.myCommand.lockingPolicy()) { case META: // do nothing no locks are taken for metadata break; case READ: vcs.getCommandLock().readLock().lock(); break; case WRITE_SUSPENDABLE: suspendable = true; //noinspection fallthrough case WRITE: vcs.getCommandLock().writeLock().lock(); break; } try { if (suspendable) { final Object EXIT = new Object(); final Object SUSPEND = new Object(); final Object RESUME = new Object(); final LinkedBlockingQueue<Object> queue = new LinkedBlockingQueue<Object>(); Runnable suspend = new Runnable() { public void run() { queue.add(SUSPEND); } }; Runnable resume = new Runnable() { public void run() { queue.add(RESUME); } }; handler.setSuspendResume(suspend, resume); handler.start(); if (handler.isStarted()) { if (postStartAction != null) { postStartAction.run(); } ApplicationManager.getApplication().executeOnPooledThread(new Runnable() { public void run() { handler.waitFor(); queue.add(EXIT); } }); boolean suspended = false; while (true) { Object action; while (true) { try { action = queue.take(); break; } catch (InterruptedException e) { if (LOG.isDebugEnabled()) { LOG.debug("queue.take() is interrupted", e); } } } if (action == EXIT) { if (suspended) { LOG.error("Exiting while RW lock is suspended (reacquiring W-lock command)"); vcs.getCommandLock().writeLock().lock(); } break; } else if (action == SUSPEND) { if (suspended) { LOG.error("Suspending suspended W-lock (ignoring command)"); } else { vcs.getCommandLock().writeLock().unlock(); suspended = true; } } else if (action == RESUME) { if (!suspended) { LOG.error("Resuming not suspended W-lock (ignoring command)"); } else { vcs.getCommandLock().writeLock().lock(); suspended = false; } } } } } else { handler.start(); if (handler.isStarted()) { if (postStartAction != null) { postStartAction.run(); } handler.waitFor(); } } } finally { switch (handler.myCommand.lockingPolicy()) { case META: // do nothing no locks are taken for metadata break; case READ: vcs.getCommandLock().readLock().unlock(); break; case WRITE_SUSPENDABLE: case WRITE: vcs.getCommandLock().writeLock().unlock(); break; } } } /** * Run synchronously using progress indicator, but collect exceptions instead of showing error dialog * * @param handler a handler to use * @return the collection of exception collected during operation */ public static Collection<VcsException> doSynchronouslyWithExceptions(final LineHandler handler) { final ProgressIndicator progressIndicator = ProgressManager.getInstance().getProgressIndicator(); return doSynchronouslyWithExceptions(handler, progressIndicator, null); } /** * Run synchronously using progress indicator, but collect exception instead of showing error dialog * * @param handler a handler to use * @param progressIndicator a progress indicator * @param operationName * @return the collection of exception collected during operation */ public static Collection<VcsException> doSynchronouslyWithExceptions(final LineHandler handler, final ProgressIndicator progressIndicator, @Nullable String operationName) { handler.addLineListener(new LineHandlerListenerProgress(progressIndicator, handler, operationName, false)); runInCurrentThread(handler, progressIndicator, false, operationName); return handler.errors(); } public static String formatOperationName(String operation, @NotNull VirtualFile root) { return operation + " '" + root.getName() + "'..."; } /** * A base class for handler listener that implements error handling logic */ private abstract static class HandlerListenerBase implements HandlerListener { /** * a handler */ protected final Handler myHandler; /** * a operation name for the handler */ protected final String myOperationName; /** * if true, the errors are shown when process is terminated */ protected boolean myShowErrors; /** * A constructor * * @param handler a handler instance * @param operationName an operation name */ public HandlerListenerBase(final Handler handler, final String operationName) { this(handler, operationName, true); } /** * A constructor * * @param handler a handler instance * @param operationName an operation name * @param showErrors if true, the errors are shown when process is terminated */ public HandlerListenerBase(final Handler handler, final String operationName, boolean showErrors) { myHandler = handler; myOperationName = operationName; myShowErrors = showErrors; } /** * {@inheritDoc} */ public void processTerminated(final int exitCode) { if (exitCode != 0 && !myHandler.isIgnoredErrorCode(exitCode)) { ensureError(exitCode); if (myShowErrors) { EventQueue.invokeLater(new Runnable() { public void run() { UiUtil.showOperationErrors(myHandler.project(), myHandler.errors(), myOperationName); } }); } } } /** * Ensure that at least one error is available in case if the process exited with non-zero exit code * * @param exitCode the exit code of the process */ protected void ensureError(final int exitCode) { if (myHandler.errors().isEmpty()) { String text = getErrorText(); if ((text == null || text.length() == 0) && myHandler.errors().isEmpty()) { //noinspection ThrowableInstanceNeverThrown myHandler.addError(new VcsException(Bundle.message("error.exit", exitCode))); } else { //noinspection ThrowableInstanceNeverThrown myHandler.addError(new VcsException(text)); } } } /** * @return error text for the handler, if null or empty string a default message is used. */ protected abstract String getErrorText(); /** * {@inheritDoc} */ public void startFailed(final Throwable exception) { //noinspection ThrowableInstanceNeverThrown myHandler.addError(new VcsException("Git start failed: " + exception.getMessage(), exception)); if (myShowErrors) { EventQueue.invokeLater(new Runnable() { public void run() { UiUtil.showOperationError(myHandler.project(), myOperationName, exception.getMessage()); } }); } } } /** * A base class for line handler listeners */ private abstract static class LineHandlerListenerBase extends HandlerListenerBase implements LineHandlerListener { /** * A constructor * * @param handler a handler instance * @param operationName an operation name * @param showErrors if true, the errors are shown when process is terminated */ public LineHandlerListenerBase(Handler handler, String operationName, boolean showErrors) { super(handler, operationName, showErrors); } /** * Error indicators for the line */ @NonNls private static final String[] ERROR_INDICATORS = {"ERROR:", "error", "FATAL:", "fatal", "Cannot apply", "Could not", "Interactive rebase already started", "refusing to pull", "cannot rebase:"}; /** * Check if the line is an error line * * @param text a line to check * @return true if the error line */ protected static boolean isErrorLine(String text) { for (String prefix : ERROR_INDICATORS) { if (text.startsWith(prefix)) { return true; } } return false; } } /** * A base class for line handler listeners */ public static class LineHandlerListenerProgress extends LineHandlerListenerBase { /** * a progress manager to use */ private final ProgressIndicator myProgressIndicator; /** * A constructor * * @param manager the project manager * @param handler a handler instance * @param operationName an operation name * @param showErrors if true, the errors are shown when process is terminated */ public LineHandlerListenerProgress(final ProgressIndicator manager, Handler handler, String operationName, boolean showErrors) { super(handler, operationName, showErrors); //To change body of overridden methods use File | Settings | File Templates. myProgressIndicator = manager; } /** * {@inheritDoc} */ protected String getErrorText() { // all lines are already calculated as errors return ""; } /** * {@inheritDoc} */ public void onLineAvailable(final String line, final Key outputType) { if (isErrorLine(line.trim())) { //noinspection ThrowableInstanceNeverThrown myHandler.addError(new VcsException(line)); } if (myProgressIndicator != null) { myProgressIndicator.setText2(line); } } } }