/*
* Copyright 2000-2010 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.history.wholeTree;
import com.intellij.openapi.progress.ProcessCanceledException;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Getter;
import com.intellij.openapi.vcs.ObjectsConvertor;
import com.intellij.openapi.vcs.VcsException;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.util.AsynchConsumer;
import com.intellij.util.BufferedListConsumer;
import com.intellij.util.Consumer;
import com.intellij.util.containers.Convertor;
import org.community.intellij.plugins.communitycase.Branch;
import org.community.intellij.plugins.communitycase.changes.ChangeUtils;
import org.community.intellij.plugins.communitycase.history.browser.ChangesFilter;
import org.community.intellij.plugins.communitycase.history.browser.LowLevelAccessImpl;
import org.community.intellij.plugins.communitycase.history.browser.ShaHash;
import org.community.intellij.plugins.communitycase.history.browser.SymbolicRefs;
import java.util.*;
/**
* @author irengrig
*/
public class LoaderAndRefresherImpl implements LoaderAndRefresher<CommitHashPlusParents> {
private final static int ourTestCount = 5;
private final static int ourPreload = 100;
private static final int ourPackSize = 3000;
private final Collection<String> myStartingPoints;
private final Mediator.Ticket myTicket;
private final Collection<ChangesFilter.Filter> myFilters;
private final Mediator myMediator;
private final DetailsCache myDetailsCache;
private final Project myProject;
private volatile boolean myInterrupted;
private final Getter<Boolean> myProgressAnalog;
private BufferedListConsumer<CommitHashPlusParents> myBufferConsumer;
private Consumer<List<CommitHashPlusParents>> myRealConsumer;
private final MyRootHolder myRootHolder;
private final UsersIndex myUsersIndex;
private final boolean myLoadParents;
private RepeatingLoadConsumer<CommitHashPlusParents> myRepeatingLoadConsumer;
private LowLevelAccessImpl myLowLevelAccess;
private SymbolicRefs mySymbolicRefs;
public LoaderAndRefresherImpl(final Mediator.Ticket ticket,
Collection<ChangesFilter.Filter> filters,
Mediator mediator,
Collection<String> startingPoints,
DetailsCache detailsCache, Project project, MyRootHolder rootHolder, final UsersIndex usersIndex) {
myRootHolder = rootHolder;
myUsersIndex = usersIndex;
myLoadParents = filters == null || filters.isEmpty();
myTicket = ticket;
myFilters = filters;
myMediator = mediator;
myStartingPoints = startingPoints;
myDetailsCache = detailsCache;
myProject = project;
myInterrupted = false;
myProgressAnalog = new Getter<Boolean>() {
@Override
public Boolean get() {
return myInterrupted;
}
};
myLowLevelAccess = new LowLevelAccessImpl(myProject, myRootHolder.getRoot());
myRealConsumer = new Consumer<List<CommitHashPlusParents>>() {
@Override
public void consume(final List<CommitHashPlusParents> list) {
final List<CommitI> buffer = new ArrayList<CommitI>();
final List<List<AbstractHash>> parents = myLoadParents ? new ArrayList<List<AbstractHash>>() : null;
for (CommitHashPlusParents commitHashPlusParents : list) {
CommitI commit = new Commit(commitHashPlusParents.getHash(), commitHashPlusParents.getTime(),
myUsersIndex.put(commitHashPlusParents.getAuthorName()));
commit = myRootHolder.decorateByRoot(commit);
buffer.add(commit);
if (myLoadParents) {
parents.add(commitHashPlusParents.getParents());
}
}
if(! myMediator.appendResult(myTicket, buffer, parents)) {
myInterrupted = true;
}
}
};
myBufferConsumer = new BufferedListConsumer<CommitHashPlusParents>(15, myRealConsumer, 400);
myRepeatingLoadConsumer = new RepeatingLoadConsumer<CommitHashPlusParents>(myProject, myBufferConsumer.asConsumer());
}
public void interrupt() {
myInterrupted = true;
}
public boolean isInterrupted() {
return myInterrupted;
}
@Override
public boolean flushIntoUI() {
myBufferConsumer.flush();
return ! myInterrupted;
}
@Override
public LoadAlgorithm.Result<CommitHashPlusParents> load(final LoadAlgorithm.LoadType loadType, long continuation) {
if (myInterrupted) return new LoadAlgorithm.Result<CommitHashPlusParents>(true, 0, myRepeatingLoadConsumer.getLast());
initSymbRefs();
if (! myStartingPoints.isEmpty()) {
if (! checkStartingPoints()) return new LoadAlgorithm.Result<CommitHashPlusParents>(true, 0, myRepeatingLoadConsumer.getLast());
}
myRepeatingLoadConsumer.reset();
int count = ourPackSize;
boolean shouldFull = true;
if (LoadAlgorithm.LoadType.TEST.equals(loadType)) {
count = ourTestCount;
} else if (LoadAlgorithm.LoadType.SHORT.equals(loadType) || LoadAlgorithm.LoadType.SHORT_START.equals(loadType)) {
shouldFull = false;
} else if (LoadAlgorithm.LoadType.FULL_PREVIEW.equals(loadType)) {
count = ourPreload;
}
long start;
boolean isOver = false;
while (true) {
start = System.currentTimeMillis();
step(count, shouldFull, continuation);
if (isInterrupted()) return new LoadAlgorithm.Result<CommitHashPlusParents>(true, 0, myRepeatingLoadConsumer.getLast());
final List<AbstractHash> lastParents = myRepeatingLoadConsumer.getLast() == null ? null : myRepeatingLoadConsumer.getLast().getParents();
// at least 1 record would be found, the latest
isOver = lastParents == null || lastParents.isEmpty() || (myRepeatingLoadConsumer.getTotalRecordsInPack() < count);
if (isOver) {
break;
}
if (myRepeatingLoadConsumer.sincePoint() == 0) {
count *= 2;
myRepeatingLoadConsumer.reset();
} else {
break;
}
}
final long end = System.currentTimeMillis();
return new LoadAlgorithm.Result<CommitHashPlusParents>(isOver, end - start, myRepeatingLoadConsumer.getLast());
}
private void step(final int count, final boolean shouldFull, final long continuation) {
if (shouldFull) {
loadFull(count, continuation);
} else {
loadShort(continuation, count);
}
}
private boolean checkStartingPoints() {
for (String point : myStartingPoints) {
if (point.startsWith(Branch.REFS_REMOTES_PREFIX)) {
if (mySymbolicRefs.getRemoteBranches().contains(point.substring(Branch.REFS_REMOTES_PREFIX.length()))) {
return true;
}
} else {
point = point.startsWith(Branch.REFS_HEADS_PREFIX) ? point.substring(Branch.REFS_HEADS_PREFIX.length()) : point;
if (mySymbolicRefs.getLocalBranches().contains(point) || mySymbolicRefs.getTags().contains(point)) {
return true;
}
}
}
return false;
}
private void initSymbRefs() {
if (mySymbolicRefs == null) {
try {
mySymbolicRefs = myLowLevelAccess.getRefs();
myMediator.reportSymbolicRefs(myTicket, myRootHolder.getRoot(), mySymbolicRefs);
}
catch (VcsException e) {
myMediator.acceptException(e);
}
}
}
// true - load is complete //if (Commit.getParentsHashes().isEmpty())
private void loadFull(final int count, final long continuation) {
try {
final Collection<ChangesFilter.Filter> filters = addContinuation(continuation);
myLowLevelAccess.loadCommits(myStartingPoints, Collections.<String>emptyList(), filters, new AsynchConsumer<org.community.intellij.plugins.communitycase.history.browser.Commit>() {
@Override
public void consume(org.community.intellij.plugins.communitycase.history.browser.Commit Commit) {
myDetailsCache.acceptAnswer(Collections.singleton(Commit), myRootHolder.getRoot());
myRepeatingLoadConsumer.consume(CommitToCommitConvertor.getInstance().convert(Commit));
}
@Override
public void finished() {
}
}, count, myProgressAnalog, mySymbolicRefs);
}
catch (VcsException e) {
myMediator.acceptException(e);
}
}
private Collection<ChangesFilter.Filter> addContinuation(long continuation) {
Collection<ChangesFilter.Filter> filters;
if (continuation > 0) {
filters = new ArrayList<ChangesFilter.Filter>(myFilters);
filters.add(new ChangesFilter.BeforeDate(new Date(continuation)));
} else {
filters = myFilters;
}
return filters;
}
public void loadByHashesAside(final List<String> hashes) {
final List<CommitI> result = new ArrayList<CommitI>();
final List<List<AbstractHash>> parents = myLoadParents ? new ArrayList<List<AbstractHash>>() : null;
for (String hash : hashes) {
try {
final ShaHash shaHash = ChangeUtils.commitExists(myProject, myRootHolder.getRoot(), hash);
if (shaHash == null) continue;
final List<org.community.intellij.plugins.communitycase.history.browser.Commit> commits = myLowLevelAccess.getCommitDetails(Collections.singletonList(shaHash.getValue()), mySymbolicRefs);
myDetailsCache.acceptAnswer(commits, myRootHolder.getRoot());
appendCommits(result, parents, commits);
}
catch (VcsException e1) {
continue;
}
}
if (! result.isEmpty()) {
myMediator.appendResult(myTicket, result, parents);
}
}
private void appendCommits(List<CommitI> result, List<List<AbstractHash>> parents, List<org.community.intellij.plugins.communitycase.history.browser.Commit> commits) {
for (org.community.intellij.plugins.communitycase.history.browser.Commit commit : commits) {
final Commit commitObj =
new Commit(commit.getShortHash().getString(), commit.getDate().getTime(), myUsersIndex.put(commit.getAuthor()));
if (parents != null) {
final Set<String> parentsHashes = commit.getParentsHashes();
parents.add(ObjectsConvertor.convert(parentsHashes, new Convertor<String, AbstractHash>() {
@Override
public AbstractHash convert(String o) {
return AbstractHash.create(o);
}
}));
}
result.add(myRootHolder.decorateByRoot(commitObj));
}
}
private void loadShort(final long continuation, int maxCount) {
final Collection<ChangesFilter.Filter> filters = addContinuation(continuation);
try {
myLowLevelAccess.loadHashesWithParents(myStartingPoints, filters, myRepeatingLoadConsumer, myProgressAnalog, maxCount);
}
catch (VcsException e) {
myMediator.acceptException(e);
}
}
interface MyRootHolder {
VirtualFile getRoot();
CommitI decorateByRoot(final CommitI commitI);
}
static class OneRootHolder implements MyRootHolder {
private final VirtualFile myVirtualFile;
OneRootHolder(VirtualFile virtualFile) {
myVirtualFile = virtualFile;
}
@Override
public CommitI decorateByRoot(CommitI commitI) {
return commitI;
}
@Override
public VirtualFile getRoot() {
return myVirtualFile;
}
}
static class ManyCaseHolder implements MyRootHolder {
private final RootsHolder myRootsHolder;
private final int myNum;
ManyCaseHolder(int num, RootsHolder rootsHolder) {
myNum = num;
myRootsHolder = rootsHolder;
}
@Override
public CommitI decorateByRoot(CommitI commitI) {
return new MultipleRepositoryCommitDecorator(commitI, myNum);
}
@Override
public VirtualFile getRoot() {
return myRootsHolder.get(myNum);
}
}
private static class RepeatingLoadConsumer<T> implements AsynchConsumer<T> {
private final Project myProject;
private final Consumer<T> myConsumer;
private int myCnt;
private T myLastT;
private int mySavedPoint;
private int myTotalRecordsInPack;
private boolean myPointMeet;
private RepeatingLoadConsumer(final Project project, Consumer<T> consumer) {
myProject = project;
myConsumer = consumer;
mySavedPoint = 0;
myCnt = 0;
myLastT = null;
myPointMeet = false;
}
public int reset() {
mySavedPoint = myCnt;
myTotalRecordsInPack = 0;
myPointMeet = false;
return mySavedPoint;
}
public int getTotalRecordsInPack() {
return myTotalRecordsInPack;
}
public int sincePoint() {
return myCnt - mySavedPoint;
}
public T getLast() {
return myLastT;
}
@Override
public void consume(T t) {
if (! myProject.isOpen()) throw new ProcessCanceledException();
++ myTotalRecordsInPack;
if (myLastT == null) {
myPointMeet = true;
}
if (! myPointMeet) {
myPointMeet = t.equals(myLastT);
} else {
++ myCnt;
myConsumer.consume(t);
myLastT = t;
}
}
@Override
public void finished() {
}
}
}