| /* |
| * Copyright 2000-2011 JetBrains s.r.o. |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package com.intellij.openapi.editor.impl; |
| |
| import com.intellij.openapi.editor.TextChange; |
| import com.intellij.openapi.util.text.StringUtil; |
| import com.intellij.util.text.CharArrayUtil; |
| import com.intellij.util.text.CharSequenceBackedByArray; |
| import org.jetbrains.annotations.NotNull; |
| |
| /** |
| * Default {@link TextChange} implementation with mutable state. |
| * |
| * @author Denis Zhdanov |
| * @since Jul 7, 2010 5:24:06 PM |
| */ |
| public class TextChangeImpl implements TextChange { |
| |
| private final StringBuilder myText = new StringBuilder(); |
| |
| private char[] myChars; |
| private int myStart; |
| private int myEnd; |
| |
| /** |
| * Shorthand for creating change object with the given arguments where <code>'end index'</code> has the same value as |
| * <code>'start index'</code>. |
| * |
| * @param text text affected by the current change |
| * @param start start index (inclusive) of text range affected by the change encapsulated by the current object |
| * @throws IllegalArgumentException if given start index is invalid |
| */ |
| public TextChangeImpl(@NotNull CharSequence text, int start) throws IllegalArgumentException { |
| this(text, start, start); |
| } |
| |
| /** |
| * Creates new <code>TextChange</code> object with the given arguments. It encapsulates information about the change that |
| * may be applied to the target document. |
| * |
| * @param text text that is introduced by the current change |
| * @param start start index of the target document location where current change is to be applied |
| * @param end end index of the target document where current change is to be applied, i.e. it's assumed that current text |
| * change appliance to particular document causes replacement of its original text at <code>[start; end)</code> |
| * interval by the text encapsulated by the current change. I.e. original text is replaced by the new one |
| * @throws IllegalArgumentException if given start or end index in invalid or they are inconsistent to each other |
| */ |
| public TextChangeImpl(@NotNull CharSequence text, int start, int end) throws IllegalArgumentException { |
| if (start < 0) { |
| throw new IllegalArgumentException(String.format("Can't construct new %s object. Reason: given start index (%d) is negative. " |
| + "End index: %d, text: '%s'", getClass().getName(), start, end, text)); |
| } |
| if (end < start) { |
| throw new IllegalArgumentException(String.format("Can't construct new %s object. Reason: given end index (%d) is less than " |
| + "start index (%d). Text: '%s'", getClass().getName(), end, start, text)); |
| } |
| myText.append(text); |
| myStart = start; |
| myEnd = end; |
| } |
| |
| /** |
| * @return start index (inclusive) of text range affected by the change encapsulated at the current object |
| */ |
| @Override |
| public int getStart() { |
| return myStart; |
| } |
| |
| public void setStart(int start) { |
| assert start >= 0 : start; |
| myStart = start; |
| } |
| |
| /** |
| * @return end index (exclusive) of text range affected by the change encapsulated at the current object |
| */ |
| @Override |
| public int getEnd() { |
| return myEnd; |
| } |
| |
| public void setEnd(int end) { |
| myEnd = end; |
| } |
| |
| /** |
| * Allows to retrieve text that is directly affected by the change encapsulated by the current object. |
| * |
| * @return text related to the change encapsulated by the current object |
| */ |
| @Override |
| @NotNull |
| public CharSequence getText() { |
| return myText; |
| } |
| |
| /** |
| * Allows to get change text as a char array. Note that it's not guaranteed that change text directly maps to the returned char array, |
| * i.e. change to array content is not obeyed to be reflected in {@link #getText()} result. |
| * <p/> |
| * Generally speaking, this method is introduced just as a step toward existing high-performance services that work in terms |
| * of char arrays. Resulting array is instantiated on-demand via {@link CharArrayUtil#fromSequence(CharSequence)}, hence, it |
| * doesn't hit memory if, for example, {@link CharSequenceBackedByArray} is used as initial change text. |
| * |
| * @return stored change text as a char array |
| */ |
| @Override |
| @NotNull |
| public char[] getChars() { |
| if (myChars == null) { |
| myChars = CharArrayUtil.fromSequence(myText); |
| } |
| return myChars; |
| } |
| |
| /** |
| * Difference in document symbols number after current change appliance. |
| * <p/> |
| * <b>Note:</b> returned number may be either positive or not. For example it may be negative for <code>'remove'</code> |
| * or <code>'replace'</code> changes (number of text symbols is less than number of symbols at target change interval) |
| * |
| * @return difference in document symbols number after current change appliance |
| */ |
| public int getDiff() { |
| return myText.length() - myEnd + myStart; |
| } |
| |
| /** |
| * Applies given offset applied to the {@link #getStart() start} and {@link #getEnd() end} properties of current text change object. |
| * |
| * @param offset offset to apply to the current change object |
| * @throws IllegalArgumentException if start index becomes zero after given offset appliance (it is not applied then) |
| */ |
| public void advance(int offset) throws IllegalArgumentException { |
| if (offset == 0) { |
| return; |
| } |
| int newStart = myStart + offset; |
| if (newStart < 0) { |
| throw new IllegalArgumentException(String.format( |
| "Can't apply given offset (%d) to the current text change object (%s). Reason: new start index becomes negative after that (%d)", |
| offset, this, newStart |
| )); |
| } |
| |
| setStart(newStart); |
| setEnd(myEnd + offset); |
| } |
| |
| /** |
| * Shorthand for calling {@link #isWithinBounds(int, int)} with zero start offset and given length as and end offset |
| * |
| * @param length target length |
| * @return <code>true</code> if current change is within the target bounds; <code>false</code> otherwise |
| */ |
| public boolean isWithinBounds(int length) { |
| return isWithinBounds(0, length); |
| } |
| |
| /** |
| * Allows to check if current change is within the given bounds. |
| * |
| * @param start target bounds start offset (inclusive) |
| * @param end target bounds end offset (exclusive) |
| * @return <code>true</code> if current change is within the target bounds; <code>false</code> otherwise |
| */ |
| public boolean isWithinBounds(int start, int end) { |
| return myStart >= start && myEnd <= end && myStart <= myEnd; |
| } |
| |
| @Override |
| public boolean equals(Object o) { |
| if (this == o) return true; |
| if (o == null || getClass() != o.getClass()) return false; |
| |
| TextChangeImpl that = (TextChangeImpl)o; |
| return myStart == that.myStart && myEnd == that.myEnd && StringUtil.equals(myText, that.myText); |
| } |
| |
| @Override |
| public int hashCode() { |
| int result = StringUtil.hashCode(myText); |
| result = 31 * result + myStart; |
| return 31 * result + myEnd; |
| } |
| |
| @Override |
| public String toString() { |
| return String.format("%d-%d: '%s'", myStart, myEnd, myText); |
| } |
| } |