001: /*******************************************************************************
002: * Copyright (c) 2000, 2006 IBM Corporation and others.
003: * All rights reserved. This program and the accompanying materials
004: * are made available under the terms of the Eclipse Public License v1.0
005: * which accompanies this distribution, and is available at
006: * http://www.eclipse.org/legal/epl-v10.html
007: *
008: * Contributors:
009: * IBM Corporation - initial API and implementation
010: *******************************************************************************/package org.eclipse.text.edits;
011:
012: import java.util.ArrayList;
013: import java.util.Arrays;
014: import java.util.HashMap;
015: import java.util.Iterator;
016: import java.util.List;
017: import java.util.Map;
018:
019: import org.eclipse.core.runtime.Assert;
020:
021: import org.eclipse.jface.text.BadLocationException;
022: import org.eclipse.jface.text.IDocument;
023: import org.eclipse.jface.text.IRegion;
024: import org.eclipse.jface.text.Region;
025:
026: /**
027: * A move source edit denotes the source of a move operation. Move
028: * source edits are only valid inside an edit tree if they have a
029: * corresponding target edit. Furthermore the corresponding target
030: * edit can't be a direct or indirect child of the source edit.
031: * Violating one of two requirements will result in a <code>
032: * MalformedTreeException</code> when executing the edit tree.
033: * <p>
034: * A move source edit can manage an optional source modifier. A
035: * source modifier can provide a set of replace edits which will
036: * to applied to the source before it gets inserted at the target
037: * position.
038: *
039: * @see org.eclipse.text.edits.MoveTargetEdit
040: * @see org.eclipse.text.edits.CopySourceEdit
041: *
042: * @since 3.0
043: */
044: public final class MoveSourceEdit extends TextEdit {
045:
046: private MoveTargetEdit fTarget;
047: private ISourceModifier fModifier;
048:
049: private String fSourceContent;
050: private MultiTextEdit fSourceRoot;
051:
052: /**
053: * Constructs a new move source edit.
054: *
055: * @param offset the edit's offset
056: * @param length the edit's length
057: */
058: public MoveSourceEdit(int offset, int length) {
059: super (offset, length);
060: }
061:
062: /**
063: * Constructs a new copy source edit.
064: *
065: * @param offset the edit's offset
066: * @param length the edit's length
067: * @param target the edit's target
068: */
069: public MoveSourceEdit(int offset, int length, MoveTargetEdit target) {
070: this (offset, length);
071: setTargetEdit(target);
072: }
073:
074: /*
075: * Copy constructor
076: */
077: private MoveSourceEdit(MoveSourceEdit other) {
078: super (other);
079: if (other.fModifier != null)
080: fModifier = other.fModifier.copy();
081: }
082:
083: /**
084: * Returns the associated target edit or <code>null</code>
085: * if no target edit is associated yet.
086: *
087: * @return the target edit or <code>null</code>
088: */
089: public MoveTargetEdit getTargetEdit() {
090: return fTarget;
091: }
092:
093: /**
094: * Sets the target edit.
095: *
096: * @param edit the new target edit.
097: *
098: * @exception MalformedTreeException is thrown if the target edit
099: * is a direct or indirect child of the source edit
100: */
101: public void setTargetEdit(MoveTargetEdit edit) {
102: fTarget = edit;
103: fTarget.setSourceEdit(this );
104: }
105:
106: /**
107: * Returns the current source modifier or <code>null</code>
108: * if no source modifier is set.
109: *
110: * @return the source modifier
111: */
112: public ISourceModifier getSourceModifier() {
113: return fModifier;
114: }
115:
116: /**
117: * Sets the optional source modifier.
118: *
119: * @param modifier the source modifier or <code>null</code>
120: * if no source modification is need.
121: */
122: public void setSourceModifier(ISourceModifier modifier) {
123: fModifier = modifier;
124: }
125:
126: //---- API for MoveTargetEdit ---------------------------------------------
127:
128: String getContent() {
129: // The source content can be null if the edit wasn't executed
130: // due to an exclusion list of the text edit processor. Return
131: // the empty string which can be moved without any harm.
132: if (fSourceContent == null)
133: return ""; //$NON-NLS-1$
134: return fSourceContent;
135: }
136:
137: MultiTextEdit getSourceRoot() {
138: return fSourceRoot;
139: }
140:
141: void clearContent() {
142: fSourceContent = null;
143: fSourceRoot = null;
144: }
145:
146: //---- Copying -------------------------------------------------------------
147:
148: /*
149: * @see TextEdit#doCopy
150: */
151: protected TextEdit doCopy() {
152: return new MoveSourceEdit(this );
153: }
154:
155: /*
156: * @see TextEdit#postProcessCopy
157: */
158: protected void postProcessCopy(TextEditCopier copier) {
159: if (fTarget != null) {
160: MoveSourceEdit source = (MoveSourceEdit) copier
161: .getCopy(this );
162: MoveTargetEdit target = (MoveTargetEdit) copier
163: .getCopy(fTarget);
164: if (source != null && target != null)
165: source.setTargetEdit(target);
166: }
167: }
168:
169: //---- Visitor -------------------------------------------------------------
170:
171: /*
172: * @see TextEdit#accept0
173: */
174: protected void accept0(TextEditVisitor visitor) {
175: boolean visitChildren = visitor.visit(this );
176: if (visitChildren) {
177: acceptChildren(visitor);
178: }
179: }
180:
181: //---- consistency check ----------------------------------------------------------------
182:
183: int traverseConsistencyCheck(TextEditProcessor processor,
184: IDocument document, List sourceEdits) {
185: int result = super .traverseConsistencyCheck(processor,
186: document, sourceEdits);
187: // Since source computation takes place in a recursive fashion (see
188: // performSourceComputation) we only do something if we don't have a
189: // computed source already.
190: if (fSourceContent == null) {
191: if (sourceEdits.size() <= result) {
192: List list = new ArrayList();
193: list.add(this );
194: for (int i = sourceEdits.size(); i < result; i++)
195: sourceEdits.add(null);
196: sourceEdits.add(list);
197: } else {
198: List list = (List) sourceEdits.get(result);
199: if (list == null) {
200: list = new ArrayList();
201: sourceEdits.add(result, list);
202: }
203: list.add(this );
204: }
205: }
206: return result;
207: }
208:
209: void performConsistencyCheck(TextEditProcessor processor,
210: IDocument document) throws MalformedTreeException {
211: if (fTarget == null)
212: throw new MalformedTreeException(getParent(), this ,
213: TextEditMessages
214: .getString("MoveSourceEdit.no_target")); //$NON-NLS-1$
215: if (fTarget.getSourceEdit() != this )
216: throw new MalformedTreeException(
217: getParent(),
218: this ,
219: TextEditMessages
220: .getString("MoveSourceEdit.different_source")); //$NON-NLS-1$
221: /* Causes AST rewrite to fail
222: if (getRoot() != fTarget.getRoot())
223: throw new MalformedTreeException(getParent(), this, TextEditMessages.getString("MoveSourceEdit.different_tree")); //$NON-NLS-1$
224: */
225: }
226:
227: //---- source computation --------------------------------------------------------------
228:
229: void traverseSourceComputation(TextEditProcessor processor,
230: IDocument document) {
231: // always perform source computation independent of processor.considerEdit
232: // The target might need the source and the source is computed in a
233: // temporary buffer.
234: performSourceComputation(processor, document);
235: }
236:
237: void performSourceComputation(TextEditProcessor processor,
238: IDocument document) {
239: try {
240: TextEdit[] children = removeChildren();
241: if (children.length > 0) {
242: String content = document.get(getOffset(), getLength());
243: EditDocument subDocument = new EditDocument(content);
244: fSourceRoot = new MultiTextEdit(getOffset(),
245: getLength());
246: fSourceRoot.addChildren(children);
247: fSourceRoot.internalMoveTree(-getOffset());
248: int processingStyle = getStyle(processor);
249: TextEditProcessor subProcessor = TextEditProcessor
250: .createSourceComputationProcessor(subDocument,
251: fSourceRoot, processingStyle);
252: subProcessor.performEdits();
253: if (needsTransformation())
254: applyTransformation(subDocument, processingStyle);
255: fSourceContent = subDocument.get();
256: } else {
257: fSourceContent = document.get(getOffset(), getLength());
258: if (needsTransformation()) {
259: EditDocument subDocument = new EditDocument(
260: fSourceContent);
261: applyTransformation(subDocument,
262: getStyle(processor));
263: fSourceContent = subDocument.get();
264: }
265: }
266: } catch (BadLocationException cannotHappen) {
267: Assert.isTrue(false);
268: }
269: }
270:
271: private int getStyle(TextEditProcessor processor) {
272: // we never need undo while performing local edits.
273: if ((processor.getStyle() & TextEdit.UPDATE_REGIONS) != 0)
274: return TextEdit.UPDATE_REGIONS;
275: return TextEdit.NONE;
276: }
277:
278: //---- document updating ----------------------------------------------------------------
279:
280: int performDocumentUpdating(IDocument document)
281: throws BadLocationException {
282: document.replace(getOffset(), getLength(), ""); //$NON-NLS-1$
283: fDelta = -getLength();
284: return fDelta;
285: }
286:
287: //---- region updating --------------------------------------------------------------
288:
289: /*
290: * @see TextEdit#deleteChildren
291: */
292: boolean deleteChildren() {
293: return false;
294: }
295:
296: //---- content transformation --------------------------------------------------
297:
298: private boolean needsTransformation() {
299: return fModifier != null;
300: }
301:
302: private void applyTransformation(IDocument document, int style)
303: throws MalformedTreeException {
304: if ((style & TextEdit.UPDATE_REGIONS) != 0
305: && fSourceRoot != null) {
306: Map editMap = new HashMap();
307: TextEdit newEdit = createEdit(editMap);
308: List replaces = new ArrayList(Arrays.asList(fModifier
309: .getModifications(document.get())));
310: insertEdits(newEdit, replaces);
311: try {
312: newEdit.apply(document, style);
313: } catch (BadLocationException cannotHappen) {
314: Assert.isTrue(false);
315: }
316: restorePositions(editMap);
317: } else {
318: MultiTextEdit newEdit = new MultiTextEdit(0, document
319: .getLength());
320: TextEdit[] replaces = fModifier.getModifications(document
321: .get());
322: for (int i = 0; i < replaces.length; i++) {
323: newEdit.addChild(replaces[i]);
324: }
325: try {
326: newEdit.apply(document, style);
327: } catch (BadLocationException cannotHappen) {
328: Assert.isTrue(false);
329: }
330: }
331: }
332:
333: private TextEdit createEdit(Map editMap) {
334: MultiTextEdit result = new MultiTextEdit(0, fSourceRoot
335: .getLength());
336: editMap.put(result, fSourceRoot);
337: createEdit(fSourceRoot, result, editMap);
338: return result;
339: }
340:
341: private static void createEdit(TextEdit source, TextEdit target,
342: Map editMap) {
343: TextEdit[] children = source.getChildren();
344: for (int i = 0; i < children.length; i++) {
345: TextEdit child = children[i];
346: // a deleted child remains deleted even if the temporary buffer
347: // gets modified.
348: if (child.isDeleted())
349: continue;
350: RangeMarker marker = new RangeMarker(child.getOffset(),
351: child.getLength());
352: target.addChild(marker);
353: editMap.put(marker, child);
354: createEdit(child, marker, editMap);
355: }
356: }
357:
358: private void insertEdits(TextEdit root, List edits) {
359: while (edits.size() > 0) {
360: ReplaceEdit edit = (ReplaceEdit) edits.remove(0);
361: insert(root, edit, edits);
362: }
363: }
364:
365: private static void insert(TextEdit parent, ReplaceEdit edit,
366: List edits) {
367: if (!parent.hasChildren()) {
368: parent.addChild(edit);
369: return;
370: }
371: TextEdit[] children = parent.getChildren();
372: // First dive down to find the right parent.
373: int removed = 0;
374: for (int i = 0; i < children.length; i++) {
375: TextEdit child = children[i];
376: if (child.covers(edit)) {
377: insert(child, edit, edits);
378: return;
379: } else if (edit.covers(child)) {
380: parent.removeChild(i - removed++);
381: edit.addChild(child);
382: } else {
383: IRegion intersect = intersect(edit, child);
384: if (intersect != null) {
385: ReplaceEdit[] splits = splitEdit(edit, intersect);
386: insert(child, splits[0], edits);
387: edits.add(splits[1]);
388: return;
389: }
390: }
391: }
392: parent.addChild(edit);
393: }
394:
395: public static IRegion intersect(TextEdit op1, TextEdit op2) {
396: int offset1 = op1.getOffset();
397: int length1 = op1.getLength();
398: int end1 = offset1 + length1 - 1;
399: int offset2 = op2.getOffset();
400: if (end1 < offset2)
401: return null;
402: int length2 = op2.getLength();
403: int end2 = offset2 + length2 - 1;
404: if (end2 < offset1)
405: return null;
406:
407: int end = Math.min(end1, end2);
408: if (offset1 < offset2) {
409: return new Region(offset2, end - offset2 + 1);
410: }
411: return new Region(offset1, end - offset1 + 1);
412: }
413:
414: private static ReplaceEdit[] splitEdit(ReplaceEdit edit,
415: IRegion intersect) {
416: if (edit.getOffset() != intersect.getOffset())
417: return splitIntersectRight(edit, intersect);
418: return splitIntersectLeft(edit, intersect);
419: }
420:
421: private static ReplaceEdit[] splitIntersectRight(ReplaceEdit edit,
422: IRegion intersect) {
423: ReplaceEdit[] result = new ReplaceEdit[2];
424: // this is the actual delete. We use replace to only deal with one type
425: result[0] = new ReplaceEdit(intersect.getOffset(), intersect
426: .getLength(), ""); //$NON-NLS-1$
427: result[1] = new ReplaceEdit(edit.getOffset(), intersect
428: .getOffset()
429: - edit.getOffset(), edit.getText());
430: return result;
431: }
432:
433: private static ReplaceEdit[] splitIntersectLeft(ReplaceEdit edit,
434: IRegion intersect) {
435: ReplaceEdit[] result = new ReplaceEdit[2];
436: result[0] = new ReplaceEdit(intersect.getOffset(), intersect
437: .getLength(), edit.getText());
438: result[1] = new ReplaceEdit( // this is the actual delete. We use replace to only deal with one type
439: intersect.getOffset() + intersect.getLength(), edit
440: .getLength()
441: - intersect.getLength(), ""); //$NON-NLS-1$
442: return result;
443: }
444:
445: private static void restorePositions(Map editMap) {
446: for (Iterator iter = editMap.keySet().iterator(); iter
447: .hasNext();) {
448: TextEdit marker = (TextEdit) iter.next();
449: TextEdit edit = (TextEdit) editMap.get(marker);
450: if (marker.isDeleted()) {
451: edit.markAsDeleted();
452: } else {
453: edit
454: .adjustOffset(marker.getOffset()
455: - edit.getOffset());
456: edit
457: .adjustLength(marker.getLength()
458: - edit.getLength());
459: }
460: }
461: }
462: }
|