/*******************************************************************************
* Copyright (c) 2012-2015 Codenvy, S.A.
* 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:
* Codenvy, S.A. - initial API and implementation
*******************************************************************************/
package org.eclipse.che.ide.ext.java.jdt.text;
import org.eclipse.che.ide.api.editor.text.BadPositionCategoryException;
import org.eclipse.che.ide.api.editor.text.Position;
/**
* Default implementation of {@link PositionUpdater}.
* <p>
* A default position updater must be configured with the position category whose positions it will update. Other position
* categories are not affected by this updater.
* </p>
* <p>
* This implementation follows the specification below:
* </p>
* <ul>
* <li>Inserting or deleting text before the position shifts the position accordingly.</li>
* <li>Inserting text at the position offset shifts the position accordingly.</li>
* <li>Inserting or deleting text strictly contained by the position shrinks or stretches the position.</li>
* <li>Inserting or deleting text after a position does not affect the position.</li>
* <li>Deleting text which strictly contains the position deletes the position. Note that the position is not deleted if its only
* shrunken to length zero. To delete a position, the modification must delete from <i>strictly before</i> to <i>strictly
* after</i> the position.</li>
* <li>Replacing text contained by the position shrinks or expands the position (but does not shift it), such that the final
* position contains the original position and the replacing text.</li>
* <li>Replacing text overlapping the position in other ways is considered as a sequence of first deleting the replaced text and
* afterwards inserting the new text. Thus, a position is shrunken and can then be shifted (if the replaced text overlaps the
* offset of the position).</li>
* </ul>
* This class can be used as is or be adapted by subclasses. Fields are protected to allow subclasses direct access. Because of
* the frequency with which position updaters are used this is a performance decision.
*/
public class DefaultPositionUpdater implements PositionUpdater {
/** The position category the updater draws responsible for */
private final String fCategory;
/** Caches the currently investigated position */
protected Position fPosition;
/** Caches the original state of the investigated position */
protected Position fOriginalPosition = new Position(0, 0);
/** Caches the offset of the replaced text */
protected int fOffset;
/** Caches the length of the replaced text */
protected int fLength;
/** Caches the length of the newly inserted text */
protected int fReplaceLength;
/** Caches the document */
protected Document fDocument;
/**
* Creates a new default position updater for the given category.
*
* @param category
* the category the updater is responsible for
*/
public DefaultPositionUpdater(String category) {
fCategory = category;
}
/**
* Returns the category this updater is responsible for.
*
* @return the category this updater is responsible for
*/
protected String getCategory() {
return fCategory;
}
/**
* Returns whether the current event describes a well formed replace by which the current position is directly affected.
*
* @return <code>true</code> the current position is directly affected
* @since 3.0
*/
protected boolean isAffectingReplace() {
return fLength > 0 && fReplaceLength > 0 && fPosition.length < fOriginalPosition.length;
}
/** Adapts the currently investigated position to an insertion. */
protected void adaptToInsert() {
int myStart = fPosition.offset;
int myEnd = fPosition.offset + fPosition.length - 1;
myEnd = Math.max(myStart, myEnd);
int yoursStart = fOffset;
int yoursEnd = fOffset + fReplaceLength - 1;
yoursEnd = Math.max(yoursStart, yoursEnd);
if (myEnd < yoursStart)
return;
if (myStart < yoursStart)
fPosition.length += fReplaceLength;
else
fPosition.offset += fReplaceLength;
}
/** Adapts the currently investigated position to a deletion. */
protected void adaptToRemove() {
int myStart = fPosition.offset;
int myEnd = fPosition.offset + fPosition.length - 1;
myEnd = Math.max(myStart, myEnd);
int yoursStart = fOffset;
int yoursEnd = fOffset + fLength - 1;
yoursEnd = Math.max(yoursStart, yoursEnd);
if (myEnd < yoursStart)
return;
if (myStart <= yoursStart) {
if (yoursEnd <= myEnd)
fPosition.length -= fLength;
else
fPosition.length -= (myEnd - yoursStart + 1);
} else if (yoursStart < myStart) {
if (yoursEnd < myStart)
fPosition.offset -= fLength;
else {
fPosition.offset -= (myStart - yoursStart);
fPosition.length -= (yoursEnd - myStart + 1);
}
}
// validate position to allowed values
if (fPosition.offset < 0)
fPosition.offset = 0;
if (fPosition.length < 0)
fPosition.length = 0;
}
/**
* Adapts the currently investigated position to the replace operation. First it checks whether the change replaces only a
* non-zero range inside the range of the position (including the borders). If not, it performs first the deletion of the
* previous text and afterwards the insertion of the new text.
*/
protected void adaptToReplace() {
if (fLength > 0 && fPosition.offset <= fOffset && fOffset + fLength <= fPosition.offset + fPosition.length) {
fPosition.length += fReplaceLength - fLength;
} else {
if (fLength > 0)
adaptToRemove();
if (fReplaceLength > 0)
adaptToInsert();
}
}
/**
* Determines whether the currently investigated position has been deleted by the replace operation specified in the current
* event. If so, it deletes the position and removes it from the document's position category.
*
* @return <code>true</code> if position has not been deleted
*/
protected boolean notDeleted() {
if (fOffset < fPosition.offset && (fPosition.offset + fPosition.length < fOffset + fLength)) {
fPosition.delete();
try {
fDocument.removePosition(fCategory, fPosition);
} catch (BadPositionCategoryException x) {
}
return false;
}
return true;
}
/*
* @see org.eclipse.jface.text.IPositionUpdater#update(org.eclipse.jface.text. DocumentEvent)
*/
public void update(DocumentEvent event) {
try {
fOffset = event.getOffset();
fLength = event.getLength();
fReplaceLength = (event.getText() == null ? 0 : event.getText().length());
fDocument = event.getDocument();
Position[] category = fDocument.getPositions(fCategory);
for (int i = 0; i < category.length; i++) {
fPosition = category[i];
fOriginalPosition.offset = fPosition.offset;
fOriginalPosition.length = fPosition.length;
if (notDeleted())
adaptToReplace();
}
} catch (BadPositionCategoryException x) {
// do nothing
} finally {
fDocument = null;
}
}
}