blob: ccfa15548772e6aba591204c84ed48f0177e61d8 [file] [log] [blame]
/*
* Copyright 2000-2014 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.vcs.history;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.progress.ProcessCanceledException;
import com.intellij.openapi.util.ThrowableComputable;
import com.intellij.openapi.vcs.AbstractVcs;
import com.intellij.openapi.vcs.FilePath;
import com.intellij.openapi.vcs.VcsException;
import com.intellij.openapi.vcs.actions.VcsContextFactory;
import com.intellij.openapi.vcs.annotate.AnnotationProvider;
import com.intellij.openapi.vcs.annotate.FileAnnotation;
import com.intellij.openapi.vcs.annotate.VcsAnnotation;
import com.intellij.openapi.vcs.annotate.VcsCacheableAnnotationProvider;
import com.intellij.openapi.vcs.changes.ContentRevision;
import com.intellij.openapi.vcs.diff.DiffProvider;
import com.intellij.openapi.vfs.VirtualFile;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* @author irengrig
* Date: 3/17/11
* Time: 7:51 PM
*/
public class VcsAnnotationCachedProxy implements AnnotationProvider {
private final VcsHistoryCache myCache;
private final AbstractVcs myVcs;
private final static Logger LOG = Logger.getInstance("#com.intellij.openapi.vcs.history.VcsAnnotationCachedProxy");
private final AnnotationProvider myAnnotationProvider;
public VcsAnnotationCachedProxy(final AbstractVcs vcs, final VcsHistoryCache cache) {
assert vcs.getAnnotationProvider() instanceof VcsCacheableAnnotationProvider;
myVcs = vcs;
myCache = cache;
myAnnotationProvider = myVcs.getAnnotationProvider();
}
@Override
public FileAnnotation annotate(final VirtualFile file) throws VcsException {
final DiffProvider diffProvider = myVcs.getDiffProvider();
final VcsRevisionNumber currentRevision = diffProvider.getCurrentRevision(file);
return annotate(file, currentRevision, true, new ThrowableComputable<FileAnnotation, VcsException>() {
@Override
public FileAnnotation compute() throws VcsException {
return myAnnotationProvider.annotate(file);
}
});
}
@Override
public FileAnnotation annotate(final VirtualFile file, final VcsFileRevision revision) throws VcsException {
return annotate(file, revision.getRevisionNumber(), false, new ThrowableComputable<FileAnnotation, VcsException>() {
@Override
public FileAnnotation compute() throws VcsException {
return myAnnotationProvider.annotate(file, revision);
}
});
}
/**
* @param currentRevision - just a hint for optimization
*/
private FileAnnotation annotate(VirtualFile file, final VcsRevisionNumber revisionNumber, final boolean currentRevision,
final ThrowableComputable<FileAnnotation, VcsException> delegate) throws VcsException {
final AnnotationProvider annotationProvider = myAnnotationProvider;
final FilePath filePath = VcsContextFactory.SERVICE.getInstance().createFilePathOn(file);
final VcsCacheableAnnotationProvider cacheableAnnotationProvider = (VcsCacheableAnnotationProvider)annotationProvider;
VcsAnnotation vcsAnnotation = null;
if (revisionNumber != null) {
vcsAnnotation = myCache.get(VcsContextFactory.SERVICE.getInstance().createFilePathOn(file), myVcs.getKeyInstanceMethod(), revisionNumber);
}
if (vcsAnnotation != null) {
final VcsHistoryProvider historyProvider = myVcs.getVcsHistoryProvider();
final VcsAbstractHistorySession history = getHistory(revisionNumber, filePath, historyProvider, vcsAnnotation.getFirstRevision());
if (history == null) return null;
// question is whether we need "not moved" path here?
final ContentRevision fileContent = myVcs.getDiffProvider().createFileContent(revisionNumber, file);
final FileAnnotation restored = cacheableAnnotationProvider.
restore(vcsAnnotation, history, fileContent.getContent(), currentRevision,
revisionNumber);
if (restored != null) {
return restored;
}
}
final FileAnnotation fileAnnotation = delegate.compute();
vcsAnnotation = cacheableAnnotationProvider.createCacheable(fileAnnotation);
if (vcsAnnotation == null) return fileAnnotation;
if (revisionNumber != null) {
myCache.put(filePath, myVcs.getKeyInstanceMethod(), revisionNumber, vcsAnnotation);
}
if (myVcs.getVcsHistoryProvider() instanceof VcsCacheableHistorySessionFactory) {
loadHistoryInBackgroundToCache(revisionNumber, filePath, vcsAnnotation);
}
return fileAnnotation;
}
// todo will be removed - when annotation will be presented together with history
private void loadHistoryInBackgroundToCache(final VcsRevisionNumber revisionNumber,
final FilePath filePath,
final VcsAnnotation vcsAnnotation) {
ApplicationManager.getApplication().executeOnPooledThread(new Runnable() {
@Override
public void run() {
try {
getHistory(revisionNumber, filePath, myVcs.getVcsHistoryProvider(), vcsAnnotation.getFirstRevision());
}
catch (VcsException e) {
LOG.info(e);
}
}
});
}
private VcsAbstractHistorySession getHistory(VcsRevisionNumber revision, FilePath filePath, VcsHistoryProvider historyProvider,
@Nullable final VcsRevisionNumber firstRevision) throws VcsException {
final boolean historyCacheSupported = historyProvider instanceof VcsCacheableHistorySessionFactory;
if (historyCacheSupported) {
final VcsCacheableHistorySessionFactory cacheableHistorySessionFactory = (VcsCacheableHistorySessionFactory)historyProvider;
final VcsAbstractHistorySession cachedSession =
myCache.getMaybePartial(filePath, myVcs.getKeyInstanceMethod(), cacheableHistorySessionFactory);
if (cachedSession != null && ! cachedSession.getRevisionList().isEmpty()) {
final VcsFileRevision recentRevision = cachedSession.getRevisionList().get(0);
if (recentRevision.getRevisionNumber().compareTo(revision) >= 0 && (firstRevision == null || cachedSession.getHistoryAsMap().containsKey(firstRevision))) {
return cachedSession;
}
}
}
// history may be also cut
final VcsAbstractHistorySession sessionFor;
if (firstRevision != null) {
sessionFor = limitedHistory(filePath, firstRevision);
} else {
sessionFor = (VcsAbstractHistorySession) historyProvider.createSessionFor(filePath);
}
if (sessionFor != null && historyCacheSupported) {
final VcsCacheableHistorySessionFactory cacheableHistorySessionFactory = (VcsCacheableHistorySessionFactory)historyProvider;
final FilePath correctedPath = cacheableHistorySessionFactory.getUsedFilePath(sessionFor);
myCache.put(filePath, correctedPath, myVcs.getKeyInstanceMethod(), sessionFor, cacheableHistorySessionFactory, firstRevision == null);
}
return sessionFor;
}
@Override
public boolean isAnnotationValid(VcsFileRevision rev) {
return myAnnotationProvider.isAnnotationValid(rev);
}
private VcsAbstractHistorySession limitedHistory(final FilePath filePath, @NotNull final VcsRevisionNumber firstNumber) throws VcsException {
final VcsAbstractHistorySession[] result = new VcsAbstractHistorySession[1];
final VcsException[] exc = new VcsException[1];
try {
myVcs.getVcsHistoryProvider().reportAppendableHistory(filePath, new VcsAppendableHistorySessionPartner() {
@Override
public void reportCreatedEmptySession(VcsAbstractHistorySession session) {
result[0] = session;
}
@Override
public void acceptRevision(VcsFileRevision revision) {
result[0].appendRevision(revision);
if (firstNumber.equals(revision.getRevisionNumber())) throw new ProcessCanceledException();
}
@Override
public void reportException(VcsException exception) {
exc[0] = exception;
}
@Override
public void finished() {
}
@Override
public void beforeRefresh() {
}
@Override
public void forceRefresh() {
}
});
} catch (ProcessCanceledException e) {
// ok
}
if (exc[0] != null) {
throw exc[0];
}
return result[0];
}
}