KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > eclipse > ltk > core > refactoring > TextChange


1 /*******************************************************************************
2  * Copyright (c) 2000, 2006 IBM Corporation and others.
3  * All rights reserved. This program and the accompanying materials
4  * are made available under the terms of the Eclipse Public License v1.0
5  * which accompanies this distribution, and is available at
6  * http://www.eclipse.org/legal/epl-v10.html
7  *
8  * Contributors:
9  * IBM Corporation - initial API and implementation
10  *******************************************************************************/

11 package org.eclipse.ltk.core.refactoring;
12
13 import java.util.ArrayList JavaDoc;
14 import java.util.Arrays JavaDoc;
15 import java.util.List JavaDoc;
16
17 import org.eclipse.text.edits.MalformedTreeException;
18 import org.eclipse.text.edits.MultiTextEdit;
19 import org.eclipse.text.edits.TextEdit;
20 import org.eclipse.text.edits.TextEditCopier;
21 import org.eclipse.text.edits.TextEditGroup;
22 import org.eclipse.text.edits.TextEditProcessor;
23 import org.eclipse.text.edits.UndoEdit;
24
25 import org.eclipse.core.runtime.Assert;
26 import org.eclipse.core.runtime.CoreException;
27 import org.eclipse.core.runtime.IProgressMonitor;
28 import org.eclipse.core.runtime.NullProgressMonitor;
29 import org.eclipse.core.runtime.SubProgressMonitor;
30
31 import org.eclipse.jface.text.BadLocationException;
32 import org.eclipse.jface.text.Document;
33 import org.eclipse.jface.text.DocumentRewriteSession;
34 import org.eclipse.jface.text.DocumentRewriteSessionType;
35 import org.eclipse.jface.text.IDocument;
36 import org.eclipse.jface.text.IDocumentExtension4;
37 import org.eclipse.jface.text.IRegion;
38 import org.eclipse.jface.text.Region;
39
40 import org.eclipse.ltk.internal.core.refactoring.Changes;
41
42 /**
43  * A text change is a special change object that applies a {@link TextEdit
44  * text edit tree} to a document. The text change manages the text edit tree.
45  * Access to the document must be provided by concrete subclasses via the method
46  * {@link #acquireDocument(IProgressMonitor) aquireDocument},
47  * {@link #commit(IDocument document, IProgressMonitor pm) commitDocument}, and
48  * {@link #releaseDocument(IDocument, IProgressMonitor) releaseDocument}.
49  * <p>
50  * A text change offers the ability to access the original content of
51  * the document as well as creating a preview of the change. The edit
52  * tree gets copied when creating any king of preview. Therefore no region
53  * updating on the original edit tree takes place when requesting a preview
54  * (for more information on region updating see class {@link TextEdit TextEdit}.
55  * If region tracking is required for a preview it can be enabled via a call
56  * to the method {@link #setKeepPreviewEdits(boolean) setKeepPreviewEdits}.
57  * If enabled the text change keeps the copied edit tree executed for the
58  * preview allowing clients to map an original edit to an executed edit. The
59  * executed edit can then be used to determine its position in the preview.
60  * </p>
61  * <p>
62  * Note: this class is not intended to be subclassed outside the refactoring
63  * framework.
64  * </p>
65  *
66  * @since 3.0
67  */

68 public abstract class TextChange extends TextEditBasedChange {
69
70     private TextEdit fEdit;
71     private TextEditCopier fCopier;
72
73     /**
74      * Creates a new text change with the specified name. The name is a
75      * human-readable value that is displayed to users. The name does not
76      * need to be unique, but it must not be <code>null</code>.
77      * <p>
78      * The text type of this text change is set to <code>txt</code>.
79      * </p>
80      *
81      * @param name the name of the text change
82      *
83      * @see #setTextType(String)
84      */

85     protected TextChange(String JavaDoc name) {
86         super(name);
87     }
88
89     //---- Edit management -----------------------------------------------
90

91     /**
92      * Sets the root text edit that should be applied to the
93      * document represented by this text change.
94      *
95      * @param edit the root text edit. The root text edit
96      * can only be set once.
97      */

98     public void setEdit(TextEdit edit) {
99         Assert.isTrue(fEdit == null, "Root edit can only be set once"); //$NON-NLS-1$
100
Assert.isTrue(edit != null);
101         fEdit= edit;
102     }
103     
104     /**
105      * Returns the root text edit.
106      *
107      * @return the root text edit or <code>null</code> if no root edit has been
108      * set
109      */

110     public TextEdit getEdit() {
111         return fEdit;
112     }
113     
114     /**
115      * Adds a {@link TextEditGroup text edit group}. This method is a convenient
116      * method for calling <code>change.addTextEditChangeGroup(new
117      * TextEditChangeGroup(change, group));</code>.
118      *
119      * @param group the text edit group to add
120      */

121     public void addTextEditGroup(TextEditGroup group) {
122         addTextEditChangeGroup(new TextEditChangeGroup(this, group));
123     }
124     
125     /**
126      * Adds a {@link TextEditChangeGroup text edit change group}. Calling the methods
127      * requires that a root edit has been set via the method {@link #setEdit(TextEdit)
128      * setEdit}. The edits managed by the given text edit change group must be part of
129      * the change's root edit.
130      *
131      * @param group the text edit change group to add
132      */

133     public void addTextEditChangeGroup(TextEditChangeGroup group) {
134         Assert.isTrue(fEdit != null, "Can only add a description if a root edit exists"); //$NON-NLS-1$
135
addChangeGroup(group);
136     }
137     
138     /**
139      * Returns the {@link TextEditChangeGroup text edit change groups} managed by this
140      * text change.
141      *
142      * @return the text edit change groups
143      */

144     public TextEditChangeGroup[] getTextEditChangeGroups() {
145         final TextEditBasedChangeGroup[] groups= getChangeGroups();
146         final TextEditChangeGroup[] result= new TextEditChangeGroup[groups.length];
147         System.arraycopy(groups, 0, result, 0, groups.length);
148         return result;
149     }
150     
151     /**
152      * Adds the given edit to the edit tree. The edit is added as a top
153      * level edit.
154      *
155      * @param edit the text edit to add
156      *
157      * @throws MalformedTreeException if the edit can't be added. Reason
158      * is that is overlaps with an already existing edit
159      *
160      * @since 3.1
161      */

162     public void addEdit(TextEdit edit) throws MalformedTreeException {
163         Assert.isTrue(fEdit != null, "root must exist to add an edit"); //$NON-NLS-1$
164
fEdit.addChild(edit);
165     }
166     
167     //---- Document management -----------------------------------------------
168

169     /**
170      * Acquires a reference to the document to be changed by this text
171      * change. A document acquired by this call <em>MUST</em> be released
172      * via a call to {@link #releaseDocument(IDocument, IProgressMonitor)}.
173      * <p>
174      * The method <code>releaseDocument</code> must be call as many times as
175      * <code>aquireDocument</code> has been called.
176      * </p>
177      *
178      * @param pm a progress monitor
179      *
180      * @return a reference to the document to be changed
181      *
182      * @throws CoreException if the document can't be acquired
183      */

184     protected abstract IDocument acquireDocument(IProgressMonitor pm) throws CoreException;
185     
186     /**
187      * Commits the document acquired via a call to {@link #acquireDocument(IProgressMonitor)
188      * aquireDocument}. It is up to the implementors of this method to decide what committing
189      * a document means. Typically, the content of the document is written back to the file
190      * system.
191      *
192      * @param document the document to commit
193      * @param pm a progress monitor
194      *
195      * @throws CoreException if the document can't be committed
196      */

197     protected abstract void commit(IDocument document, IProgressMonitor pm) throws CoreException;
198     
199     /**
200      * Releases the document acquired via a call to {@link #acquireDocument(IProgressMonitor)
201      * aquireDocument}.
202      *
203      * @param document the document to release
204      * @param pm a progress monitor
205      *
206      * @throws CoreException if the document can't be released
207      */

208     protected abstract void releaseDocument(IDocument document, IProgressMonitor pm) throws CoreException;
209     
210     /**
211      * Hook to create an undo change for the given undo edit. This hook
212      * gets called while performing the change to construct the corresponding
213      * undo change object.
214      *
215      * @param edit the {@link UndoEdit} to create an undo change for
216      *
217      * @return the undo change or <code>null</code> if no undo change can
218      * be created. Returning <code>null</code> results in the fact that
219      * the whole change tree can't be undone. So returning <code>null</code>
220      * is only recommended if an exception occurred during creating the
221      * undo change.
222      */

223     protected abstract Change createUndoChange(UndoEdit edit);
224     
225     /**
226      * {@inheritDoc}
227      */

228     public Change perform(IProgressMonitor pm) throws CoreException {
229         pm.beginTask("", 3); //$NON-NLS-1$
230
IDocument document= null;
231         DocumentRewriteSession session= null;
232         
233         try {
234             document= acquireDocument(new SubProgressMonitor(pm, 1));
235             if (document instanceof IDocumentExtension4) {
236                 session= ((IDocumentExtension4)document).startRewriteSession(
237                     DocumentRewriteSessionType.UNRESTRICTED);
238             }
239             TextEditProcessor processor= createTextEditProcessor(document, TextEdit.CREATE_UNDO, false);
240             UndoEdit undo= processor.performEdits();
241             commit(document, new SubProgressMonitor(pm, 1));
242             return createUndoChange(undo);
243         } catch (BadLocationException e) {
244             throw Changes.asCoreException(e);
245         } finally {
246             if (document != null) {
247                 try {
248                     if (session != null) {
249                         ((IDocumentExtension4)document).stopRewriteSession(session);
250                     }
251                 } finally {
252                     releaseDocument(document, new SubProgressMonitor(pm, 1));
253                 }
254             }
255             pm.done();
256         }
257     }
258     
259     //---- Method to access the current content of the text change ---------
260

261     /**
262      * Returns the document this text change is associated to. The
263      * document returned is computed at the point in time when this
264      * method is called. So calling this method multiple times may
265      * return different document instances.
266      * <p>
267      * The returned document must not be modified by the client. Doing
268      * so will result in an unexpected behavior when the change is
269      * performed.
270      * </p>
271      *
272      * @param pm a progress monitor to report progress or <code>null</code>
273      * if no progress reporting is desired
274      * @return the document this change is working on
275      *
276      * @throws CoreException if the document can't be acquired
277      */

278     public IDocument getCurrentDocument(IProgressMonitor pm) throws CoreException {
279         if (pm == null)
280             pm= new NullProgressMonitor();
281         IDocument result= null;
282         pm.beginTask("", 2); //$NON-NLS-1$
283
try{
284             result= acquireDocument(new SubProgressMonitor(pm, 1));
285         } finally {
286             if (result != null)
287                 releaseDocument(result, new SubProgressMonitor(pm, 1));
288         }
289         pm.done();
290         return result;
291     }
292     
293     /**
294      * {@inheritDoc}
295      */

296     public String JavaDoc getCurrentContent(IProgressMonitor pm) throws CoreException {
297         return getCurrentDocument(pm).get();
298     }
299     
300     /**
301      * {@inheritDoc}
302      */

303     public String JavaDoc getCurrentContent(IRegion region, boolean expandRegionToFullLine, int surroundingLines, IProgressMonitor pm) throws CoreException {
304         Assert.isNotNull(region);
305         Assert.isTrue(surroundingLines >= 0);
306         IDocument document= getCurrentDocument(pm);
307         Assert.isTrue(document.getLength() >= region.getOffset() + region.getLength());
308         return getContent(document, region, expandRegionToFullLine, surroundingLines);
309     }
310
311     //---- Method to access the preview content of the text change ---------
312

313     /**
314      * Returns the edit that got executed during preview generation
315      * instead of the given original. The method requires that <code>
316      * setKeepPreviewEdits</code> is set to <code>true</code> and that
317      * a preview has been requested via one of the <code>getPreview*
318      * </code> methods.
319      * <p>
320      * The method returns <code>null</code> if the original isn't managed
321      * by this text change.
322      * </p>
323      *
324      * @param original the original edit managed by this text change
325      *
326      * @return the edit executed during preview generation
327      */

328     public TextEdit getPreviewEdit(TextEdit original) {
329         Assert.isTrue(getKeepPreviewEdits() && fCopier != null && original != null);
330         return fCopier.getCopy(original);
331     }
332     
333     /**
334      * Returns the edits that were executed during preview generation
335      * instead of the given array of original edits. The method requires
336      * that <code>setKeepPreviewEdits</code> is set to <code>true</code>
337      * and that a preview has been requested via one of the <code>
338      * getPreview*</code> methods.
339      * <p>
340      * The method returns an empty array if none of the original edits
341      * is managed by this text change.
342      * </p>
343      *
344      * @param originals an array of original edits managed by this text
345      * change
346      *
347      * @return an array of edits containing the corresponding edits
348      * executed during preview generation
349      */

350     public TextEdit[] getPreviewEdits(TextEdit[] originals) {
351         Assert.isTrue(getKeepPreviewEdits() && fCopier != null && originals != null);
352         if (originals.length == 0)
353             return new TextEdit[0];
354         List JavaDoc result= new ArrayList JavaDoc(originals.length);
355         for (int i= 0; i < originals.length; i++) {
356             TextEdit copy= fCopier.getCopy(originals[i]);
357             if (copy != null)
358                 result.add(copy);
359         }
360         return (TextEdit[]) result.toArray(new TextEdit[result.size()]);
361     }
362     
363     /**
364      * Returns a document containing a preview of the text change. The
365      * preview is computed by executing the all managed text edits. The
366      * method considers the active state of the added {@link TextEditChangeGroup
367      * text edit change groups}.
368      *
369      * @param pm a progress monitor to report progress or <code>null</code>
370      * if no progress reporting is desired
371      * @return a document containing the preview of the text change
372      *
373      * @throws CoreException if the preview can't be created
374      */

375     public IDocument getPreviewDocument(IProgressMonitor pm) throws CoreException {
376         PreviewAndRegion result= getPreviewDocument(ALL_EDITS, pm);
377         return result.document;
378     }
379     
380     /**
381      * {@inheritDoc}
382      */

383     public String JavaDoc getPreviewContent(IProgressMonitor pm) throws CoreException {
384         return getPreviewDocument(pm).get();
385     }
386
387     /**
388      * Returns a preview of the text change clipped to a specific region.
389      * The preview is created by applying the text edits managed by the
390      * given array of {@link TextEditChangeGroup text edit change groups}.
391      * The region is determined as follows:
392      * <ul>
393      * <li>if <code>expandRegionToFullLine</code> is <code>false</code>
394      * then the parameter <code>region</code> determines the clipping.
395      * </li>
396      * <li>if <code>expandRegionToFullLine</code> is <code>true</code>
397      * then the region determined by the parameter <code>region</code>
398      * is extended to cover full lines.
399      * </li>
400      * <li>if <code>surroundingLines</code> &gt; 0 then the given number
401      * of surrounding lines is added. The value of <code>surroundingLines
402      * </code> is only considered if <code>expandRegionToFullLine</code>
403      * is <code>true</code>
404      * </li>
405      * </ul>
406      *
407      * @param changeGroups a set of change groups for which a preview is to be
408      * generated
409      * @param region the starting region for the clipping
410      * @param expandRegionToFullLine if <code>true</code> is passed the region
411      * is extended to cover full lines
412      * @param surroundingLines the number of surrounding lines to be added to
413      * the clipping region. Is only considered if <code>expandRegionToFullLine
414      * </code> is <code>true</code>
415      * @param pm a progress monitor to report progress or <code>null</code>
416      * if no progress reporting is desired
417      *
418      * @return the current content of the text change clipped to a region
419      * determined by the given parameters.
420      *
421      * @throws CoreException if an exception occurs while generating the preview
422      *
423      * @see #getCurrentContent(IRegion, boolean, int, IProgressMonitor)
424      */

425     public String JavaDoc getPreviewContent(TextEditChangeGroup[] changeGroups, IRegion region, boolean expandRegionToFullLine, int surroundingLines, IProgressMonitor pm) throws CoreException {
426         return getPreviewContent((TextEditBasedChangeGroup[])changeGroups, region, expandRegionToFullLine, surroundingLines, pm);
427     }
428
429     /**
430      * Returns a preview of the text change clipped to a specific region.
431      * The preview is created by applying the text edits managed by the
432      * given array of {@link TextEditChangeGroup text edit change groups}.
433      * The region is determined as follows:
434      * <ul>
435      * <li>if <code>expandRegionToFullLine</code> is <code>false</code>
436      * then the parameter <code>region</code> determines the clipping.
437      * </li>
438      * <li>if <code>expandRegionToFullLine</code> is <code>true</code>
439      * then the region determined by the parameter <code>region</code>
440      * is extended to cover full lines.
441      * </li>
442      * <li>if <code>surroundingLines</code> &gt; 0 then the given number
443      * of surrounding lines is added. The value of <code>surroundingLines
444      * </code> is only considered if <code>expandRegionToFullLine</code>
445      * is <code>true</code>
446      * </li>
447      * </ul>
448      *
449      * @param changeGroups a set of change groups for which a preview is to be
450      * generated
451      * @param region the starting region for the clipping
452      * @param expandRegionToFullLine if <code>true</code> is passed the region
453      * is extended to cover full lines
454      * @param surroundingLines the number of surrounding lines to be added to
455      * the clipping region. Is only considered if <code>expandRegionToFullLine
456      * </code> is <code>true</code>
457      * @param pm a progress monitor to report progress or <code>null</code>
458      * if no progress reporting is desired
459      *
460      * @return the current content of the text change clipped to a region
461      * determined by the given parameters.
462      *
463      * @throws CoreException if an exception occurs while generating the preview
464      *
465      * @see #getCurrentContent(IRegion, boolean, int, IProgressMonitor)
466      *
467      * @since 3.2
468      */

469     public String JavaDoc getPreviewContent(TextEditBasedChangeGroup[] changeGroups, IRegion region, boolean expandRegionToFullLine, int surroundingLines, IProgressMonitor pm) throws CoreException {
470         IRegion currentRegion= getRegion(changeGroups);
471         Assert.isTrue(region.getOffset() <= currentRegion.getOffset() &&
472             currentRegion.getOffset() + currentRegion.getLength() <= region.getOffset() + region.getLength());
473         // Make sure that all edits in the change groups are rooted under the edit the text change stand for.
474
TextEdit root= getEdit();
475         Assert.isNotNull(root, "No root edit"); //$NON-NLS-1$
476
for (int c= 0; c < changeGroups.length; c++) {
477             TextEditBasedChangeGroup group= changeGroups[c];
478             TextEdit[] edits= group.getTextEdits();
479             for (int e= 0; e < edits.length; e++) {
480                 
481                 // TODO: enable once following bug is fixed
482
// https://bugs.eclipse.org/bugs/show_bug.cgi?id=130909
483
// Assert.isTrue(root == edits[e].getRoot(), "Wrong root edit"); //$NON-NLS-1$
484
}
485         }
486         PreviewAndRegion result= getPreviewDocument(changeGroups, pm);
487         int delta;
488         if (result.region == null) { // all edits were delete edits so no new region
489
delta= -currentRegion.getLength();
490         } else {
491             delta= result.region.getLength() - currentRegion.getLength();
492         }
493         return getContent(result.document, new Region(region.getOffset(), region.getLength() + delta), expandRegionToFullLine, surroundingLines);
494         
495     }
496
497     //---- private helper methods --------------------------------------------------
498

499     private PreviewAndRegion getPreviewDocument(TextEditBasedChangeGroup[] changes, IProgressMonitor pm) throws CoreException {
500         IDocument document= new Document(getCurrentDocument(pm).get());
501         boolean trackChanges= getKeepPreviewEdits();
502         setKeepPreviewEdits(true);
503         TextEditProcessor processor= changes == ALL_EDITS
504             ? createTextEditProcessor(document, TextEdit.NONE, true)
505             : createTextEditProcessor(document, TextEdit.NONE, changes);
506         try {
507             processor.performEdits();
508             return new PreviewAndRegion(document, getNewRegion(changes));
509         } catch (BadLocationException e) {
510             throw Changes.asCoreException(e);
511         } finally {
512             setKeepPreviewEdits(trackChanges);
513         }
514     }
515     
516     private TextEditProcessor createTextEditProcessor(IDocument document, int flags, boolean preview) {
517         if (fEdit == null)
518             return new TextEditProcessor(document, new MultiTextEdit(0,0), flags);
519         List JavaDoc excludes= new ArrayList JavaDoc(0);
520         TextEditBasedChangeGroup[] groups= getChangeGroups();
521         for (int index= 0; index < groups.length; index++) {
522             TextEditBasedChangeGroup edit= groups[index];
523             if (!edit.isEnabled()) {
524                 excludes.addAll(Arrays.asList(edit.getTextEditGroup().getTextEdits()));
525             }
526         }
527         if (preview) {
528             fCopier= new TextEditCopier(fEdit);
529             TextEdit copiedEdit= fCopier.perform();
530             boolean keep= getKeepPreviewEdits();
531             if (keep)
532                 flags= flags | TextEdit.UPDATE_REGIONS;
533             LocalTextEditProcessor result= new LocalTextEditProcessor(document, copiedEdit, flags);
534             result.setExcludes(mapEdits(
535                 (TextEdit[])excludes.toArray(new TextEdit[excludes.size()]),
536                 fCopier));
537             if (!keep)
538                 fCopier= null;
539             return result;
540         } else {
541             LocalTextEditProcessor result= new LocalTextEditProcessor(document, fEdit, flags | TextEdit.UPDATE_REGIONS);
542             result.setExcludes((TextEdit[])excludes.toArray(new TextEdit[excludes.size()]));
543             return result;
544         }
545     }
546     
547     private TextEditProcessor createTextEditProcessor(IDocument document, int flags, TextEditBasedChangeGroup[] changes) {
548         if (fEdit == null)
549             return new TextEditProcessor(document, new MultiTextEdit(0,0), flags);
550         List JavaDoc includes= new ArrayList JavaDoc(0);
551         for (int c= 0; c < changes.length; c++) {
552             TextEditBasedChangeGroup change= changes[c];
553             Assert.isTrue(change.getTextEditChange() == this);
554             if (change.isEnabled()) {
555                 includes.addAll(Arrays.asList(change.getTextEditGroup().getTextEdits()));
556             }
557         }
558         fCopier= new TextEditCopier(fEdit);
559         TextEdit copiedEdit= fCopier.perform();
560         boolean keep= getKeepPreviewEdits();
561         if (keep)
562             flags= flags | TextEdit.UPDATE_REGIONS;
563         LocalTextEditProcessor result= new LocalTextEditProcessor(document, copiedEdit, flags);
564         result.setIncludes(mapEdits(
565             (TextEdit[])includes.toArray(new TextEdit[includes.size()]),
566             fCopier));
567         if (!keep)
568             fCopier= null;
569         return result;
570     }
571     
572     private IRegion getRegion(TextEditBasedChangeGroup[] changes) {
573         if (changes == ALL_EDITS) {
574             if (fEdit == null)
575                 return null;
576             return fEdit.getRegion();
577         } else {
578             List JavaDoc edits= new ArrayList JavaDoc();
579             for (int i= 0; i < changes.length; i++) {
580                 edits.addAll(Arrays.asList(changes[i].getTextEditGroup().getTextEdits()));
581             }
582             if (edits.size() == 0)
583                 return null;
584             return TextEdit.getCoverage((TextEdit[]) edits.toArray(new TextEdit[edits.size()]));
585         }
586     }
587     
588     private IRegion getNewRegion(TextEditBasedChangeGroup[] changes) {
589         if (changes == ALL_EDITS) {
590             if (fEdit == null)
591                 return null;
592             return fCopier.getCopy(fEdit).getRegion();
593         } else {
594             List JavaDoc result= new ArrayList JavaDoc();
595             for (int c= 0; c < changes.length; c++) {
596                 TextEdit[] edits= changes[c].getTextEditGroup().getTextEdits();
597                 for (int e= 0; e < edits.length; e++) {
598                     TextEdit copy= fCopier.getCopy(edits[e]);
599                     if (copy != null)
600                         result.add(copy);
601                 }
602             }
603             if (result.size() == 0)
604                 return null;
605             return TextEdit.getCoverage((TextEdit[]) result.toArray(new TextEdit[result.size()]));
606         }
607     }
608
609     /**
610      * {@inheritDoc}
611      */

612     public void setKeepPreviewEdits(boolean keep) {
613         super.setKeepPreviewEdits(keep);
614         
615         if (!keep)
616             fCopier= null;
617     }
618 }
619
Popular Tags