| /******************************************************************************* |
| * Copyright (c) 2005 IBM Corporation and others. |
| * All rights reserved. This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License v1.0 |
| * which accompanies this distribution, and is available at |
| * http://www.eclipse.org/legal/epl-v10.html |
| * |
| * Contributors: |
| * IBM Corporation - initial API and implementation |
| *******************************************************************************/ |
| package org.eclipse.ltk.core.refactoring; |
| |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.Iterator; |
| import java.util.LinkedList; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| |
| import org.eclipse.text.edits.InsertEdit; |
| import org.eclipse.text.edits.MalformedTreeException; |
| import org.eclipse.text.edits.MultiTextEdit; |
| import org.eclipse.text.edits.ReplaceEdit; |
| import org.eclipse.text.edits.TextEdit; |
| import org.eclipse.text.edits.TextEditCopier; |
| import org.eclipse.text.edits.TextEditGroup; |
| import org.eclipse.text.edits.TextEditProcessor; |
| import org.eclipse.text.edits.UndoEdit; |
| |
| import org.eclipse.core.runtime.CoreException; |
| import org.eclipse.core.runtime.IPath; |
| import org.eclipse.core.runtime.IProgressMonitor; |
| import org.eclipse.core.runtime.NullProgressMonitor; |
| import org.eclipse.core.runtime.OperationCanceledException; |
| import org.eclipse.core.runtime.SubProgressMonitor; |
| |
| import org.eclipse.core.filebuffers.FileBuffers; |
| import org.eclipse.core.filebuffers.ITextFileBuffer; |
| import org.eclipse.core.filebuffers.ITextFileBufferManager; |
| |
| import org.eclipse.core.resources.IFile; |
| |
| import org.eclipse.jface.text.BadLocationException; |
| import org.eclipse.jface.text.BadPositionCategoryException; |
| import org.eclipse.jface.text.Document; |
| import org.eclipse.jface.text.DocumentEvent; |
| import org.eclipse.jface.text.DocumentRewriteSession; |
| import org.eclipse.jface.text.DocumentRewriteSessionType; |
| import org.eclipse.jface.text.IDocument; |
| import org.eclipse.jface.text.IDocumentExtension4; |
| import org.eclipse.jface.text.IDocumentListener; |
| import org.eclipse.jface.text.IPositionUpdater; |
| import org.eclipse.jface.text.IRegion; |
| import org.eclipse.jface.text.Position; |
| import org.eclipse.jface.text.Region; |
| |
| import org.eclipse.ltk.internal.core.refactoring.Assert; |
| import org.eclipse.ltk.internal.core.refactoring.BufferValidationState; |
| import org.eclipse.ltk.internal.core.refactoring.Changes; |
| import org.eclipse.ltk.internal.core.refactoring.MultiStateUndoChange; |
| import org.eclipse.ltk.internal.core.refactoring.ContentStamps; |
| import org.eclipse.ltk.internal.core.refactoring.NonDeletingPositionUpdater; |
| import org.eclipse.ltk.internal.core.refactoring.RefactoringCorePlugin; |
| |
| /** |
| * A multi state text file change is a special change object that applies a sequence of {@link TextEdit |
| * text edit trees} to a document. The multi state text file change manages the text edit trees. |
| * <p> |
| * A multi state text file change offers the ability to access the original content of |
| * the document as well as creating a preview of the change. The edit |
| * trees get copied when creating any kind of preview. Therefore no region |
| * updating on the original edit trees takes place when requesting a preview |
| * (for more information on region updating see class {@link TextEdit TextEdit}. |
| * If region tracking is required for a preview it can be enabled via a call |
| * to the method {@link #setKeepPreviewEdits(boolean) setKeepPreviewEdits}. |
| * If enabled the multi state text file change keeps the copied edit trees executed for the |
| * preview allowing clients to map an original edit to an executed edit. The |
| * executed edit can then be used to determine its position in the preview. |
| * </p> |
| * |
| * @since 3.2 |
| */ |
| public class MultiStateTextFileChange extends TextEditBasedChange { |
| |
| private static final class ComposableBufferChange { |
| |
| private TextEdit fEdit; |
| |
| private List fGroups; |
| |
| private final TextEdit getEdit() { |
| return fEdit; |
| } |
| |
| private final List getGroups() { |
| return fGroups; |
| } |
| |
| private final void setEdit(final TextEdit edit) { |
| Assert.isNotNull(edit); |
| |
| fEdit= edit; |
| } |
| |
| private final void setGroups(final List groups) { |
| Assert.isNotNull(groups); |
| |
| fGroups= groups; |
| } |
| } |
| |
| private static final class ComposableBufferChangeGroup extends TextEditBasedChangeGroup { |
| |
| private final Set fEdits= new HashSet(); |
| |
| private ComposableBufferChangeGroup(final MultiStateTextFileChange change, final TextEditGroup group) { |
| super(change, group); |
| |
| final TextEdit[] edits= group.getTextEdits(); |
| for (int index= 0; index < edits.length; index++) |
| cacheEdit(edits[index]); |
| } |
| |
| private final void cacheEdit(final TextEdit edit) { |
| fEdits.add(edit); |
| |
| final TextEdit[] edits= edit.getChildren(); |
| for (int index= 0; index < edits.length; index++) |
| cacheEdit(edits[index]); |
| } |
| |
| private final boolean containsEdit(final TextEdit edit) { |
| return fEdits.contains(edit); |
| } |
| |
| private final Set getCachedEdits() { |
| return fEdits; |
| } |
| } |
| |
| private static final class ComposableEditPosition extends Position { |
| |
| private String fText; |
| |
| private final String getText() { |
| return fText; |
| } |
| |
| private final void setText(final String text) { |
| Assert.isNotNull(text); |
| |
| fText= text; |
| } |
| } |
| |
| private static final class ComposableUndoEdit { |
| |
| private ComposableBufferChangeGroup fGroup; |
| |
| private TextEdit fOriginal; |
| |
| private ReplaceEdit fUndo; |
| |
| private final ComposableBufferChangeGroup getGroup() { |
| return fGroup; |
| } |
| |
| private final TextEdit getOriginal() { |
| return fOriginal; |
| } |
| |
| private final String getOriginalText() { |
| if (fOriginal instanceof ReplaceEdit) { |
| return ((ReplaceEdit) getOriginal()).getText(); |
| } else if (fOriginal instanceof InsertEdit) { |
| return ((InsertEdit) getOriginal()).getText(); |
| } |
| return ""; //$NON-NLS-1$ |
| } |
| |
| private final ReplaceEdit getUndo() { |
| return fUndo; |
| } |
| |
| private final void setGroup(final ComposableBufferChangeGroup group) { |
| Assert.isNotNull(group); |
| |
| fGroup= group; |
| } |
| |
| private final void setOriginal(final TextEdit edit) { |
| fOriginal= edit; |
| } |
| |
| private final void setUndo(final ReplaceEdit undo) { |
| Assert.isNotNull(undo); |
| |
| fUndo= undo; |
| } |
| } |
| |
| /** The position category for the resulting edit positions */ |
| private static final String COMPOSABLE_POSITION_CATEGORY= "ComposableEditPositionCategory_" + System.currentTimeMillis(); //$NON-NLS-1$ |
| |
| /** The position category for the preview region range marker */ |
| private static final String MARKER_POSITION_CATEGORY= "MarkerPositionCategory_" + System.currentTimeMillis(); //$NON-NLS-1$ |
| |
| /** The text file buffer */ |
| private ITextFileBuffer fBuffer; |
| |
| /** The last string obtained from a document */ |
| private String fCachedString; |
| |
| /** |
| * The internal change objects (element type: |
| * <code>ComposableBufferChange</code>) |
| */ |
| private final ArrayList fChanges= new ArrayList(4); |
| |
| /** The content stamp */ |
| private ContentStamp fContentStamp; |
| |
| /** The text edit copier */ |
| private TextEditCopier fCopier; |
| |
| /** The text file buffer reference count */ |
| private int fCount; |
| |
| /** The dirty flag */ |
| private boolean fDirty; |
| |
| /** The affected file */ |
| private IFile fFile; |
| |
| /** The save mode */ |
| private int fSaveMode= TextFileChange.KEEP_SAVE_STATE; |
| |
| /** The validation state */ |
| private BufferValidationState fValidationState; |
| |
| /** |
| * Creates a new composite text file change. |
| * <p> |
| * The default text type is <code>txt</code>. |
| * </p> |
| * |
| * @param name |
| * the name of the composite text file change |
| * @param file |
| * the text file to apply the change to |
| */ |
| public MultiStateTextFileChange(final String name, final IFile file) { |
| super(name); |
| |
| Assert.isNotNull(file); |
| fFile= file; |
| |
| setTextType("txt"); //$NON-NLS-1$ |
| } |
| |
| /** |
| * Acquires a document from the file buffer manager. |
| * |
| * @param monitor |
| * the progress monitor to use |
| * @return the document |
| * @throws CoreException |
| */ |
| private IDocument acquireDocument(final IProgressMonitor monitor) throws CoreException { |
| if (fCount > 0) |
| return fBuffer.getDocument(); |
| |
| final ITextFileBufferManager manager= FileBuffers.getTextFileBufferManager(); |
| final IPath path= fFile.getFullPath(); |
| |
| manager.connect(path, monitor); |
| fCount++; |
| |
| fBuffer= manager.getTextFileBuffer(path); |
| |
| final IDocument document= fBuffer.getDocument(); |
| fContentStamp= ContentStamps.get(fFile, document); |
| |
| return document; |
| } |
| |
| /** |
| * Adds a new text change to this composite change. |
| * <p> |
| * The text change which is added is not changed in any way. Rather |
| * the contents of the text change are retrieved and stored internally |
| * in this composite text change. |
| * </p> |
| * |
| * @param change |
| * the text change to add |
| */ |
| public final void addChange(final TextChange change) { |
| Assert.isNotNull(change); |
| |
| final ComposableBufferChange result= new ComposableBufferChange(); |
| result.setEdit(change.getEdit()); |
| |
| final TextEditBasedChangeGroup[] groups= change.getChangeGroups(); |
| final List list= new ArrayList(groups.length); |
| |
| for (int index= 0; index < groups.length; index++) { |
| |
| final TextEditBasedChangeGroup group= new ComposableBufferChangeGroup(this, groups[index].getTextEditGroup()); |
| list.add(group); |
| |
| addChangeGroup(group); |
| } |
| result.setGroups(list); |
| fChanges.add(result); |
| } |
| |
| // Copied from TextChange |
| private TextEditProcessor createTextEditProcessor(ComposableBufferChange change, IDocument document, int flags, boolean preview) { |
| List excludes= new ArrayList(0); |
| for (final Iterator iterator= change.getGroups().iterator(); iterator.hasNext();) { |
| TextEditBasedChangeGroup group= (TextEditBasedChangeGroup) iterator.next(); |
| if (!group.isEnabled()) |
| excludes.addAll(Arrays.asList(group.getTextEdits())); |
| } |
| |
| if (preview) { |
| fCopier= new TextEditCopier(change.getEdit()); |
| TextEdit copiedEdit= fCopier.perform(); |
| boolean keep= getKeepPreviewEdits(); |
| if (keep) |
| flags= flags | TextEdit.UPDATE_REGIONS; |
| LocalTextEditProcessor result= new LocalTextEditProcessor(document, copiedEdit, flags); |
| result.setExcludes(mapEdits((TextEdit[]) excludes.toArray(new TextEdit[excludes.size()]), fCopier)); |
| if (!keep) |
| fCopier= null; |
| return result; |
| } else { |
| LocalTextEditProcessor result= new LocalTextEditProcessor(document, change.getEdit(), flags | TextEdit.UPDATE_REGIONS); |
| result.setExcludes((TextEdit[]) excludes.toArray(new TextEdit[excludes.size()])); |
| return result; |
| } |
| } |
| |
| /** |
| * Creates the corresponding text edit to the event. |
| * |
| * @param document |
| * the document |
| * @param offset |
| * the offset of the event |
| * @param length |
| * the length of the event |
| * @param text |
| * the text of the event |
| * @return the undo edit |
| */ |
| private ReplaceEdit createUndoEdit(final IDocument document, final int offset, final int length, final String text) { |
| String currentText= null; |
| try { |
| currentText= document.get(offset, length); |
| } catch (BadLocationException cannotHappen) { |
| // Cannot happen |
| } |
| |
| if (fCachedString != null && fCachedString.equals(currentText)) |
| currentText= fCachedString; |
| else |
| fCachedString= currentText; |
| |
| return new ReplaceEdit(offset, text != null ? text.length() : 0, currentText); |
| } |
| |
| /* |
| * @see org.eclipse.ltk.core.refactoring.Change#dispose() |
| */ |
| public final void dispose() { |
| fValidationState.dispose(); |
| } |
| |
| /* |
| * @see org.eclipse.ltk.core.refactoring.TextEditBasedChange#getCurrentContent(org.eclipse.core.runtime.IProgressMonitor) |
| */ |
| public final String getCurrentContent(final IProgressMonitor monitor) throws CoreException { |
| return getCurrentDocument(monitor).get(); |
| } |
| |
| /* |
| * @see org.eclipse.ltk.core.refactoring.TextEditBasedChange#getCurrentContent(org.eclipse.jface.text.IRegion,boolean,int,org.eclipse.core.runtime.IProgressMonitor) |
| */ |
| public final String getCurrentContent(final IRegion region, final boolean expand, final int surround, final IProgressMonitor monitor) throws CoreException { |
| Assert.isNotNull(region); |
| Assert.isTrue(surround >= 0); |
| final IDocument document= getCurrentDocument(monitor); |
| Assert.isTrue(document.getLength() >= region.getOffset() + region.getLength()); |
| return getContent(document, region, expand, surround); |
| } |
| |
| /** |
| * Returns a document representing the current state of the buffer, |
| * prior to the application of the change. |
| * <p> |
| * The returned document should not be modified. |
| * </p> |
| * |
| * @param monitor |
| * the progress monitor to use, or <code>null</code> |
| * @return the current document, or the empty document |
| * @throws CoreException |
| * if no document could be acquired |
| */ |
| public final IDocument getCurrentDocument(IProgressMonitor monitor) throws CoreException { |
| if (monitor == null) |
| monitor= new NullProgressMonitor(); |
| IDocument result= null; |
| monitor.beginTask("", 2); //$NON-NLS-1$ |
| try { |
| result= acquireDocument(new SubProgressMonitor(monitor, 1)); |
| } finally { |
| if (result != null) |
| releaseDocument(result, new SubProgressMonitor(monitor, 1)); |
| } |
| monitor.done(); |
| if (result == null) |
| result= new Document(); |
| return result; |
| } |
| |
| /* |
| * @see org.eclipse.ltk.core.refactoring.Change#getModifiedElement() |
| */ |
| public final Object getModifiedElement() { |
| return new Object[] { fFile}; |
| } |
| |
| /* |
| * @see org.eclipse.ltk.core.refactoring.TextEditBasedChange#getPreviewContent(org.eclipse.ltk.core.refactoring.TextEditBasedChangeGroup[],org.eclipse.jface.text.IRegion,boolean,int,org.eclipse.core.runtime.IProgressMonitor) |
| */ |
| public final String getPreviewContent(final TextEditBasedChangeGroup[] groups, final IRegion region, final boolean expand, final int surround, final IProgressMonitor monitor) throws CoreException { |
| |
| final Set cachedGroups= new HashSet(Arrays.asList(groups)); |
| final IDocument document= new Document(getCurrentDocument(monitor).get()); |
| |
| // Marks the region in the document to be previewed |
| final Position range= new Position(region.getOffset(), region.getLength()); |
| try { |
| |
| ComposableBufferChange change= null; |
| |
| final TextEditBasedChangeGroup[] changedGroups= getChangeGroups(); |
| |
| LinkedList compositeUndo= new LinkedList(); |
| for (int index= 0; index < fChanges.size(); index++) { |
| change= (ComposableBufferChange) fChanges.get(index); |
| |
| TextEdit copy= null; |
| try { |
| // Have to use a copy |
| fCopier= new TextEditCopier(change.getEdit()); |
| copy= fCopier.perform(); |
| |
| // Create a mapping from the copied edits to its originals |
| final Map originalMap= new HashMap(); |
| for (final Iterator outer= change.getGroups().iterator(); outer.hasNext();) { |
| |
| final ComposableBufferChangeGroup group= (ComposableBufferChangeGroup) outer.next(); |
| for (final Iterator inner= group.getCachedEdits().iterator(); inner.hasNext();) { |
| |
| final TextEdit originalEdit= (TextEdit) inner.next(); |
| final TextEdit copiedEdit= fCopier.getCopy(originalEdit); |
| |
| if (copiedEdit != null) |
| originalMap.put(copiedEdit, originalEdit); |
| else |
| RefactoringCorePlugin.logErrorMessage("Could not find a copy for the indexed text edit " + originalEdit.toString()); //$NON-NLS-1$ |
| } |
| } |
| |
| final ComposableBufferChangeGroup[] currentGroup= { null}; |
| final TextEdit[] currentEdit= { null}; |
| |
| // Text edit processor which sets the change group and |
| // current edit when processing |
| final TextEditProcessor processor= new TextEditProcessor(document, copy, TextEdit.NONE) { |
| |
| protected final boolean considerEdit(final TextEdit edit) { |
| |
| final TextEdit originalEdit= (TextEdit) originalMap.get(edit); |
| if (originalEdit != null) { |
| |
| currentEdit[0]= originalEdit; |
| |
| boolean found= false; |
| for (int offset= 0; offset < changedGroups.length && !found; offset++) { |
| |
| final ComposableBufferChangeGroup group= (ComposableBufferChangeGroup) changedGroups[offset]; |
| if (group.containsEdit(originalEdit)) { |
| |
| currentGroup[0]= group; |
| found= true; |
| } |
| } |
| if (!found) |
| currentGroup[0]= null; |
| |
| } else if (!(edit instanceof MultiTextEdit)) { |
| RefactoringCorePlugin.logErrorMessage("Could not find the original of the copied text edit " + edit.toString()); //$NON-NLS-1$ |
| } |
| return true; |
| } |
| }; |
| |
| final LinkedList eventUndos= new LinkedList(); |
| |
| // Listener to record the undos on the document (offsets |
| // relative to the document event) |
| final IDocumentListener listener= new IDocumentListener() { |
| |
| public final void documentAboutToBeChanged(final DocumentEvent event) { |
| final ComposableUndoEdit edit= new ComposableUndoEdit(); |
| |
| edit.setGroup(currentGroup[0]); |
| edit.setOriginal(currentEdit[0]); |
| edit.setUndo(createUndoEdit(document, event.getOffset(), event.getLength(), event.getText())); |
| |
| eventUndos.addFirst(edit); |
| } |
| |
| public final void documentChanged(final DocumentEvent event) { |
| // Do nothing |
| } |
| }; |
| |
| try { |
| // Record undos in LIFO order |
| document.addDocumentListener(listener); |
| processor.performEdits(); |
| } finally { |
| document.removeDocumentListener(listener); |
| } |
| |
| compositeUndo.addFirst(eventUndos); |
| |
| } finally { |
| fCopier= null; |
| } |
| } |
| |
| final IPositionUpdater positionUpdater= new IPositionUpdater() { |
| |
| public final void update(final DocumentEvent event) { |
| |
| final int eventOffset= event.getOffset(); |
| final int eventLength= event.getLength(); |
| final int eventOldEndOffset= eventOffset + eventLength; |
| final String eventText= event.getText(); |
| final int eventNewLength= eventText == null ? 0 : eventText.length(); |
| final int eventNewEndOffset= eventOffset + eventNewLength; |
| final int deltaLength= eventNewLength - eventLength; |
| |
| try { |
| |
| final Position[] positions= event.getDocument().getPositions(COMPOSABLE_POSITION_CATEGORY); |
| for (int index= 0; index < positions.length; index++) { |
| |
| final Position position= positions[index]; |
| if (position.isDeleted()) |
| continue; |
| |
| final int offset= position.getOffset(); |
| final int length= position.getLength(); |
| final int end= offset + length; |
| |
| if (offset > eventOldEndOffset) { |
| // position comes way after change - shift |
| position.setOffset(offset + deltaLength); |
| } else if (end < eventOffset) { |
| // position comes way before change - leave |
| // alone |
| } else if (offset == eventOffset) { |
| // leave alone, since the edits are overlapping |
| } else if (offset <= eventOffset && end >= eventOldEndOffset) { |
| // event completely internal to the position |
| // - |
| // adjust length |
| position.setLength(length + deltaLength); |
| } else if (offset < eventOffset) { |
| // event extends over end of position - include |
| // the |
| // replacement text into the position |
| position.setLength(eventNewEndOffset - offset); |
| } else if (end > eventOldEndOffset) { |
| // event extends from before position into it - |
| // adjust |
| // offset and length, including the replacement |
| // text into |
| // the position |
| position.setOffset(eventOffset); |
| int deleted= eventOldEndOffset - offset; |
| position.setLength(length - deleted + eventNewLength); |
| } else { |
| // event comprises the position - keep it at the |
| // same |
| // position, but always inside the replacement |
| // text |
| int newOffset= Math.min(offset, eventNewEndOffset); |
| int newEndOffset= Math.min(end, eventNewEndOffset); |
| position.setOffset(newOffset); |
| position.setLength(newEndOffset - newOffset); |
| } |
| } |
| } catch (BadPositionCategoryException exception) { |
| // ignore and return |
| } |
| } |
| }; |
| |
| try { |
| |
| document.addPositionCategory(COMPOSABLE_POSITION_CATEGORY); |
| document.addPositionUpdater(positionUpdater); |
| |
| // Apply undos in LIFO order to get to the original document |
| // Track the undos of edits which are in change groups to be |
| // previewed and insert |
| // Undo edits for them (corresponding to the linearized net |
| // effect on the original document) |
| final LinkedList undoQueue= new LinkedList(); |
| for (final Iterator outer= compositeUndo.iterator(); outer.hasNext();) { |
| for (final Iterator inner= ((List) outer.next()).iterator(); inner.hasNext();) { |
| |
| final ComposableUndoEdit edit= (ComposableUndoEdit) inner.next(); |
| final ReplaceEdit undo= edit.getUndo(); |
| |
| final int offset= undo.getOffset(); |
| final int length= undo.getLength(); |
| final String text= undo.getText(); |
| |
| ComposableEditPosition position= new ComposableEditPosition(); |
| if (cachedGroups.contains(edit.getGroup())) { |
| |
| if (text == null || text.equals("")) { //$NON-NLS-1$ |
| position.offset= offset; |
| if (length != 0) { |
| // Undo is a delete, create final insert |
| // edit |
| position.length= 0; |
| position.setText(edit.getOriginalText()); |
| } else |
| RefactoringCorePlugin.logErrorMessage("Dubious undo edit found: " + undo.toString()); //$NON-NLS-1$ |
| |
| } else if (length == 0) { |
| position.offset= offset; |
| if (text != null && !text.equals("")) { //$NON-NLS-1$ |
| // Undo is an insert, create final delete |
| // edit |
| position.setText(""); //$NON-NLS-1$ |
| position.length= text.length(); |
| } else |
| RefactoringCorePlugin.logErrorMessage("Dubious undo edit found: " + undo.toString()); //$NON-NLS-1$ |
| |
| } else { |
| // Undo is a replace, create final replace edit |
| position.offset= offset; |
| position.length= length; |
| position.setText(edit.getOriginalText()); |
| } |
| |
| document.addPosition(COMPOSABLE_POSITION_CATEGORY, position); |
| } |
| |
| position= new ComposableEditPosition(); |
| position.offset= undo.getOffset(); |
| position.length= undo.getLength(); |
| position.setText(undo.getText()); |
| |
| undoQueue.add(position); |
| } |
| |
| for (final Iterator iterator= undoQueue.iterator(); iterator.hasNext();) { |
| final ComposableEditPosition position= (ComposableEditPosition) iterator.next(); |
| |
| document.replace(position.offset, position.length, position.getText()); |
| iterator.remove(); |
| } |
| } |
| |
| // Use a simple non deleting position updater for the range |
| final IPositionUpdater markerUpdater= new NonDeletingPositionUpdater(MARKER_POSITION_CATEGORY); |
| |
| try { |
| |
| final Position[] positions= document.getPositions(COMPOSABLE_POSITION_CATEGORY); |
| document.addPositionCategory(MARKER_POSITION_CATEGORY); |
| document.addPositionUpdater(markerUpdater); |
| document.addPosition(MARKER_POSITION_CATEGORY, range); |
| |
| for (int index= 0; index < positions.length; index++) { |
| final ComposableEditPosition position= (ComposableEditPosition) positions[index]; |
| |
| document.replace(position.offset, position.length, position.getText() != null ? position.getText() : ""); //$NON-NLS-1$ |
| } |
| |
| } catch (BadPositionCategoryException exception) { |
| RefactoringCorePlugin.log(exception); |
| } finally { |
| document.removePositionUpdater(markerUpdater); |
| try { |
| document.removePosition(MARKER_POSITION_CATEGORY, range); |
| document.removePositionCategory(MARKER_POSITION_CATEGORY); |
| } catch (BadPositionCategoryException exception) { |
| // Cannot happen |
| } |
| } |
| } catch (BadPositionCategoryException exception) { |
| RefactoringCorePlugin.log(exception); |
| } finally { |
| document.removePositionUpdater(positionUpdater); |
| try { |
| document.removePositionCategory(COMPOSABLE_POSITION_CATEGORY); |
| } catch (BadPositionCategoryException exception) { |
| RefactoringCorePlugin.log(exception); |
| } |
| } |
| |
| return getContent(document, new Region(range.offset, range.length), expand, surround); |
| |
| } catch (MalformedTreeException exception) { |
| RefactoringCorePlugin.log(exception); |
| } catch (BadLocationException exception) { |
| RefactoringCorePlugin.log(exception); |
| } |
| return getPreviewDocument(monitor).get(); |
| } |
| |
| /* |
| * @see org.eclipse.ltk.core.refactoring.TextEditBasedChange#getPreviewContent(org.eclipse.core.runtime.IProgressMonitor) |
| */ |
| public final String getPreviewContent(final IProgressMonitor monitor) throws CoreException { |
| return getPreviewDocument(monitor).get(); |
| } |
| |
| /** |
| * Returns a document representing the preview of the refactored buffer, |
| * after the application of the change object. |
| * |
| * @param monitor |
| * the progress monitor to use, or <code>null</code> |
| * @return the preview document, or an empty document |
| * @throws CoreException |
| * if no document could be acquired |
| */ |
| public final IDocument getPreviewDocument(IProgressMonitor monitor) throws CoreException { |
| if (monitor == null) |
| monitor= new NullProgressMonitor(); |
| |
| IDocument result= null; |
| IDocument document= null; |
| DocumentRewriteSession session= null; |
| |
| try { |
| |
| document= acquireDocument(new SubProgressMonitor(monitor, 1)); |
| if (document != null) { |
| result= new Document(document.get()); |
| |
| if (result instanceof IDocumentExtension4) |
| session= ((IDocumentExtension4) result).startRewriteSession(DocumentRewriteSessionType.UNRESTRICTED); |
| |
| performChanges(result, null, true); |
| } |
| |
| } catch (BadLocationException exception) { |
| throw Changes.asCoreException(exception); |
| } finally { |
| if (document != null) { |
| try { |
| if (session != null) |
| ((IDocumentExtension4) result).stopRewriteSession(session); |
| } finally { |
| releaseDocument(document, new SubProgressMonitor(monitor, 1)); |
| } |
| } |
| monitor.done(); |
| } |
| if (result == null) |
| result= new Document(); |
| return result; |
| } |
| |
| /** |
| * Returns the save mode of this change. |
| * |
| * @return the save mode |
| */ |
| public final int getSaveMode() { |
| return fSaveMode; |
| } |
| |
| /* |
| * @see org.eclipse.ltk.core.refactoring.Change#initializeValidationData(org.eclipse.core.runtime.IProgressMonitor) |
| */ |
| public final void initializeValidationData(final IProgressMonitor monitor) { |
| monitor.beginTask("", 1); //$NON-NLS-1$ |
| fValidationState= BufferValidationState.create(fFile); |
| monitor.worked(1); |
| } |
| |
| /* |
| * @see org.eclipse.ltk.core.refactoring.Change#isValid(org.eclipse.core.runtime.IProgressMonitor) |
| */ |
| public final RefactoringStatus isValid(final IProgressMonitor monitor) throws CoreException, OperationCanceledException { |
| monitor.beginTask("", 1); //$NON-NLS-1$ |
| |
| final ITextFileBuffer buffer= FileBuffers.getTextFileBufferManager().getTextFileBuffer(fFile.getFullPath()); |
| fDirty= buffer != null && buffer.isDirty(); |
| |
| final RefactoringStatus status= fValidationState.isValid(needsSaving()); |
| if (needsSaving()) { |
| status.merge(Changes.validateModifiesFiles(new IFile[] { fFile})); |
| } else { |
| // we are reading the file. So it should be at least in sync |
| status.merge(Changes.checkInSync(new IFile[] { fFile})); |
| } |
| monitor.worked(1); |
| return status; |
| } |
| |
| /** |
| * Does the change need saving? |
| * |
| * @return <code>true</code> if it needs saving, <code>false</code> |
| * otherwise |
| */ |
| public final boolean needsSaving() { |
| return (fSaveMode & TextFileChange.FORCE_SAVE) != 0 || (!fDirty && (fSaveMode & TextFileChange.KEEP_SAVE_STATE) != 0); |
| } |
| |
| /* |
| * @see org.eclipse.ltk.core.refactoring.Change#perform(org.eclipse.core.runtime.IProgressMonitor) |
| */ |
| public final Change perform(final IProgressMonitor monitor) throws CoreException { |
| monitor.beginTask("", 3); //$NON-NLS-1$ |
| |
| IDocument document= null; |
| DocumentRewriteSession session= null; |
| |
| try { |
| document= acquireDocument(new SubProgressMonitor(monitor, 1)); |
| if (document instanceof IDocumentExtension4) |
| session= ((IDocumentExtension4) document).startRewriteSession(DocumentRewriteSessionType.UNRESTRICTED); |
| |
| final LinkedList undoList= new LinkedList(); |
| performChanges(document, undoList, false); |
| |
| if (needsSaving()) |
| fBuffer.commit(new SubProgressMonitor(monitor, 1), false); |
| |
| return new MultiStateUndoChange(getName(), fFile, (UndoEdit[]) undoList.toArray(new UndoEdit[undoList.size()]), fContentStamp, fSaveMode); |
| |
| } catch (BadLocationException exception) { |
| throw Changes.asCoreException(exception); |
| } finally { |
| if (document != null) { |
| try { |
| if (session != null) |
| ((IDocumentExtension4) document).stopRewriteSession(session); |
| } finally { |
| releaseDocument(document, new SubProgressMonitor(monitor, 1)); |
| } |
| } |
| monitor.done(); |
| } |
| } |
| |
| /** |
| * Performs the changes on the specified document. |
| * |
| * @param document |
| * the document to perform the changes on |
| * @param undoList |
| * the undo list, or <code>null</code> to discard the undos |
| * @param preview |
| * <code>true</code> if the changes are performed for preview, |
| * <code>false</code> otherwise |
| * @throws BadLocationException |
| * if the edit tree could not be applied |
| */ |
| private void performChanges(final IDocument document, final LinkedList undoList, final boolean preview) throws BadLocationException { |
| |
| for (final Iterator iterator= fChanges.iterator(); iterator.hasNext();) { |
| final ComposableBufferChange change= (ComposableBufferChange) iterator.next(); |
| |
| final UndoEdit edit= createTextEditProcessor(change, document, undoList != null ? TextEdit.CREATE_UNDO : TextEdit.NONE, preview).performEdits(); |
| if (undoList != null) |
| undoList.addFirst(edit); |
| } |
| } |
| |
| /** |
| * Releases the document. |
| * |
| * @param document |
| * the document to release |
| * @param monitor |
| * the progress monitor |
| * @throws CoreException |
| */ |
| private void releaseDocument(final IDocument document, final IProgressMonitor monitor) throws CoreException { |
| Assert.isTrue(fCount > 0); |
| |
| if (fCount == 1) |
| FileBuffers.getTextFileBufferManager().disconnect(fFile.getFullPath(), monitor); |
| |
| fCount--; |
| } |
| |
| /* |
| * @see org.eclipse.ltk.core.refactoring.TextEditBasedChange#setKeepPreviewEdits(boolean) |
| */ |
| public final void setKeepPreviewEdits(final boolean keep) { |
| super.setKeepPreviewEdits(keep); |
| |
| if (!keep) |
| fCopier= null; |
| } |
| |
| /** |
| * Sets the save mode. |
| * |
| * @param mode |
| * The mode to set |
| */ |
| public final void setSaveMode(final int mode) { |
| fSaveMode= mode; |
| } |
| } |