/** * This file is licensed under the University of Illinois/NCSA Open Source License. See LICENSE.TXT for details. */ package edu.illinois.codingtracker.operations.textchanges; import org.eclipse.compare.internal.CompareEditor; import org.eclipse.core.commands.ExecutionException; import org.eclipse.jface.text.BadLocationException; import org.eclipse.jface.text.DocumentEvent; import org.eclipse.jface.text.IDocument; import org.eclipse.jface.text.source.ISourceViewer; import org.eclipse.text.undo.DocumentUndoManagerRegistry; import org.eclipse.text.undo.IDocumentUndoManager; import org.eclipse.ui.texteditor.AbstractDecoratedTextEditor; import edu.illinois.codingtracker.helpers.Configuration; import edu.illinois.codingtracker.compare.helpers.EditorHelper; import edu.illinois.codingtracker.operations.OperationLexer; import edu.illinois.codingtracker.operations.OperationTextChunk; import edu.illinois.codingtracker.operations.UserOperation; /** * * @author Stas Negara * */ @SuppressWarnings("restriction") public abstract class TextChangeOperation extends UserOperation { protected String replacedText; protected String newText; protected int offset; protected int length; //The following fields are computed during replay, do not serialize/deserialize them! protected IDocument currentDocument= null; protected ISourceViewer currentViewer= null; private boolean isRecordedWhileRefactoring= false; public TextChangeOperation() { super(); } public TextChangeOperation(DocumentEvent documentEvent, String replacedText) { super(); this.replacedText= replacedText; newText= documentEvent.getText(); offset= documentEvent.getOffset(); length= documentEvent.getLength(); } protected IDocumentUndoManager getCurrentDocumentUndoManager() { return DocumentUndoManagerRegistry.getDocumentUndoManager(currentDocument); } @Override protected void populateTextChunk(OperationTextChunk textChunk) { textChunk.append(replacedText); textChunk.append(newText); textChunk.append(offset); textChunk.append(length); } @Override protected void initializeFrom(OperationLexer operationLexer) { replacedText= operationLexer.readString(); newText= operationLexer.readString(); offset= operationLexer.readInt(); length= operationLexer.readInt(); } @Override public void replay() throws BadLocationException, ExecutionException { if (isReplayedRefactoring) { isRecordedWhileRefactoring= true; } else { updateCurrentState(); preReplay(); replayTextChange(); postReplay(); } } private void preReplay() throws BadLocationException { if (!Configuration.isInPostprocessMode) { //This is not executed while postprocessing to improve performance. currentViewer.revealRange(offset, length > newText.length() ? length : newText.length()); //This is not executed while postprocessing to avoid USER Objects leak. currentViewer.setSelectedRange(offset, length); } if (!replacedText.equals(currentDocument.get(offset, length))) { throw new RuntimeException("Replaced text is not present in the document: " + this); } } private void postReplay() throws BadLocationException { if (!Configuration.isInPostprocessMode) { //This is not executed while postprocessing to avoid USER Objects leak. currentViewer.setSelectedRange(offset, newText.length()); } if (!newText.equals(currentDocument.get(offset, newText.length()))) { throw new RuntimeException("New text does not appear in the document: " + this); } } private void replayTextChange() throws BadLocationException, ExecutionException { //Timestamp updates are not reproducible, because the corresponding UndoableOperation2ChangeAdapter operation //is executed as a simple text change if (!isTimestampUpdate()) { if (Configuration.isInTestMode) { replaySpecificTextChange(); } else { currentDocument.replace(offset, length, newText); } } } private void updateCurrentState() { EditorHelper.activateEditor(currentEditor); if (currentEditor instanceof CompareEditor) { currentViewer= EditorHelper.getEditingSourceViewer((CompareEditor)currentEditor); } else if (currentEditor instanceof AbstractDecoratedTextEditor) { currentViewer= EditorHelper.getEditingSourceViewer((AbstractDecoratedTextEditor)currentEditor); } currentDocument= currentViewer.getDocument(); } @Override public String toString() { StringBuffer sb= new StringBuffer(); sb.append("Replaced text: " + replacedText + "\n"); sb.append("New text: " + newText + "\n"); sb.append("Offset: " + offset + "\n"); sb.append("Length: " + length + "\n"); sb.append(super.toString()); return sb.toString(); } @Override public boolean isTestReplayRecorded() { return isRecordedWhileRefactoring || !isTimestampUpdate(); } /** * If a recorded text change operation does not change anything in the document, it is a * timestamp update (happens when an UndoableOperation2ChangeAdapter is undone/redone) * * @return */ private boolean isTimestampUpdate() { return newText.isEmpty() && replacedText.isEmpty() && offset == 0 && length == 0; } protected abstract void replaySpecificTextChange() throws BadLocationException, ExecutionException; }