/** * This file is licensed under the University of Illinois/NCSA Open Source License. See LICENSE.TXT for details. */ package edu.illinois.codingtracker.recording; import java.util.HashMap; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import org.eclipse.compare.internal.CompareEditor; import org.eclipse.core.filebuffers.ITextFileBuffer; import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IResource; import org.eclipse.core.runtime.IPath; import org.eclipse.jdt.core.IJavaProject; import org.eclipse.jdt.core.JavaCore; import org.eclipse.jface.text.DocumentEvent; import org.eclipse.ltk.core.refactoring.RefactoringDescriptor; import org.eclipse.ltk.core.refactoring.history.RefactoringExecutionEvent; import edu.illinois.codingtracker.compare.helpers.EditorHelper; import edu.illinois.codingtracker.helpers.Debugger; import edu.illinois.codingtracker.helpers.FileRevision; import edu.illinois.codingtracker.helpers.ResourceHelper; import edu.illinois.codingtracker.operations.conflicteditors.ClosedConflictEditorOperation; import edu.illinois.codingtracker.operations.conflicteditors.OpenedConflictEditorOperation; import edu.illinois.codingtracker.operations.conflicteditors.SavedConflictEditorOperation; import edu.illinois.codingtracker.operations.files.ClosedFileOperation; import edu.illinois.codingtracker.operations.files.EditedFileOperation; import edu.illinois.codingtracker.operations.files.EditedUnsychronizedFileOperation; import edu.illinois.codingtracker.operations.files.SavedFileOperation; import edu.illinois.codingtracker.operations.files.UpdatedFileOperation; import edu.illinois.codingtracker.operations.files.snapshoted.CVSCommittedFileOperation; import edu.illinois.codingtracker.operations.files.snapshoted.CVSInitiallyCommittedFileOperation; import edu.illinois.codingtracker.operations.files.snapshoted.NewFileOperation; import edu.illinois.codingtracker.operations.files.snapshoted.RefreshedFileOperation; import edu.illinois.codingtracker.operations.files.snapshoted.SVNCommittedFileOperation; import edu.illinois.codingtracker.operations.files.snapshoted.SVNInitiallyCommittedFileOperation; import edu.illinois.codingtracker.operations.junit.TestCaseFinishedOperation; import edu.illinois.codingtracker.operations.junit.TestCaseStartedOperation; import edu.illinois.codingtracker.operations.junit.TestSessionFinishedOperation; import edu.illinois.codingtracker.operations.junit.TestSessionLaunchedOperation; import edu.illinois.codingtracker.operations.junit.TestSessionStartedOperation; import edu.illinois.codingtracker.operations.options.ProjectOptionsChangedOperation; import edu.illinois.codingtracker.operations.options.WorkspaceOptionsChangedOperation; import edu.illinois.codingtracker.operations.refactorings.FinishedRefactoringOperation; import edu.illinois.codingtracker.operations.refactorings.NewStartedRefactoringOperation; import edu.illinois.codingtracker.operations.refactorings.NewStartedRefactoringOperation.RefactoringMode; import edu.illinois.codingtracker.operations.references.ReferencingProjectsChangedOperation; import edu.illinois.codingtracker.operations.resources.CopiedResourceOperation; import edu.illinois.codingtracker.operations.resources.CreatedResourceOperation; import edu.illinois.codingtracker.operations.resources.DeletedResourceOperation; import edu.illinois.codingtracker.operations.resources.ExternallyModifiedResourceOperation; import edu.illinois.codingtracker.operations.resources.MovedResourceOperation; import edu.illinois.codingtracker.operations.starts.LaunchedApplicationOperation; import edu.illinois.codingtracker.operations.textchanges.ConflictEditorTextChangeOperation; import edu.illinois.codingtracker.operations.textchanges.PerformedConflictEditorTextChangeOperation; import edu.illinois.codingtracker.operations.textchanges.PerformedTextChangeOperation; import edu.illinois.codingtracker.operations.textchanges.RedoneConflictEditorTextChangeOperation; import edu.illinois.codingtracker.operations.textchanges.RedoneTextChangeOperation; import edu.illinois.codingtracker.operations.textchanges.TextChangeOperation; import edu.illinois.codingtracker.operations.textchanges.UndoneConflictEditorTextChangeOperation; import edu.illinois.codingtracker.operations.textchanges.UndoneTextChangeOperation; /** * * @author Stas Negara * */ @SuppressWarnings("restriction") public class OperationRecorder { private static volatile OperationRecorder recorderInstance= null; private static final KnownFilesRecorder knownFilesRecorder= KnownFilesRecorder.getInstance(); private IFile lastEditedFile= null; public static OperationRecorder getInstance() { if (recorderInstance == null) { recorderInstance= new OperationRecorder(); } return recorderInstance; } private OperationRecorder() { } public void recordRefreshedFile(IFile refreshedFile, String replacedText) { boolean isFileKnown= knownFilesRecorder.isFileKnown(refreshedFile, true); if (!isFileKnown) { ensureFileIsKnown(refreshedFile, false); } TextRecorder.record(new RefreshedFileOperation(refreshedFile, replacedText, isFileKnown)); } public void recordChangedText(DocumentEvent documentEvent, String replacedText, String oldDocumentText, IFile editedFile, boolean isUndoing, boolean isRedoing) { if (ResourceHelper.isFileBufferNotSynchronized(editedFile)) { if (!editedFile.equals(lastEditedFile)) { recordEditedUnsynchronizedFile(editedFile, oldDocumentText); } } else { ITextFileBuffer textFileBuffer= ResourceHelper.getTextFileBuffer(editedFile.getFullPath()); if (textFileBuffer != null && textFileBuffer.getEncoding() != null) { ensureFileIsKnown(editedFile, true, textFileBuffer.getEncoding()); } else { ensureFileIsKnown(editedFile, true); } if (!editedFile.equals(lastEditedFile)) { recordEditedFile(editedFile); } } lastEditedFile= editedFile; Debugger.debugDocumentEvent(documentEvent, replacedText); TextRecorder.record(getTextChangeOperation(documentEvent, replacedText, isUndoing, isRedoing)); } private TextChangeOperation getTextChangeOperation(DocumentEvent documentEvent, String replacedText, boolean isUndoing, boolean isRedoing) { TextChangeOperation textChangeOperation= null; if (isUndoing) { textChangeOperation= new UndoneTextChangeOperation(documentEvent, replacedText); } else if (isRedoing) { textChangeOperation= new RedoneTextChangeOperation(documentEvent, replacedText); } else { textChangeOperation= new PerformedTextChangeOperation(documentEvent, replacedText); } return textChangeOperation; } public void recordConflictEditorChangedText(DocumentEvent documentEvent, String replacedText, String editorID, boolean isUndoing, boolean isRedoing) { ConflictEditorTextChangeOperation conflictEditorTextChangeOperation= null; if (isUndoing) { conflictEditorTextChangeOperation= new UndoneConflictEditorTextChangeOperation(editorID, documentEvent, replacedText); } else if (isRedoing) { conflictEditorTextChangeOperation= new RedoneConflictEditorTextChangeOperation(editorID, documentEvent, replacedText); } else { conflictEditorTextChangeOperation= new PerformedConflictEditorTextChangeOperation(editorID, documentEvent, replacedText); } TextRecorder.record(conflictEditorTextChangeOperation); } private void recordEditedUnsynchronizedFile(IFile editedFile, String editorContent) { TextRecorder.record(new EditedUnsychronizedFileOperation(editedFile, editorContent)); } private void recordEditedFile(IFile editedFile) { TextRecorder.record(new EditedFileOperation(editedFile)); } /** * Note that editedFile might be null. * * @param editorID * @param editedFile * @param initialContent */ public void recordOpenedConflictEditor(String editorID, IFile editedFile, String initialContent) { String editedFilePath= ""; if (editedFile != null) { ensureFileIsKnown(editedFile, true); editedFilePath= ResourceHelper.getPortableResourcePath(editedFile); } TextRecorder.record(new OpenedConflictEditorOperation(editorID, editedFilePath, initialContent)); } public void recordCreatedResource(IResource createdResource, int updateFlags, boolean success) { if (success && createdResource instanceof IFile) { ensureFileIsKnown((IFile)createdResource, false); } TextRecorder.record(new CreatedResourceOperation(createdResource, updateFlags, success)); } public void recordMovedResource(IResource movedResource, IPath destination, int updateFlags, boolean success) { invalidateLastEditedFile(movedResource); knownFilesRecorder.moveKnownFiles(movedResource, destination, success); TextRecorder.record(new MovedResourceOperation(movedResource, destination, updateFlags, success)); } public void recordCopiedResource(IResource copiedResource, IPath destination, int updateFlags, boolean success) { knownFilesRecorder.copyKnownFiles(copiedResource, destination, success); TextRecorder.record(new CopiedResourceOperation(copiedResource, destination, updateFlags, success)); } public void recordDeletedResource(IResource deletedResource, int updateFlags, boolean success) { invalidateLastEditedFile(deletedResource); knownFilesRecorder.removeKnownFilesForResource(deletedResource); TextRecorder.record(new DeletedResourceOperation(deletedResource, updateFlags, success)); } public void recordExternallyModifiedFiles(Set<IFile> externallyModifiedJavaFiles, boolean areDeleted) { for (IFile externallyModifiedJavaFile : externallyModifiedJavaFiles) { TextRecorder.record(new ExternallyModifiedResourceOperation(externallyModifiedJavaFile, areDeleted)); } } public void recordSavedFile(IFile savedFile, boolean success) { TextRecorder.record(new SavedFileOperation(savedFile, success)); //TODO: Saving does not mean the file is known if its encoding differs from the saved editor encoding //But, could look for the cases when the encoding is the same //ensureFileIsKnown(savedFile, false); } public void recordSavedCompareEditor(CompareEditor compareEditor, boolean success) { TextRecorder.record(new SavedConflictEditorOperation(EditorHelper.getConflictEditorID(compareEditor), success)); //TODO: Saving does not mean the file is known if its encoding differs from the saved conflict editor encoding //But, could look for the cases when the encoding is the same //ensureFileIsKnown(EditorHelper.getEditedJavaFile(compareEditor), false); } public void recordUpdatedFiles(Set<FileRevision> updatedFileRevisions) { for (FileRevision fileRevision : updatedFileRevisions) { TextRecorder.record(new UpdatedFileOperation(fileRevision.getFile(), fileRevision.getRevision(), fileRevision.getCommittedRevision())); } } /** * Records the committed files including their content. * * @param committedFileRevisions * @param isInitialCommit * @param isSVNCommit */ public void recordCommittedFiles(Set<FileRevision> committedFileRevisions, boolean isInitialCommit, boolean isSVNCommit) { if (committedFileRevisions.size() > 0) { for (FileRevision fileRevision : committedFileRevisions) { IFile file= fileRevision.getFile(); String revision= fileRevision.getRevision(); String committedRevision= fileRevision.getCommittedRevision(); if (isInitialCommit) { if (isSVNCommit) { TextRecorder.record(new SVNInitiallyCommittedFileOperation(file, revision, committedRevision)); } else { TextRecorder.record(new CVSInitiallyCommittedFileOperation(file, revision, committedRevision)); } } else { if (isSVNCommit) { TextRecorder.record(new SVNCommittedFileOperation(file, revision, committedRevision)); } else { TextRecorder.record(new CVSCommittedFileOperation(file, revision, committedRevision)); } } knownFilesRecorder.addKnownFile(file, ResourceHelper.getCharsetNameForFile(file)); } knownFilesRecorder.recordKnownFiles(); } } public void recordClosedFile(IFile file) { invalidateLastEditedFile(file); TextRecorder.record(new ClosedFileOperation(file)); } public void recordClosedConflictEditor(String editorID) { TextRecorder.record(new ClosedConflictEditorOperation(editorID)); } public void recordLaunchedTestSession(String testRunName, String launchedProjectName) { TextRecorder.record(new TestSessionLaunchedOperation(testRunName, launchedProjectName)); } public void recordStartedTestSession(String testRunName) { TextRecorder.record(new TestSessionStartedOperation(testRunName)); } public void recordFinishedTestSession(String testRunName) { TextRecorder.record(new TestSessionFinishedOperation(testRunName)); } public void recordStartedTestCase(String testRunName, String testClassName, String testMethodName) { TextRecorder.record(new TestCaseStartedOperation(testRunName, testClassName, testMethodName)); } public void recordFinishedTestCase(String testRunName, String result) { TextRecorder.record(new TestCaseFinishedOperation(testRunName, result)); } public void recordLaunchedApplication(String launchMode, String launchName, String application, String product, boolean useProduct) { TextRecorder.record(new LaunchedApplicationOperation(launchMode, launchName, application, product, useProduct)); } public void recordStartedRefactoring(RefactoringDescriptor refactoringDescriptor, int eventType) { RefactoringMode mode= null; switch (eventType) { case RefactoringExecutionEvent.ABOUT_TO_PERFORM: mode= RefactoringMode.PERFORM; break; case RefactoringExecutionEvent.ABOUT_TO_REDO: mode= RefactoringMode.REDO; break; case RefactoringExecutionEvent.ABOUT_TO_UNDO: mode= RefactoringMode.UNDO; break; } TextRecorder.record(new NewStartedRefactoringOperation(mode, refactoringDescriptor)); } public void recordFinishedRefactoring(boolean success) { TextRecorder.record(new FinishedRefactoringOperation(success)); } private void ensureFileIsKnown(IFile file, boolean snapshotIfWasNotKnown) { ensureFileIsKnown(file, snapshotIfWasNotKnown, ResourceHelper.getCharsetNameForFile(file)); } private void ensureFileIsKnown(IFile file, boolean snapshotIfWasNotKnown, String charsetName) { //TODO: Is creating a new HashMap for a single file too expensive? Map<IFile, String> fileMap= new HashMap<IFile, String>(1); fileMap.put(file, charsetName); ensureFilesAreKnown(fileMap, snapshotIfWasNotKnown); } public void ensureFilesAreKnown(Map<IFile, String> fileMap, boolean snapshotIfWasNotKnown) { boolean hasChanged= false; for (Entry<IFile, String> entry : fileMap.entrySet()) { //TODO: Is it possible to have a known file, whose CVS/Entries is not known? If not, merge the following two if statements. IFile cvsEntriesFile= getCVSEntriesForFile(entry.getKey()); if (cvsEntriesFile != null && !knownFilesRecorder.isFileKnown(cvsEntriesFile, false)) { knownFilesRecorder.addCVSEntriesFile(cvsEntriesFile); hasChanged= true; } if (!knownFilesRecorder.isFileKnown(entry.getKey(), entry.getValue(), true)) { knownFilesRecorder.addKnownFile(entry.getKey(), entry.getValue()); hasChanged= true; //save the content of a previously unknown file if (snapshotIfWasNotKnown && entry.getKey().exists()) { //TODO: Remove after ensured in ResourceHelper: Actually, should always exist here TextRecorder.record(new NewFileOperation(entry.getKey(), entry.getValue())); } } } if (hasChanged) { knownFilesRecorder.recordKnownFiles(); } } private IFile getCVSEntriesForFile(IFile file) { IPath cvsEntriesPath= file.getFullPath().removeLastSegments(1).append("CVS").append("Entries"); IResource cvsEntriesResource= ResourceHelper.findWorkspaceMember(cvsEntriesPath); if (cvsEntriesResource != null) { return (IFile)cvsEntriesResource; } return null; } @SuppressWarnings("unchecked") public void ensureOptionsAreCurrent(Set<IJavaProject> javaProjects) { ensureWorkspaceOptionsAreCurrent(); for (IJavaProject javaProject : javaProjects) { Map<String, String> projectOptions= javaProject.getOptions(false); String projectName= javaProject.getElementName(); if (!knownFilesRecorder.areProjectOptionsCurrent(projectName, projectOptions)) { knownFilesRecorder.recordProjectOptions(projectName, projectOptions); TextRecorder.record(new ProjectOptionsChangedOperation(projectName, projectOptions)); } } } @SuppressWarnings("unchecked") private void ensureWorkspaceOptionsAreCurrent() { Map<String, String> workspaceOptions= JavaCore.getOptions(); if (!knownFilesRecorder.areWorkspaceOptionsCurrent(workspaceOptions)) { knownFilesRecorder.recordWorkspaceOptions(workspaceOptions); TextRecorder.record(new WorkspaceOptionsChangedOperation(workspaceOptions)); } } public void ensureReferencingProjectsAreCurrent(String projectName, Set<String> referencingProjectNames) { if (!knownFilesRecorder.areReferencingProjectsCurrent(projectName, referencingProjectNames)) { knownFilesRecorder.recordReferencingProjects(projectName, referencingProjectNames); TextRecorder.record(new ReferencingProjectsChangedOperation(projectName, referencingProjectNames)); } } private void invalidateLastEditedFile(IResource resource) { if (lastEditedFile != null && resource.getFullPath().isPrefixOf(lastEditedFile.getFullPath())) { lastEditedFile= null; } } }