/* * Copyright 2000-2016 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 com.intellij.openapi.command; import com.intellij.codeInsight.FileModificationService; import com.intellij.openapi.application.*; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.progress.ProcessCanceledException; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.Computable; import com.intellij.openapi.util.Ref; import com.intellij.openapi.util.ThrowableComputable; import com.intellij.psi.PsiFile; import com.intellij.util.ArrayUtil; import com.intellij.util.ThrowableRunnable; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.Arrays; import java.util.Collection; public abstract class WriteCommandAction<T> extends BaseActionRunnable<T> { private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.command.WriteCommandAction"); private static final String DEFAULT_COMMAND_NAME = "Undefined"; private static final String DEFAULT_GROUP_ID = null; public interface Builder { @NotNull Builder withName(@Nullable String name); @NotNull Builder withGroupId(@Nullable String groupId); <E extends Throwable> void run(@NotNull ThrowableRunnable<E> action) throws E; <R, E extends Throwable> R compute(@NotNull ThrowableComputable<R, E> action) throws E; } private static class BuilderImpl implements Builder { private final Project myProject; private final PsiFile[] myFiles; private String myCommandName = DEFAULT_COMMAND_NAME; private String myGroupId = DEFAULT_GROUP_ID; private BuilderImpl(Project project, PsiFile... files) { myProject = project; myFiles = files; } @NotNull @Override public Builder withName(String name) { myCommandName = name; return this; } @NotNull @Override public Builder withGroupId(String groupId) { myGroupId = groupId; return this; } @Override public <E extends Throwable> void run(@NotNull final ThrowableRunnable<E> action) throws E { new WriteCommandAction(myProject, myCommandName, myGroupId, myFiles) { @Override protected void run(@NotNull Result result) throws Throwable { action.run(); } }.execute(); } @Override public <R, E extends Throwable> R compute(@NotNull final ThrowableComputable<R, E> action) throws E { return new WriteCommandAction<R>(myProject, myCommandName, myGroupId, myFiles) { @Override protected void run(@NotNull Result<R> result) throws Throwable { result.setResult(action.compute()); } }.execute().getResultObject(); } } @NotNull public static Builder writeCommandAction(Project project) { return new BuilderImpl(project); } @NotNull public static Builder writeCommandAction(@NotNull PsiFile first, @NotNull PsiFile... others) { return new BuilderImpl(first.getProject(), ArrayUtil.prepend(first, others)); } private final String myCommandName; private final String myGroupID; private final Project myProject; private final PsiFile[] myPsiFiles; protected WriteCommandAction(@Nullable Project project, /*@NotNull*/ PsiFile... files) { this(project, DEFAULT_COMMAND_NAME, files); } protected WriteCommandAction(@Nullable Project project, @Nullable String commandName, /*@NotNull*/ PsiFile... files) { this(project, commandName, DEFAULT_GROUP_ID, files); } protected WriteCommandAction(@Nullable Project project, @Nullable String commandName, @Nullable String groupID, /*@NotNull*/ PsiFile... files) { myCommandName = commandName; myGroupID = groupID; myProject = project; if (files == null) LOG.warn("'files' parameter must not be null", new Throwable()); myPsiFiles = files == null || files.length == 0 ? PsiFile.EMPTY_ARRAY : files; } public final Project getProject() { return myProject; } public final String getCommandName() { return myCommandName; } public String getGroupID() { return myGroupID; } @NotNull @Override public RunResult<T> execute() { Application application = ApplicationManager.getApplication(); boolean dispatchThread = application.isDispatchThread(); if (!dispatchThread && application.isReadAccessAllowed()) { LOG.error("Must not start write action from within read action in the other thread - deadlock is coming"); throw new IllegalStateException(); } final RunResult<T> result = new RunResult<>(this); if (dispatchThread) { performWriteCommandAction(result); } else { try { TransactionGuard.getInstance().submitTransactionAndWait(() -> performWriteCommandAction(result)); } catch (ProcessCanceledException ignored) { } } return result; } private void performWriteCommandAction(@NotNull RunResult<T> result) { if (!FileModificationService.getInstance().preparePsiElementsForWrite(Arrays.asList(myPsiFiles))) return; // this is needed to prevent memory leak, since the command is put into undo queue final RunResult[] results = {result}; doExecuteCommand(() -> { //noinspection deprecation ApplicationManager.getApplication().runWriteAction(() -> { results[0].run(); results[0] = null; }); }); } protected boolean isGlobalUndoAction() { return false; } protected UndoConfirmationPolicy getUndoConfirmationPolicy() { return UndoConfirmationPolicy.DO_NOT_REQUEST_CONFIRMATION; } /** * See {@link CommandProcessor#executeCommand(Project, Runnable, String, Object, UndoConfirmationPolicy, boolean)} for details. */ protected boolean shouldRecordActionForActiveDocument() { return true; } public void performCommand() throws Throwable { //this is needed to prevent memory leak, since command // is put into undo queue final RunResult[] results = {new RunResult<>(this)}; final Ref<Throwable> exception = new Ref<>(); doExecuteCommand(() -> { exception.set(results[0].run().getThrowable()); results[0] = null; }); Throwable throwable = exception.get(); if (throwable != null) throw throwable; } private void doExecuteCommand(final Runnable runnable) { Runnable wrappedRunnable = () -> { if (isGlobalUndoAction()) CommandProcessor.getInstance().markCurrentCommandAsGlobal(getProject()); runnable.run(); }; CommandProcessor.getInstance().executeCommand(getProject(), wrappedRunnable, getCommandName(), getGroupID(), getUndoConfirmationPolicy(), shouldRecordActionForActiveDocument()); } /** * WriteCommandAction without result */ public abstract static class Simple<T> extends WriteCommandAction<T> { protected Simple(Project project, /*@NotNull*/ PsiFile... files) { super(project, files); } protected Simple(Project project, String commandName, /*@NotNull*/ PsiFile... files) { super(project, commandName, files); } protected Simple(Project project, String name, String groupID, /*@NotNull*/ PsiFile... files) { super(project, name, groupID, files); } @Override protected void run(@NotNull Result<T> result) throws Throwable { run(); } protected abstract void run() throws Throwable; } public static void runWriteCommandAction(Project project, @NotNull Runnable runnable) { runWriteCommandAction(project, DEFAULT_COMMAND_NAME, DEFAULT_GROUP_ID, runnable); } public static void runWriteCommandAction(Project project, @Nullable final String commandName, @Nullable final String groupID, @NotNull final Runnable runnable, @NotNull PsiFile... files) { new Simple(project, commandName, groupID, files) { @Override protected void run() throws Throwable { runnable.run(); } }.execute(); } @SuppressWarnings("LambdaUnfriendlyMethodOverload") public static <T> T runWriteCommandAction(Project project, @NotNull final Computable<T> computable) { return new WriteCommandAction<T>(project) { @Override protected void run(@NotNull Result<T> result) throws Throwable { result.setResult(computable.compute()); } }.execute().getResultObject(); } @SuppressWarnings("LambdaUnfriendlyMethodOverload") public static <T, E extends Throwable> T runWriteCommandAction(Project project, @NotNull final ThrowableComputable<T, E> computable) throws E { RunResult<T> result = new WriteCommandAction<T>(project, "") { @Override protected void run(@NotNull Result<T> result) throws Throwable { result.setResult(computable.compute()); } }.execute(); Throwable t = result.getThrowable(); if (t != null) { @SuppressWarnings("unchecked") E e = (E)t; throw e; } return result.throwException().getResultObject(); } //<editor-fold desc="Deprecated stuff."> /** @deprecated use {@link FileModificationService#preparePsiElementsForWrite(Collection)} (to be removed in IDEA 2018) */ @SuppressWarnings("unused") public static boolean ensureFilesWritable(@NotNull Project project, @NotNull Collection<PsiFile> psiFiles) { return FileModificationService.getInstance().preparePsiElementsForWrite(psiFiles); } //</editor-fold> }