diff: avoid possible issues with disposed RequestProcessor
[idea/community.git] / platform / vcs-impl / src / com / intellij / openapi / vcs / changes / CacheChangeProcessor.java
1 /*
2  * Copyright 2000-2015 JetBrains s.r.o.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.intellij.openapi.vcs.changes;
17
18 import com.intellij.diff.chains.DiffRequestProducerException;
19 import com.intellij.diff.impl.DiffRequestProcessor;
20 import com.intellij.diff.requests.*;
21 import com.intellij.diff.tools.util.SoftHardCacheMap;
22 import com.intellij.diff.util.DiffTaskQueue;
23 import com.intellij.diff.util.DiffUserDataKeys;
24 import com.intellij.diff.util.DiffUserDataKeysEx.ScrollToPolicy;
25 import com.intellij.icons.AllIcons;
26 import com.intellij.openapi.actionSystem.AnAction;
27 import com.intellij.openapi.actionSystem.AnActionEvent;
28 import com.intellij.openapi.diagnostic.Logger;
29 import com.intellij.openapi.progress.ProcessCanceledException;
30 import com.intellij.openapi.progress.ProgressIndicator;
31 import com.intellij.openapi.progress.util.ProgressWindow;
32 import com.intellij.openapi.project.DumbAwareAction;
33 import com.intellij.openapi.project.Project;
34 import com.intellij.openapi.util.Pair;
35 import com.intellij.openapi.vcs.changes.actions.diff.ChangeDiffRequestProducer;
36 import com.intellij.util.Function;
37 import com.intellij.util.containers.ContainerUtil;
38 import org.jetbrains.annotations.*;
39
40 import java.util.Collections;
41 import java.util.List;
42
43 public abstract class CacheChangeProcessor extends DiffRequestProcessor {
44   private static final Logger LOG = Logger.getInstance(CacheChangeProcessor.class);
45
46   @NotNull private final SoftHardCacheMap<Change, Pair<Change, DiffRequest>> myRequestCache =
47     new SoftHardCacheMap<Change, Pair<Change, DiffRequest>>(5, 5);
48
49   @Nullable private Change myCurrentChange;
50
51   @NotNull private final DiffTaskQueue myQueue = new DiffTaskQueue();
52
53   public CacheChangeProcessor(@NotNull Project project) {
54     super(project);
55   }
56
57   public CacheChangeProcessor(@NotNull Project project, @NotNull String place) {
58     super(project, place);
59   }
60
61   //
62   // Abstract
63   //
64
65   @NotNull
66   protected abstract List<Change> getSelectedChanges();
67
68   @NotNull
69   protected abstract List<Change> getAllChanges();
70
71   protected abstract void selectChange(@NotNull Change change);
72
73   //
74   // Update
75   //
76
77   @Override
78   protected void reloadRequest() {
79     updateRequest(true, false, null);
80   }
81
82   @CalledInAwt
83   public void updateRequest(final boolean force, @Nullable final ScrollToPolicy scrollToChangePolicy) {
84     updateRequest(force, true, scrollToChangePolicy);
85   }
86
87   @CalledInAwt
88   public void updateRequest(final boolean force, boolean useCache, @Nullable final ScrollToPolicy scrollToChangePolicy) {
89     if (isDisposed()) return;
90     final Change change = myCurrentChange;
91
92     DiffRequest cachedRequest = loadRequestFast(change, useCache);
93     if (cachedRequest != null) {
94       applyRequest(cachedRequest, force, scrollToChangePolicy);
95       return;
96     }
97
98     // TODO: check if current loading change is the same as we want to load now? (and not interrupt loading)
99     myQueue.executeAndTryWait(
100       new Function<ProgressIndicator, Runnable>() {
101         @Override
102         public Runnable fun(ProgressIndicator indicator) {
103           final DiffRequest request = loadRequest(change, indicator);
104           return new Runnable() {
105             @Override
106             @CalledInAwt
107             public void run() {
108               myRequestCache.put(change, Pair.create(change, request));
109               applyRequest(request, force, scrollToChangePolicy);
110             }
111           };
112         }
113       },
114       new Runnable() {
115         @Override
116         public void run() {
117           applyRequest(new LoadingDiffRequest(ChangeDiffRequestProducer.getRequestTitle(change)), force, scrollToChangePolicy);
118         }
119       },
120       ProgressWindow.DEFAULT_PROGRESS_DIALOG_POSTPONE_TIME_MILLIS
121     );
122   }
123
124   @Nullable
125   @CalledInAwt
126   @Contract("null, _ -> !null")
127   protected DiffRequest loadRequestFast(@Nullable Change change, boolean useCache) {
128     if (change == null) return NoDiffRequest.INSTANCE;
129
130     if (useCache) {
131       Pair<Change, DiffRequest> pair = myRequestCache.get(change);
132       if (pair != null) {
133         Change oldChange = pair.first;
134         if (ChangeDiffRequestProducer.isEquals(oldChange, change)) {
135           return pair.second;
136         }
137       }
138     }
139
140     if (change.getBeforeRevision() instanceof FakeRevision || change.getAfterRevision() instanceof FakeRevision) {
141       return new LoadingDiffRequest(ChangeDiffRequestProducer.getRequestTitle(change));
142     }
143
144     return null;
145   }
146
147   @NotNull
148   @CalledInBackground
149   private DiffRequest loadRequest(@NotNull Change change, @NotNull ProgressIndicator indicator) {
150     ChangeDiffRequestProducer presentable = ChangeDiffRequestProducer.create(getProject(), change);
151     if (presentable == null) return new ErrorDiffRequest("Can't show diff");
152     try {
153       return presentable.process(getContext(), indicator);
154     }
155     catch (ProcessCanceledException e) {
156       OperationCanceledDiffRequest request = new OperationCanceledDiffRequest(presentable.getName());
157       request.putUserData(DiffUserDataKeys.CONTEXT_ACTIONS, Collections.<AnAction>singletonList(new ReloadRequestAction(change)));
158       return request;
159     }
160     catch (DiffRequestProducerException e) {
161       return new ErrorDiffRequest(presentable, e);
162     }
163     catch (Exception e) {
164       LOG.warn(e);
165       return new ErrorDiffRequest(presentable, e);
166     }
167   }
168
169   //
170   // Impl
171   //
172
173   @Override
174   @CalledInAwt
175   protected void onDispose() {
176     super.onDispose();
177     myQueue.abort();
178     myRequestCache.clear();
179   }
180
181   @NotNull
182   @Override
183   public Project getProject() {
184     return super.getProject();
185   }
186
187   //
188   // Navigation
189   //
190
191   /*
192    * Multiple selection:
193    * - iterate inside selection
194    *
195    * Single selection:
196    * - iterate all changes
197    * - update selection after movement
198    *
199    * current element should always be among allChanges and selection (if they are not empty)
200    */
201
202   public void clear() {
203     myCurrentChange = null;
204     updateRequest();
205   }
206
207   @CalledInAwt
208   public void refresh() {
209     List<Change> selectedChanges = getSelectedChanges();
210
211     if (selectedChanges.isEmpty()) {
212       myCurrentChange = null;
213       updateRequest();
214       return;
215     }
216
217     Change selectedChange = myCurrentChange != null ? ContainerUtil.find(selectedChanges, myCurrentChange) : null;
218     if (selectedChange == null) {
219       myCurrentChange = selectedChanges.get(0);
220       updateRequest();
221       return;
222     }
223
224     if (!ChangeDiffRequestProducer.isEquals(myCurrentChange, selectedChange)) {
225       myCurrentChange = selectedChange;
226       updateRequest();
227     }
228   }
229
230   @Override
231   protected boolean hasNextChange() {
232     if (myCurrentChange == null) return false;
233
234     List<Change> selectedChanges = getSelectedChanges();
235     if (selectedChanges.isEmpty()) return false;
236
237     if (selectedChanges.size() > 1) {
238       int index = selectedChanges.indexOf(myCurrentChange);
239       return index != -1 && index < selectedChanges.size() - 1;
240     }
241     else {
242       List<Change> allChanges = getAllChanges();
243       int index = allChanges.indexOf(myCurrentChange);
244       return index != -1 && index < allChanges.size() - 1;
245     }
246   }
247
248   @Override
249   protected boolean hasPrevChange() {
250     if (myCurrentChange == null) return false;
251
252     List<Change> selectedChanges = getSelectedChanges();
253     if (selectedChanges.isEmpty()) return false;
254
255     if (selectedChanges.size() > 1) {
256       int index = selectedChanges.indexOf(myCurrentChange);
257       return index != -1 && index > 0;
258     }
259     else {
260       List<Change> allChanges = getAllChanges();
261       int index = allChanges.indexOf(myCurrentChange);
262       return index != -1 && index > 0;
263     }
264   }
265
266   @Override
267   protected void goToNextChange(boolean fromDifferences) {
268     List<Change> selectedChanges = getSelectedChanges();
269     List<Change> allChanges = getAllChanges();
270
271     if (selectedChanges.size() > 1) {
272       int index = selectedChanges.indexOf(myCurrentChange);
273       myCurrentChange = selectedChanges.get(index + 1);
274     }
275     else {
276       int index = allChanges.indexOf(myCurrentChange);
277       myCurrentChange = allChanges.get(index + 1);
278       selectChange(myCurrentChange);
279     }
280
281     updateRequest(false, fromDifferences ? ScrollToPolicy.FIRST_CHANGE : null);
282   }
283
284   @Override
285   protected void goToPrevChange(boolean fromDifferences) {
286     List<Change> selectedChanges = getSelectedChanges();
287     List<Change> allChanges = getAllChanges();
288
289     if (selectedChanges.size() > 1) {
290       int index = selectedChanges.indexOf(myCurrentChange);
291       myCurrentChange = selectedChanges.get(index - 1);
292     }
293     else {
294       int index = allChanges.indexOf(myCurrentChange);
295       myCurrentChange = allChanges.get(index - 1);
296       selectChange(myCurrentChange);
297     }
298
299     updateRequest(false, fromDifferences ? ScrollToPolicy.LAST_CHANGE : null);
300   }
301
302   @Override
303   protected boolean isNavigationEnabled() {
304     return getSelectedChanges().size() > 1 || getAllChanges().size() > 1;
305   }
306
307   //
308   // Actions
309   //
310
311   protected class ReloadRequestAction extends DumbAwareAction {
312     @NotNull private final Change myChange;
313
314     public ReloadRequestAction(@NotNull Change change) {
315       super("Reload", null, AllIcons.Actions.Refresh);
316       myChange = change;
317     }
318
319     @Override
320     public void actionPerformed(AnActionEvent e) {
321       myRequestCache.remove(myChange);
322       updateRequest(true);
323     }
324   }
325 }