diff --git a/platform/vcs-log/impl/src/com/intellij/vcs/log/data/AbstractDataGetter.java b/platform/vcs-log/impl/src/com/intellij/vcs/log/data/AbstractDataGetter.java
new file mode 100644
index 000000000000..6a21ad975cd3
--- /dev/null
+++ b/platform/vcs-log/impl/src/com/intellij/vcs/log/data/AbstractDataGetter.java
@@ -0,0 +1,204 @@
+package com.intellij.vcs.log.data;
+
+import com.intellij.openapi.Disposable;
+import com.intellij.openapi.util.Disposer;
+import com.intellij.openapi.vcs.VcsException;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.util.Function;
+import com.intellij.util.ThrowableConsumer;
+import com.intellij.util.containers.ContainerUtil;
+import com.intellij.util.containers.MultiMap;
+import com.intellij.util.ui.UIUtil;
+import com.intellij.vcs.log.VcsLogHashMap;
+import com.intellij.vcs.log.VcsLogProvider;
+import com.intellij.vcs.log.VcsShortCommitDetails;
+import com.intellij.vcs.log.ui.tables.GraphTableModel;
+import com.intellij.vcs.log.util.SequentialLimitedLifoExecutor;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.awt.*;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * The DataGetter realizes the following pattern of getting some data (parametrized by {@code T}) from the VCS:
+ *
+ * - it tries to get it from the cache;
+ * - if it fails, it tries to get it from the VCS, and additionally loads several commits around the requested one,
+ * to avoid querying the VCS if user investigates details of nearby commits.
+ * - The loading happens asynchronously: a fake {@link LoadingDetails} object is returned
+ *
+ *
+ * @author Kirill Likhodedov
+ */
+abstract class AbstractDataGetter implements Disposable, DataGetter {
+
+ private static final int UP_PRELOAD_COUNT = 20;
+ private static final int DOWN_PRELOAD_COUNT = 40;
+ private static final int MAX_LOADING_TASKS = 10;
+
+ @NotNull protected final VcsLogHashMap myHashMap;
+ @NotNull private final Map myLogProviders;
+ @NotNull private final VcsCommitCache myCache;
+ @NotNull private final SequentialLimitedLifoExecutor myLoader;
+
+ /**
+ * The sequence number of the current "loading" task.
+ */
+ private long myCurrentTaskIndex = 0;
+
+ @NotNull private final Collection myLoadingFinishedListeners = new ArrayList();
+
+ AbstractDataGetter(@NotNull VcsLogHashMap hashMap,
+ @NotNull Map logProviders,
+ @NotNull VcsCommitCache cache,
+ @NotNull Disposable parentDisposable) {
+ myHashMap = hashMap;
+ myLogProviders = logProviders;
+ myCache = cache;
+ Disposer.register(parentDisposable, this);
+ myLoader = new SequentialLimitedLifoExecutor(this, MAX_LOADING_TASKS,
+ new ThrowableConsumer() {
+ @Override
+ public void consume(TaskDescriptor task) throws VcsException {
+ preLoadCommitData(task.myCommits);
+ UIUtil.invokeAndWaitIfNeeded(new Runnable() {
+ @Override
+ public void run() {
+ for (Runnable loadingFinishedListener : myLoadingFinishedListeners) {
+ loadingFinishedListener.run();
+ }
+ }
+ });
+ }
+ });
+ }
+
+ @Override
+ public void dispose() {
+ myLoadingFinishedListeners.clear();
+ }
+
+ @Override
+ @Nullable
+ public T getCommitData(int row, @NotNull GraphTableModel tableModel) {
+ assert EventQueue.isDispatchThread();
+ Integer hash = tableModel.getCommitIdAtRow(row);
+ T details = getFromCache(hash);
+ if (details != null) {
+ return details;
+ }
+ runLoadAroundCommitData(row, tableModel);
+ return myCache.get(hash); // now it is in the cache as "Loading Details".
+ }
+
+ @Override
+ @Nullable
+ public T getCommitDataIfAvailable(int hash) {
+ return getFromCache(hash);
+ }
+
+ @Nullable
+ private T getFromCache(@NotNull Integer commitId) {
+ T details = myCache.get(commitId);
+ if (details != null) {
+ if (details instanceof LoadingDetails) {
+ if (((LoadingDetails)details).getLoadingTaskIndex() <= myCurrentTaskIndex - MAX_LOADING_TASKS) {
+ // don't let old "loading" requests stay in the cache forever
+ myCache.remove(commitId);
+ return null;
+ }
+ }
+ return details;
+ }
+ return getFromAdditionalCache(commitId);
+ }
+
+ /**
+ * Lookup somewhere else but the standard cache.
+ */
+ @Nullable
+ protected abstract T getFromAdditionalCache(int commitId);
+
+ private void runLoadAroundCommitData(int row, @NotNull GraphTableModel tableModel) {
+ long taskNumber = myCurrentTaskIndex++;
+ MultiMap commits = getCommitsAround(row, tableModel, UP_PRELOAD_COUNT, DOWN_PRELOAD_COUNT);
+ for (Map.Entry> hashesByRoots : commits.entrySet()) {
+ VirtualFile root = hashesByRoots.getKey();
+ Collection hashes = hashesByRoots.getValue();
+
+ // fill the cache with temporary "Loading" values to avoid producing queries for each commit that has not been cached yet,
+ // even if it will be loaded within a previous query
+ for (int commitId : hashes) {
+ if (!myCache.isKeyCached(commitId)) {
+ myCache.put(commitId, (T)new LoadingDetails(myHashMap.getHash(commitId), taskNumber, root));
+ }
+ }
+ }
+
+ TaskDescriptor task = new TaskDescriptor(commits);
+ myLoader.queue(task);
+ }
+
+ @NotNull
+ private static MultiMap getCommitsAround(int selectedRow,
+ @NotNull GraphTableModel model,
+ int above,
+ int below) {
+ MultiMap commits = MultiMap.create();
+ for (int row = Math.max(0, selectedRow - above); row < selectedRow + below && row < model.getRowCount(); row++) {
+ Integer hash = model.getCommitIdAtRow(row);
+ VirtualFile root = model.getRoot(row);
+ commits.putValue(root, hash);
+ }
+ return commits;
+ }
+
+ private void preLoadCommitData(@NotNull MultiMap commits) throws VcsException {
+ for (Map.Entry> entry : commits.entrySet()) {
+ List hashStrings = ContainerUtil.map(entry.getValue(), new Function() {
+ @Override
+ public String fun(Integer commitId) {
+ return myHashMap.getHash(commitId).asString();
+ }
+ });
+ List extends T> details = readDetails(myLogProviders.get(entry.getKey()), entry.getKey(), hashStrings);
+ saveInCache(details);
+ }
+ }
+
+ public void saveInCache(final List extends T> details) {
+ UIUtil.invokeAndWaitIfNeeded(new Runnable() {
+ @Override
+ public void run() {
+ for (T data : details) {
+ myCache.put(myHashMap.getCommitIndex(data.getId()), data);
+ }
+ }
+ });
+ }
+
+ @NotNull
+ protected abstract List extends T> readDetails(@NotNull VcsLogProvider logProvider, @NotNull VirtualFile root,
+ @NotNull List hashes) throws VcsException;
+
+ /**
+ * This listener will be notified when any details loading process finishes.
+ * The notification will happen in the EDT.
+ */
+ public void addDetailsLoadedListener(@NotNull Runnable runnable) {
+ myLoadingFinishedListeners.add(runnable);
+ }
+
+ private static class TaskDescriptor {
+ private final MultiMap myCommits;
+
+ private TaskDescriptor(MultiMap commits) {
+ myCommits = commits;
+ }
+ }
+
+}
diff --git a/platform/vcs-log/impl/src/com/intellij/vcs/log/data/CommitDetailsGetter.java b/platform/vcs-log/impl/src/com/intellij/vcs/log/data/CommitDetailsGetter.java
index 7e1d44c81efe..e2a331f07805 100644
--- a/platform/vcs-log/impl/src/com/intellij/vcs/log/data/CommitDetailsGetter.java
+++ b/platform/vcs-log/impl/src/com/intellij/vcs/log/data/CommitDetailsGetter.java
@@ -15,7 +15,7 @@ import java.util.Map;
/**
* The CommitDetailsGetter is responsible for getting {@link VcsFullCommitDetails complete commit details} from the cache or from the VCS.
*/
-public class CommitDetailsGetter extends DataGetter {
+public class CommitDetailsGetter extends AbstractDataGetter {
CommitDetailsGetter(@NotNull VcsLogHashMap hashMap,
@NotNull Map logProviders,
diff --git a/platform/vcs-log/impl/src/com/intellij/vcs/log/data/DataGetter.java b/platform/vcs-log/impl/src/com/intellij/vcs/log/data/DataGetter.java
index d6c508d38490..4da4b90a5ed7 100644
--- a/platform/vcs-log/impl/src/com/intellij/vcs/log/data/DataGetter.java
+++ b/platform/vcs-log/impl/src/com/intellij/vcs/log/data/DataGetter.java
@@ -1,202 +1,29 @@
+/*
+ * Copyright 2000-2015 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.vcs.log.data;
-import com.intellij.openapi.Disposable;
-import com.intellij.openapi.util.Disposer;
-import com.intellij.openapi.vcs.VcsException;
-import com.intellij.openapi.vfs.VirtualFile;
-import com.intellij.util.Function;
-import com.intellij.util.ThrowableConsumer;
-import com.intellij.util.containers.ContainerUtil;
-import com.intellij.util.containers.MultiMap;
-import com.intellij.util.ui.UIUtil;
-import com.intellij.vcs.log.VcsLogHashMap;
-import com.intellij.vcs.log.VcsLogProvider;
import com.intellij.vcs.log.VcsShortCommitDetails;
import com.intellij.vcs.log.ui.tables.GraphTableModel;
-import com.intellij.vcs.log.util.SequentialLimitedLifoExecutor;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
-import java.awt.*;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.List;
-import java.util.Map;
-
-/**
- * The DataGetter realizes the following pattern of getting some data (parametrized by {@code T}) from the VCS:
- *
- * - it tries to get it from the cache;
- * - if it fails, it tries to get it from the VCS, and additionally loads several commits around the requested one,
- * to avoid querying the VCS if user investigates details of nearby commits.
- * - The loading happens asynchronously: a fake {@link LoadingDetails} object is returned
- *
- *
- * @author Kirill Likhodedov
- */
-public abstract class DataGetter implements Disposable {
-
- private static final int UP_PRELOAD_COUNT = 20;
- private static final int DOWN_PRELOAD_COUNT = 40;
- private static final int MAX_LOADING_TASKS = 10;
-
- @NotNull protected final VcsLogHashMap myHashMap;
- @NotNull private final Map myLogProviders;
- @NotNull private final VcsCommitCache myCache;
- @NotNull private final SequentialLimitedLifoExecutor myLoader;
-
- /**
- * The sequence number of the current "loading" task.
- */
- private long myCurrentTaskIndex = 0;
-
- @NotNull private final Collection myLoadingFinishedListeners = new ArrayList();
-
- DataGetter(@NotNull VcsLogHashMap hashMap,
- @NotNull Map logProviders,
- @NotNull VcsCommitCache cache,
- @NotNull Disposable parentDisposable) {
- myHashMap = hashMap;
- myLogProviders = logProviders;
- myCache = cache;
- Disposer.register(parentDisposable, this);
- myLoader = new SequentialLimitedLifoExecutor(this, MAX_LOADING_TASKS,
- new ThrowableConsumer() {
- @Override
- public void consume(TaskDescriptor task) throws VcsException {
- preLoadCommitData(task.myCommits);
- UIUtil.invokeAndWaitIfNeeded(new Runnable() {
- @Override
- public void run() {
- for (Runnable loadingFinishedListener : myLoadingFinishedListeners) {
- loadingFinishedListener.run();
- }
- }
- });
- }
- });
- }
-
- @Override
- public void dispose() {
- myLoadingFinishedListeners.clear();
- }
+public interface DataGetter {
+ @Nullable
+ T getCommitData(int row, @NotNull GraphTableModel tableModel);
@Nullable
- public T getCommitData(int row, @NotNull GraphTableModel tableModel) {
- assert EventQueue.isDispatchThread();
- Integer hash = tableModel.getCommitIdAtRow(row);
- T details = getFromCache(hash);
- if (details != null) {
- return details;
- }
- runLoadAroundCommitData(row, tableModel);
- return myCache.get(hash); // now it is in the cache as "Loading Details".
- }
-
- @Nullable
- public T getCommitDataIfAvailable(int hash) {
- return getFromCache(hash);
- }
-
- @Nullable
- private T getFromCache(@NotNull Integer commitId) {
- T details = myCache.get(commitId);
- if (details != null) {
- if (details instanceof LoadingDetails) {
- if (((LoadingDetails)details).getLoadingTaskIndex() <= myCurrentTaskIndex - MAX_LOADING_TASKS) {
- // don't let old "loading" requests stay in the cache forever
- myCache.remove(commitId);
- return null;
- }
- }
- return details;
- }
- return getFromAdditionalCache(commitId);
- }
-
- /**
- * Lookup somewhere else but the standard cache.
- */
- @Nullable
- protected abstract T getFromAdditionalCache(int commitId);
-
- private void runLoadAroundCommitData(int row, @NotNull GraphTableModel tableModel) {
- long taskNumber = myCurrentTaskIndex++;
- MultiMap commits = getCommitsAround(row, tableModel, UP_PRELOAD_COUNT, DOWN_PRELOAD_COUNT);
- for (Map.Entry> hashesByRoots : commits.entrySet()) {
- VirtualFile root = hashesByRoots.getKey();
- Collection hashes = hashesByRoots.getValue();
-
- // fill the cache with temporary "Loading" values to avoid producing queries for each commit that has not been cached yet,
- // even if it will be loaded within a previous query
- for (int commitId : hashes) {
- if (!myCache.isKeyCached(commitId)) {
- myCache.put(commitId, (T)new LoadingDetails(myHashMap.getHash(commitId), taskNumber, root));
- }
- }
- }
-
- TaskDescriptor task = new TaskDescriptor(commits);
- myLoader.queue(task);
- }
-
- @NotNull
- private static MultiMap getCommitsAround(int selectedRow,
- @NotNull GraphTableModel model,
- int above,
- int below) {
- MultiMap commits = MultiMap.create();
- for (int row = Math.max(0, selectedRow - above); row < selectedRow + below && row < model.getRowCount(); row++) {
- Integer hash = model.getCommitIdAtRow(row);
- VirtualFile root = model.getRoot(row);
- commits.putValue(root, hash);
- }
- return commits;
- }
-
- private void preLoadCommitData(@NotNull MultiMap commits) throws VcsException {
- for (Map.Entry> entry : commits.entrySet()) {
- List hashStrings = ContainerUtil.map(entry.getValue(), new Function() {
- @Override
- public String fun(Integer commitId) {
- return myHashMap.getHash(commitId).asString();
- }
- });
- List extends T> details = readDetails(myLogProviders.get(entry.getKey()), entry.getKey(), hashStrings);
- saveInCache(details);
- }
- }
-
- public void saveInCache(final List extends T> details) {
- UIUtil.invokeAndWaitIfNeeded(new Runnable() {
- @Override
- public void run() {
- for (T data : details) {
- myCache.put(myHashMap.getCommitIndex(data.getId()), data);
- }
- }
- });
- }
-
- @NotNull
- protected abstract List extends T> readDetails(@NotNull VcsLogProvider logProvider, @NotNull VirtualFile root,
- @NotNull List hashes) throws VcsException;
-
- /**
- * This listener will be notified when any details loading process finishes.
- * The notification will happen in the EDT.
- */
- public void addDetailsLoadedListener(@NotNull Runnable runnable) {
- myLoadingFinishedListeners.add(runnable);
- }
-
- private static class TaskDescriptor {
- private final MultiMap myCommits;
-
- private TaskDescriptor(MultiMap commits) {
- myCommits = commits;
- }
- }
-
+ T getCommitDataIfAvailable(int hash);
}
diff --git a/platform/vcs-log/impl/src/com/intellij/vcs/log/data/MiniDetailsGetter.java b/platform/vcs-log/impl/src/com/intellij/vcs/log/data/MiniDetailsGetter.java
index 7a54b261749c..1c63f4c762a7 100644
--- a/platform/vcs-log/impl/src/com/intellij/vcs/log/data/MiniDetailsGetter.java
+++ b/platform/vcs-log/impl/src/com/intellij/vcs/log/data/MiniDetailsGetter.java
@@ -13,7 +13,7 @@ import org.jetbrains.annotations.Nullable;
import java.util.List;
import java.util.Map;
-public class MiniDetailsGetter extends DataGetter {
+public class MiniDetailsGetter extends AbstractDataGetter {
@NotNull private final Map myTopCommitsDetailsCache;
diff --git a/platform/vcs-log/impl/src/com/intellij/vcs/log/data/VisiblePackBuilder.java b/platform/vcs-log/impl/src/com/intellij/vcs/log/data/VisiblePackBuilder.java
index f3c9afc873b0..9759fde59aa6 100644
--- a/platform/vcs-log/impl/src/com/intellij/vcs/log/data/VisiblePackBuilder.java
+++ b/platform/vcs-log/impl/src/com/intellij/vcs/log/data/VisiblePackBuilder.java
@@ -42,13 +42,13 @@ class VisiblePackBuilder {
@NotNull private final VcsLogHashMap myHashMap;
@NotNull private final Map myTopCommitsDetailsCache;
- @NotNull private final CommitDetailsGetter myCommitDetailsGetter;
+ @NotNull private final DataGetter myCommitDetailsGetter;
@NotNull private final Map myLogProviders;
VisiblePackBuilder(@NotNull Map providers,
@NotNull VcsLogHashMap hashMap,
@NotNull Map topCommitsDetailsCache,
- @NotNull CommitDetailsGetter detailsGetter) {
+ @NotNull DataGetter detailsGetter) {
myHashMap = hashMap;
myTopCommitsDetailsCache = topCommitsDetailsCache;
myCommitDetailsGetter = detailsGetter;
diff --git a/platform/vcs-log/impl/test/com/intellij/vcs/log/data/VisiblePackBuilderTest.kt b/platform/vcs-log/impl/test/com/intellij/vcs/log/data/VisiblePackBuilderTest.kt
index d18d2358248f..b673578c931d 100644
--- a/platform/vcs-log/impl/test/com/intellij/vcs/log/data/VisiblePackBuilderTest.kt
+++ b/platform/vcs-log/impl/test/com/intellij/vcs/log/data/VisiblePackBuilderTest.kt
@@ -15,37 +15,26 @@
*/
package com.intellij.vcs.log.data
-import org.junit.Test
import com.intellij.mock.MockVirtualFile
-import com.intellij.vcs.log.impl.TestVcsLogProvider
-import com.intellij.vcs.log.impl.TestVcsLogProvider.*
-import com.intellij.vcs.log.VcsLogHashMap
-import com.intellij.openapi.vfs.VirtualFile
import com.intellij.openapi.Disposable
-import com.intellij.vcs.log.graph.PermanentGraph
-import com.intellij.vcs.log.impl.VcsLogFilterCollectionImpl
-import com.intellij.vcs.log.Hash
-import com.intellij.vcs.log.impl.HashImpl
-import java.util.HashMap
-import com.intellij.vcs.log.graph.GraphCommit
-import java.util.ArrayList
-import com.intellij.vcs.log.graph.GraphCommitImpl
-import com.intellij.vcs.log.VcsRef
-import java.util.HashSet
-import com.intellij.vcs.log.impl.VcsRefImpl
-import com.intellij.vcs.log.VcsLogFilterCollection
-import kotlin.test.assertEquals
-import com.intellij.vcs.log.VcsLogUserFilter
-import com.intellij.vcs.log.impl.VcsCommitMetadataImpl
-import com.intellij.vcs.log.VcsUser
-import com.intellij.vcs.log.impl.VcsUserImpl
-import com.intellij.vcs.log.ui.filter.VcsLogUserFilterImpl
-import kotlin.test.assertTrue
-import com.intellij.vcs.log.graph.VisibleGraph
-import com.intellij.vcs.log.VcsLogBranchFilter
-import com.intellij.vcs.log.TimedVcsCommit
+import com.intellij.openapi.vfs.VirtualFile
import com.intellij.util.Function
-import com.intellij.vcs.log.impl.TimedVcsCommitImpl
+import com.intellij.vcs.log.*
+import com.intellij.vcs.log.graph.GraphCommit
+import com.intellij.vcs.log.graph.GraphCommitImpl
+import com.intellij.vcs.log.graph.PermanentGraph
+import com.intellij.vcs.log.graph.VisibleGraph
+import com.intellij.vcs.log.impl.*
+import com.intellij.vcs.log.impl.TestVcsLogProvider.BRANCH_TYPE
+import com.intellij.vcs.log.impl.TestVcsLogProvider.DEFAULT_USER
+import com.intellij.vcs.log.ui.filter.VcsLogUserFilterImpl
+import com.intellij.vcs.log.ui.tables.GraphTableModel
+import org.junit.Test
+import java.util.ArrayList
+import java.util.HashMap
+import java.util.HashSet
+import kotlin.test.assertEquals
+import kotlin.test.assertTrue
class VisiblePackBuilderTest {
@@ -159,7 +148,17 @@ class VisiblePackBuilderTest {
it.value.user, it.value.subject, it.value.user, 1L)
Pair(it.key.getId(), metadata)
}.toMap()
- val builder = VisiblePackBuilder(providers, hashMap, detailsCache, CommitDetailsGetter(hashMap, providers, TRIVIAL_DISPOSABLE))
+
+ val commitDetailsGetter = object : DataGetter {
+ override fun getCommitData(row: Int, tableModel: GraphTableModel): VcsFullCommitDetails? {
+ return null;
+ }
+
+ override fun getCommitDataIfAvailable(hash: Int): VcsFullCommitDetails? {
+ return null;
+ }
+ }
+ val builder = VisiblePackBuilder(providers, hashMap, detailsCache, commitDetailsGetter)
return builder.build(dataPack, PermanentGraph.SortType.Normal, filters, CommitCountStage.INITIAL).first
}
@@ -237,11 +236,5 @@ class VisiblePackBuilderTest {
override fun findHashByString(string: String) = throw UnsupportedOperationException()
}
-
- object TRIVIAL_DISPOSABLE : Disposable {
- override fun dispose() {
- throw UnsupportedOperationException()
- }
- }
}