| /******************************************************************************* |
| * 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> > 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> > 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; |
| } |
| } |