/*
* Copyright 2000-2009 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.annotate;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.vcs.FileStatus;
import com.intellij.openapi.vcs.FileStatusListener;
import com.intellij.openapi.vcs.FileStatusManager;
import com.intellij.openapi.vcs.VcsException;
import com.intellij.openapi.vcs.annotate.AnnotationListener;
import com.intellij.openapi.vcs.annotate.AnnotationSourceSwitcher;
import com.intellij.openapi.vcs.annotate.LineAnnotationAspect;
import com.intellij.openapi.vcs.annotate.LineAnnotationAspectAdapter;
import com.intellij.openapi.vcs.history.VcsFileRevision;
import com.intellij.openapi.vcs.history.VcsRevisionNumber;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.openapi.vfs.VirtualFileAdapter;
import com.intellij.openapi.vfs.VirtualFileEvent;
import com.intellij.openapi.vfs.VirtualFileManager;
import com.intellij.util.EventDispatcher;
import com.intellij.util.text.DateFormatUtil;
import org.community.intellij.plugins.communitycase.actions.ShowAllSubmittedFilesAction;
import org.community.intellij.plugins.communitycase.i18n.Bundle;
import org.jetbrains.annotations.NotNull;
import java.util.*;
/**
* file annotation implementation
* <p/>
* Based on the JetBrains SVNAnnotationProvider.
*/
public class IntellijFileAnnotation implements com.intellij.openapi.vcs.annotate.FileAnnotation {
private final static Logger LOG = Logger.getInstance("#"+IntellijFileAnnotation.class.getName());
/**
* annotated content
*/
private final StringBuffer myContentBuffer = new StringBuffer();
/**
* The currently annotated lines
*/
private final ArrayList<LineInfo> myLines = new ArrayList<LineInfo>();
/**
* The project reference
*/
private final Project myProject;
/**
* Annotation change listeners
*/
private final EventDispatcher<AnnotationListener> myListeners = EventDispatcher.create(AnnotationListener.class);
/**
* Map from revision numbers to revisions
*/
private final Map<VcsRevisionNumber, VcsFileRevision> myRevisionMap = new HashMap<VcsRevisionNumber, VcsFileRevision>();
/**
* listener for file system events
*/
private final VirtualFileAdapter myFileListener;
private final MyFileStatusListener myFileStatusListener;
/**
* the virtual file for which annotations are generated
*/
private final VirtualFile myFile;
/**
* If true, file system is monitored for changes
*/
private final boolean myMonitorFlag;
private final LineAnnotationAspect DATE_ASPECT = new AnnotationAspect(AnnotationAspect.DATE, true) {
public String doGetValue(LineInfo info) {
final Date date = info.getDate();
return date == null ? "" : DateFormatUtil.formatPrettyDate(date);
}
};
private final LineAnnotationAspect REVISION_ASPECT = new AnnotationAspect(AnnotationAspect.REVISION, false) {
@Override
protected String doGetValue(LineInfo lineInfo) {
final VcsRevisionNumber revision = lineInfo.getRevision();
return revision == null ? "" : String.valueOf(revision.asString());
}
};
private final LineAnnotationAspect AUTHOR_ASPECT = new AnnotationAspect(AnnotationAspect.AUTHOR, true) {
@Override
protected String doGetValue(LineInfo lineInfo) {
final String author = lineInfo.getAuthor();
return author == null ? "" : author;
}
};
/**
* A constructor
*
* @param project the project of annotation provider
* @param file the root
* @param monitorFlag if false the file system will not be listened for changes (used for annotated files from the repository).
*/
public IntellijFileAnnotation(@NotNull final Project project, @NotNull VirtualFile file, final boolean monitorFlag) {
myProject = project;
myFile = file;
myMonitorFlag = monitorFlag;
if (myMonitorFlag) {
myFileListener = new VirtualFileAdapter() {
@Override
public void contentsChanged(final VirtualFileEvent event) {
if (myFile != event.getFile()) return;
if (!event.isFromRefresh()) return;
fireAnnotationChanged();
}
};
VirtualFileManager.getInstance().addVirtualFileListener(myFileListener);
myFileStatusListener = new MyFileStatusListener();
FileStatusManager.getInstance(myProject).addFileStatusListener(myFileStatusListener);
}
else {
myFileListener = null;
myFileStatusListener = null;
}
}
/**
* Add revisions to the list (from log)
*
* @param revisions revisions to add
*/
public void addLogEntries(List<VcsFileRevision> revisions) {
for (VcsFileRevision vcsFileRevision : revisions) {
myRevisionMap.put(vcsFileRevision.getRevisionNumber(), vcsFileRevision);
}
}
/**
* Fire annotation changed event
*/
private void fireAnnotationChanged() {
myListeners.getMulticaster().onAnnotationChanged();
}
/**
* {@inheritDoc}
*/
public void addListener(AnnotationListener listener) {
myListeners.addListener(listener);
}
/**
* {@inheritDoc}
*/
public void removeListener(AnnotationListener listener) {
myListeners.removeListener(listener);
}
/**
* {@inheritDoc}
*/
public void dispose() {
if (myMonitorFlag) {
VirtualFileManager.getInstance().removeVirtualFileListener(myFileListener);
FileStatusManager.getInstance(myProject).removeFileStatusListener(myFileStatusListener);
}
}
/**
* {@inheritDoc}
*/
public LineAnnotationAspect[] getAspects() {
return new LineAnnotationAspect[]{REVISION_ASPECT, DATE_ASPECT, AUTHOR_ASPECT};
}
/**
* {@inheritDoc}
*/
public String getToolTip(final int lineNumber) {
if (myLines.size() <= lineNumber || lineNumber < 0) {
return "";
}
final LineInfo info = myLines.get(lineNumber);
if (info == null) {
return "";
}
VcsFileRevision fileRevision = myRevisionMap.get(info.getRevision());
if (fileRevision != null) {
return Bundle
.message("annotation.tool.tip", info.getRevision().asString(), fileRevision.getAuthor(), fileRevision.getRevisionDate(),
fileRevision.getCommitMessage());
}
else {
return "";
}
}
/**
* {@inheritDoc}
*/
public String getAnnotatedContent() {
return myContentBuffer.toString();
}
/**
* {@inheritDoc}
*/
public List<VcsFileRevision> getRevisions() {
final List<VcsFileRevision> result = new ArrayList<VcsFileRevision>(myRevisionMap.values());
Collections.sort(result, new Comparator<VcsFileRevision>() {
public int compare(final VcsFileRevision o1, final VcsFileRevision o2) {
return -1 * o1.getRevisionNumber().compareTo(o2.getRevisionNumber());
}
});
return result;
}
public boolean revisionsNotEmpty() {
return ! myRevisionMap.isEmpty();
}
public AnnotationSourceSwitcher getAnnotationSourceSwitcher() {
return null;
}
@Override
public int getLineCount() {
return myLines.size();
}
/**
* {@inheritDoc}
*/
public VcsRevisionNumber getLineRevisionNumber(final int lineNumber) {
if (myLines.size() <= lineNumber || lineNumber < 0 || myLines.get(lineNumber) == null) {
return null;
}
final LineInfo lineInfo = myLines.get(lineNumber);
return lineInfo == null ? null : lineInfo.getRevision();
}
private boolean lineNumberCheck(int lineNumber) {
return myLines.size() <= lineNumber || lineNumber < 0 || myLines.get(lineNumber) == null;
}
@Override
public Date getLineDate(int lineNumber) {
if (lineNumberCheck(lineNumber)) {
return null;
}
final LineInfo lineInfo = myLines.get(lineNumber);
return lineInfo == null ? null : lineInfo.getDate();
}
/**
* Get revision number for the line.
*/
public VcsRevisionNumber originalRevision(int lineNumber) {
return getLineRevisionNumber(lineNumber);
}
/**
* Append line info
*
* @param date the revision date
* @param revision the revision number
* @param author the author
* @param line the line content
* @param lineNumber the line number for revision
* @throws VcsException in case when line could not be processed
*/
public void appendLineInfo(final Date date,
final VcsRevisionNumber revision,
final String author,
final String line,
final long lineNumber) throws VcsException {
int expectedLineNo = myLines.size() + 1;
if (lineNumber != expectedLineNo) {
throw new VcsException("Adding for info for line " + lineNumber + " but we are expecting it to be for " + expectedLineNo);
}
myLines.add(new LineInfo(date, revision, author));
myContentBuffer.append(line);
}
/**
* Revision annotation aspect implementation
*/
private abstract class AnnotationAspect extends LineAnnotationAspectAdapter {
public AnnotationAspect(String id, boolean showByDefault) {
super(id, showByDefault);
}
public String getValue(int lineNumber) {
if (myLines.size() <= lineNumber || lineNumber < 0 || myLines.get(lineNumber) == null) {
return "";
}
else {
return doGetValue(myLines.get(lineNumber));
}
}
protected abstract String doGetValue(LineInfo lineInfo);
@Override
protected void showAffectedPaths(int lineNum) {
if (lineNum >= 0 && lineNum < myLines.size()) {
final LineInfo info = myLines.get(lineNum);
VcsFileRevision revision = myRevisionMap.get(info.getRevision());
if (revision != null) {
ShowAllSubmittedFilesAction.showSubmittedFiles(myProject, revision, myFile);
}
}
}
}
/**
* Line information
*/
static class LineInfo {
/**
* date of the change
*/
private final Date myDate;
/**
* revision number
*/
private final VcsRevisionNumber myRevision;
/**
* the author of the change
*/
private final String myAuthor;
/**
* A constructor
*
* @param date date of the change
* @param revision revision number
* @param author the author of the change
*/
public LineInfo(final Date date, final VcsRevisionNumber revision, final String author) {
myDate = date;
myRevision = revision;
myAuthor = author;
}
/**
* @return the revision date
*/
public Date getDate() {
return myDate;
}
/**
* @return the revision number
*/
public VcsRevisionNumber getRevision() {
return myRevision;
}
/**
* @return the author of the change
*/
public String getAuthor() {
return myAuthor;
}
}
private class MyFileStatusListener implements FileStatusListener {
public void fileStatusesChanged() {
}
public void fileStatusChanged(@NotNull VirtualFile virtualFile) {
if (myFile.equals(virtualFile)) {
checkAndFire();
}
}
private void checkAndFire() {
// for the case of commit changes... remove annotation gutter
if (FileStatus.NOT_CHANGED.equals(FileStatusManager.getInstance(myProject).getStatus(myFile))) {
fireAnnotationChanged();
}
}
}
}