/** * 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.io.File; import java.util.HashSet; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import org.eclipse.core.internal.resources.Folder; import org.eclipse.core.resources.IContainer; import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IResource; import org.eclipse.core.resources.IResourceChangeEvent; import org.eclipse.core.resources.IResourceChangeListener; import org.eclipse.core.resources.IResourceDelta; import org.eclipse.core.resources.IResourceDeltaVisitor; import org.eclipse.core.resources.ResourcesPlugin; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IPath; import edu.illinois.codingtracker.helpers.Debugger; import edu.illinois.codingtracker.helpers.FileRevision; import edu.illinois.codingtracker.helpers.Messages; import edu.illinois.codingtracker.helpers.ResourceHelper; /** * * @author Stas Negara * */ @SuppressWarnings("restriction") public class CVSResourceChangeListener extends BasicListener implements IResourceChangeListener { private final IResourceDeltaVisitor resourceDeltaVisitor= new ResourceDeltaVisitor(); //Populated sets: private final Set<IFile> addedJavaFiles= new HashSet<IFile>(); private final Set<IFile> changedJavaFiles= new HashSet<IFile>(); private final Set<IFile> removedJavaFiles= new HashSet<IFile>(); private final Set<IFile> cvsEntriesAddedSet= new HashSet<IFile>(); private final Set<IFile> cvsEntriesChangedOrRemovedSet= new HashSet<IFile>(); //Calculated sets: private final Set<FileRevision> updatedJavaFileRevisions= new HashSet<FileRevision>(); private final Set<FileRevision> cvsInitiallyCommittedJavaFileRevisions= new HashSet<FileRevision>(); private final Set<FileRevision> cvsCommittedJavaFileRevisions= new HashSet<FileRevision>(); public static void register() { ResourcesPlugin.getWorkspace().addResourceChangeListener(new CVSResourceChangeListener(), IResourceChangeEvent.POST_CHANGE); } @Override public void resourceChanged(IResourceChangeEvent event) { if (isRefactoring) { //No CVS operations are part of a refactoring. //Note that this check is unreliable because this notification usually arrives in a thread that is different from the thread, //which performs the refactoring (setting isRefactoring accordingly). return; } IResourceDelta delta= event.getDelta(); if (delta != null) { //why could it be null? initializeSets(); populateSets(delta); //proceed only if this is a CVS operation (i.e. at least one CVS/Entries file is affected) if (!cvsEntriesAddedSet.isEmpty() || !cvsEntriesChangedOrRemovedSet.isEmpty()) { calculateCVSSets(); recordSets(); updateKnownFiles(); } } } private void initializeSets() { addedJavaFiles.clear(); changedJavaFiles.clear(); removedJavaFiles.clear(); cvsEntriesAddedSet.clear(); cvsEntriesChangedOrRemovedSet.clear(); updatedJavaFileRevisions.clear(); cvsInitiallyCommittedJavaFileRevisions.clear(); cvsCommittedJavaFileRevisions.clear(); } private void populateSets(IResourceDelta delta) { try { //used IContainer.INCLUDE_TEAM_PRIVATE_MEMBERS to see changes in version control admin area delta.accept(resourceDeltaVisitor, IContainer.INCLUDE_TEAM_PRIVATE_MEMBERS); } catch (CoreException e) { Debugger.logExceptionToErrorLog(e, Messages.CodeChangeTracker_FailedToVisitResourceDelta); } } private void calculateCVSSets() { processAddedCVSEntriesFiles(); processChangedOrRemovedCVSEntriesFiles(); } private void processAddedCVSEntriesFiles() { boolean hasChangedKnownFiles= false; for (IFile cvsEntriesFile : cvsEntriesAddedSet) { IPath relativePath= cvsEntriesFile.getFullPath().removeLastSegments(2); Map<IFile, String> newRevisions= ResourceHelper.getEntriesRevisions(cvsEntriesFile, relativePath); boolean isInitialCommit= false; for (Entry<IFile, String> newEntry : newRevisions.entrySet()) { IFile entryFile= newEntry.getKey(); FileRevision fileRevision= getCVSFileRevision(entryFile, newEntry.getValue()); if (changedJavaFiles.contains(entryFile)) { updatedJavaFileRevisions.add(fileRevision); } else if (!addedJavaFiles.contains(entryFile)) { cvsInitiallyCommittedJavaFileRevisions.add(fileRevision); isInitialCommit= true; } } if (isInitialCommit || doesContainKnownFiles(relativePath)) { knownFilesRecorder.addCVSEntriesFile(cvsEntriesFile); hasChangedKnownFiles= true; } } if (hasChangedKnownFiles) { knownFilesRecorder.recordKnownFiles(); } } private FileRevision getCVSFileRevision(IFile file, String revision) { //CVS does not have a separate committed revision, so use a placeholder "0" instead return new FileRevision(file, revision, "0"); } private boolean doesContainKnownFiles(IPath path) { IResource resource= ResourceHelper.findWorkspaceMember(path); if (resource instanceof Folder) { Folder containerFolder= (Folder)resource; try { IResource[] members= containerFolder.members(); for (IResource member : members) { if (member instanceof IFile && knownFilesRecorder.isFileKnown((IFile)member, false)) { return true; } } } catch (CoreException e) { Debugger.logExceptionToErrorLog(e, Messages.Recorder_CVSFolderMembersFailure); } } return false; } private void processChangedOrRemovedCVSEntriesFiles() { boolean hasChangedKnownFiles= false; for (IFile cvsEntriesFile : cvsEntriesChangedOrRemovedSet) { if (cvsEntriesFile.exists()) { IPath relativePath= cvsEntriesFile.getFullPath().removeLastSegments(2); Map<IFile, String> newRevisions= ResourceHelper.getEntriesRevisions(cvsEntriesFile, relativePath); File trackedCVSEntriesFile= knownFilesRecorder.getTrackedCVSEntriesFile(cvsEntriesFile); if (trackedCVSEntriesFile.exists()) { Map<IFile, String> previousRevisions= ResourceHelper.getEntriesRevisions(trackedCVSEntriesFile, relativePath); processCVSRevisionsDifference(newRevisions, previousRevisions); knownFilesRecorder.addCVSEntriesFile(cvsEntriesFile); //overwrite the existing tracked entries file with the new one hasChangedKnownFiles= true; } else { for (Entry<IFile, String> newEntry : newRevisions.entrySet()) { IFile entryFile= newEntry.getKey(); if (changedJavaFiles.contains(entryFile)) { updatedJavaFileRevisions.add(getCVSFileRevision(entryFile, newEntry.getValue())); } } } } else { // CVS entries file was deleted, so stop tracking it knownFilesRecorder.removeKnownFile(cvsEntriesFile); hasChangedKnownFiles= true; } } if (hasChangedKnownFiles) { knownFilesRecorder.recordKnownFiles(); } } private void processCVSRevisionsDifference(Map<IFile, String> newRevisions, Map<IFile, String> previousRevisions) { for (Entry<IFile, String> newEntry : newRevisions.entrySet()) { IFile entryFile= newEntry.getKey(); String newRevision= newEntry.getValue(); FileRevision newFileRevision= getCVSFileRevision(entryFile, newRevision); String previousRevision= previousRevisions.get(entryFile); if (previousRevision == null) { if (!addedJavaFiles.contains(entryFile) && !changedJavaFiles.contains(entryFile)) { cvsInitiallyCommittedJavaFileRevisions.add(newFileRevision); } } else if (!previousRevision.equals(newRevision)) { if (changedJavaFiles.contains(entryFile)) { updatedJavaFileRevisions.add(newFileRevision); } else if (!addedJavaFiles.contains(entryFile)) { cvsCommittedJavaFileRevisions.add(newFileRevision); } } } } private void recordSets() { operationRecorder.recordUpdatedFiles(updatedJavaFileRevisions); operationRecorder.recordCommittedFiles(cvsInitiallyCommittedJavaFileRevisions, true, false); operationRecorder.recordCommittedFiles(cvsCommittedJavaFileRevisions, false, false); } private void updateKnownFiles() { removedJavaFiles.addAll(ResourceHelper.getFilesFromRevisions(updatedJavaFileRevisions)); //updated files become unknown (like removed) knownFilesRecorder.removeKnownFiles(removedJavaFiles); } private class ResourceDeltaVisitor implements IResourceDeltaVisitor { @Override public boolean visit(IResourceDelta delta) throws CoreException { IResource resource= delta.getResource(); if (resource instanceof IFile) { IFile file= (IFile)resource; if (file.getName().equals("Entries") && file.getParent().getName().equals("CVS")) { if (delta.getKind() == IResourceDelta.ADDED) { cvsEntriesAddedSet.add(file); } else { cvsEntriesChangedOrRemovedSet.add(file); } } else { if (ResourceHelper.isJavaFile(file)) { visitJavaFile(delta, file); } } } return true; } private void visitJavaFile(IResourceDelta delta, IFile file) { switch (delta.getKind()) { case IResourceDelta.ADDED: addedJavaFiles.add(file); break; case IResourceDelta.REMOVED: removedJavaFiles.add(file); break; case IResourceDelta.CHANGED: if ((delta.getFlags() & IResourceDelta.CONTENT) != 0) { changedJavaFiles.add(file); } break; } } } }