diff --git a/platform/platform-impl/src/com/intellij/ui/tree/AsyncTreeModel.java b/platform/platform-impl/src/com/intellij/ui/tree/AsyncTreeModel.java index e59584187cb2..abb1c9d189c8 100644 --- a/platform/platform-impl/src/com/intellij/ui/tree/AsyncTreeModel.java +++ b/platform/platform-impl/src/com/intellij/ui/tree/AsyncTreeModel.java @@ -17,6 +17,7 @@ package com.intellij.ui.tree; import com.intellij.openapi.Disposable; import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.progress.ProcessCanceledException; import com.intellij.openapi.util.Disposer; import com.intellij.ui.LoadingNode; import com.intellij.util.concurrency.Command; @@ -41,7 +42,6 @@ import java.util.LinkedHashMap; import java.util.List; import java.util.Map.Entry; import java.util.function.Consumer; -import java.util.function.Function; import java.util.function.IntFunction; import java.util.function.Predicate; import java.util.function.Supplier; @@ -364,7 +364,7 @@ public final class AsyncTreeModel extends AbstractTreeModel implements Disposabl return emptyList(); } - private static abstract class ObsolescentCommand implements Obsolescent, Command, Function { + private abstract class ObsolescentCommand implements Obsolescent, Command> { final AsyncPromise promise = new AsyncPromise<>(); final String name; final Object object; @@ -376,6 +376,10 @@ public final class AsyncTreeModel extends AbstractTreeModel implements Disposabl LOG.debug("create command: ", this); } + abstract Node getNode(Object object); + + abstract void setNode(Node node); + boolean isPending() { return Promise.State.PENDING == promise.getState(); } @@ -386,10 +390,28 @@ public final class AsyncTreeModel extends AbstractTreeModel implements Disposabl } @Override - public Node get() { + public Reference get() { started = true; LOG.debug("background command: ", this); - return isObsolete() ? null : apply(object); + return Reference.create(() -> getNode(object)); + } + + @Override + public void accept(Reference reference) { + if (isObsolete()) { + LOG.debug("obsolete command: ", this); + } + else if (reference == null) { + LOG.debug("failed command: ", this); + } + else if (reference.isValid()) { + LOG.debug("foreground command: ", this); + setNode(reference.get()); + } + else { + LOG.debug("restart command: ", this); + processor.process(this); // restart command + } } } @@ -405,20 +427,14 @@ public final class AsyncTreeModel extends AbstractTreeModel implements Disposabl } @Override - public Node apply(Object object) { + Node getNode(Object object) { if (object == null) object = model.getRoot(); if (object == null || isObsolete()) return null; return new Node(object, model.isLeaf(object)); } @Override - public void accept(Node loaded) { - if (isObsolete()) { - LOG.debug("obsolete command: ", this); - return; - } - LOG.debug("foreground command: ", this); - + void setNode(Node loaded) { Node root = tree.root; if (root == null && loaded == null) { LOG.debug("no root"); @@ -478,13 +494,11 @@ public final class AsyncTreeModel extends AbstractTreeModel implements Disposabl @Override public boolean isObsolete() { - if (this == node.queue.get()) return false; - LOG.debug("obsolete command: ", this); - return true; + return this != node.queue.get(); } @Override - public Node apply(Object object) { + Node getNode(Object object) { Node loaded = new Node(object, model.isLeaf(object)); if (loaded.leaf) return loaded; @@ -492,7 +506,7 @@ public final class AsyncTreeModel extends AbstractTreeModel implements Disposabl //noinspection unchecked ChildrenProvider provider = (ChildrenProvider)model; List children = provider.getChildren(object); - if (children == null) return null; // cancel this command + if (children == null) throw new ProcessCanceledException(); // cancel this command loaded.children = load(children.size(), index -> children.get(index)); } else { @@ -525,11 +539,7 @@ public final class AsyncTreeModel extends AbstractTreeModel implements Disposabl } @Override - public void accept(Node loaded) { - if (isObsolete()) { - LOG.debug("obsolete command: ", this); - return; - } + void setNode(Node loaded) { if (loaded == null || loaded.isLoadingRequired()) { LOG.debug("cancelled command: ", this); return; @@ -539,8 +549,6 @@ public final class AsyncTreeModel extends AbstractTreeModel implements Disposabl LOG.warn("ignore removed node: " + node.object); return; } - LOG.debug("foreground command: ", this); - List oldChildren = node.getChildren(); List newChildren = loaded.getChildren(); if (oldChildren.isEmpty() && newChildren.isEmpty()) { diff --git a/platform/platform-impl/src/com/intellij/ui/tree/Reference.java b/platform/platform-impl/src/com/intellij/ui/tree/Reference.java new file mode 100644 index 000000000000..d1d22dea3e35 --- /dev/null +++ b/platform/platform-impl/src/com/intellij/ui/tree/Reference.java @@ -0,0 +1,84 @@ +/* + * Copyright 2000-2017 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.ui.tree; + +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.progress.ProcessCanceledException; +import com.intellij.openapi.progress.ProgressManager; +import org.jetbrains.annotations.NotNull; + +import java.util.function.Supplier; + +import static java.awt.EventQueue.isDispatchThread; + +/** + * @author Sergey.Malenkov + */ +final class Reference { + private static final Logger LOG = Logger.getInstance(Reference.class); + private volatile boolean valid; + private volatile T value; + + boolean isValid() { + return valid; + } + + void invalidate() { + valid = false; + } + + T set(T value) { + T old = this.value; + this.value = value; + valid = true; + return old; + } + + T get() { + return value; + } + + static Reference create(@NotNull Supplier supplier) { + Reference reference = new Reference<>(); + try { + Runnable process = () -> reference.set(supplier.get()); + ProgressManager manager = getProgressManager(); + if (manager == null || isDispatchThread()) { + process.run(); + } + else { + manager.runInReadActionWithWriteActionPriority(process, null); + } + } + catch (ProcessCanceledException ignore) { + } + catch (Exception exception) { + LOG.warn(exception); + return null; + } + return reference; + } + + private static ProgressManager getProgressManager() { + try { + return ProgressManager.getInstance(); + } + catch (NullPointerException exception) { + LOG.debug("progress manager is not available"); + return null; // in tests without application + } + } +} diff --git a/platform/platform-tests/testSrc/com/intellij/ui/tree/AsyncTreeModelTest.java b/platform/platform-tests/testSrc/com/intellij/ui/tree/AsyncTreeModelTest.java index eef5e52ff8fb..fcf4ab59c1f2 100644 --- a/platform/platform-tests/testSrc/com/intellij/ui/tree/AsyncTreeModelTest.java +++ b/platform/platform-tests/testSrc/com/intellij/ui/tree/AsyncTreeModelTest.java @@ -16,6 +16,7 @@ package com.intellij.ui.tree; import com.intellij.openapi.Disposable; +import com.intellij.openapi.progress.ProcessCanceledException; import com.intellij.openapi.util.Disposer; import com.intellij.util.concurrency.Invoker; import com.intellij.util.concurrency.InvokerSupplier; @@ -417,6 +418,11 @@ public final class AsyncTreeModelTest { } private void pause() { + if (this instanceof InvokerSupplier && .9 < Math.random()) { + // sometimes throw an exception to cancel current operation + if (PRINT) System.out.println("interrupt access to model:" + toString()); + throw new ProcessCanceledException(); + } if (delay > 0) { try { Thread.sleep(delay);