/**
* This file is licensed under the University of Illinois/NCSA Open Source License. See LICENSE.TXT for details.
*/
package edu.illinois.codingtracker.listeners;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.logging.Level;
import org.eclipse.compare.internal.CompareEditor;
import org.eclipse.core.internal.resources.IResourceListener;
import org.eclipse.core.internal.resources.Resource;
import org.eclipse.core.resources.IContainer;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.IPath;
import org.tmatesoft.svn.core.SVNException;
import org.tmatesoft.svn.core.internal.wc.admin.SVNAdminArea;
import org.tmatesoft.svn.core.internal.wc.admin.SVNAdminAreaFactory;
import org.tmatesoft.svn.core.internal.wc.admin.SVNEntry;
import edu.illinois.codingtracker.helpers.FileRevision;
import edu.illinois.codingtracker.helpers.ResourceHelper;
/**
*
* @author Stas Negara
*
*/
@SuppressWarnings("restriction")
public class ResourceListener extends BasicListener implements IResourceListener {
private enum Manipulation {
ADDED, REMOVED, CHANGED
};
//Populated sets:
private final Set<IFile> externallyAddedJavaFiles= new HashSet<IFile>();
private final Set<IFile> externallyChangedJavaFiles= new HashSet<IFile>();
private final Set<IFile> externallyRemovedJavaFiles= new HashSet<IFile>();
private final Set<IFile> svnAddedJavaFiles= new HashSet<IFile>();
private final Set<IFile> svnChangedJavaFiles= new HashSet<IFile>();
//Calculated sets:
private final Set<IFile> externallyModifiedJavaFiles= new HashSet<IFile>();
private final Set<FileRevision> updatedJavaFileRevisions= new HashSet<FileRevision>();
private final Set<FileRevision> svnInitiallyCommittedJavaFileRevisions= new HashSet<FileRevision>();
private final Set<FileRevision> svnCommittedJavaFileRevisions= new HashSet<FileRevision>();
//SVN entries caching
private final Map<String, SVNAdminArea> svnAdminAreaCache= new HashMap<String, SVNAdminArea>();
public static void register() {
Resource.resourceListener= new ResourceListener();
}
@Override
public void createdResource(IResource resource, int updateFlags, boolean success) {
if (isRecordedResource(resource) && isRefactoring) {//Record only during refactorings to avoid recording huge checked out projects
operationRecorder.recordCreatedResource(resource, updateFlags, success);
}
}
@Override
public void movedResource(IResource resource, IPath destination, int updateFlags, boolean success) {
if (isRecordedResource(resource)) {
operationRecorder.recordMovedResource(resource, destination, updateFlags, success);
}
}
@Override
public void copiedResource(IResource resource, IPath destination, int updateFlags, boolean success) {
if (isRecordedResource(resource)) {
operationRecorder.recordCopiedResource(resource, destination, updateFlags, success);
}
}
@Override
public void deletedResource(IResource resource, int updateFlags, boolean success) {
if (isRecordedResource(resource)) {
operationRecorder.recordDeletedResource(resource, updateFlags, success);
}
}
@Override
public void externallyModifiedResource(IResource resource, boolean isDeleted) {
if (resource instanceof IFile) {
Manipulation manipulation= isDeleted ? Manipulation.REMOVED : Manipulation.CHANGED;
handleExternalFileManipulation((IFile)resource, manipulation);
}
}
@Override
public void externallyCreatedResource(IResource resource) {
if (resource instanceof IFile) {
handleExternalFileManipulation((IFile)resource, Manipulation.ADDED);
}
}
@Override
public void refreshedResource(IResource resource) {
if (!isRefactoring) {
svnAdminAreaCache.clear(); //Clear SVN entries cache before processing
calculateSets();
recordSets();
updateKnownFiles();
}
//Always clear sets for the following refresh operation.
clearExternallyManipulatedFileSets();
}
@Override
public void savedFile(IFile file, boolean success) {
if (ResourceHelper.isJavaFile(file)) {
operationRecorder.recordSavedFile(file, success);
}
}
@Override
public void savedCompareEditor(Object compareEditor, boolean success) {
operationRecorder.recordSavedCompareEditor((CompareEditor)compareEditor, success);
}
private boolean isRecordedResource(IResource resource) {
if (resource instanceof IFile) {
return ResourceHelper.isJavaFile((IFile)resource);
}
return true;
}
private void handleExternalFileManipulation(IFile file, Manipulation manipulation) {
if (ResourceHelper.isJavaFile(file)) {
switch (manipulation) {
case ADDED:
externallyAddedJavaFiles.add(file);
break;
case REMOVED:
externallyRemovedJavaFiles.add(file);
break;
case CHANGED:
externallyChangedJavaFiles.add(file);
break;
}
} else if ("svn-base".equals(file.getFileExtension())) {
IFile javaSourceFile= getJavaSourceFileForSVNFile(file);
if (javaSourceFile != null) {
//TODO: Consider REMOVED to track files that a removed as a part of an update operation
switch (manipulation) {
case ADDED:
svnAddedJavaFiles.add(javaSourceFile);
break;
case CHANGED:
svnChangedJavaFiles.add(javaSourceFile);
break;
}
}
}
}
/**
* Returns null if there is no corresponding Java source file (e.g. when the SVN file is not
* from text-base folder).
*
* @param svnFile
* @return
*/
private IFile getJavaSourceFileForSVNFile(IFile svnFile) {
String fileName= svnFile.getName();
if (fileName.endsWith(".java.svn-base")) {
IPath fileFullPath= svnFile.getFullPath();
String parentDir= fileFullPath.segment(fileFullPath.segmentCount() - 2);
if (parentDir.equals("text-base")) {
String javaSourceFileName= fileName.substring(0, fileName.lastIndexOf("."));
IPath javaSourceFilePath= fileFullPath.removeLastSegments(3).append(javaSourceFileName);
IResource javaResource= ResourceHelper.findWorkspaceMember(javaSourceFilePath);
if (javaResource instanceof IFile && javaResource.exists()) {
return (IFile)javaResource;
}
}
}
return null;
}
private void calculateSets() {
//should be done only in this order
calculateSVNSets();
calculateExternallyModifiedJavaFiles();
}
private void calculateSVNSets() {
for (IFile file : svnChangedJavaFiles) {
FileRevision fileRevision= getSVNFileRevision(file);
if (externallyChangedJavaFiles.contains(file)) {
updatedJavaFileRevisions.add(fileRevision); //if both the java file and its svn storage have changed, then its an update
} else if (!externallyAddedJavaFiles.contains(file)) {
svnCommittedJavaFileRevisions.add(fileRevision); //if only svn storage of a java file has changed, its a commit
}
}
for (IFile file : svnAddedJavaFiles) {
//if only svn storage was added for a file, its an initial commit
if (!externallyAddedJavaFiles.contains(file) && !externallyChangedJavaFiles.contains(file)) {
svnInitiallyCommittedJavaFileRevisions.add(getSVNFileRevision(file));
}
}
}
private void calculateExternallyModifiedJavaFiles() {
for (IFile file : externallyChangedJavaFiles) {
boolean isUpdated= false;
for (FileRevision fileRevision : updatedJavaFileRevisions) {
if (fileRevision.getFile().equals(file)) {
isUpdated= true;
break;
}
}
if (!isUpdated) {
externallyModifiedJavaFiles.add(file);
}
}
}
private void recordSets() {
operationRecorder.recordExternallyModifiedFiles(externallyRemovedJavaFiles, true);
operationRecorder.recordExternallyModifiedFiles(externallyModifiedJavaFiles, false);
operationRecorder.recordUpdatedFiles(updatedJavaFileRevisions);
operationRecorder.recordCommittedFiles(svnInitiallyCommittedJavaFileRevisions, true, true);
operationRecorder.recordCommittedFiles(svnCommittedJavaFileRevisions, false, true);
}
private void updateKnownFiles() {
externallyRemovedJavaFiles.addAll(ResourceHelper.getFilesFromRevisions(updatedJavaFileRevisions)); //updated files become unknown (like removed)
externallyRemovedJavaFiles.addAll(externallyModifiedJavaFiles); //externally modified files become unknown
knownFilesRecorder.removeKnownFiles(externallyRemovedJavaFiles);
}
private void clearExternallyManipulatedFileSets() {
externallyAddedJavaFiles.clear();
externallyChangedJavaFiles.clear();
externallyRemovedJavaFiles.clear();
svnAddedJavaFiles.clear();
svnChangedJavaFiles.clear();
externallyModifiedJavaFiles.clear();
updatedJavaFileRevisions.clear();
svnInitiallyCommittedJavaFileRevisions.clear();
svnCommittedJavaFileRevisions.clear();
}
private FileRevision getSVNFileRevision(IFile file) {
FileRevision fileRevision= new FileRevision(file, "0", "0"); //default file revision
try {
IContainer parent= file.getParent();
String parentPath= ResourceHelper.getPortableResourcePath(parent);
SVNAdminArea svnAdminArea= svnAdminAreaCache.get(parentPath);
if (svnAdminArea == null) {
svnAdminArea= SVNAdminAreaFactory.open(ResourceHelper.getFileForResource(parent), Level.OFF);
}
if (svnAdminArea != null) {
svnAdminAreaCache.put(parentPath, svnAdminArea);
SVNEntry svnEntry= svnAdminArea.getEntry(file.getName(), true);
if (svnEntry != null) {
fileRevision= new FileRevision(file, String.valueOf(svnEntry.getRevision()), String.valueOf(svnEntry.getCommittedRevision()));
}
}
} catch (SVNException e) {
//ignore SVN exceptions
} catch (Exception e) {
//ignore all other exceptions as well
}
return fileRevision;
}
}