blob: 2398a9d211824cd42eb59202ef89597b00e7bb1c [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2005, 2006 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.Iterator;
import java.util.List;
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.core.runtime.Assert;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IRegion;
import org.eclipse.ltk.internal.core.refactoring.Changes;
/**
* An abstract base implementation of a change which is based on text edits.
*
* @since 3.2
*/
public abstract class TextEditBasedChange extends Change {
/**
* Text edit processor which has the ability to selectively include or exclude single text edits.
*/
static final class LocalTextEditProcessor extends TextEditProcessor {
public static final int EXCLUDE= 1;
public static final int INCLUDE= 2;
private TextEdit[] fExcludes;
private TextEdit[] fIncludes;
protected LocalTextEditProcessor(IDocument document, TextEdit root, int flags) {
super(document, root, flags);
}
public void setIncludes(TextEdit[] includes) {
Assert.isNotNull(includes);
Assert.isTrue(fExcludes == null);
fIncludes= flatten(includes);
}
public void setExcludes(TextEdit[] excludes) {
Assert.isNotNull(excludes);
Assert.isTrue(fIncludes == null);
fExcludes= flatten(excludes);
}
protected boolean considerEdit(TextEdit edit) {
if (fExcludes != null) {
for (int i= 0; i < fExcludes.length; i++) {
if (edit.equals(fExcludes[i]))
return false;
}
return true;
}
if (fIncludes != null) {
for (int i= 0; i < fIncludes.length; i++) {
if (edit.equals(fIncludes[i]))
return true;
}
return false;
}
return true;
}
private TextEdit[] flatten(TextEdit[] edits) {
List result= new ArrayList(5);
for (int i= 0; i < edits.length; i++) {
flatten(result, edits[i]);
}
return (TextEdit[])result.toArray(new TextEdit[result.size()]);
}
private void flatten(List result, TextEdit edit) {
result.add(edit);
TextEdit[] children= edit.getChildren();
for (int i= 0; i < children.length; i++) {
flatten(result, children[i]);
}
}
}
/**
* Value objects encapsulating a document with an associated region.
*/
static final class PreviewAndRegion {
public IDocument document;
public IRegion region;
public PreviewAndRegion(IDocument d, IRegion r) {
document= d;
region= r;
}
}
/**
* A special object denoting all edits managed by the change. This even
* includes those edits not managed by a {@link TextEditBasedChangeGroup}.
*/
static final TextEditBasedChangeGroup[] ALL_EDITS= new TextEditBasedChangeGroup[0];
/** The list of change groups */
private List fChangeGroups;
private GroupCategorySet fCombiedGroupCategories;
/** The name of the change */
private String fName;
/** The text type */
private String fTextType;
/** Should the positions of edits be tracked during change generation? */
private boolean fTrackEdits;
/**
* Creates a new abstract text edit change with the specified name. The name is a
* human-readable value that is displayed to users. The name does not
* need to be unique, but it must not be <code>null</code>.
* <p>
* The text type of this text edit change is set to <code>txt</code>.
* </p>
*
* @param name the name of the text edit change
*
* @see #setTextType(String)
*/
protected TextEditBasedChange(String name) {
Assert.isNotNull(name, "Name must not be null"); //$NON-NLS-1$
fChangeGroups= new ArrayList(5);
fName= name;
fTextType= "txt"; //$NON-NLS-1$
}
/**
* Adds a {@link TextEditBasedChangeGroup text edit change group}.
* The edits managed by the given text edit change group must be part of
* the change's root edit.
*
* @param group the text edit change group to add
*/
public void addChangeGroup(TextEditBasedChangeGroup group) {
Assert.isTrue(group != null);
fChangeGroups.add(group);
if (fCombiedGroupCategories != null) {
fCombiedGroupCategories= GroupCategorySet.union(fCombiedGroupCategories, group.getGroupCategorySet());
}
}
/**
* Adds a {@link TextEditGroup text edit group}. This method is a convenience
* method for calling <code>change.addChangeGroup(new
* TextEditBasedChangeGroup(change, group));</code>.
*
* @param group the text edit group to add
*/
public void addTextEditGroup(TextEditGroup group) {
addChangeGroup(new TextEditBasedChangeGroup(this, group));
}
/**
* Returns <code>true</code> if the change has one of the given group
* categories. Otherwise <code>false</code> is returned.
*
* @param groupCategories the group categories to check
*
* @return whether the change has one of the given group
* categories
*
* @since 3.2
*/
public boolean hasOneGroupCategory(List groupCategories) {
if (fCombiedGroupCategories == null) {
fCombiedGroupCategories= GroupCategorySet.NONE;
for (Iterator iter= fChangeGroups.iterator(); iter.hasNext();) {
TextEditBasedChangeGroup group= (TextEditBasedChangeGroup)iter.next();
fCombiedGroupCategories= GroupCategorySet.union(fCombiedGroupCategories, group.getGroupCategorySet());
}
}
return fCombiedGroupCategories.containsOneCategory(groupCategories);
}
/**
* Returns the {@link TextEditBasedChangeGroup text edit change groups} managed by this
* buffer change.
*
* @return the text edit change groups
*/
public final TextEditBasedChangeGroup[] getChangeGroups() {
return (TextEditBasedChangeGroup[])fChangeGroups.toArray(new TextEditBasedChangeGroup[fChangeGroups.size()]);
}
String getContent(IDocument document, IRegion region, boolean expandRegionToFullLine, int surroundingLines) throws CoreException {
try {
if (expandRegionToFullLine) {
int startLine= Math.max(document.getLineOfOffset(region.getOffset()) - surroundingLines, 0);
int endLine;
if (region.getLength() == 0) {
endLine= Math.min(
document.getLineOfOffset(region.getOffset()) + surroundingLines,
document.getNumberOfLines() - 1);
} else {
endLine= Math.min(
document.getLineOfOffset(region.getOffset() + region.getLength() - 1) + surroundingLines,
document.getNumberOfLines() - 1);
}
int offset= document.getLineInformation(startLine).getOffset();
IRegion endLineRegion= document.getLineInformation(endLine);
int length = endLineRegion.getOffset() + endLineRegion.getLength() - offset;
return document.get(offset, length);
} else {
return document.get(region.getOffset(), region.getLength());
}
} catch (BadLocationException e) {
throw Changes.asCoreException(e);
}
}
/**
* Returns the current content of the document this text
* change is associated with.
*
* @param pm a progress monitor to report progress or <code>null</code>
* if no progress reporting is desired
* @return the current content of the text edit change
*
* @exception CoreException if the content can't be accessed
*/
public abstract String getCurrentContent(IProgressMonitor pm) throws CoreException;
/**
* Returns the current content of the text edit change clipped to a specific
* region. The region is determined as follows:
* <ul>
* <li>if <code>expandRegionToFullLine</code> is <code>false</code>
* then the parameter <code>region</code> determines the clipping.
* </li>
* <li>if <code>expandRegionToFullLine</code> is <code>true</code>
* then the region determined by the parameter <code>region</code>
* is extended to cover full lines.
* </li>
* <li>if <code>surroundingLines</code> &gt; 0 then the given number
* of surrounding lines is added. The value of <code>surroundingLines
* </code> is only considered if <code>expandRegionToFullLine</code>
* is <code>true</code>
* </li>
* </ul>
*
* @param region the starting region for the text to be returned
* @param expandRegionToFullLine if <code>true</code> is passed the region
* is extended to cover full lines
* @param surroundingLines the number of surrounding lines to be added to
* the clipping region. Is only considered if <code>expandRegionToFullLine
* </code> is <code>true</code>
* @param pm a progress monitor to report progress or <code>null</code>
* if no progress reporting is desired
*
* @return the current content of the text edit change clipped to a region
* determined by the given parameters.
*
* @throws CoreException if an exception occurs while accessing the current content
*/
public abstract String getCurrentContent(IRegion region, boolean expandRegionToFullLine, int surroundingLines, IProgressMonitor pm) throws CoreException;
/**
* Returns whether preview edits are remembered for further region
* tracking or not.
*
* @return <code>true</code> if executed text edits are remembered
* during preview generation; otherwise <code>false</code>
*/
public boolean getKeepPreviewEdits() {
return fTrackEdits;
}
/**
* {@inheritDoc}
*/
public String getName() {
return fName;
}
/**
* Returns a preview of the text edit change clipped to a specific region.
* The preview is created by applying the text edits managed by the
* given array of {@link TextEditBasedChangeGroup text edit change groups}.
* The region is determined as follows:
* <ul>
* <li>if <code>expandRegionToFullLine</code> is <code>false</code>
* then the parameter <code>region</code> determines the clipping.
* </li>
* <li>if <code>expandRegionToFullLine</code> is <code>true</code>
* then the region determined by the parameter <code>region</code>
* is extended to cover full lines.
* </li>
* <li>if <code>surroundingLines</code> &gt; 0 then the given number
* of surrounding lines is added. The value of <code>surroundingLines
* </code> is only considered if <code>expandRegionToFullLine</code>
* is <code>true</code>
* </li>
* </ul>
*
* @param changeGroups a set of change groups for which a preview is to be
* generated
* @param region the starting region for the clipping
* @param expandRegionToFullLine if <code>true</code> is passed the region
* is extended to cover full lines
* @param surroundingLines the number of surrounding lines to be added to
* the clipping region. Is only considered if <code>expandRegionToFullLine
* </code> is <code>true</code>
* @param pm a progress monitor to report progress or <code>null</code>
* if no progress reporting is desired
*
* @return the current content of the text change clipped to a region
* determined by the given parameters.
*
* @throws CoreException if an exception occurs while generating the preview
*
* @see #getCurrentContent(IRegion, boolean, int, IProgressMonitor)
*/
public abstract String getPreviewContent(TextEditBasedChangeGroup[] changeGroups, IRegion region, boolean expandRegionToFullLine, int surroundingLines, IProgressMonitor pm) throws CoreException;
/**
* Returns the preview content as a string.
*
* @param pm a progress monitor to report progress or <code>null</code>
* if no progress reporting is desired
* @return the preview
*
* @throws CoreException if the preview can't be created
*/
public abstract String getPreviewContent(IProgressMonitor pm) throws CoreException;
/**
* Returns the text edit change's text type.
*
* @return the text edit change's text type
*/
public String getTextType() {
return fTextType;
}
TextEdit[] mapEdits(TextEdit[] edits, TextEditCopier copier) {
if (edits == null)
return null;
final List result= new ArrayList(edits.length);
for (int i= 0; i < edits.length; i++) {
TextEdit edit= copier.getCopy(edits[i]);
if (edit != null)
result.add(edit);
}
return (TextEdit[]) result.toArray(new TextEdit[result.size()]);
}
/**
* {@inheritDoc}
*/
public void setEnabled(boolean enabled) {
super.setEnabled(enabled);
for (Iterator iter= fChangeGroups.iterator(); iter.hasNext();) {
TextEditBasedChangeGroup element= (TextEditBasedChangeGroup) iter.next();
element.setEnabled(enabled);
}
}
/**
* Controls whether the text edit change should keep executed edits during
* preview generation.
*
* @param keep if <code>true</code> executed preview edits are kept
*/
public void setKeepPreviewEdits(boolean keep) {
fTrackEdits= keep;
}
/**
* Sets the text type. The text type is used to determine the content
* merge viewer used to present the difference between the original
* and the preview content in the user interface. Content merge viewers
* are defined via the extension point <code>org.eclipse.compare.contentMergeViewers</code>.
* <p>
* The default text type is <code>txt</code>.
* </p>
*
* @param type the text type. If <code>null</code> is passed the text type is
* reseted to the default text type <code>txt</code>.
*/
public void setTextType(String type) {
if (type == null)
type= "txt"; //$NON-NLS-1$
fTextType= type;
}
}