| /* |
| * Copyright 2000-2014 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.diff.impl.string; |
| |
| import com.intellij.openapi.diff.LineTokenizerBase; |
| import com.intellij.openapi.util.text.StringUtil; |
| import com.intellij.util.containers.ContainerUtil; |
| import org.jetbrains.annotations.NotNull; |
| import org.jetbrains.annotations.Nullable; |
| |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.List; |
| |
| public class DiffString implements CharSequence { |
| @NotNull public static final DiffString EMPTY = new DiffString(new char[0], 0, 0); |
| |
| @NotNull private final char[] myData; |
| private final int myStart; |
| private final int myLength; |
| private int myHash; |
| |
| @Nullable |
| public static DiffString createNullable(@Nullable String string) { |
| if (string == null) return null; |
| return create(string); |
| } |
| |
| @NotNull |
| public static DiffString create(@NotNull String string) { |
| if (string.isEmpty()) return EMPTY; |
| return create(string.toCharArray()); |
| } |
| |
| @NotNull |
| static DiffString create(@NotNull char[] data) { |
| return create(data, 0, data.length); |
| } |
| |
| @NotNull |
| static DiffString create(@NotNull char[] data, int start, int length) { |
| if (length == 0) return EMPTY; |
| checkBounds(start, length, data.length); |
| return new DiffString(data, start, length); |
| } |
| |
| private DiffString(@NotNull char[] data, int start, int length) { |
| myData = data; |
| myStart = start; |
| myLength = length; |
| } |
| |
| @Override |
| public int length() { |
| return myLength; |
| } |
| |
| public boolean isEmpty() { |
| return myLength == 0; |
| } |
| |
| @Override |
| public char charAt(int index) { |
| if (index < 0 || index >= myLength) { |
| throw new StringIndexOutOfBoundsException(index); |
| } |
| return data(index); |
| } |
| |
| public char data(int index) { |
| return myData[myStart + index]; |
| } |
| |
| @NotNull |
| public DiffString substring(int start) { |
| return substring(start, myLength); |
| } |
| |
| @NotNull |
| public DiffString substring(int start, int end) { |
| if (start == 0 && end == myLength) return this; |
| checkBounds(start, end - start, myLength); |
| return create(myData, myStart + start, end - start); |
| } |
| |
| @Override |
| public DiffString subSequence(int start, int end) { |
| return substring(start, end); |
| } |
| |
| @NotNull |
| @Override |
| public String toString() { |
| return new String(myData, myStart, myLength); |
| } |
| |
| @NotNull |
| public DiffString copy() { |
| return create(Arrays.copyOfRange(myData, myStart, myStart + myLength)); |
| } |
| |
| public void copyData(@NotNull char[] dst, int start) { |
| checkBounds(start, myLength, dst.length); |
| System.arraycopy(myData, myStart, dst, start, myLength); |
| } |
| |
| @Override |
| public boolean equals(@Nullable Object o) { |
| if (this == o) return true; |
| if (o == null || getClass() != o.getClass()) return false; |
| |
| DiffString that = (DiffString)o; |
| |
| if (myLength != that.myLength) return false; |
| if (hashCode() != that.hashCode()) return false; |
| for (int i = 0; i < myLength; i++) { |
| if (data(i) != that.data(i)) return false; |
| } |
| |
| return true; |
| } |
| |
| @Override |
| public int hashCode() { |
| int h = myHash; |
| if (h == 0) { |
| h = StringUtil.stringHashCode(myData, myStart, myStart + myLength); |
| if (h == 0) h = 1; |
| myHash = h; |
| } |
| return h; |
| } |
| |
| @Nullable |
| public static DiffString concatenateNullable(@Nullable DiffString s1, @Nullable DiffString s2) { |
| if (s1 == null || s2 == null) { |
| if (s1 != null) return s1; |
| if (s2 != null) return s2; |
| return null; |
| } |
| |
| return concatenate(s1, s2); |
| } |
| |
| @NotNull |
| public static DiffString concatenate(@NotNull DiffString s1, @NotNull DiffString s2) { |
| if (s1.isEmpty()) return s2; |
| if (s2.isEmpty()) return s1; |
| |
| if (s1.myData == s2.myData && s1.myStart + s1.myLength == s2.myStart) { |
| return create(s1.myData, s1.myStart, s1.myLength + s2.myLength); |
| } |
| |
| char[] data = new char[s1.myLength + s2.myLength]; |
| System.arraycopy(s1.myData, s1.myStart, data, 0, s1.myLength); |
| System.arraycopy(s2.myData, s2.myStart, data, s1.myLength, s2.myLength); |
| return create(data); |
| } |
| |
| public static boolean canInplaceConcatenate(@NotNull DiffString s1, @NotNull DiffString s2) { |
| if (s1.isEmpty()) return true; |
| if (s2.isEmpty()) return true; |
| |
| if (s1.myData == s2.myData && s1.myStart + s1.myLength == s2.myStart) { |
| return true; |
| } |
| |
| return false; |
| } |
| |
| @NotNull |
| public static DiffString concatenateCopying(@NotNull DiffString[] strings) { |
| return concatenateCopying(strings, 0, strings.length); |
| } |
| |
| @NotNull |
| public static DiffString concatenateCopying(@NotNull DiffString[] strings, int start, int length) { |
| checkBounds(start, length, strings.length); |
| |
| int len = 0; |
| for (int i = 0; i < length; i++) { |
| DiffString string = strings[start + i]; |
| len += string == null ? 0 : string.myLength; |
| } |
| |
| if (len == 0) return EMPTY; |
| |
| char[] data = new char[len]; |
| int index = 0; |
| for (int i = 0; i < length; i++) { |
| DiffString string = strings[start + i]; |
| if (string == null || string.isEmpty()) continue; |
| System.arraycopy(string.myData, string.myStart, data, index, string.myLength); |
| index += string.myLength; |
| } |
| return create(data); |
| } |
| |
| @NotNull |
| public static DiffString concatenate(@NotNull DiffString s, char c) { |
| if (s.myStart + s.myLength < s.myData.length && s.data(s.myLength) == c) { |
| return create(s.myData, s.myStart, s.myLength + 1); |
| } |
| |
| char[] data = new char[s.myLength + 1]; |
| System.arraycopy(s.myData, s.myStart, data, 0, s.myLength); |
| data[s.myLength] = c; |
| return create(data); |
| } |
| |
| @NotNull |
| public static DiffString concatenate(char c, @NotNull DiffString s) { |
| if (s.myStart > 0 && s.data(-1) == c) { |
| return create(s.myData, s.myStart - 1, s.myLength + 1); |
| } |
| |
| char[] data = new char[s.myLength + 1]; |
| System.arraycopy(s.myData, s.myStart, data, 1, s.myLength); |
| data[0] = c; |
| return create(data); |
| } |
| |
| @NotNull |
| public static DiffString concatenate(@NotNull DiffString[] strings) { |
| return concatenate(strings, 0, strings.length); |
| } |
| |
| @NotNull |
| public static DiffString concatenate(@NotNull DiffString[] strings, int start, int length) { |
| checkBounds(start, length, strings.length); |
| |
| char[] data = null; |
| int startIndex = 0; |
| int endIndex = 0; |
| |
| boolean linearized = true; |
| for (int i = 0; i < length; i++) { |
| DiffString string = strings[start + i]; |
| if (string == null || string.isEmpty()) continue; |
| if (data == null) { |
| data = string.myData; |
| startIndex = string.myStart; |
| endIndex = string.myStart + string.myLength; |
| continue; |
| } |
| if (data != string.myData || string.myStart != endIndex) { |
| linearized = false; |
| break; |
| } |
| endIndex += string.myLength; |
| } |
| |
| if (linearized) { |
| if (data == null) return EMPTY; |
| return create(data, startIndex, endIndex - startIndex); |
| } |
| |
| return concatenateCopying(strings, start, length); |
| } |
| |
| @NotNull |
| public DiffString append(char c) { |
| return concatenate(this, c); |
| } |
| |
| @NotNull |
| public DiffString preappend(char c) { |
| return concatenate(c, this); |
| } |
| |
| public static boolean isWhiteSpace(char c) { |
| return StringUtil.isWhiteSpace(c); |
| } |
| |
| public boolean isEmptyOrSpaces() { |
| if (isEmpty()) return true; |
| |
| for (int i = 0; i < myLength; i++) { |
| if (!isWhiteSpace(data(i))) return false; |
| } |
| return true; |
| } |
| |
| @NotNull |
| public DiffString trim() { |
| int start = 0; |
| int end = myLength; |
| |
| while (start < end && isWhiteSpace(data(start))) start++; |
| while (end > start && isWhiteSpace(data(end - 1))) end--; |
| |
| return substring(start, end); |
| } |
| |
| @NotNull |
| public DiffString trimLeading() { |
| int i = 0; |
| |
| while (i < myLength && isWhiteSpace(data(i))) i++; |
| |
| return substring(i, myLength); |
| } |
| |
| @NotNull |
| public DiffString trimTrailing() { |
| int end = myLength; |
| |
| while (end > 0 && isWhiteSpace(data(end - 1))) end--; |
| |
| return substring(0, end); |
| } |
| |
| @NotNull |
| public DiffString getLeadingSpaces() { |
| int i = 0; |
| |
| while (i < myLength && data(i) == ' ') i++; |
| |
| return substring(0, i); |
| } |
| |
| @NotNull |
| public DiffString skipSpaces() { |
| DiffString s = trim(); |
| int count = 0; |
| for (int i = 0; i < s.myLength; i++) { |
| if (isWhiteSpace(s.data(i))) count++; |
| } |
| if (count == 0) return s; |
| |
| char[] data = new char[s.myLength - count]; |
| int index = 0; |
| for (int i = 0; i < s.myLength; i++) { |
| if (isWhiteSpace(s.data(i))) continue; |
| data[index] = s.data(i); |
| index++; |
| } |
| return create(data); |
| } |
| |
| public int indexOf(char c) { |
| return StringUtil.indexOf(this, c); |
| } |
| |
| public boolean endsWith(char c) { |
| if (isEmpty()) return false; |
| return data(myLength - 1) == c; |
| } |
| |
| public static void checkBounds(int start, int length, int maxLength) { |
| if (start < 0) { |
| throw new StringIndexOutOfBoundsException(start); |
| } |
| if (length < 0) { |
| throw new StringIndexOutOfBoundsException(length); |
| } |
| if (start + length > maxLength) { |
| throw new StringIndexOutOfBoundsException(start + length); |
| } |
| } |
| |
| @NotNull |
| public DiffString[] tokenize() { |
| return new LineTokenizer(this).execute(); |
| } |
| |
| public static class LineTokenizer extends LineTokenizerBase<DiffString> { |
| @NotNull private final DiffString myText; |
| |
| public LineTokenizer(@NotNull DiffString text) { |
| myText = text; |
| } |
| |
| @NotNull |
| public DiffString[] execute() { |
| ArrayList<DiffString> lines = new ArrayList<DiffString>(); |
| doExecute(lines); |
| return ContainerUtil.toArray(lines, new DiffString[lines.size()]); |
| } |
| |
| @Override |
| protected void addLine(List<DiffString> lines, int start, int end, boolean appendNewLine) { |
| if (appendNewLine) { |
| lines.add(myText.substring(start, end).append('\n')); |
| } |
| else { |
| lines.add(myText.substring(start, end)); |
| } |
| } |
| |
| @Override |
| protected char charAt(int index) { |
| return myText.data(index); |
| } |
| |
| @Override |
| protected int length() { |
| return myText.length(); |
| } |
| |
| @NotNull |
| @Override |
| protected String substring(int start, int end) { |
| return myText.substring(start, end).toString(); |
| } |
| } |
| } |