
io.questdb.std.Chars Maven / Gradle / Ivy
Show all versions of questdb Show documentation
/*******************************************************************************
* ___ _ ____ ____
* / _ \ _ _ ___ ___| |_| _ \| __ )
* | | | | | | |/ _ \/ __| __| | | | _ \
* | |_| | |_| | __/\__ \ |_| |_| | |_) |
* \__\_\\__,_|\___||___/\__|____/|____/
*
* Copyright (c) 2014-2019 Appsicle
* Copyright (c) 2019-2024 QuestDB
*
* 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 io.questdb.std;
import io.questdb.cairo.CairoException;
import io.questdb.griffin.engine.functions.constants.CharConstant;
import io.questdb.griffin.engine.functions.str.TrimType;
import io.questdb.std.str.CharSink;
import io.questdb.std.str.Path;
import io.questdb.std.str.StringSink;
import io.questdb.std.str.Utf16Sink;
import io.questdb.std.str.Utf8Sink;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.nio.ByteBuffer;
import java.util.Arrays;
import static io.questdb.std.Numbers.hexDigits;
public final class Chars {
static final String[] CHAR_STRINGS;
static final char[] base64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".toCharArray();
// inverted alphabets for base64 decoding could be just byte arrays. this would save 3 * 128 bytes per alphabet
// but benchmarks show that int arrays make decoding faster.
// it's probably because it does not require any type conversions in the hot decoding loop
static final int[] base64Inverted = base64CreateInvertedAlphabet(base64);
static final char[] base64Url = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_".toCharArray();
static final int[] base64UrlInverted = base64CreateInvertedAlphabet(base64Url);
private Chars() {
}
/**
* Avoid this when possible due to the allocation of a new char array
* This should be only used when a JDK method forces you to use a byte array
*
* It's responsibility of the caller to ensure that the input string is ASCII
*
* @param ascii ascii string to convert to byte array
* @return byte array representation of the input string
*/
public static byte[] asciiToByteArray(@NotNull CharSequence ascii) {
byte[] dst = new byte[ascii.length()];
for (int i = 0, n = ascii.length(); i < n; i++) {
assert ascii.charAt(i) < 128;
dst[i] = (byte) ascii.charAt(i);
}
return dst;
}
public static void base64Decode(CharSequence encoded, @NotNull Utf8Sink target) {
base64Decode(encoded, target, base64Inverted);
}
/**
* Decodes base64u encoded string into a byte buffer.
*
* This method does not check for padding. It's up to the caller to ensure that the target
* buffer has enough space to accommodate decoded data. Otherwise, {@link java.nio.BufferOverflowException}
* will be thrown.
*
* @param encoded base64 encoded string
* @param target target buffer
* @throws CairoException if encoded string is invalid
* @throws java.nio.BufferOverflowException if target buffer is too small
*/
public static void base64Decode(CharSequence encoded, @NotNull ByteBuffer target) {
base64Decode(encoded, target, base64Inverted);
}
public static void base64Encode(@Nullable BinarySequence sequence, int maxLength, @NotNull CharSink> buffer) {
int pad = base64Encode(sequence, maxLength, buffer, base64);
for (int j = 0; j < pad; j++) {
buffer.putAscii("=");
}
}
public static void base64UrlDecode(@Nullable CharSequence encoded, @NotNull Utf8Sink target) {
base64Decode(encoded, target, base64UrlInverted);
}
/**
* Decodes base64url encoded string into a byte buffer.
*
* This method does not check for padding. It's up to the caller to ensure that the target
* buffer has enough space to accommodate decoded data. Otherwise, {@link java.nio.BufferOverflowException}
* will be thrown.
*
* @param encoded base64url encoded string
* @param target target buffer
* @throws CairoException if encoded string is invalid
* @throws java.nio.BufferOverflowException if target buffer is too small
*/
public static void base64UrlDecode(CharSequence encoded, ByteBuffer target) {
base64Decode(encoded, target, base64UrlInverted);
}
public static void base64UrlEncode(BinarySequence sequence, int maxLength, CharSink> buffer) {
base64Encode(sequence, maxLength, buffer, base64Url);
// base64 url does not use padding
}
public static int charBytes(char c) {
if (c < 0x80) {
return 1;
} else if (c < 0x800) {
return 2;
} else if (Character.isSurrogate(c)) {
return 1; // replaced with '?'
} else {
return 3;
}
}
public static int compare(CharSequence l, CharSequence r) {
if (l == r) {
return 0;
}
if (l == null) {
return -1;
}
if (r == null) {
return 1;
}
final int ll = l.length();
final int rl = r.length();
final int min = Math.min(ll, rl);
for (int i = 0; i < min; i++) {
final int k = l.charAt(i) - r.charAt(i);
if (k != 0) {
return k;
}
}
return Integer.compare(ll, rl);
}
public static int compareDescending(CharSequence l, CharSequence r) {
return compare(r, l);
}
public static boolean contains(@NotNull CharSequence sequence, @NotNull CharSequence term) {
return indexOf(sequence, 0, sequence.length(), term) != -1;
}
// Term has to be lower-case.
public static boolean containsLowerCase(@NotNull CharSequence sequence, @NotNull CharSequence termLC) {
return indexOfLowerCase(sequence, 0, sequence.length(), termLC) != -1;
}
public static void copyStrChars(CharSequence value, int pos, int len, long address) {
for (int i = 0; i < len; i++) {
char c = value.charAt(i + pos);
Unsafe.getUnsafe().putChar(address + 2L * i, c);
}
}
public static boolean empty(@Nullable CharSequence value) {
return value == null || value.length() < 1;
}
public static boolean endsWith(CharSequence cs, CharSequence ends) {
if (ends == null || cs == null) {
return false;
}
int l = ends.length();
if (l == 0) {
return true;
}
int csl = cs.length();
return !(csl == 0 || csl < l) && equals(ends, cs, csl - l, csl);
}
public static boolean endsWith(@Nullable CharSequence cs, char c) {
if (cs == null) {
return false;
}
final int csl = cs.length();
return csl != 0 && c == cs.charAt(csl - 1);
}
// Pattern has to be in lower-case.
public static boolean endsWithLowerCase(@Nullable CharSequence cs, @Nullable CharSequence endsLC) {
if (endsLC == null || cs == null) {
return false;
}
int l = endsLC.length();
if (l == 0) {
return true;
}
int csl = cs.length();
return !(csl == 0 || csl < l) && equalsLowerCase(endsLC, cs, csl - l, csl);
}
public static boolean equals(@NotNull CharSequence l, @NotNull CharSequence r) {
if (l == r) {
return true;
}
int ll;
if ((ll = l.length()) != r.length()) {
return false;
}
return equalsChars(l, r, ll);
}
public static boolean equals(@NotNull String l, @NotNull String r) {
return l.equals(r);
}
public static boolean equals(@NotNull CharSequence l, @NotNull CharSequence r, int rLo, int rHi) {
if (l == r) {
return true;
}
int ll = l.length();
if (ll != rHi - rLo) {
return false;
}
for (int i = 0; i < ll; i++) {
if (l.charAt(i) != r.charAt(i + rLo)) {
return false;
}
}
return true;
}
public static boolean equals(@NotNull CharSequence l, int lLo, int lHi, @NotNull CharSequence r, int rLo, int rHi) {
if (l == r) {
return true;
}
int ll = lHi - lLo;
if (ll != rHi - rLo) {
return false;
}
for (int i = 0; i < ll; i++) {
if (l.charAt(i + lLo) != r.charAt(i + rLo)) {
return false;
}
}
return true;
}
public static boolean equals(@NotNull CharSequence l, char r) {
return l.length() == 1 && l.charAt(0) == r;
}
/**
* Case-insensitive comparison of two char sequences.
*
* @param l left sequence
* @param r right sequence
* @return true if sequences match exactly (ignoring char case)
*/
public static boolean equalsIgnoreCase(@NotNull CharSequence l, @NotNull CharSequence r) {
if (l == r) {
return true;
}
int ll = l.length();
if (ll != r.length()) {
return false;
}
return equalsCharsIgnoreCase(l, r, ll);
}
/**
* Case-insensitive comparison of two char sequences, with subsequence over second.
*
* @param l left sequence
* @param r right sequence
* @param rLo right sequence lower bound
* @param rHi right sequence upper bound
* @return true if sequences match exactly (ignoring char case)
*/
public static boolean equalsIgnoreCase(@NotNull CharSequence l, @NotNull CharSequence r, int rLo, int rHi) {
if (l == r) {
return true;
}
int ll = l.length();
if (ll != rHi - rLo) {
return false;
}
return equalsCharsIgnoreCase(l, r, ll, rLo, rHi);
}
public static boolean equalsIgnoreCaseNc(@NotNull CharSequence l, @Nullable CharSequence r) {
return r != null && equalsIgnoreCase(l, r);
}
// Left side has to be lower-case.
public static boolean equalsLowerCase(@NotNull CharSequence lLC, @NotNull CharSequence r, int rLo, int rHi) {
if (lLC == r) {
return true;
}
int ll = lLC.length();
if (ll != rHi - rLo) {
return false;
}
for (int i = 0; i < ll; i++) {
if (lLC.charAt(i) != Character.toLowerCase(r.charAt(i + rLo))) {
return false;
}
}
return true;
}
public static boolean equalsLowerCase(@NotNull CharSequence l, int lLo, int lHi, @NotNull CharSequence r, int rLo, int rHi) {
if (l == r) {
return true;
}
int ll = lHi - lLo;
if (ll != rHi - rLo) {
return false;
}
for (int i = 0; i < ll; i++) {
if (Character.toLowerCase(l.charAt(i + lLo)) != Character.toLowerCase(r.charAt(i + rLo))) {
return false;
}
}
return true;
}
public static boolean equalsLowerCaseAscii(@NotNull CharSequence l, int lLo, int lHi, @NotNull CharSequence r, int rLo, int rHi) {
if (l == r) {
return true;
}
int ll = lHi - lLo;
if (ll != rHi - rLo) {
return false;
}
for (int i = 0; i < ll; i++) {
if (toLowerCaseAscii(l.charAt(i + lLo)) != toLowerCaseAscii(r.charAt(i + rLo))) {
return false;
}
}
return true;
}
public static boolean equalsLowerCaseAscii(@NotNull CharSequence l, @NotNull CharSequence r) {
int ll = l.length();
if (ll != r.length()) {
return false;
}
for (int i = 0; i < ll; i++) {
if (toLowerCaseAscii(l.charAt(i)) != toLowerCaseAscii(r.charAt(i))) {
return false;
}
}
return true;
}
public static boolean equalsLowerCaseAsciiNc(@NotNull CharSequence l, @Nullable CharSequence r) {
return r != null && equalsLowerCaseAscii(l, r);
}
public static boolean equalsNc(@Nullable CharSequence l, char r) {
return (l == null && r == CharConstant.ZERO.getChar(null)) || (l != null && equals(l, r));
}
public static boolean equalsNc(@NotNull CharSequence l, @Nullable CharSequence r) {
return r != null && equals(l, r);
}
public static boolean equalsNc(CharSequence l, CharSequence r, int rLo, int rHi) {
return l != null && equals(l, r, rLo, rHi);
}
public static boolean equalsNullable(@Nullable CharSequence l, @Nullable CharSequence r) {
if (l == null && r == null) {
return true;
}
if (l == null || r == null) {
return false;
}
int ll;
if ((ll = l.length()) != r.length()) {
return false;
}
return equalsChars(l, r, ll);
}
/**
* Strictly greater than (>) comparison of two UTF16 sequences in lexicographical
* order. For example, for:
* l = aaaaa
* r = aaaaaaa
* the l > r will produce "false", however for:
* l = bbbb
* r = aaaaaaa
* the l > r will produce "true", because b > a.
*
* @param l left sequence, can be null
* @param r right sequence, can be null
* @return if either l or r is "null", the return value false, otherwise sequences are compared lexicographically.
*/
public static boolean greaterThan(@Nullable CharSequence l, @Nullable CharSequence r) {
if (l == null || r == null) {
return false;
}
final int ll = l.length();
final int rl = r.length();
final int min = Math.min(ll, rl);
for (int i = 0; i < min; i++) {
final int k = l.charAt(i) - r.charAt(i);
if (k != 0) {
return k > 0;
}
}
return ll > rl;
}
public static int hashCode(@NotNull CharSequence value, int lo, int hi) {
if (hi == lo) {
return 0;
}
int h = 0;
for (int p = lo; p < hi; p++) {
h = 31 * h + value.charAt(p);
}
return h;
}
public static int hashCode(char @NotNull [] value, int lo, int hi) {
if (hi == lo) {
return 0;
}
int h = 0;
for (int p = lo; p < hi; p++) {
h = 31 * h + value[p];
}
return h;
}
public static int hashCode(@NotNull CharSequence value) {
if (value instanceof String) {
return value.hashCode();
}
int len = value.length();
if (len == 0) {
return 0;
}
int h = 0;
for (int p = 0; p < len; p++) {
h = 31 * h + value.charAt(p);
}
return h;
}
public static int hashCode(@NotNull String value) {
return value.hashCode();
}
public static int indexOf(@NotNull CharSequence seq, int seqLo, int seqHi, @NotNull CharSequence term) {
int termLen = term.length();
if (termLen == 0) {
return 0;
}
char first = term.charAt(0);
int max = seqHi - termLen;
for (int i = seqLo; i <= max; ++i) {
if (seq.charAt(i) != first) {
do {
++i;
} while (i <= max && seq.charAt(i) != first);
}
if (i <= max) {
int j = i + 1;
int end = j + termLen - 1;
for (int k = 1; j < end && seq.charAt(j) == term.charAt(k); ++k) {
++j;
}
if (j == end) {
return i;
}
}
}
return -1;
}
public static int indexOf(@NotNull CharSequence seq, int seqLo, int seqHi, @NotNull CharSequence term, int occurrence) {
int m = term.length();
if (m == 0) {
return 0;
}
if (occurrence == 0) {
return -1;
}
int foundIndex = -1;
int count = 0;
if (occurrence > 0) {
for (int i = seqLo; i < seqHi; i++) {
if (foundIndex == -1) {
if (seqHi - i < m) {
return -1;
}
if (seq.charAt(i) == term.charAt(0)) {
foundIndex = i;
}
} else { // first character matched, try to match the rest of the term
if (seq.charAt(i) != term.charAt(i - foundIndex)) {
// start again from after where the first character was found
i = foundIndex;
foundIndex = -1;
}
}
if (foundIndex != -1 && i - foundIndex == m - 1) {
count++;
if (count == occurrence) {
return foundIndex;
} else {
foundIndex = -1;
}
}
}
} else { // if occurrence is negative, search in reverse
for (int i = seqHi - 1; i >= seqLo; i--) {
if (foundIndex == -1) {
if (i - seqLo + 1 < m) {
return -1;
}
if (seq.charAt(i) == term.charAt(m - 1)) {
foundIndex = i;
}
} else { // last character matched, try to match the rest of the term
if (seq.charAt(i) != term.charAt(m - 1 + i - foundIndex)) {
// start again from after where the first character was found
i = foundIndex;
foundIndex = -1;
}
}
if (foundIndex != -1 && foundIndex - i == m - 1) {
count--;
if (count == occurrence) {
return foundIndex + 1 - m;
} else {
foundIndex = -1;
}
}
}
}
return -1;
}
public static int indexOf(CharSequence seq, char c) {
return indexOf(seq, 0, c);
}
public static int indexOf(CharSequence seq, final int seqLo, char c) {
return indexOf(seq, seqLo, seq.length(), c);
}
public static int indexOf(CharSequence seq, int seqLo, int seqHi, char c) {
return indexOf(seq, seqLo, seqHi, c, 1);
}
public static int indexOf(CharSequence seq, int seqLo, int seqHi, char ch, int occurrence) {
if (occurrence == 0) {
return -1;
}
int count = 0;
if (occurrence > 0) {
for (int i = seqLo; i < seqHi; i++) {
if (seq.charAt(i) == ch) {
count++;
if (count == occurrence) {
return i;
}
}
}
} else { // if occurrence is negative, search in reverse
for (int i = seqHi - 1; i >= seqLo; i--) {
if (seq.charAt(i) == ch) {
count--;
if (count == occurrence) {
return i;
}
}
}
}
return -1;
}
// Term has to be lower-case.
public static int indexOfLowerCase(@NotNull CharSequence seq, int seqLo, int seqHi, @NotNull CharSequence termLC) {
int termLen = termLC.length();
if (termLen == 0) {
return 0;
}
char first = termLC.charAt(0);
int max = seqHi - termLen;
for (int i = seqLo; i <= max; ++i) {
if (Character.toLowerCase(seq.charAt(i)) != first) {
do {
++i;
} while (i <= max && Character.toLowerCase(seq.charAt(i)) != first);
}
if (i <= max) {
int j = i + 1;
int end = j + termLen - 1;
for (int k = 1; j < end && Character.toLowerCase(seq.charAt(j)) == termLC.charAt(k); ++k) {
++j;
}
if (j == end) {
return i;
}
}
}
return -1;
}
public static int indexOfUnquoted(@NotNull CharSequence seq, char ch) {
return indexOfUnquoted(seq, ch, 0, seq.length(), 1);
}
public static int indexOfUnquoted(@NotNull CharSequence seq, char ch, int seqLo, int seqHi, int occurrence) {
if (occurrence == 0) {
return -1;
}
int count = 0;
boolean inQuotes = false;
if (occurrence > 0) {
for (int i = seqLo; i < seqHi; i++) {
if (seq.charAt(i) == '\"') {
inQuotes = !inQuotes;
}
if (seq.charAt(i) == ch && !inQuotes) {
count++;
if (count == occurrence) {
return i;
}
}
}
} else { // if occurrence is negative, search in reverse
for (int i = seqHi - 1; i >= seqLo; i--) {
if (seq.charAt(i) == '\"') {
inQuotes = !inQuotes;
}
if (seq.charAt(i) == ch && !inQuotes) {
count--;
if (count == occurrence) {
return i;
}
}
}
}
return -1;
}
public static boolean isAscii(@NotNull CharSequence cs) {
for (int i = 0, n = cs.length(); i < n; i++) {
if (cs.charAt(i) > 127) {
return false;
}
}
return true;
}
public static boolean isBlank(CharSequence s) {
if (s == null) {
return true;
}
int len = s.length();
for (int i = 0; i < len; i++) {
int c = s.charAt(i);
if (!Character.isWhitespace(c)) {
return false;
}
}
return true;
}
public static boolean isOnlyDecimals(CharSequence s) {
int len = s.length();
for (int i = len - 1; i > -1; i--) {
int digit = s.charAt(i);
if (digit < '0' || digit > '9') {
return false;
}
}
return len > 0;
}
public static boolean isQuote(char c) {
switch (c) {
case '\'':
case '"':
case '`':
return true;
default:
return false;
}
}
public static boolean isQuoted(CharSequence s) {
if (s == null || s.length() < 2) {
return false;
}
char open = s.charAt(0);
return isQuote(open) && open == s.charAt(s.length() - 1);
}
public static int lastIndexOf(@NotNull CharSequence sequence, int sequenceLo, int sequenceHi, @NotNull CharSequence term) {
return indexOf(sequence, sequenceLo, sequenceHi, term, -1);
}
/**
* Strictly greater than (<) comparison of two UTF16 sequences in lexicographical
* order. For example, for:
* l = aaaaa
* r = aaaaaaa
* the l > r will produce "false", however for:
* l = bbbb
* r = aaaaaaa
* the l < r will produce "true", because b < a.
*
* @param l left sequence, can be null
* @param r right sequence, can be null
* @return if either l or r is "null", the return value false, otherwise sequences are compared lexicographically.
*/
public static boolean lessThan(@Nullable CharSequence l, @Nullable CharSequence r) {
if (l == null || r == null) {
return false;
}
final int ll = l.length();
final int rl = r.length();
final int min = Math.min(ll, rl);
for (int i = 0; i < min; i++) {
final int k = l.charAt(i) - r.charAt(i);
if (k != 0) {
return k < 0;
}
}
return ll < rl;
}
public static boolean lessThan(@Nullable CharSequence l, @Nullable CharSequence r, boolean negated) {
final boolean eq = Chars.equalsNullable(l, r);
return negated ? (eq || Chars.greaterThan(l, r)) : (!eq && Chars.lessThan(l, r));
}
public static int lowerCaseAsciiHashCode(CharSequence value, int lo, int hi) {
if (hi == lo) {
return 0;
}
int h = 0;
for (int p = lo; p < hi; p++) {
h = 31 * h + toLowerCaseAscii(value.charAt(p));
}
return h;
}
public static int lowerCaseAsciiHashCode(CharSequence value) {
int len = value.length();
if (len == 0) {
return 0;
}
int h = 0;
for (int p = 0; p < len; p++) {
h = 31 * h + toLowerCaseAscii(value.charAt(p));
}
return h;
}
public static int lowerCaseHashCode(CharSequence value, int lo, int hi) {
if (hi == lo) {
return 0;
}
int h = 0;
for (int p = lo; p < hi; p++) {
h = 31 * h + Character.toLowerCase(value.charAt(p));
}
return h;
}
public static int lowerCaseHashCode(CharSequence value) {
int len = value.length();
if (len == 0) {
return 0;
}
int h = 0;
for (int p = 0; p < len; p++) {
h = 31 * h + Character.toLowerCase(value.charAt(p));
}
return h;
}
public static boolean noMatch(CharSequence l, int llo, int lhi, CharSequence r, int rlo, int rhi) {
int lp = llo;
int rp = rlo;
while (lp < lhi && rp < rhi) {
if (Character.toLowerCase(l.charAt(lp++)) != r.charAt(rp++)) {
return true;
}
}
return lp != lhi || rp != rhi;
}
public static CharSequence repeat(String s, int times) {
return new CharSequence() {
@Override
public char charAt(int index) {
return s.charAt(index % s.length());
}
@Override
public int length() {
return s.length() * times;
}
@Override
@NotNull
public CharSequence subSequence(int start, int end) {
throw new UnsupportedOperationException();
}
};
}
/**
* Split character sequence into a list of lpsz strings. This function
* uses space as a delimiter and it honours spaces in double quotes. Main
* use for this code is to produce list of C-compatible argument values from
* command line.
*
* @param args command line
* @return list of 0-terminated strings
*/
public static ObjList splitLpsz(CharSequence args) {
final ObjList paths = new ObjList<>();
int n = args.length();
int lastLen = 0;
int lastIndex = 0;
boolean inQuote = false;
for (int i = 0; i < n; i++) {
char b = args.charAt(i);
switch (b) {
case ' ':
// ab c
if (lastLen > 0) {
if (inQuote) {
lastLen++;
} else {
paths.add(new Path().of(args, lastIndex, lastLen + lastIndex));
lastLen = 0;
}
}
break;
case '"':
inQuote = !inQuote;
break;
default:
if (lastLen == 0) {
lastIndex = i;
}
lastLen++;
break;
}
}
if (lastLen > 0) {
paths.add(new Path().of(args, lastIndex, lastLen + lastIndex));
}
return paths;
}
public static boolean startsWith(@Nullable CharSequence cs, @Nullable CharSequence starts) {
if (cs == null || starts == null) {
return false;
}
int l = starts.length();
return l == 0 || cs.length() >= l && equalsChars(cs, starts, l);
}
public static boolean startsWith(CharSequence _this, int thisLo, int thisHi, CharSequence that) {
int len = that.length();
int thisLen = thisHi - thisLo;
if (thisLen < len) {
return false;
}
for (int i = 0; i < len; i++) {
if (_this.charAt(thisLo + i) != that.charAt(i)) {
return false;
}
}
return true;
}
public static boolean startsWith(CharSequence _this, char c) {
return _this.length() > 0 && _this.charAt(0) == c;
}
public static boolean startsWithIgnoreCase(CharSequence cs, CharSequence startsWith) {
if (cs == null || startsWith == null) {
return false;
}
int l = startsWith.length();
if (l == 0) {
return true;
}
return cs.length() >= l && equalsWithIgnoreCase(startsWith, cs, l);
}
// Pattern has to be lower-case.
public static boolean startsWithLowerCase(@Nullable CharSequence cs, @Nullable CharSequence startsLC) {
if (cs == null || startsLC == null) {
return false;
}
int l = startsLC.length();
if (l == 0) {
return true;
}
return cs.length() >= l && equalsCharsLowerCase(startsLC, cs, l);
}
public static void toLowerCase(@Nullable CharSequence str, @NotNull CharSink> sink) {
if (str != null) {
toLowerCase(str, 0, str.length(), sink);
}
}
public static void toLowerCase(@NotNull CharSequence str, int lo, int hi, @NotNull CharSink> sink) {
for (int i = lo; i < hi; i++) {
sink.put(Character.toLowerCase(str.charAt(i)));
}
}
public static String toLowerCaseAscii(@Nullable CharSequence value) {
if (value == null) {
return null;
}
final int len = value.length();
if (len == 0) {
return "";
}
final Utf16Sink b = Misc.getThreadLocalSink();
for (int i = 0; i < len; i++) {
b.put(toLowerCaseAscii(value.charAt(i)));
}
return b.toString();
}
public static char toLowerCaseAscii(char character) {
return character > 64 && character < 91 ? (char) (character + 32) : character;
}
public static void toSink(@Nullable BinarySequence sequence, @NotNull CharSink> sink) {
if (sequence == null) {
return;
}
// limit what we print
int len = (int) sequence.length();
for (int i = 0; i < len; i++) {
if (i > 0) {
if ((i % 16) == 0) {
sink.put('\n');
Numbers.appendHexPadded(sink, i);
}
} else {
Numbers.appendHexPadded(sink, i);
}
sink.put(' ');
final byte b = sequence.byteAt(i);
final int v;
if (b < 0) {
v = 256 + b;
} else {
v = b;
}
if (v < 0x10) {
sink.put('0');
sink.put(hexDigits[b]);
} else {
sink.put(hexDigits[v / 0x10]);
sink.put(hexDigits[v % 0x10]);
}
}
}
public static String toString(char value) {
if (value < CHAR_STRINGS.length) {
return CHAR_STRINGS[value];
}
return Character.toString(value);
}
public static String toString(CharSequence s) {
return s == null ? null : s.toString();
}
public static String toString(CharSequence cs, int start, int end) {
final Utf16Sink b = Misc.getThreadLocalSink();
b.put(cs, start, end);
return b.toString();
}
public static String toString(@NotNull CharSequence cs, int start, int end, char unescape) {
final Utf16Sink b = Misc.getThreadLocalSink();
final int lastChar = end - 1;
for (int i = start; i < end; i++) {
char c = cs.charAt(i);
b.put(c);
if (c == unescape && i < lastChar && cs.charAt(i + 1) == unescape) {
i++;
}
}
return b.toString();
}
public static void toUpperCase(@Nullable CharSequence str, @NotNull CharSink> sink) {
if (str != null) {
final int len = str.length();
for (int i = 0; i < len; i++) {
sink.put(Character.toUpperCase(str.charAt(i)));
}
}
}
public static void trim(TrimType type, CharSequence str, StringSink sink) {
if (str == null) {
return;
}
int startIdx = 0;
int endIdx = str.length() - 1;
if (type == TrimType.LTRIM || type == TrimType.TRIM) {
while (startIdx < endIdx && str.charAt(startIdx) == ' ') {
startIdx++;
}
}
if (type == TrimType.RTRIM || type == TrimType.TRIM) {
while (startIdx < endIdx && str.charAt(endIdx) == ' ') {
endIdx--;
}
}
sink.clear();
if (startIdx != endIdx) {
sink.put(str, startIdx, endIdx + 1);
}
}
private static int[] base64CreateInvertedAlphabet(char[] alphabet) {
int[] inverted = new int[128]; // ASCII only
Arrays.fill(inverted, (byte) -1);
int length = alphabet.length;
for (int i = 0; i < length; i++) {
char letter = alphabet[i];
assert letter < 128;
inverted[letter] = (byte) i;
}
return inverted;
}
private static void base64Decode(@Nullable CharSequence encoded, @NotNull Utf8Sink target, int[] invertedAlphabet) {
if (encoded == null) {
return;
}
// skip trailing '=' they are just for padding and have no meaning
int length = encoded.length();
for (; length > 0; length--) {
if (encoded.charAt(length - 1) != '=') {
break;
}
}
int remainder = length % 4;
int sourcePos = 0;
// first decode all 4 byte chunks. this is *the* hot loop, be careful when changing it
for (int end = length - remainder; sourcePos < end; sourcePos += 4) {
int b0 = base64InvertedLookup(invertedAlphabet, encoded.charAt(sourcePos)) << 18;
int b1 = base64InvertedLookup(invertedAlphabet, encoded.charAt(sourcePos + 1)) << 12;
int b2 = base64InvertedLookup(invertedAlphabet, encoded.charAt(sourcePos + 2)) << 6;
int b4 = base64InvertedLookup(invertedAlphabet, encoded.charAt(sourcePos + 3));
int wrk = b0 | b1 | b2 | b4;
// we use absolute positions to write to the byte buffer in the hot loop
// benchmarking shows that it is faster than using relative positions
target.putAny((byte) (wrk >>> 16));
target.putAny((byte) ((wrk >>> 8) & 0xFF));
target.putAny((byte) (wrk & 0xFF));
}
// now decode remainder
int wrk;
switch (remainder) {
case 0:
// nothing to do, yay!
break;
case 1:
// invalid encoding, we can't have 1 byte remainder as
// even 1 byte encodes to 2 chars
throw CairoException.nonCritical().put("invalid base64 encoding [string=").put(encoded).put(']');
case 2:
wrk = base64InvertedLookup(invertedAlphabet, encoded.charAt(sourcePos)) << 18;
wrk |= base64InvertedLookup(invertedAlphabet, encoded.charAt(sourcePos + 1)) << 12;
target.putAny((byte) (wrk >>> 16));
break;
case 3:
wrk = base64InvertedLookup(invertedAlphabet, encoded.charAt(sourcePos)) << 18;
wrk |= base64InvertedLookup(invertedAlphabet, encoded.charAt(sourcePos + 1)) << 12;
wrk |= base64InvertedLookup(invertedAlphabet, encoded.charAt(sourcePos + 2)) << 6;
target.putAny((byte) (wrk >>> 16));
target.putAny((byte) ((wrk >>> 8) & 0xFF));
}
}
private static void base64Decode(CharSequence encoded, ByteBuffer target, int[] invertedAlphabet) {
if (encoded == null) {
return;
}
assert target != null;
// skip trailing '=' they are just for padding and have no meaning
int length = encoded.length();
for (; length > 0; length--) {
if (encoded.charAt(length - 1) != '=') {
break;
}
}
int remainder = length % 4;
int sourcePos = 0;
int targetPos = target.position();
// first decode all 4 byte chunks. this is *the* hot loop, be careful when changing it
for (int end = length - remainder; sourcePos < end; sourcePos += 4, targetPos += 3) {
int b0 = base64InvertedLookup(invertedAlphabet, encoded.charAt(sourcePos)) << 18;
int b1 = base64InvertedLookup(invertedAlphabet, encoded.charAt(sourcePos + 1)) << 12;
int b2 = base64InvertedLookup(invertedAlphabet, encoded.charAt(sourcePos + 2)) << 6;
int b4 = base64InvertedLookup(invertedAlphabet, encoded.charAt(sourcePos + 3));
int wrk = b0 | b1 | b2 | b4;
// we use absolute positions to write to the byte buffer in the hot loop
// benchmarking shows that it is faster than using relative positions
target.put(targetPos, (byte) (wrk >>> 16));
target.put(targetPos + 1, (byte) ((wrk >>> 8) & 0xFF));
target.put(targetPos + 2, (byte) (wrk & 0xFF));
}
target.position(targetPos);
// now decode remainder
int wrk;
switch (remainder) {
case 0:
// nothing to do, yay!
break;
case 1:
// invalid encoding, we can't have 1 byte remainder as
// even 1 byte encodes to 2 chars
throw CairoException.nonCritical().put("invalid base64 encoding [string=").put(encoded).put(']');
case 2:
wrk = base64InvertedLookup(invertedAlphabet, encoded.charAt(sourcePos)) << 18;
wrk |= base64InvertedLookup(invertedAlphabet, encoded.charAt(sourcePos + 1)) << 12;
target.put((byte) (wrk >>> 16));
break;
case 3:
wrk = base64InvertedLookup(invertedAlphabet, encoded.charAt(sourcePos)) << 18;
wrk |= base64InvertedLookup(invertedAlphabet, encoded.charAt(sourcePos + 1)) << 12;
wrk |= base64InvertedLookup(invertedAlphabet, encoded.charAt(sourcePos + 2)) << 6;
target.put((byte) (wrk >>> 16));
target.put((byte) ((wrk >>> 8) & 0xFF));
}
}
private static int base64Encode(@Nullable BinarySequence sequence, int maxLength, @NotNull CharSink> buffer, char @NotNull [] alphabet) {
if (sequence == null) {
return 0;
}
final long len = Math.min(maxLength, sequence.length());
int pad = 0;
for (int i = 0; i < len; i += 3) {
int b = ((sequence.byteAt(i) & 0xFF) << 16) & 0xFFFFFF;
if (i + 1 < len) {
b |= (sequence.byteAt(i + 1) & 0xFF) << 8;
} else {
pad++;
}
if (i + 2 < len) {
b |= (sequence.byteAt(i + 2) & 0xFF);
} else {
pad++;
}
for (int j = 0; j < 4 - pad; j++) {
int c = (b & 0xFC0000) >> 18;
buffer.putAscii(alphabet[c]);
b <<= 6;
}
}
return pad;
}
private static int base64InvertedLookup(int[] invertedAlphabet, char ch) {
if (ch > 127) {
throw CairoException.nonCritical().put("non-ascii character while decoding base64 [ch=").put((int) (ch)).put(']');
}
int index = invertedAlphabet[ch];
if (index == -1) {
throw CairoException.nonCritical().put("invalid base64 character [ch=").put(ch).put(']');
}
return index;
}
private static boolean equalsChars(@NotNull CharSequence l, @NotNull CharSequence r, int len) {
for (int i = 0; i < len; i++) {
if (l.charAt(i) != r.charAt(i)) {
return false;
}
}
return true;
}
private static boolean equalsCharsIgnoreCase(@NotNull CharSequence l, @NotNull CharSequence r, int len) {
for (int i = 0; i < len; i++) {
if (Character.toLowerCase(l.charAt(i)) != Character.toLowerCase(r.charAt(i))) {
return false;
}
}
return true;
}
private static boolean equalsCharsIgnoreCase(@NotNull CharSequence l, @NotNull CharSequence r, int len, int rLo, int rHi) {
assert len == (rHi - rLo);
for (int i = 0; i < len; i++) {
if (Character.toLowerCase(l.charAt(i)) != Character.toLowerCase(r.charAt(i + rLo))) {
return false;
}
}
return true;
}
// Left side has to be lower-case.
private static boolean equalsCharsLowerCase(@NotNull CharSequence lLC, @NotNull CharSequence r, int len) {
for (int i = 0; i < len; i++) {
if (lLC.charAt(i) != Character.toLowerCase(r.charAt(i))) {
return false;
}
}
return true;
}
private static boolean equalsWithIgnoreCase(@NotNull CharSequence lLC, @NotNull CharSequence r, int len) {
for (int i = 0; i < len; i++) {
if (Character.toLowerCase(lLC.charAt(i)) != Character.toLowerCase(r.charAt(i))) {
return false;
}
}
return true;
}
static {
CHAR_STRINGS = new String[128];
for (char c = 0; c < 128; c++) {
CHAR_STRINGS[c] = Character.toString(c);
}
}
}