blob: 5396650ebcd05955ad99a9d5bca202e292607c99 [file] [log] [blame]
/*
* Copyright 2000-2009 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.util.text;
import com.intellij.openapi.util.TextRange;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.Reader;
import java.nio.CharBuffer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class CharArrayUtil {
private static final int GET_CHARS_THRESHOLD = 10;
private CharArrayUtil() {
}
/**
* Copies all symbols from the given char sequence to the given array
*
* @param src source data holder
* @param dst output data buffer
* @param dstOffset start offset to use within the given output data buffer
*/
public static void getChars(@NotNull CharSequence src, @NotNull char[] dst, int dstOffset) {
getChars(src, dst, dstOffset, src.length());
}
/**
* Copies necessary number of symbols from the given char sequence start to the given array.
*
* @param src source data holder
* @param dst output data buffer
* @param dstOffset start offset to use within the given output data buffer
* @param len number of source data symbols to copy to the given buffer
*/
public static void getChars(@NotNull CharSequence src, @NotNull char[] dst, int dstOffset, int len) {
getChars(src, dst, 0, dstOffset, len);
}
/**
* Copies necessary number of symbols from the given char sequence to the given array.
*
* @param src source data holder
* @param dst output data buffer
* @param srcOffset source text offset
* @param dstOffset start offset to use within the given output data buffer
* @param len number of source data symbols to copy to the given buffer
*/
public static void getChars(@NotNull CharSequence src, @NotNull char[] dst, int srcOffset, int dstOffset, int len) {
if (src instanceof CharArrayExternalizable) {
((CharArrayExternalizable)src).getChars(srcOffset, srcOffset + len, dst, dstOffset);
return;
}
if (len >= GET_CHARS_THRESHOLD) {
if (src instanceof String) {
((String)src).getChars(srcOffset, srcOffset + len, dst, dstOffset);
return;
}
else if (src instanceof CharBuffer) {
final CharBuffer buffer = (CharBuffer)src;
final int i = buffer.position();
buffer.position(i + srcOffset);
buffer.get(dst, dstOffset, len);
buffer.position(i);
return;
}
else if (src instanceof CharSequenceBackedByArray) {
((CharSequenceBackedByArray)src.subSequence(srcOffset, srcOffset + len)).getChars(dst, dstOffset);
return;
}
else if (src instanceof StringBuffer) {
((StringBuffer)src).getChars(srcOffset, srcOffset + len, dst, dstOffset);
return;
}
else if (src instanceof StringBuilder) {
((StringBuilder)src).getChars(srcOffset, srcOffset + len, dst, dstOffset);
return;
}
}
for (int i = 0, j = srcOffset, max = srcOffset + len; j < max && i < dst.length; i++, j++) {
dst[i + dstOffset] = src.charAt(j);
}
}
/**
* @deprecated use {@link #fromSequence(CharSequence)}
*/
@NotNull
public static char[] fromSequenceStrict(@NotNull CharSequence seq) {
return fromSequence(seq);
}
@Nullable
public static char[] fromSequenceWithoutCopying(@Nullable CharSequence seq) {
if (seq instanceof CharSequenceBackedByArray) {
return ((CharSequenceBackedByArray)seq).getChars();
}
if (seq instanceof CharBuffer) {
final CharBuffer buffer = (CharBuffer)seq;
if (buffer.hasArray() && !buffer.isReadOnly() && buffer.arrayOffset() == 0 && buffer.position() == 0) {
return buffer.array();
}
}
return null;
}
/**
* @return the underlying char[] array if any, or the new chara array if not
*/
@NotNull
public static char[] fromSequence(@NotNull CharSequence seq) {
char[] underlying = fromSequenceWithoutCopying(seq);
return underlying != null ? Arrays.copyOf(underlying, underlying.length) : fromSequence(seq, 0, seq.length());
}
/**
* @return a new char array containing the sub-sequence's chars
*/
@NotNull
public static char[] fromSequence(@NotNull CharSequence seq, int start, int end) {
char[] result = new char[end - start];
getChars(seq, result, start, 0, end - start);
return result;
}
public static int shiftForward(@NotNull CharSequence buffer, int offset, @NotNull String chars) {
return shiftForward(buffer, offset, buffer.length(), chars);
}
/**
* Tries to find an offset from the <code>[startOffset; endOffset)</code> interval such that a char from the given buffer is
* not contained at the given 'chars' string.
* <p/>
* Example:
* {@code buffer="abc", startOffset=0, endOffset = 3, chars="ab". Result: 2}
*
* @param buffer target buffer which symbols should be checked
* @param startOffset start offset to use within the given buffer (inclusive)
* @param endOffset end offset to use within the given buffer (exclusive)
* @param chars pass-through symbols
* @return offset from the <code>[startOffset; endOffset)</code> which points to a symbol at the given buffer such
* as that that symbol is not contained at the given 'chars';
* <code>endOffset</code> otherwise
*/
public static int shiftForward(@NotNull CharSequence buffer, final int startOffset, final int endOffset, @NotNull String chars) {
for (int offset = startOffset, limit = Math.min(endOffset, buffer.length()); offset < limit; offset++) {
char c = buffer.charAt(offset);
int i;
for (i = 0; i < chars.length(); i++) {
if (c == chars.charAt(i)) break;
}
if (i >= chars.length()) {
return offset;
}
}
return endOffset;
}
public static int shiftForwardCarefully(@NotNull CharSequence buffer, int offset, @NotNull String chars) {
if (offset + 1 >= buffer.length()) return offset;
if (!isSuitable(chars, buffer.charAt(offset))) return offset;
offset++;
while (true) {
if (offset >= buffer.length()) return offset - 1;
char c = buffer.charAt(offset);
if (!isSuitable(chars, c)) return offset - 1;
offset++;
}
}
private static boolean isSuitable(@NotNull String chars, final char c) {
for (int i = 0; i < chars.length(); i++) {
if (c == chars.charAt(i)) return true;
}
return false;
}
public static int shiftForward(@NotNull char[] buffer, int offset, @NotNull String chars) {
return shiftForward(new CharArrayCharSequence(buffer), offset, chars);
}
public static int shiftBackward(@NotNull CharSequence buffer, int offset, @NotNull String chars) {
return shiftBackward(buffer, 0, offset, chars);
}
public static int shiftBackward(@NotNull CharSequence buffer, int minOffset, int maxOffset, @NotNull String chars) {
if (maxOffset >= buffer.length()) return maxOffset;
int offset = maxOffset;
while (true) {
if (offset < minOffset) break;
char c = buffer.charAt(offset);
int i;
for (i = 0; i < chars.length(); i++) {
if (c == chars.charAt(i)) break;
}
if (i == chars.length()) break;
offset--;
}
return offset;
}
public static int shiftBackward(@NotNull char[] buffer, int offset, @NotNull String chars) {
return shiftBackward(new CharArrayCharSequence(buffer), offset, chars);
}
public static int shiftForwardUntil(@NotNull CharSequence buffer, int offset, @NotNull String chars) {
while (true) {
if (offset >= buffer.length()) break;
char c = buffer.charAt(offset);
int i;
for (i = 0; i < chars.length(); i++) {
if (c == chars.charAt(i)) break;
}
if (i < chars.length()) break;
offset++;
}
return offset;
}
//Commented in order to apply to the green code policy as the method is unused.
//
//public static int shiftBackwardUntil(char[] buffer, int offset, String chars) {
// return shiftBackwardUntil(new CharArrayCharSequence(buffer), offset, chars);
//}
/**
* Calculates offset that points to the given buffer and has the following characteristics:
* <p/>
* <ul>
* <li>is less than or equal to the given offset;</li>
* <li>
* it's guaranteed that all symbols of the given buffer that are located at <code>(returned offset; given offset]</code>
* interval differ from the given symbols;
* </li>
* </ul>
* <p/>
* Example: suppose that this method is called with buffer that holds <code>'test data'</code> symbols, offset that points
* to the last symbols and <code>'sf'</code> as a chars to exclude. Offset that points to <code>'s'</code> symbol
* is returned then, i.e. all symbols of the given buffer that are located after it and not after given offset
* (<code>'t data'</code>) are guaranteed to not contain given chars (<code>'sf'</code>).
*
* @param buffer symbols buffer to check
* @param offset initial symbols buffer offset to use
* @param chars chars to exclude
* @return offset of the given buffer that guarantees that all symbols at <code>(returned offset; given offset]</code>
* interval of the given buffer differ from symbols of given <code>'chars'</code> arguments;
* given offset is returned if it is outside of given buffer bounds;
* <code>'-1'</code> is returned if all document symbols that precede given offset differ from symbols
* of the given <code>'chars to exclude'</code>
*/
public static int shiftBackwardUntil(@NotNull CharSequence buffer, int offset, @NotNull String chars) {
if (offset >= buffer.length()) return offset;
while (true) {
if (offset < 0) break;
char c = buffer.charAt(offset);
int i;
for (i = 0; i < chars.length(); i++) {
if (c == chars.charAt(i)) break;
}
if (i < chars.length()) break;
offset--;
}
return offset;
}
public static boolean regionMatches(@NotNull char[] buffer, int offset, int bufferEnd, @NotNull CharSequence s) {
final int len = s.length();
if (offset + len > bufferEnd) return false;
if (offset < 0) return false;
for (int i = 0; i < len; i++) {
if (buffer[offset + i] != s.charAt(i)) return false;
}
return true;
}
public static boolean regionMatches(@NotNull CharSequence buffer, int offset, int bufferEnd, @NotNull CharSequence s) {
final int len = s.length();
if (offset + len > bufferEnd) return false;
if (offset < 0) return false;
//if (buffer instanceof String && s instanceof String) {
// return ((String)buffer).regionMatches(offset, (String)s, 0, len);
//}
for (int i = 0; i < len; i++) {
if (buffer.charAt(offset + i) != s.charAt(i)) return false;
}
return true;
}
public static boolean regionMatches(@NotNull CharSequence buffer, int offset, @NotNull CharSequence s) {
if (offset + s.length() > buffer.length()) return false;
if (offset < 0) return false;
for (int i = 0; i < s.length(); i++) {
if (buffer.charAt(offset + i) != s.charAt(i)) return false;
}
return true;
}
public static boolean equals(@NotNull char[] buffer1, int start1, int end1, @NotNull char[] buffer2, int start2, int end2) {
if (end1 - start1 != end2 - start2) return false;
for (int i = start1; i < end1; i++) {
if (buffer1[i] != buffer2[i - start1 + start2]) return false;
}
return true;
}
public static int indexOf(@NotNull char[] buffer, @NotNull String pattern, int fromIndex) {
char[] chars = pattern.toCharArray();
int limit = buffer.length - chars.length + 1;
if (fromIndex < 0) {
fromIndex = 0;
}
SearchLoop:
for (int i = fromIndex; i < limit; i++) {
for (int j = 0; j < chars.length; j++) {
if (chars[j] != buffer[i + j]) continue SearchLoop;
}
return i;
}
return -1;
}
public static int indexOf(@NotNull CharSequence buffer, @NotNull CharSequence pattern, int fromIndex) {
return indexOf(buffer, pattern, fromIndex, buffer.length());
}
/**
* Tries to find index of given pattern at the given buffer.
* <p/>
* <b>Note:</b> given <code>'toIndex'</code> value restricts examination to <code>'toIndex -1'</code> value (exclusive).. I.e. invocation like below
* doesn't find the match:
* <pre>
* String buffer = "aab";
* String pattern = "ab";
* CharArrayUtil.indexOf(buffer, pattern, 0, buffer.length()); // right boundary is "aab".length() - 1 = 2 (exclusive)
* </pre>
* <p/>
* This is historical behavior and is not going to be changed in order to preserve backward compatibility.
*
* @param buffer characters buffer which contents should be checked for the given pattern
* @param pattern target characters sequence to find at the given buffer
* @param fromIndex start index (inclusive). Zero is used if given index is negative
* @param toIndex defines end index (exclusive) by the formula <code>'toIndex - 1'</code>
* @return index of the given pattern at the given buffer if the match is found; <code>-1</code> otherwise
*/
public static int indexOf(@NotNull CharSequence buffer, @NotNull CharSequence pattern, int fromIndex, final int toIndex) {
final int patternLength = pattern.length();
int limit = toIndex - patternLength + 1;
if (fromIndex < 0) {
fromIndex = 0;
}
SearchLoop:
for (int i = fromIndex; i < limit; i++) {
for (int j = 0; j < patternLength; j++) {
if (pattern.charAt(j) != buffer.charAt(i + j)) continue SearchLoop;
}
return i;
}
return -1;
}
/**
* Tries to find index that points to the first location of the given symbol at the given char array at range <code>[from; to)</code>.
*
* @param buffer target symbols holder to check
* @param symbol target symbol which offset should be found
* @param fromIndex start index to search (inclusive)
* @param toIndex end index to search (exclusive)
* @return index that points to the first location of the given symbol at the given char array at range
* <code>[from; to)</code> if target symbol is found;
* <code>-1</code> otherwise
*/
public static int indexOf(@NotNull char[] buffer, final char symbol, int fromIndex, final int toIndex) {
if (fromIndex < 0) {
fromIndex = 0;
}
for (int i = fromIndex; i < toIndex; i++) {
if (buffer[i] == symbol) {
return i;
}
}
return -1;
}
/**
* Tries to find index that points to the last location of the given symbol at the given char array at range <code>[from; to)</code>.
*
* @param buffer target symbols holder to check
* @param symbol target symbol which offset should be found
* @param fromIndex start index to search (inclusive)
* @param toIndex end index to search (exclusive)
* @return index that points to the last location of the given symbol at the given char array at range
* <code>[from; to)</code> if target symbol is found;
* <code>-1</code> otherwise
*/
public static int lastIndexOf(@NotNull char[] buffer, final char symbol, int fromIndex, final int toIndex) {
if (fromIndex < 0) {
fromIndex = 0;
}
for (int i = toIndex - 1; i >= fromIndex; i--) {
if (buffer[i] == symbol) {
return i;
}
}
return -1;
}
public static int lastIndexOf(@NotNull CharSequence buffer, @NotNull String pattern, int maxIndex) {
char[] chars = pattern.toCharArray();
int end = buffer.length() - chars.length;
if (maxIndex > end) {
maxIndex = end;
}
SearchLoop:
for (int i = maxIndex; i >= 0; i--) {
for (int j = 0; j < chars.length; j++) {
if (chars[j] != buffer.charAt(i + j)) continue SearchLoop;
}
return i;
}
return -1;
}
public static int lastIndexOf(@NotNull char[] buffer, @NotNull String pattern, int maxIndex) {
char[] chars = pattern.toCharArray();
int end = buffer.length - chars.length;
if (maxIndex > end) {
maxIndex = end;
}
SearchLoop:
for (int i = maxIndex; i >= 0; i--) {
for (int j = 0; j < chars.length; j++) {
if (chars[j] != buffer[i + j]) continue SearchLoop;
}
return i;
}
return -1;
}
@NotNull
public static byte[] toByteArray(@NotNull char[] chars) throws IOException {
return toByteArray(chars, chars.length);
}
@NotNull
public static byte[] toByteArray(@NotNull char[] chars, int size) throws IOException {
ByteArrayOutputStream out = new ByteArrayOutputStream();
OutputStreamWriter writer = new OutputStreamWriter(out);
try {
writer.write(chars, 0, size);
}
finally {
writer.close();
}
return out.toByteArray();
}
public static boolean containsOnlyWhiteSpaces(@Nullable CharSequence chars) {
if (chars == null) return true;
for (int i = 0; i < chars.length(); i++) {
final char c = chars.charAt(i);
if (c == ' ' || c == '\t' || c == '\n' || c == '\r') continue;
return false;
}
return true;
}
//Commented in order to apply to green code policy as the method is unused.
//
//public static boolean subArraysEqual(char[] ca1, int startOffset1, int endOffset1,char[] ca2, int startOffset2, int endOffset2) {
// if (endOffset1 - startOffset1 != endOffset2 - startOffset2) return false;
// for (int i = startOffset1; i < endOffset1; i++) {
// char c1 = ca1[i];
// char c2 = ca2[i - startOffset1 + startOffset2];
// if (c1 != c2) return false;
// }
// return true;
//}
@NotNull
public static TextRange[] getIndents(@NotNull CharSequence charsSequence, int shift) {
List<TextRange> result = new ArrayList<TextRange>();
int whitespaceEnd = -1;
int lastTextFound = 0;
for(int i = charsSequence.length() - 1; i >= 0; i--){
final char charAt = charsSequence.charAt(i);
final boolean isWhitespace = Character.isWhitespace(charAt);
if(charAt == '\n'){
result.add(new TextRange(i, (whitespaceEnd >= 0 ? whitespaceEnd : i) + 1).shiftRight(shift));
whitespaceEnd = -1;
}
else if(whitespaceEnd >= 0 ){
if(isWhitespace){
continue;
}
lastTextFound = result.size();
whitespaceEnd = -1;
}
else if(isWhitespace){
whitespaceEnd = i;
} else {
lastTextFound = result.size();
}
}
if(whitespaceEnd > 0) result.add(new TextRange(0, whitespaceEnd + 1).shiftRight(shift));
if (lastTextFound < result.size()) {
result = result.subList(0, lastTextFound);
}
return result.toArray(new TextRange[result.size()]);
}
public static boolean containLineBreaks(@NotNull CharSequence seq) {
return containLineBreaks(seq, 0, seq.length());
}
public static boolean containLineBreaks(@Nullable CharSequence seq, int fromOffset, int endOffset) {
if (seq == null) return false;
for (int i = fromOffset; i < endOffset; i++) {
final char c = seq.charAt(i);
if (c == '\n' || c == '\r') return true;
}
return false;
}
/**
* Allows to answer if target region of the given text contains only white space symbols (tabulations, white spaces and line feeds).
*
* @param text text to check
* @param start start offset within the given text to check (inclusive)
* @param end end offset within the given text to check (exclusive)
* @return <code>true</code> if target region of the given text contains white space symbols only; <code>false</code> otherwise
*/
public static boolean isEmptyOrSpaces(@NotNull CharSequence text, int start, int end) {
for (int i = start; i < end; i++) {
char c = text.charAt(i);
if (c != ' ' && c != '\t' && c != '\n') {
return false;
}
}
return true;
}
@NotNull
public static Reader readerFromCharSequence(@NotNull CharSequence text) {
char[] chars = fromSequenceWithoutCopying(text);
//noinspection IOResourceOpenedButNotSafelyClosed
return chars == null ? new CharSequenceReader(text.toString()) : new UnsyncCharArrayReader(chars, 0, text.length());
}
}