0001: /*******************************************************************************
0002: * Copyright (c) 2000, 2006 IBM Corporation and others.
0003: * All rights reserved. This program and the accompanying materials
0004: * are made available under the terms of the Eclipse Public License v1.0
0005: * which accompanies this distribution, and is available at
0006: * http://www.eclipse.org/legal/epl-v10.html
0007: *
0008: * Contributors:
0009: * IBM Corporation - initial API and implementation
0010: *******************************************************************************/package org.eclipse.text.edits;
0011:
0012: import java.util.ArrayList;
0013: import java.util.Collections;
0014: import java.util.Comparator;
0015: import java.util.Iterator;
0016: import java.util.List;
0017:
0018: import org.eclipse.core.runtime.Assert;
0019:
0020: import org.eclipse.jface.text.BadLocationException;
0021: import org.eclipse.jface.text.IDocument;
0022: import org.eclipse.jface.text.IRegion;
0023: import org.eclipse.jface.text.Region;
0024:
0025: /**
0026: * A text edit describes an elementary text manipulation operation. Edits are
0027: * executed by applying them to a document (e.g. an instance of <code>IDocument
0028: * </code>).
0029: * <p>
0030: * Text edits form a tree. Clients can navigate the tree upwards, from child to
0031: * parent, as well as downwards. Newly created edits are un-parented. New edits
0032: * are added to the tree by calling one of the <code>add</code> methods on a parent
0033: * edit.
0034: * </p>
0035: * <p>
0036: * An edit tree is well formed in the following sense:
0037: * <ul>
0038: * <li>a parent edit covers all its children</li>
0039: * <li>children don't overlap</li>
0040: * <li>an edit with length 0 can't have any children</li>
0041: * </ul>
0042: * Any manipulation of the tree that violates one of the above requirements results
0043: * in a <code>MalformedTreeException</code>.
0044: * </p>
0045: * <p>
0046: * Insert edits are represented by an edit of length 0. If more than one insert
0047: * edit exists at the same offset then the edits are executed in the order in which
0048: * they have been added to a parent. The following code example:
0049: * <pre>
0050: * IDocument document= new Document("org");
0051: * MultiTextEdit edit= new MultiTextEdit();
0052: * edit.addChild(new InsertEdit(0, "www."));
0053: * edit.addChild(new InsertEdit(0, "eclipse."));
0054: * edit.apply(document);
0055: * </pre>
0056: * therefore results in string: "www.eclipse.org".
0057: * </p>
0058: * <p>
0059: * Text edits can be executed in a mode where the edit's region is updated to
0060: * reflect the edit's position in the changed document. Region updating is enabled
0061: * by default or can be requested by passing <code>UPDATE_REGIONS</code> to the
0062: * {@link #apply(IDocument, int) apply(IDocument, int)} method. In the above example
0063: * the region of the <code>InsertEdit(0, "eclipse.")</code> edit after executing
0064: * the root edit is <code>[3, 8]</code>. If the region of an edit got deleted during
0065: * change execution the region is set to <code>[-1, -1]</code> and the method {@link
0066: * #isDeleted() isDeleted} returns <code>true</code>.
0067: * </p>
0068: * This class isn't intended to be subclassed outside of the edit framework. Clients
0069: * are only allowed to subclass <code>MultiTextEdit</code>.
0070: *
0071: * @since 3.0
0072: */
0073: public abstract class TextEdit {
0074:
0075: /**
0076: * Flags indicating that neither <code>CREATE_UNDO</code> nor
0077: * <code>UPDATE_REGIONS</code> is set.
0078: */
0079: public static final int NONE = 0;
0080:
0081: /**
0082: * Flags indicating that applying an edit tree to a document
0083: * is supposed to create a corresponding undo edit. If not
0084: * specified <code>null</code> is returned from method <code>
0085: * apply</code>.
0086: */
0087: public static final int CREATE_UNDO = 1 << 0;
0088:
0089: /**
0090: * Flag indicating that the edit's region will be updated to
0091: * reflect its position in the changed document. If not specified
0092: * when applying an edit tree to a document the edit's region will
0093: * be arbitrary. It is even not guaranteed that the tree is still
0094: * well formed.
0095: */
0096: public static final int UPDATE_REGIONS = 1 << 1;
0097:
0098: private static class InsertionComparator implements Comparator {
0099: public int compare(Object o1, Object o2)
0100: throws MalformedTreeException {
0101: TextEdit edit1 = (TextEdit) o1;
0102: TextEdit edit2 = (TextEdit) o2;
0103:
0104: int offset1 = edit1.getOffset();
0105: int length1 = edit1.getLength();
0106:
0107: int offset2 = edit2.getOffset();
0108: int length2 = edit2.getLength();
0109:
0110: if (offset1 == offset2 && length1 == 0 && length2 == 0) {
0111: return 0;
0112: }
0113: if (offset1 + length1 <= offset2) {
0114: return -1;
0115: }
0116: if (offset2 + length2 <= offset1) {
0117: return 1;
0118: }
0119: throw new MalformedTreeException(null, edit1,
0120: TextEditMessages.getString("TextEdit.overlapping")); //$NON-NLS-1$
0121: }
0122: }
0123:
0124: private static final TextEdit[] EMPTY_ARRAY = new TextEdit[0];
0125: private static final InsertionComparator INSERTION_COMPARATOR = new InsertionComparator();
0126:
0127: private static final int DELETED_VALUE = -1;
0128:
0129: private int fOffset;
0130: private int fLength;
0131:
0132: private TextEdit fParent;
0133: private List fChildren;
0134:
0135: int fDelta;
0136:
0137: /**
0138: * Create a new text edit. Parent is initialized to <code>
0139: * null<code> and the edit doesn't have any children.
0140: *
0141: * @param offset the edit's offset
0142: * @param length the edit's length
0143: */
0144: protected TextEdit(int offset, int length) {
0145: Assert.isTrue(offset >= 0 && length >= 0);
0146: fOffset = offset;
0147: fLength = length;
0148: fDelta = 0;
0149: }
0150:
0151: /**
0152: * Copy constructor
0153: *
0154: * @param source the source to copy form
0155: */
0156: protected TextEdit(TextEdit source) {
0157: fOffset = source.fOffset;
0158: fLength = source.fLength;
0159: fDelta = 0;
0160: }
0161:
0162: //---- Region management -----------------------------------------------
0163:
0164: /**
0165: * Returns the range that this edit is manipulating. The returned
0166: * <code>IRegion</code> contains the edit's offset and length at
0167: * the point in time when this call is made. Any subsequent changes
0168: * to the edit's offset and length aren't reflected in the returned
0169: * region object.
0170: * <p>
0171: * Creating a region for a deleted edit will result in an assertion
0172: * failure.
0173: *
0174: * @return the manipulated region
0175: */
0176: public final IRegion getRegion() {
0177: return new Region(getOffset(), getLength());
0178: }
0179:
0180: /**
0181: * Returns the offset of the edit. An offset is a 0-based
0182: * character index. Returns <code>-1</code> if the edit
0183: * is marked as deleted.
0184: *
0185: * @return the offset of the edit
0186: */
0187: public int getOffset() {
0188: return fOffset;
0189: }
0190:
0191: /**
0192: * Returns the length of the edit. Returns <code>-1</code>
0193: * if the edit is marked as deleted.
0194: *
0195: * @return the length of the edit
0196: */
0197: public int getLength() {
0198: return fLength;
0199: }
0200:
0201: /**
0202: * Returns the inclusive end position of this edit. The inclusive end
0203: * position denotes the last character of the region manipulated by
0204: * this edit. The returned value is the result of the following
0205: * calculation:
0206: * <pre>
0207: * getOffset() + getLength() - 1;
0208: * <pre>
0209: *
0210: * @return the inclusive end position
0211: */
0212: public final int getInclusiveEnd() {
0213: return getOffset() + getLength() - 1;
0214: }
0215:
0216: /**
0217: * Returns the exclusive end position of this edit. The exclusive end
0218: * position denotes the next character of the region manipulated by
0219: * this edit. The returned value is the result of the following
0220: * calculation:
0221: * <pre>
0222: * getOffset() + getLength();
0223: * </pre>
0224: *
0225: * @return the exclusive end position
0226: */
0227: public final int getExclusiveEnd() {
0228: return getOffset() + getLength();
0229: }
0230:
0231: /**
0232: * Returns whether this edit has been deleted or not.
0233: *
0234: * @return <code>true</code> if the edit has been
0235: * deleted; otherwise <code>false</code> is returned.
0236: */
0237: public final boolean isDeleted() {
0238: return fOffset == DELETED_VALUE && fLength == DELETED_VALUE;
0239: }
0240:
0241: /**
0242: * Move all offsets in the tree by the given delta. This node must be a
0243: * root node. The resulting offsets must be greater or equal to zero.
0244: *
0245: * @param delta the delta
0246: * @since 3.1
0247: */
0248: public final void moveTree(int delta) {
0249: Assert.isTrue(fParent == null);
0250: Assert.isTrue(getOffset() + delta >= 0);
0251: internalMoveTree(delta);
0252: }
0253:
0254: /**
0255: * Returns <code>true</code> if the edit covers the given edit
0256: * <code>other</code>. It is up to the concrete text edit to
0257: * decide if a edit of length zero can cover another edit.
0258: *
0259: * @param other the other edit
0260: * @return <code>true<code> if the edit covers the other edit;
0261: * otherwise <code>false</code> is returned.
0262: */
0263: public boolean covers(TextEdit other) {
0264: if (getLength() == 0 && !canZeroLengthCover())
0265: return false;
0266:
0267: if (!other.isDefined())
0268: return true;
0269:
0270: int this Offset = getOffset();
0271: int otherOffset = other.getOffset();
0272: return this Offset <= otherOffset
0273: && otherOffset + other.getLength() <= this Offset
0274: + getLength();
0275: }
0276:
0277: /**
0278: * Returns <code>true</code> if an edit with length zero can cover
0279: * another edit. Returns <code>false</code> otherwise.
0280: *
0281: * @return whether an edit of length zero can cover another edit
0282: */
0283: protected boolean canZeroLengthCover() {
0284: return false;
0285: }
0286:
0287: /**
0288: * Returns whether the region of this edit is defined or not.
0289: *
0290: * @return whether the region of this edit is defined or not
0291: *
0292: * @since 3.1
0293: */
0294: boolean isDefined() {
0295: return true;
0296: }
0297:
0298: //---- parent and children management -----------------------------
0299:
0300: /**
0301: * Returns the edit's parent. The method returns <code>null</code>
0302: * if this edit hasn't been add to another edit.
0303: *
0304: * @return the edit's parent
0305: */
0306: public final TextEdit getParent() {
0307: return fParent;
0308: }
0309:
0310: /**
0311: * Returns the root edit of the edit tree.
0312: *
0313: * @return the root edit of the edit tree
0314: * @since 3.1
0315: */
0316: public final TextEdit getRoot() {
0317: TextEdit result = this ;
0318: while (result.fParent != null) {
0319: result = result.fParent;
0320: }
0321: return result;
0322: }
0323:
0324: /**
0325: * Adds the given edit <code>child</code> to this edit.
0326: *
0327: * @param child the child edit to add
0328: * @exception MalformedTreeException is thrown if the child
0329: * edit can't be added to this edit. This is the case if the child
0330: * overlaps with one of its siblings or if the child edit's region
0331: * isn't fully covered by this edit.
0332: */
0333: public final void addChild(TextEdit child)
0334: throws MalformedTreeException {
0335: internalAdd(child);
0336: }
0337:
0338: /**
0339: * Adds all edits in <code>edits</code> to this edit.
0340: *
0341: * @param edits the text edits to add
0342: * @exception MalformedTreeException is thrown if one of
0343: * the given edits can't be added to this edit.
0344: *
0345: * @see #addChild(TextEdit)
0346: */
0347: public final void addChildren(TextEdit[] edits)
0348: throws MalformedTreeException {
0349: for (int i = 0; i < edits.length; i++) {
0350: internalAdd(edits[i]);
0351: }
0352: }
0353:
0354: /**
0355: * Removes the edit specified by the given index from the list
0356: * of children. Returns the child edit that was removed from
0357: * the list of children. The parent of the returned edit is
0358: * set to <code>null</code>.
0359: *
0360: * @param index the index of the edit to remove
0361: * @return the removed edit
0362: * @exception IndexOutOfBoundsException if the index
0363: * is out of range
0364: */
0365: public final TextEdit removeChild(int index) {
0366: if (fChildren == null)
0367: throw new IndexOutOfBoundsException(
0368: "Index: " + index + " Size: 0"); //$NON-NLS-1$//$NON-NLS-2$
0369: TextEdit result = (TextEdit) fChildren.remove(index);
0370: result.internalSetParent(null);
0371: if (fChildren.isEmpty())
0372: fChildren = null;
0373: return result;
0374: }
0375:
0376: /**
0377: * Removes the first occurrence of the given child from the list
0378: * of children.
0379: *
0380: * @param child the child to be removed
0381: * @return <code>true</code> if the edit contained the given
0382: * child; otherwise <code>false</code> is returned
0383: */
0384: public final boolean removeChild(TextEdit child) {
0385: Assert.isNotNull(child);
0386: if (fChildren == null)
0387: return false;
0388: boolean result = fChildren.remove(child);
0389: if (result) {
0390: child.internalSetParent(null);
0391: if (fChildren.isEmpty())
0392: fChildren = null;
0393: }
0394: return result;
0395: }
0396:
0397: /**
0398: * Removes all child edits from and returns them. The parent
0399: * of the removed edits is set to <code>null</code>.
0400: *
0401: * @return an array of the removed edits
0402: */
0403: public final TextEdit[] removeChildren() {
0404: if (fChildren == null)
0405: return EMPTY_ARRAY;
0406: int size = fChildren.size();
0407: TextEdit[] result = new TextEdit[size];
0408: for (int i = 0; i < size; i++) {
0409: result[i] = (TextEdit) fChildren.get(i);
0410: result[i].internalSetParent(null);
0411: }
0412: fChildren = null;
0413: return result;
0414: }
0415:
0416: /**
0417: * Returns <code>true</code> if this edit has children. Otherwise
0418: * <code>false</code> is returned.
0419: *
0420: * @return <code>true</code> if this edit has children; otherwise
0421: * <code>false</code> is returned
0422: */
0423: public final boolean hasChildren() {
0424: return fChildren != null && !fChildren.isEmpty();
0425: }
0426:
0427: /**
0428: * Returns the edit's children. If the edit doesn't have any
0429: * children an empty array is returned.
0430: *
0431: * @return the edit's children
0432: */
0433: public final TextEdit[] getChildren() {
0434: if (fChildren == null)
0435: return EMPTY_ARRAY;
0436: return (TextEdit[]) fChildren.toArray(new TextEdit[fChildren
0437: .size()]);
0438: }
0439:
0440: /**
0441: * Returns the size of the managed children.
0442: *
0443: * @return the size of the children
0444: */
0445: public final int getChildrenSize() {
0446: if (fChildren == null)
0447: return 0;
0448: return fChildren.size();
0449: }
0450:
0451: /**
0452: * Returns the text range spawned by the given array of text edits.
0453: * The method requires that the given array contains at least one
0454: * edit. If all edits passed are deleted the method returns <code>
0455: * null</code>.
0456: *
0457: * @param edits an array of edits
0458: * @return the text range spawned by the given array of edits or
0459: * <code>null</code> if all edits are marked as deleted
0460: */
0461: public static IRegion getCoverage(TextEdit[] edits) {
0462: Assert.isTrue(edits != null && edits.length > 0);
0463:
0464: int offset = Integer.MAX_VALUE;
0465: int end = Integer.MIN_VALUE;
0466: int deleted = 0;
0467: for (int i = 0; i < edits.length; i++) {
0468: TextEdit edit = edits[i];
0469: if (edit.isDeleted()) {
0470: deleted++;
0471: } else {
0472: offset = Math.min(offset, edit.getOffset());
0473: end = Math.max(end, edit.getExclusiveEnd());
0474: }
0475: }
0476: if (edits.length == deleted)
0477: return null;
0478:
0479: return new Region(offset, end - offset);
0480: }
0481:
0482: /*
0483: * Hook called before this edit gets added to the passed
0484: * parent.
0485: */
0486: void aboutToBeAdded(TextEdit parent) {
0487: }
0488:
0489: //---- Object methods ------------------------------------------------------
0490:
0491: /**
0492: * The <code>Edit</code> implementation of this <code>Object</code>
0493: * method uses object identity (==).
0494: *
0495: * @param obj the other object
0496: * @return <code>true</code> iff <code>this == obj</code>; otherwise
0497: * <code>false</code> is returned
0498: *
0499: * @see Object#equals(java.lang.Object)
0500: */
0501: public final boolean equals(Object obj) {
0502: return this == obj; // equivalent to Object.equals
0503: }
0504:
0505: /**
0506: * The <code>Edit</code> implementation of this <code>Object</code>
0507: * method calls uses <code>Object#hashCode()</code> to compute its
0508: * hash code.
0509: *
0510: * @return the object's hash code value
0511: *
0512: * @see Object#hashCode()
0513: */
0514: public final int hashCode() {
0515: return super .hashCode();
0516: }
0517:
0518: /*
0519: * @see java.lang.Object#toString()
0520: */
0521: public String toString() {
0522: StringBuffer buffer = new StringBuffer();
0523: toStringWithChildren(buffer, 0);
0524: return buffer.toString();
0525: }
0526:
0527: /**
0528: * Adds the string representation of this text edit without
0529: * children information to the given string buffer.
0530: *
0531: * @param buffer the string buffer
0532: * @param indent the indent level in number of spaces
0533: * @since 3.3
0534: */
0535: void internalToString(StringBuffer buffer, int indent) {
0536: for (int i = indent; i > 0; i--) {
0537: buffer.append(" "); //$NON-NLS-1$
0538: }
0539: buffer.append("{"); //$NON-NLS-1$
0540: String name = getClass().getName();
0541: int index = name.lastIndexOf('.');
0542: if (index != -1) {
0543: buffer.append(name.substring(index + 1));
0544: } else {
0545: buffer.append(name);
0546: }
0547: buffer.append("} "); //$NON-NLS-1$
0548: if (isDeleted()) {
0549: buffer.append("[deleted]"); //$NON-NLS-1$
0550: } else {
0551: buffer.append("["); //$NON-NLS-1$
0552: buffer.append(getOffset());
0553: buffer.append(","); //$NON-NLS-1$
0554: buffer.append(getLength());
0555: buffer.append("]"); //$NON-NLS-1$
0556: }
0557: }
0558:
0559: /**
0560: * Adds the string representation for this text edit
0561: * and its children to the given string buffer.
0562: *
0563: * @param buffer the string buffer
0564: * @param indent the indent level in number of spaces
0565: * @since 3.3
0566: */
0567: private void toStringWithChildren(StringBuffer buffer, int indent) {
0568: internalToString(buffer, indent);
0569: if (fChildren != null) {
0570: for (Iterator iterator = fChildren.iterator(); iterator
0571: .hasNext();) {
0572: TextEdit child = (TextEdit) iterator.next();
0573: buffer.append('\n');
0574: child.toStringWithChildren(buffer, indent + 1);
0575: }
0576: }
0577: }
0578:
0579: //---- Copying -------------------------------------------------------------
0580:
0581: /**
0582: * Creates a deep copy of the edit tree rooted at this
0583: * edit.
0584: *
0585: * @return a deep copy of the edit tree
0586: * @see #doCopy()
0587: */
0588: public final TextEdit copy() {
0589: TextEditCopier copier = new TextEditCopier(this );
0590: return copier.perform();
0591: }
0592:
0593: /**
0594: * Creates and returns a copy of this edit. The copy method should be
0595: * implemented in a way so that the copy can executed without causing
0596: * any harm to the original edit. Implementors of this method are
0597: * responsible for creating deep or shallow copies of referenced
0598: * object to fulfill this requirement.
0599: * <p>
0600: * Implementers of this method should use the copy constructor <code>
0601: * Edit#Edit(Edit source) to initialize the edit part of the copy.
0602: * Implementors aren't responsible to actually copy the children or
0603: * to set the right parent.
0604: * <p>
0605: * This method <b>should not be called</b> from outside the framework.
0606: * Please use <code>copy</code> to create a copy of a edit tree.
0607: *
0608: * @return a copy of this edit.
0609: * @see #copy()
0610: * @see #postProcessCopy(TextEditCopier)
0611: * @see TextEditCopier
0612: */
0613: protected abstract TextEdit doCopy();
0614:
0615: /**
0616: * This method is called on every edit of the copied tree to do some
0617: * post-processing like connected an edit to a different edit in the tree.
0618: * <p>
0619: * This default implementation does nothing
0620: *
0621: * @param copier the copier that manages a map between original and
0622: * copied edit.
0623: * @see TextEditCopier
0624: */
0625: protected void postProcessCopy(TextEditCopier copier) {
0626: }
0627:
0628: //---- Visitor support -------------------------------------------------
0629:
0630: /**
0631: * Accepts the given visitor on a visit of the current edit.
0632: *
0633: * @param visitor the visitor object
0634: * @exception IllegalArgumentException if the visitor is null
0635: */
0636: public final void accept(TextEditVisitor visitor) {
0637: Assert.isNotNull(visitor);
0638: // begin with the generic pre-visit
0639: visitor.preVisit(this );
0640: // dynamic dispatch to internal method for type-specific visit/endVisit
0641: accept0(visitor);
0642: // end with the generic post-visit
0643: visitor.postVisit(this );
0644: }
0645:
0646: /**
0647: * Accepts the given visitor on a type-specific visit of the current edit.
0648: * This method must be implemented in all concrete text edits.
0649: * <p>
0650: * General template for implementation on each concrete TextEdit class:
0651: * <pre>
0652: * <code>
0653: * boolean visitChildren= visitor.visit(this);
0654: * if (visitChildren) {
0655: * acceptChildren(visitor);
0656: * }
0657: * </code>
0658: * </pre>
0659: * Note that the caller (<code>accept</code>) takes care of invoking
0660: * <code>visitor.preVisit(this)</code> and <code>visitor.postVisit(this)</code>.
0661: * </p>
0662: *
0663: * @param visitor the visitor object
0664: */
0665: protected abstract void accept0(TextEditVisitor visitor);
0666:
0667: /**
0668: * Accepts the given visitor on the edits children.
0669: * <p>
0670: * This method must be used by the concrete implementations of
0671: * <code>accept</code> to traverse list-values properties; it
0672: * encapsulates the proper handling of on-the-fly changes to the list.
0673: * </p>
0674: *
0675: * @param visitor the visitor object
0676: */
0677: protected final void acceptChildren(TextEditVisitor visitor) {
0678: if (fChildren == null)
0679: return;
0680: Iterator iterator = fChildren.iterator();
0681: while (iterator.hasNext()) {
0682: TextEdit curr = (TextEdit) iterator.next();
0683: curr.accept(visitor);
0684: }
0685: }
0686:
0687: //---- Execution -------------------------------------------------------
0688:
0689: /**
0690: * Applies the edit tree rooted by this edit to the given document. To check
0691: * if the edit tree can be applied to the document either catch <code>
0692: * MalformedTreeException</code> or use <code>TextEditProcessor</code> to
0693: * execute an edit tree.
0694: *
0695: * @param document the document to be manipulated
0696: * @param style flags controlling the execution of the edit tree. Valid
0697: * flags are: <code>CREATE_UNDO</code> and </code>UPDATE_REGIONS</code>.
0698: * @return a undo edit, if <code>CREATE_UNDO</code> is specified. Otherwise
0699: * <code>null</code> is returned.
0700: *
0701: * @exception MalformedTreeException is thrown if the tree isn't
0702: * in a valid state. This exception is thrown before any edit
0703: * is executed. So the document is still in its original state.
0704: * @exception BadLocationException is thrown if one of the edits
0705: * in the tree can't be executed. The state of the document is
0706: * undefined if this exception is thrown.
0707: *
0708: * @see TextEditProcessor#performEdits()
0709: */
0710: public final UndoEdit apply(IDocument document, int style)
0711: throws MalformedTreeException, BadLocationException {
0712: try {
0713: TextEditProcessor processor = new TextEditProcessor(
0714: document, this , style);
0715: return processor.performEdits();
0716: } finally {
0717: // disconnect from text edit processor
0718: fParent = null;
0719: }
0720: }
0721:
0722: /**
0723: * Applies the edit tree rooted by this edit to the given document. This
0724: * method is a convenience method for <code>apply(document, CREATE_UNDO | UPDATE_REGIONS)
0725: * </code>
0726: *
0727: * @param document the document to which to apply this edit
0728: * @return a undo edit, if <code>CREATE_UNDO</code> is specified. Otherwise
0729: * <code>null</code> is returned.
0730: * @exception MalformedTreeException is thrown if the tree isn't
0731: * in a valid state. This exception is thrown before any edit
0732: * is executed. So the document is still in its original state.
0733: * @exception BadLocationException is thrown if one of the edits
0734: * in the tree can't be executed. The state of the document is
0735: * undefined if this exception is thrown.
0736: * @see #apply(IDocument, int)
0737: */
0738: public final UndoEdit apply(IDocument document)
0739: throws MalformedTreeException, BadLocationException {
0740: return apply(document, CREATE_UNDO | UPDATE_REGIONS);
0741: }
0742:
0743: UndoEdit dispatchPerformEdits(TextEditProcessor processor)
0744: throws BadLocationException {
0745: return processor.executeDo();
0746: }
0747:
0748: void dispatchCheckIntegrity(TextEditProcessor processor)
0749: throws MalformedTreeException {
0750: processor.checkIntegrityDo();
0751: }
0752:
0753: //---- internal state accessors ----------------------------------------------------------
0754:
0755: void internalSetParent(TextEdit parent) {
0756: if (parent != null)
0757: Assert.isTrue(fParent == null);
0758: fParent = parent;
0759: }
0760:
0761: void internalSetOffset(int offset) {
0762: Assert.isTrue(offset >= 0);
0763: fOffset = offset;
0764: }
0765:
0766: void internalSetLength(int length) {
0767: Assert.isTrue(length >= 0);
0768: fLength = length;
0769: }
0770:
0771: List internalGetChildren() {
0772: return fChildren;
0773: }
0774:
0775: void internalSetChildren(List children) {
0776: fChildren = children;
0777: }
0778:
0779: void internalAdd(TextEdit child) throws MalformedTreeException {
0780: child.aboutToBeAdded(this );
0781: if (child.isDeleted())
0782: throw new MalformedTreeException(this , child,
0783: TextEditMessages.getString("TextEdit.deleted_edit")); //$NON-NLS-1$
0784: if (!covers(child))
0785: throw new MalformedTreeException(this , child,
0786: TextEditMessages
0787: .getString("TextEdit.range_outside")); //$NON-NLS-1$
0788: if (fChildren == null) {
0789: fChildren = new ArrayList(2);
0790: }
0791: int index = computeInsertionIndex(child);
0792: fChildren.add(index, child);
0793: child.internalSetParent(this );
0794: }
0795:
0796: private int computeInsertionIndex(TextEdit edit)
0797: throws MalformedTreeException {
0798: int size = fChildren.size();
0799: if (size == 0)
0800: return 0;
0801: int lastIndex = size - 1;
0802: TextEdit last = (TextEdit) fChildren.get(lastIndex);
0803: if (last.getExclusiveEnd() <= edit.getOffset())
0804: return size;
0805: try {
0806:
0807: int index = Collections.binarySearch(fChildren, edit,
0808: INSERTION_COMPARATOR);
0809:
0810: if (index < 0)
0811: // edit is not in fChildren
0812: return -index - 1;
0813:
0814: // edit is already in fChildren
0815: // make sure that multiple insertion points at the same offset are inserted last.
0816: while (index < lastIndex
0817: && INSERTION_COMPARATOR.compare(fChildren
0818: .get(index), fChildren.get(index + 1)) == 0)
0819: index++;
0820:
0821: return index + 1;
0822:
0823: } catch (MalformedTreeException e) {
0824: e.setParent(this );
0825: throw e;
0826: }
0827: }
0828:
0829: //---- Offset & Length updating -------------------------------------------------
0830:
0831: /**
0832: * Adjusts the edits offset according to the given
0833: * delta. This method doesn't update any children.
0834: *
0835: * @param delta the delta of the text replace operation
0836: */
0837: void adjustOffset(int delta) {
0838: if (isDeleted())
0839: return;
0840: fOffset += delta;
0841: Assert.isTrue(fOffset >= 0);
0842: }
0843:
0844: /**
0845: * Adjusts the edits length according to the given
0846: * delta. This method doesn't update any children.
0847: *
0848: * @param delta the delta of the text replace operation
0849: */
0850: void adjustLength(int delta) {
0851: if (isDeleted())
0852: return;
0853: fLength += delta;
0854: Assert.isTrue(fLength >= 0);
0855: }
0856:
0857: /**
0858: * Marks the edit as deleted. This method doesn't update
0859: * any children.
0860: */
0861: void markAsDeleted() {
0862: fOffset = DELETED_VALUE;
0863: fLength = DELETED_VALUE;
0864: }
0865:
0866: //---- Edit processing ----------------------------------------------
0867:
0868: /**
0869: * Traverses the edit tree to perform the consistency check.
0870: *
0871: * @param processor the text edit processor
0872: * @param document the document to be manipulated
0873: * @param sourceEdits the list of source edits to be performed before
0874: * the actual tree is applied to the document
0875: *
0876: * @return the number of indirect move or copy target edit children
0877: */
0878: int traverseConsistencyCheck(TextEditProcessor processor,
0879: IDocument document, List sourceEdits) {
0880: int result = 0;
0881: if (fChildren != null) {
0882: for (int i = fChildren.size() - 1; i >= 0; i--) {
0883: TextEdit child = (TextEdit) fChildren.get(i);
0884: result = Math.max(result, child
0885: .traverseConsistencyCheck(processor, document,
0886: sourceEdits));
0887: }
0888: }
0889: if (processor.considerEdit(this )) {
0890: performConsistencyCheck(processor, document);
0891: }
0892: return result;
0893: }
0894:
0895: void performConsistencyCheck(TextEditProcessor processor,
0896: IDocument document) {
0897: }
0898:
0899: void traverseSourceComputation(TextEditProcessor processor,
0900: IDocument document) {
0901: }
0902:
0903: void performSourceComputation(TextEditProcessor processor,
0904: IDocument document) {
0905: }
0906:
0907: int traverseDocumentUpdating(TextEditProcessor processor,
0908: IDocument document) throws BadLocationException {
0909: int delta = 0;
0910: if (fChildren != null) {
0911: for (int i = fChildren.size() - 1; i >= 0; i--) {
0912: TextEdit child = (TextEdit) fChildren.get(i);
0913: delta += child.traverseDocumentUpdating(processor,
0914: document);
0915: childDocumentUpdated();
0916: }
0917: }
0918: if (processor.considerEdit(this )) {
0919: if (delta != 0)
0920: adjustLength(delta);
0921: int r = performDocumentUpdating(document);
0922: if (r != 0)
0923: adjustLength(r);
0924: delta += r;
0925: }
0926: return delta;
0927: }
0928:
0929: /**
0930: * Hook method called when the document updating of a child edit has been
0931: * completed. When a client calls {@link #apply(IDocument)} or
0932: * {@link #apply(IDocument, int)} this method is called
0933: * {@link #getChildrenSize()} times.
0934: * <p>
0935: * May be overridden by subclasses of {@link MultiTextEdit}.
0936: *
0937: * @since 3.1
0938: */
0939: protected void childDocumentUpdated() {
0940: }
0941:
0942: abstract int performDocumentUpdating(IDocument document)
0943: throws BadLocationException;
0944:
0945: int traverseRegionUpdating(TextEditProcessor processor,
0946: IDocument document, int accumulatedDelta, boolean delete) {
0947: performRegionUpdating(accumulatedDelta, delete);
0948: if (fChildren != null) {
0949: boolean childDelete = delete || deleteChildren();
0950: for (Iterator iter = fChildren.iterator(); iter.hasNext();) {
0951: TextEdit child = (TextEdit) iter.next();
0952: accumulatedDelta = child.traverseRegionUpdating(
0953: processor, document, accumulatedDelta,
0954: childDelete);
0955: childRegionUpdated();
0956: }
0957: }
0958: return accumulatedDelta + fDelta;
0959: }
0960:
0961: /**
0962: * Hook method called when the region updating of a child edit has been
0963: * completed. When a client calls {@link #apply(IDocument)} this method is
0964: * called {@link #getChildrenSize()} times. When calling
0965: * {@link #apply(IDocument, int)} this method is called
0966: * {@link #getChildrenSize()} times, when the style parameter contains the
0967: * {@link #UPDATE_REGIONS} flag.
0968: * <p>
0969: * May be overridden by subclasses of {@link MultiTextEdit}.
0970: *
0971: * @since 3.1
0972: */
0973: protected void childRegionUpdated() {
0974: }
0975:
0976: void performRegionUpdating(int accumulatedDelta, boolean delete) {
0977: if (delete)
0978: markAsDeleted();
0979: else
0980: adjustOffset(accumulatedDelta);
0981: }
0982:
0983: abstract boolean deleteChildren();
0984:
0985: void internalMoveTree(int delta) {
0986: adjustOffset(delta);
0987: if (fChildren != null) {
0988: for (Iterator iter = fChildren.iterator(); iter.hasNext();) {
0989: ((TextEdit) iter.next()).internalMoveTree(delta);
0990: }
0991: }
0992: }
0993:
0994: void deleteTree() {
0995: markAsDeleted();
0996: if (fChildren != null) {
0997: for (Iterator iter = fChildren.iterator(); iter.hasNext();) {
0998: TextEdit child = (TextEdit) iter.next();
0999: child.deleteTree();
1000: }
1001: }
1002: }
1003: }
|