All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.osgl.util.FastStr Maven / Gradle / Ivy

There is a newer version: 1.30.0
Show newest version
package org.osgl.util;

/*-
 * #%L
 * Java Tool
 * %%
 * Copyright (C) 2014 - 2017 OSGL (Open Source General Library)
 * %%
 * 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.
 * #L%
 */

import org.osgl.$;

import java.io.UnsupportedEncodingException;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.charset.Charset;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import static java.lang.Character.*;

/**
 * FastStr implements the same contract of Str with the a char array.
 * This class is marked as Fast because of the following points:
 * 1. When unsafeOf(String) is called, it share the internal value (the char array) of the
 * String been passed in
 * 2. when unsafeOf(char[]) is called, it use the char array passed in directly without
 * copy operation
 * 3. subList and substring works at O(1) because it will NOT copy the internal char array
 * Note, this class shall be used with caution as it might prevent a very large char array
 * from been garbage collected when the char array is passed to a FastStr or substr of
 * the fast string
 */
public class FastStr extends StrBase
        implements RandomAccess, CharSequence, java.io.Serializable, Comparable {
    public static final FastStr EMPTY_STR = new FastStr() {
        @Override
        public String toString() {
            return "";
        }
    };

    @Override
    protected Class _impl() {
        return FastStr.class;
    }

    @Override
    protected FastStr _empty() {
        return EMPTY_STR;
    }

    private final char[] buf;

    // low end point inclusive
    private final int begin;

    // high end point exclusive
    private final int end;

    private int hash;

    private FastStr() {
        buf = new char[0];
        begin = 0;
        end = 0;
    }

    private FastStr(char[] buf) {
        this(buf, 0, buf.length);
    }

    private FastStr(char[] buf, int start, int end) {
        this.buf = buf;
        this.begin = start;
        this.end = end;
    }

    @Override
    public int length() {
        return end - begin;
    }

    @Override
    public boolean isEmpty() {
        return EMPTY_STR == this || buf.length == 0 || end <= begin ;
    }

    @Override
    public boolean isBlank() {
        if (isEmpty()) {
            return true;
        }
        for (int i = begin; i < end; ++i) {
            char c = buf[i];
            if (c > ' ') {
                return false;
            }
        }
        return true;
    }

    @Override
    public FastStr subList(int fromIndex, int toIndex) {
        if (fromIndex < 0) {
            throw new StringIndexOutOfBoundsException(fromIndex);
        }
        int len = size();
        if (toIndex > len) {
            throw new StringIndexOutOfBoundsException(toIndex);
        }
        int subLen = toIndex - fromIndex;
        if (subLen < 0) {
            throw new StringIndexOutOfBoundsException(subLen);
        }
        if (fromIndex == toIndex) {
            return EMPTY_STR;
        }
        int newFrom = toInternalId(fromIndex);
        int newTo = toInternalId(toIndex);
        return new FastStr(buf, newFrom, newTo);
    }

    @Override
    public FastStr takeWhile($.Function predicate) {
        if (isEmpty()) {
            return EMPTY_STR;
        }
        int sz = size(), b = toInternalId(0), e = -1;
        for (int i = 0; i < sz; ++i) {
            char c = charAt(i);
            e = toInternalId(i);
            if (!predicate.apply(c)) break;
        }
        return unsafeOf(buf, b, e);
    }

    @Override
    public FastStr dropWhile($.Function predicate) {
        int sz = size();
        if (sz == 0) return EMPTY_STR;
        int b = -1, e = toInternalId(sz);
        for (int i = 0; i < sz; ++i) {
            char c = charAt(i);
            b = toInternalId(i);
            if (!predicate.apply(c)) break;
        }
        return unsafeOf(buf, b, e);
    }

    @Override
    public FastStr remove($.Function predicate) {
        final int sz = size();
        if (sz == 0) return EMPTY_STR;
        char[] newBuf = null;
        boolean removed = false;
        int curNew = 0;
        for (int i = 0; i < sz; ++i) {
            char c = charAt(i);
            if (predicate.apply(c)) {
                if (null == newBuf) {
                    removed = true;
                    newBuf = new char[sz];
                    System.arraycopy(buf, 0, newBuf, 0, i);
                }
            } else {
                if (null != newBuf) {
                    newBuf[curNew] = c;
                }
                curNew++;
            }
        }
        if (!removed) {
            // nothing removed
            return this;
        }
        return unsafeOf(newBuf, 0, curNew);
    }

    @Override
    public FastStr insert(int index, char character) throws StringIndexOutOfBoundsException {
        E.NPE(character);
        int len = size();
        if (len < Math.abs(index)) {
            throw new StringIndexOutOfBoundsException(index);
        }
        if (index < 0) {
            index = len + index;
        }
        char[] newBuf = new char[len + 1];
        if (index > 0) {
            System.arraycopy(buf, begin, newBuf, 0, index);
        }
        if (index < len) {
            System.arraycopy(buf, begin + index, newBuf, index + 1, len - index);
        }
        newBuf[index] = character;
        return unsafeOf(newBuf, 0, len + 1);
    }

    @Override
    public FastStr insert(int index, Character character) throws StringIndexOutOfBoundsException {
        E.NPE(character);
        int len = size();
        if (len < Math.abs(index)) {
            throw new StringIndexOutOfBoundsException(index);
        }
        if (index < 0) {
            index = len + index;
        }
        char[] newBuf = new char[len + 1];
        if (index > 0) {
            System.arraycopy(buf, begin, newBuf, 0, index);
        }
        if (index < len) {
            System.arraycopy(buf, begin + index, newBuf, index + 1, len - index);
        }
        newBuf[index] = character;
        return unsafeOf(newBuf, 0, len + 1);
    }

    @Override
    public FastStr insert(int index, StrBase str) throws StringIndexOutOfBoundsException {
        return insert(index, str.toCharArray());
    }

    @Override
    public FastStr insert(int index, Character... ca) throws StringIndexOutOfBoundsException {
        return insert(0, $.asPrimitive(ca));
    }

    @Override
    public FastStr insert(int index, char... ca) throws StringIndexOutOfBoundsException {
        int delta = ca.length;
        if (delta == 0) {
            return this;
        }
        int len = size();
        if (len < Math.abs(index)) {
            throw new StringIndexOutOfBoundsException(index);
        }
        if (index < 0) {
            index = len + index;
        }
        char[] newBuf = new char[len + delta];
        if (index > 0) {
            System.arraycopy(buf, begin, newBuf, 0, index);
        }
        System.arraycopy(ca, 0,  newBuf, index, delta);
        if (index < len) {
            System.arraycopy(buf, begin + index, newBuf, index + delta, len - index);
        }
        return unsafeOf(newBuf, 0, len + delta);
    }

    @Override
    public FastStr insert(int index, String s) throws StringIndexOutOfBoundsException {
        return insert(index, s.toCharArray());
    }

    @Override
    public FastStr reverse() {
        int sz = size();
        char[] newBuf = new char[sz];
        for (int i = 0, j = sz - 1; i < sz; ) {
            newBuf[j--] = buf[toInternalId(i++)];
        }
        return new FastStr(newBuf, 0, sz);
    }

    @Override
    @SuppressWarnings("unchecked")
    public FastStr append(Collection collection) {
        int sz = size(), sz2 = collection.size();
        if (0 == sz2) return this;
        if (0 == sz) return of((Collection) collection);
        char[] newBuf = new char[sz + sz2];
        copyTo(newBuf, 0);
        int i = sz;
        Iterator itr = collection.iterator();
        while (itr.hasNext()) {
            char c = itr.next();
            newBuf[i++] = c;
        }
        return new FastStr(newBuf, 0, sz + sz2);
    }

    @Override
    public FastStr append(C.List list) {
        int sz = size(), sz2 = list.size();
        if (0 == sz2) return this;
        if (0 == sz) return of(list);
        char[] newBuf = new char[sz + sz2];
        copyTo(newBuf, 0);
        for (int i = 0; i < sz2; ++i) {
            newBuf[sz + i] = list.get(i);
        }
        return new FastStr(newBuf, 0, sz + sz2);
    }

    @Override
    public FastStr parallel() {
        return super.parallel();
    }

    @Override
    public FastStr append(char... array) {
        int sz = size(), sz2 = array.length;
        if (0 == sz2) return this;
        if (0 == sz) return of(array);
        char[] newBuf = new char[sz + sz2];
        copyTo(newBuf, 0);
        for (int i = 0; i < sz2; ++i) {
            newBuf[sz + i] = array[i];
        }
        return new FastStr(newBuf, 0, sz + sz2);
    }

    @Override
    public FastStr append(Character character) {
        int sz = size();
        char[] newBuf = new char[sz + 1];
        copyTo(newBuf, 0);
        newBuf[sz] = character;
        return new FastStr(newBuf, 0, sz + 1);
    }

    public FastStr append(FastStr s) {
        int sz = size(), sz2 = s.size();
        if (sz == 0) return s;
        if (sz2 == 0) return this;
        int newSz = sz + sz2;
        char[] newBuf = new char[newSz];
        copyTo(newBuf, 0);
        s.copyTo(newBuf, sz);
        return new FastStr(newBuf, 0, newSz);
    }

    public FastStr append(String s) {
        int sz = size(), sz2 = s.length();
        if (0 == sz) return of(s);
        if (0 == sz2) return this;
        int newSz = sz + sz2;
        char[] newBuf = new char[newSz];
        copyTo(newBuf, 0);
        boolean done = false;
        if (sz2 > 512) {
            try {
                char[] sBuf = s.toCharArray();
                System.arraycopy(sBuf, 0, newBuf, sz, sz2);
                done = true;
            } catch (RuntimeException e) {
                // ignore
            }
        }
        if (!done) {
            for (int i = 0; i < sz2; ++i) {
                newBuf[sz + i] = s.charAt(i);
            }
        }
        return new FastStr(newBuf, 0, newSz);
    }

    @Override
    @SuppressWarnings("unchecked")
    public FastStr prepend(Collection collection) {
        int sz = size(), sz2 = collection.size();
        if (0 == sz2) return this;
        if (0 == sz) return of((Collection) collection);
        int newSz = sz + sz2, i = 0;
        char[] newBuf = new char[newSz];
        Iterator itr = collection.iterator();
        while (itr.hasNext()) {
            newBuf[i++] = itr.next();
        }
        copyTo(newBuf, sz2);
        return new FastStr(newBuf, 0, newSz);
    }

    @Override
    public FastStr prepend(C.List list) {
        int sz = size();
        if (0 == sz) return of(list);
        int sz2 = list.size();
        if (0 == sz2) return this;
        if (1 == sz2) {
            return prepend(list.get(0));
        }
        int newSz = sz + sz2;
        char[] newBuf = new char[newSz];
        for (int i = 0; i < sz2; ++i) {
            newBuf[i] = list.get(i);
        }
        copyTo(newBuf, sz2);
        return new FastStr(newBuf, 0, newSz);
    }

    @Override
    public FastStr prepend(char... chars) {
        int sz = size();
        if (0 == sz) return of(chars);
        int sz2 = chars.length;
        if (0 == sz2) return this;
        if (1 == sz2) {
            return prepend(chars[0]);
        }
        int newSz = sz + sz2;
        char[] newBuf = new char[newSz];
        for (int i = 0; i < sz2; ++i) {
            newBuf[i] = chars[i];
        }
        copyTo(newBuf, sz2);
        return new FastStr(newBuf, 0, newSz);
    }

    @Override
    public FastStr prepend(Character character) {
        // check if I can back begin pointer for one step
        if (begin > 0) {
            if (buf[begin - 1] == character) {
                return FastStr.unsafeOf(buf, begin - 1, end);
            }
        }
        int sz = size();
        char[] newBuf = new char[++sz];
        newBuf[0] = character;
        copyTo(newBuf, 1);
        return new FastStr(newBuf, 0, sz);
    }


    @Override
    public FastStr prepend(FastStr s) {
        return s.append(this);
    }

    @Override
    public FastStr prepend(String s) {
        int sz = size();
        if (0 == sz) return of(s);
        int sz2 = s.length();
        if (0 == sz2) return this;
        if (sz2 == 1) {
            return prepend(s.charAt(0));
        }
        int newSz = sz + sz2;
        char[] newBuf = new char[newSz];
        boolean done = false;
        if (sz2 > 512) {
            try {
                char[] sBuf = s.toCharArray();
                System.arraycopy(sBuf, 0, newBuf, 0, sz2);
                done = true;
            } catch (RuntimeException e) {
                // ignore
            }
        }
        if (!done) {
            for (int i = 0; i < sz2; ++i) {
                newBuf[i] = s.charAt(i);
            }
        }
        copyTo(newBuf, sz2);
        return new FastStr(newBuf, 0, newSz);
    }

    @Override
    public FastStr padLeft(char c, int times) {
        char[] ca = new char[times];
        $.fill(c, ca);
        return prepend(ca);
    }

    @Override
    public FastStr lpad(char c, int times) {
        return padLeft(c, times);
    }

    @Override
    public FastStr padLeft(int times) {
        return padLeft(' ', times);
    }

    @Override
    public FastStr lpad(int times) {
        return padLeft(times);
    }

    @Override
    public FastStr padRight(char c, int times) {
        char[] ca = new char[times];
        $.fill(c, ca);
        return append(ca);
    }

    @Override
    public FastStr rpad(char c, int times) {
        return padRight(c, times);
    }

    @Override
    public FastStr padRight(int times) {
        return padRight(' ', times);
    }

    @Override
    public FastStr rpad(int times) {
        return padRight(times);
    }

    @Override
    public char charAt(int index) {
        return buf[toInternalId(index)];
    }

    @Override
    public FastStr subSequence(int start, int end) {
        return subList(start, end);
    }

    @Override
    public FastStr copy() {
        if (EMPTY_STR == this) return this;
        return unsafeOf(charArray(), 0, size());
    }

    public FastStr times(int n) {
        E.illegalArgumentIf(n < 0, "n cannot be negative");
        if (0 == n) return EMPTY_STR;
        if (1 == n) return this;
        int sz = size();
        if (0 == sz) return this;
        int newSz = sz * n;
        char[] newBuf = new char[newSz];
        for (int i = 0; i < n; ++i) {
            copyTo(newBuf, i * sz);
        }
        return new FastStr(newBuf, 0, newSz);
    }

    public FastStr canonical() {
        if (EMPTY_STR == this) return this;
        if (begin == 0) return this;
        return unsafeOf(unsafeChars(), 0, size());
    }

    @Override
    public int compareTo(FastStr o) {
        int len1 = size();
        int len2 = o.size();
        int lim = Math.min(len1, len2);
        char v1[] = buf;
        char v2[] = o.buf;

        int k = 0;
        while (k < lim) {
            char c1 = v1[toInternalId(k)];
            char c2 = v2[o.toInternalId(k)];
            if (c1 != c2) {
                return c1 - c2;
            }
            k++;
        }
        return len1 - len2;
    }

    @Override
    public String toString() {
        char[] newBuf = charArray();
        return new String(newBuf);
    }

    public FastStr toFastStr() {
        return this;
    }

    @Override
    public boolean equals(Object o) {
        if (o == this) return true;
        if (o instanceof FastStr) {
            FastStr that = (FastStr) o;
            return contentEquals(that);
        } else if (o instanceof StrBase) {
            StrBase that = (StrBase)o;
            return that.contentEquals(this);
        }
        return false;
    }

    @Override
    public int hashCode() {
        if (isEmpty()) return 0;
        int h = hash;
        if (h == 0) {
            for (int i = begin; i < end; ++i) {
                h = 31 * h + buf[i];
            }
            hash = h;
        }
        return h;
    }

    // --- String utilities ---

    public void getChars(int srcBegin, int srcEnd, char dst[], int dstBegin) {
        if (srcBegin < 0) {
            throw new StringIndexOutOfBoundsException(srcBegin);
        }
        if (srcBegin > srcEnd) {
            throw new StringIndexOutOfBoundsException(srcEnd - srcBegin);
        }
        int sz = size();
        if (srcEnd > sz) {
            throw new StringIndexOutOfBoundsException(srcEnd);
        }
        System.arraycopy(buf, toInternalId(srcBegin), dst, dstBegin, srcEnd - srcBegin);
    }

    public byte[] getBytes() {
        String s = toString();
        return s.getBytes();
    }

    public byte[] getBytes(String charsetName) {
        String s = toString();
        if (null == charsetName) {
            return s.getBytes();
        }
        try {
            return s.getBytes(charsetName);
        } catch (UnsupportedEncodingException e) {
            throw E.encodingException(e);
        }
    }

    @Override
    public byte[] getBytes(Charset charset) {
        String s = toString();
        if (null == charset) {
            return s.getBytes();
        }
        return s.getBytes(charset);
    }

    public byte[] getBytesAscII() {
        int sz = size();
        if (sz == 0) {
            return new byte[0];
        }
        return toString().getBytes(Charsets.US_ASCII);
    }

    @Override
    public byte[] getBytesUTF8() {
        int sz = size();
        if (sz == 0) {
            return new byte[0];
        }
        return toString().getBytes(Charsets.UTF_8);
    }

    /**
     * Wrapper of {@link String#contentEquals(CharSequence)}
     *
     * @param x the char sequence to be compared
     * @return true if content equals content of the specified char sequence
     */
    public boolean contentEquals(CharSequence x) {
        if (x == this) return true;
        if (isEmpty()) return x.length() == 0;
        int sz = size(), sz2 = x.length();
        if (sz != sz2) return false;
        for (int i = 0; i < sz; ++i) {
            char c = buf[toInternalId(i)];
            char c1 = x.charAt(i);
            if (c != c1) return false;
        }
        return true;
    }

    public boolean contentEquals(FastStr x) {
        if (x == this) return true;
        if (null == x) return false;
        int sz = size(), sz2 = x.size();
        if (sz != sz2) return false;
        for (int i = begin, j = x.begin; i < end; ) {
            if (buf[i++] != x.buf[j++]) return false;
        }
        return true;
    }

    /**
     * Wrapper of {@link String#equalsIgnoreCase(String)}
     *
     * @param x the char sequence to be compared
     * @return {@code true} if the argument is not {@code null} and it
     * represents an equivalent {@code String} ignoring case; {@code
     * false} otherwise
     */
    public boolean equalsIgnoreCase(CharSequence x) {
        if (x == this) return true;
        if (null == x || size() != x.length()) return false;
        if (isEmpty() && x.length() == 0) return true;
        return regionMatches(true, 0, x, 0, size());
    }

    public boolean equalsIgnoreCase(FastStr x) {
        if (x == this) return true;
        if (null == x || size() != x.size()) return false;
        return regionMatches(true, 0, x.buf, 0, size());
    }

    public int compareTo(CharSequence x) {
        int len1 = size();
        int len2 = x.length();
        int lim = Math.min(len1, len2);
        char v1[] = buf;
        try {
            char v2[] = FastStr.bufOf(x);
            int k = 0;
            while (k < lim) {
                char c1 = v1[toInternalId(k)];
                char c2 = v2[k];
                if (c1 != c2) {
                    return c1 - c2;
                }
                k++;
            }
        } catch (RuntimeException e) {
            int k = 0;
            while (k < lim) {
                char c1 = v1[toInternalId(k)];
                char c2 = x.charAt(k);
                if (c1 != c2) {
                    return c1 - c2;
                }
                k++;
            }
        }
        return len1 - len2;
    }

    public int compareToIgnoreCase(FastStr o) {
        int len1 = size();
        int len2 = o.size();
        int lim = Math.min(len1, len2);
        char v1[] = buf;
        char v2[] = o.buf;

        int k = 0;
        while (k < lim) {
            char c1 = v1[toInternalId(k)];
            char c2 = v2[o.toInternalId(k)];
            if (c1 != c2) {
                c1 = Character.toUpperCase(c1);
                c2 = Character.toUpperCase(c2);
                if (c1 != c2) {
                    c1 = Character.toLowerCase(c1);
                    c2 = Character.toLowerCase(c2);
                    if (c1 != c2) {
                        return c1 - c2;
                    }
                }
            }
            k++;
        }
        return len1 - len2;
    }

    public int compareToIgnoreCase(CharSequence o) {
        int len1 = size();
        int len2 = o.length();
        int lim = Math.min(len1, len2);
        char v1[] = buf;
        int k = 0;

        try {
            char v2[] = FastStr.of(o).unsafeChars();
            while (k < lim) {
                char c1 = v1[toInternalId(k)];
                char c2 = v2[k];
                if (c1 != c2) {
                    c1 = Character.toUpperCase(c1);
                    c2 = Character.toUpperCase(c2);
                    if (c1 != c2) {
                        c1 = Character.toLowerCase(c1);
                        c2 = Character.toLowerCase(c2);
                        if (c1 != c2) {
                            return c1 - c2;
                        }
                    }
                }
                k++;
            }
        } catch (RuntimeException e) {
            while (k < lim) {
                char c1 = v1[toInternalId(k)];
                char c2 = o.charAt(k);
                if (c1 != c2) {
                    c1 = Character.toUpperCase(c1);
                    c2 = Character.toUpperCase(c2);
                    if (c1 != c2) {
                        c1 = Character.toLowerCase(c1);
                        c2 = Character.toLowerCase(c2);
                        if (c1 != c2) {
                            return c1 - c2;
                        }
                    }
                }
                k++;
            }
        }
        return len1 - len2;
    }

    public boolean regionMatches(boolean ignoreCase, int toffset,
                                 FastStr other, int ooffset, int len
    ) {
        return regionMatches(ignoreCase, toffset, other.unsafeChars(), ooffset, len);
    }

    private boolean regionMatches(boolean ignoreCase, int toffset, char[] other, int ooffset, int len) {
        char ta[] = buf;
        int to = toInternalId(toffset);
        char pa[] = other;
        int po = ooffset;
        // Note: toffset, ooffset, or len might be near -1>>>1.
        if ((ooffset < 0) || (to < 0)
                || (toffset > (long) length() - len)
                || (ooffset > (long) other.length - len)) {
            return false;
        }
        while (len-- > 0) {
            char c1 = ta[to++];
            char c2 = pa[po++];
            if (c1 == c2) {
                continue;
            }
            if (ignoreCase) {
                // If characters don't match but case may be ignored,
                // try converting both characters to uppercase.
                // If the results match, then the comparison scan should
                // continue.
                char u1 = Character.toUpperCase(c1);
                char u2 = Character.toUpperCase(c2);
                if (u1 == u2) {
                    continue;
                }
                // Unfortunately, conversion to uppercase does not work properly
                // for the Georgian alphabet, which has strange rules about case
                // conversion.  So we need to make one last check before
                // exiting.
                if (Character.toLowerCase(u1) == Character.toLowerCase(u2)) {
                    continue;
                }
            }
            return false;
        }
        return true;
    }

    public boolean regionMatches(boolean ignoreCase, int toffset,
                                 CharSequence other, int ooffset, int len
    ) {
        char[] otherBuf = bufOf(other);
        return regionMatches(ignoreCase, toffset, otherBuf, ooffset, len);
    }

    public boolean startsWith(FastStr prefix, int toffset) {
        if (prefix.isEmpty()) return true;
        int sz2 = prefix.size(), sz = size();
        if (toffset < 0 || toffset > sz - sz2) {
            return false;
        }
        int po = 0, pc = sz2, to = toffset;
        char[] buf1 = buf, buf2 = prefix.buf;
        while (--pc >= 0) {
            if (buf1[toInternalId(to++)] != buf2[prefix.toInternalId(po++)]) {
                return false;
            }
        }
        return true;
    }

    public boolean startsWith(CharSequence suffix, int toffset) {
        if (suffix.length() == 0) return true;
        int sz2 = suffix.length(), sz = size();
        if (toffset < 0 || toffset > sz - sz2) {
            return false;
        }
        int po = 0, pc = sz2, to = toffset;
        char[] buf1 = buf;
        try {
            char[] buf2 = FastStr.bufOf(suffix);
            while (--pc >= 0) {
                if (buf1[toInternalId(to++)] != buf2[po++]) {
                    return false;
                }
            }
            return true;
        } catch (RuntimeException e) {
            while (--pc >= 0) {
                if (buf1[toInternalId(to++)] != suffix.charAt(po++)) {
                    return false;
                }
            }
            return true;
        }
    }


    public boolean endsWith(CharSequence suffix, int toffset) {
        int prefixSz = suffix.length();
        if (0 == prefixSz) {
            return true;
        }
        int matchStart = length() - toffset;
        if (matchStart < prefixSz) {
            return false;
        }
        for (int i = toInternalId(matchStart - 1), j = prefixSz - 1; j >= 0; --i, --j) {
            char c0 = buf[i];
            char c1 = suffix.charAt(j);
            if (c0 != c1) {
                return false;
            }
        }
        return true;
    }



    @Override
    public boolean endsWith(FastStr prefix, int toffset) {
        int prefixSz = prefix.length();
        if (0 == prefixSz) {
            return true;
        }
        int matchStart = length() - toffset;
        if (matchStart < prefixSz) {
            return false;
        }
        char[] prefixBuf = prefix.buf;
        for (int i = toInternalId(matchStart - 1), j = prefix.toInternalId(prefixSz - 1); j >= prefix.begin; --i, --j) {
            char c0 = buf[i];
            char c1 = prefixBuf[j];
            if (c0 != c1) {
                return false;
            }
        }
        return true;
    }

    public int indexOf(int ch, int fromIndex) {
        final int max = size();
        if (fromIndex < 0) {
            fromIndex = 0;
        } else if (fromIndex > max) {
            return -1;
        }
        fromIndex = toInternalId(fromIndex);
        if (ch < Character.MIN_SUPPLEMENTARY_CODE_POINT) {
            // handle most cases here (ch is a BMP code point or a
            // negative value (invalid code point))
            final char[] buf = this.buf;
            for (int i = fromIndex; i < end; ++i) {
                if (buf[i] == ch) {
                    return toExternalId(i);
                }
            }
            return -1;
        } else {
            return toExternalId(indexOfSupplementary(ch, fromIndex));
        }
    }

    public int lastIndexOf(int ch, int fromIndex) {
        fromIndex = toInternalId(fromIndex);
        if (ch < Character.MIN_SUPPLEMENTARY_CODE_POINT) {
            // handle most cases here (ch is a BMP code point or a
            // negative value (invalid code point))
            final char[] value = this.buf;
            int i = Math.min(fromIndex, length() - 1 + begin);
            for (; i >= begin; i--) {
                if (value[i] == ch) {
                    return toExternalId(i);
                }
            }
            return -1;
        } else {
            return toExternalId(lastIndexOfSupplementary(ch, fromIndex));
        }
    }

    @Override
    public int indexOf(CharSequence str, int fromIndex) {
        char[] strBuf = bufOf(str);
        return S.indexOf(buf, begin, size(), strBuf, 0, strBuf.length, fromIndex);
    }

    @Override
    public int indexOf(FastStr str, int fromIndex) {
        char[] buf = str.buf;
        return S.indexOf(this.buf, this.begin, this.size(), buf, str.begin, str.size(), fromIndex);
    }

    @Override
    public int lastIndexOf(CharSequence str, int fromIndex) {
        char[] strBuf = bufOf(str);
        int sz = size();
        return S.lastIndexOf(buf, begin, sz, strBuf, 0, strBuf.length, fromIndex);
    }

    @Override
    public int lastIndexOf(FastStr str, int fromIndex) {
        int sz = size();
        return S.lastIndexOf(buf, begin, sz, str.buf, str.begin, str.size(), fromIndex);
    }

    /**
     * Wrapper of {@link String#substring(int)}
     *
     * @param beginIndex the begin index
     * @return a String instance that is equivalent to a sub part of this FastStr
     */
    public String substring(int beginIndex) {
        if (beginIndex < 0) {
            throw new StringIndexOutOfBoundsException(beginIndex);
        }
        int subLen = size() - beginIndex;
        if (subLen < 0) {
            throw new StringIndexOutOfBoundsException(subLen);
        }
        return (beginIndex == 0) ? toString() : new String(buf, toInternalId(beginIndex), subLen);
    }

    /**
     * Wrapper of {@link String#substring(int, int)}
     *
     * @param beginIndex begin index
     * @param endIndex end index
     * @return a String instance that is equivalent to sub part of this FastStr
     */
    @Override
    public String substring(int beginIndex, int endIndex) {
        if (beginIndex < 0) {
            throw new StringIndexOutOfBoundsException(beginIndex);
        }
        char[] buf = this.buf;
        int sz = buf.length;
        if (endIndex > sz) {
            throw new StringIndexOutOfBoundsException(endIndex);
        }
        int subLen = endIndex - beginIndex;
        if (subLen < 0) {
            throw new StringIndexOutOfBoundsException(subLen);
        }
        return ((beginIndex == 0) && (endIndex == sz)) ? toString()
                : new String(buf, toInternalId(beginIndex), subLen);
    }

    /**
     * Wrapper of {@link String#replace(char, char)} but return FastStr instance
     *
     * @param oldChar char to be replaced
     * @param newChar char used to replace {@code oldChar}
     * @return a FastStr instance with all {@code oldChar} been replaced with {@code newChar}
     */
    @Override
    public FastStr replace(char oldChar, char newChar) {
        if (oldChar != newChar) {
            char[] val = this.buf; /* avoid getfield opcode */
            int len = length();
            int i = begin - 1;

            while (++i < end) {
                if (val[i] == oldChar) {
                    break;
                }
            }
            if (i < end) {
                char buf[] = new char[len];
                for (int j = begin; j < i; j++) {
                    buf[j - begin] = val[j];
                }
                while (i < end) {
                    char c = val[i];
                    buf[i - begin] = (c == oldChar) ? newChar : c;
                    i++;
                }
                return new FastStr(buf, 0, len);
            }
        }
        return this;
    }

    /**
     * Wrapper of {@link String#matches(String)}
     *
     * @param regex the regular expression to checked against this FastStr
     * @return {@code true} if this FastStr matches {@code regex}
     */
    @Override
    public boolean matches(String regex) {
        return Pattern.matches(regex, this);
    }

    /**
     * Wrapper of {@link String#contains(CharSequence)}
     *
     * @param s the char sequence to be found
     * @return {@code true} if {@code s} has been found
     */
    @Override
    public boolean contains(CharSequence s) {
        if (s instanceof FastStr) {
            return indexOf((FastStr) s) > -1;
        }
        return indexOf(s.toString()) > -1;
    }

    /**
     * Wrapper of {@link String#replaceFirst(String, String)} but return FastStr inance
     *
     * @param regex the regular expression specifies the place to be replaced
     * @param replacement the string to replace the found part
     * @return a FastStr that has the first found part replaced
     */
    @Override
    public FastStr replaceFirst(String regex, String replacement) {
        return unsafeOf(Pattern.compile(regex).matcher(this).replaceFirst(replacement));
    }

    /**
     * Wrapper of {@link String#replaceAll(String, String)} but return FastStr type instance
     *
     * @param regex the regular expression specifies the pattern to be replaced
     * @param replacement the replacement string
     * @return a FastStr instance with all found part replaced
     */
    @Override
    public FastStr replaceAll(String regex, String replacement) {
        return unsafeOf(Pattern.compile(regex).matcher(this).replaceAll(replacement));
    }

    /**
     * Wrapper of {@link String#replace(CharSequence, CharSequence)} but return FastStr type instance
     *
     * @param target the char sequence to be replaced
     * @param replacement the char sequence used to replace {@code target}
     * @return a FastStr instance with all {@code target} being replaced with
     *         {@code replacement}
     */
    @Override
    public FastStr replace(CharSequence target, CharSequence replacement) {
        return unsafeOf(Pattern.compile(target.toString(), Pattern.LITERAL).matcher(
                this).replaceAll(Matcher.quoteReplacement(replacement.toString())));
    }

    /**
     * Wrapper of {@link String#split(String, int)} but return an immutable List of FastStr instances
     *
     * @param regex the regular expression matches the seperator
     * @param limit the result threshold
     * @return a {@link org.osgl.util.C.List} of FastStr instances split from this FastStr
     */
    @Override
    public C.List split(String regex, int limit) {
        /* fastpath if the regex is a
         (1)one-char String and this character is not one of the
            RegEx's meta characters ".$|()[{^?*+\\", or
         (2)two-char String and the first char is the backslash and
            the second is not the ascii digit or ascii letter.
         */
        char ch;
        if (((regex.length() == 1 &&
                ".$|()[{^?*+\\".indexOf(ch = regex.charAt(0)) == -1) ||
                (regex.length() == 2 &&
                        regex.charAt(0) == '\\' &&
                        (((ch = regex.charAt(1))-'0')|('9'-ch)) < 0 &&
                        ((ch-'a')|('z'-ch)) < 0 &&
                        ((ch-'A')|('Z'-ch)) < 0)) &&
                (ch < Character.MIN_HIGH_SURROGATE ||
                        ch > Character.MAX_LOW_SURROGATE)) {
            int off = 0;
            int next = 0;
            boolean limited = limit > 0;
            C.List list = C.newList();
            while ((next = indexOf(ch, off)) != -1) {
                if (!limited || list.size() < limit - 1) {
                    list.add(substr(off, next));
                    off = next + 1;
                } else {    // last one
                    //assert (list.size() == limit - 1);
                    list.add(substr(off, buf.length));
                    off = buf.length;
                    break;
                }
            }
            // If no match was found, return this
            if (off == 0) {
                return C.listOf(this);
            }

            // Add remaining segment
            if (!limited || list.size() < limit) {
                list.add(substr(off, buf.length));
            }

            // Construct result
            int resultSize = list.size();
            if (limit == 0) {
                while (resultSize > 0 && list.get(resultSize - 1).length() == 0) {
                    resultSize--;
                }
            }
            return list.subList(0, resultSize);
        }
        String[] sa = Pattern.compile(regex).split(this, limit);
        int len = sa.length;
        FastStr[] ssa = new FastStr[len];
        for (int i = 0; i < len; ++i) {
            ssa[i] = unsafeOf(sa[i]);
        }
        return C.listOf(ssa);
    }


    /**
     * Wrapper of {@link String#toLowerCase(java.util.Locale)} but return FastStr type instance
     *
     * @param locale the locale
     * @return a FastStr instance with all characters from this FastStr
     *         be converted into lowercase based on the locale specified
     */
    public FastStr toLowerCase(Locale locale) {
        String s = toString();
        return unsafeOf(s.toLowerCase(locale));
    }

    /**
     * Wrapper of {@link String#toUpperCase(java.util.Locale)} but return FastStr type instance
     *
     * @param locale the locale
     * @return a FastStr instance with all characters from this FastStr
     *         be converted into uppercase based on the locale specified
     */
    @Override
    public FastStr toUpperCase(Locale locale) {
        String s = toString();
        return unsafeOf(s.toUpperCase(locale));
    }

    /**
     * Wrapper of {@link String#trim()} and return FastStr type instance
     *
     * @return a FastStr instance without leading and tail space characters
     *         from this FastStr instance
     */
    @Override
    public FastStr trim() {
        char[] value = this.buf;
        int len = end;
        int st = begin;
        char[] val = value;    /* avoid getfield opcode */

        while ((st < len) && (val[st] <= ' ')) {
            st++;
        }
        while ((st < len) && (val[len - 1] <= ' ')) {
            len--;
        }
        return ((st > begin) || (len < size())) ? new FastStr(value, st, len) : this;
    }

    /**
     * Alias of {@link #toCharArray()}
     *
     * @return char array buf copy of this FastStr
     */
    public char[] charArray() {
        char[] newBuf = new char[size()];
        copyTo(newBuf, 0);
        return newBuf;
    }

    /**
     * Return char array buf of this FastStr instance.
     * Note this method might return the char array buf directly
     * without copy operation
     * @return the char array buf of this FastStr
     */
    public char[] unsafeChars() {
        if (begin == 0) return buf;
        char[] newBuf = new char[size()];
        copyTo(newBuf, 0);
        return newBuf;
    }

    /**
     * Wrapper of {@link String#intern()}
     *
     * @return the intern of the string
     */
    @Override
    public String intern() {
        return toString().intern();
    }

    // -- extensions
    @Override
    public FastStr afterFirst(String s) {
        return afterFirst(unsafeOf(s));
    }

    @Override
    public FastStr afterLast(String s) {
        return afterLast(unsafeOf(s));
    }

    @Override
    public FastStr afterFirst(FastStr s) {
        int pos = indexOf(s);
        if (pos < 0) return EMPTY_STR;
        return substr(pos + s.size());
    }

    @Override
    public FastStr afterLast(FastStr s) {
        int pos = lastIndexOf(s);
        if (pos < 0) return EMPTY_STR;
        return substr(pos + s.size());
    }

    @Override
    public FastStr afterFirst(char c) {
        int pos = indexOf(c);
        if (pos < 0) return EMPTY_STR;
        return substr(pos + 1);
    }

    @Override
    public FastStr afterLast(char c) {
        int pos = lastIndexOf(c);
        if (pos < 0) return EMPTY_STR;
        return substr(pos + 1);
    }

    public FastStr beforeFirst(String s) {
        return beforeFirst(unsafeOf(s));
    }

    public FastStr beforeLast(String s) {
        return beforeLast(unsafeOf(s));
    }

    public FastStr beforeFirst(FastStr s) {
        int pos = indexOf(s);
        if (pos < 0) return EMPTY_STR;
        return substr(0, pos);
    }

    public FastStr beforeLast(FastStr s) {
        int pos = lastIndexOf(s);
        if (pos < 0) return EMPTY_STR;
        return substr(0, pos);
    }

    @Override
    public FastStr beforeFirst(char c) {
        int pos = indexOf(c);
        if (pos < 0) return EMPTY_STR;
        return substr(0, pos);
    }

    @Override
    public FastStr beforeLast(char c) {
        int pos = lastIndexOf(c);
        if (pos < 0) return EMPTY_STR;
        return substr(0, pos);
    }

    public FastStr strip(String prefix, String suffix) {
        FastStr s = this;
        if (startsWith(prefix)) s = s.substr(prefix.length());
        if (s.endsWith(suffix)) s = s.substr(0, s.size() - suffix.length());
        return s;
    }

    public FastStr strip(FastStr prefix, FastStr suffix) {
        FastStr s = this;
        if (startsWith(prefix)) s = s.substr(prefix.size());
        if (s.endsWith(suffix)) s = s.substr(0, s.size() - suffix.size());
        return s;
    }

    public FastStr urlEncode() {
        return unsafeOf(S.urlEncode(new String(buf)));
    }

    public FastStr decodeBASE64() {
        return unsafeOf(S.decodeBASE64(new String(buf)));
    }

    public FastStr encodeBASE64() {
        return unsafeOf(S.encodeBASE64(new String(buf)));
    }

    @Override
    public FastStr capFirst() {
        if (isEmpty()) return this;
        int sz = size();
        char[] buf = this.buf;
        char[] newBuf = S.unsafeCapFirst(buf, begin, sz);
        if (buf == newBuf) return this;
        return unsafeOf(newBuf, 0, sz);
    }

    @Override
    public int count(String search, boolean overlap) {
        char[] searchBuf = search.toCharArray();
        return count(searchBuf, 0, searchBuf.length, overlap);
    }

    @Override
    public int count(FastStr search, boolean overlap) {
        return count(search.buf, search.begin, search.size(), overlap);
    }

    private int toInternalId(int index) {
        return begin + index;
    }

    private int toExternalId(int index) {
        return index - begin;
    }

    private void copyTo(char[] buf, int begin) {
        System.arraycopy(this.buf, this.begin, buf, begin, size());
    }

    private int indexOfSupplementary(int ch, int fromIndex) {
        if (Character.isValidCodePoint(ch)) {
            final char[] buf = this.buf;
            final char hi = highSurrogate(ch);
            final char lo = lowSurrogate(ch);
            final int max = size() - 1;
            for (int i = fromIndex; i < max; i++) {
                if (buf[toInternalId(i)] == hi && buf[toInternalId(i + 1)] == lo) {
                    return i;
                }
            }
        }
        return -1;
    }

    private int lastIndexOfSupplementary(int ch, int fromIndex) {
        if (Character.isValidCodePoint(ch)) {
            final char[] buf = this.buf;
            char hi = highSurrogate(ch);
            char lo = lowSurrogate(ch);
            int i = Math.min(fromIndex, buf.length - 2);
            for (; i >= 0; i--) {
                if (buf[toInternalId(i)] == hi && buf[toInternalId(i + 1)] == lo) {
                    return i;
                }
            }
        }
        return -1;
    }

    private int count(char[] search, int searchOffset, int searchCount, boolean overlap) {
        if (isEmpty()) return 0;
        return S.count(buf, begin, size(), search, searchOffset, searchCount, overlap);
    }


    // ---- factory methods

    /**
     * Construct a FastStr from a char array
     * @param ca the char array
     * @return a FastStr
     */
    public static FastStr of(char[] ca) {
        if (ca.length == 0) return EMPTY_STR;
        char[] newArray = new char[ca.length];
        System.arraycopy(ca, 0, newArray, 0, ca.length);
        return new FastStr(ca);
    }

    /**
     * Construct a FastStr from a CharSequence
     * @param cs the CharSequence instance
     * @return a FastStr
     */
    public static FastStr of(CharSequence cs) {
        if (cs instanceof FastStr) {
            return (FastStr)cs;
        }
        return of(cs.toString());
    }

    /**
     * Construct a FastStr from a String
     * @param s the String
     * @return a FastStr
     */
    public static FastStr of(String s) {
        int sz = s.length();
        if (sz == 0) return EMPTY_STR;
        char[] buf = s.toCharArray();
        return new FastStr(buf, 0, sz);
    }

    /**
     * Construct a FastStr from a StringBuilder
     * @param sb the string builder
     * @return a FastStr
     */
    public static FastStr of(StringBuilder sb) {
        int sz = sb.length();
        if (0 == sz) return EMPTY_STR;
        char[] buf = new char[sz];
        for (int i = 0; i < sz; ++i) {
            buf[i] = sb.charAt(i);
        }
        return new FastStr(buf, 0, sz);
    }

    /**
     * Construct a FastStr from a StringBuffer
     * @param sb the string buffer
     * @return the FastStr
     */
    public static FastStr of(StringBuffer sb) {
        int sz = sb.length();
        if (0 == sz) return EMPTY_STR;
        char[] buf = new char[sz];
        for (int i = 0; i < sz; ++i) {
            buf[i] = sb.charAt(i);
        }
        return new FastStr(buf, 0, sz);
    }

    /**
     * Construct a FastStr instance from an iterable of characters
     * @param itr the character iterable
     * @return the FastStr
     */
    public static FastStr of(Iterable itr) {
        StringBuilder sb = new StringBuilder();
        for (Character c : itr) {
            sb.append(c);
        }
        return of(sb);
    }

    /**
     * Construct a FastStr instance from a collection of characters
     * @param col the character collection
     * @return a FastStr instance
     */
    public static FastStr of(Collection col) {
        int sz = col.size();
        if (0 == sz) return EMPTY_STR;
        char[] buf = new char[sz];
        Iterator itr = col.iterator();
        int i = 0;
        while (itr.hasNext()) {
            buf[i++] = itr.next();
        }
        return new FastStr(buf, 0, sz);
    }

    /**
     * Construct a FastStr instance from an iterator of characters
     * @param itr the character iterator
     * @return a FastStr instance consists of all chars in the iterator
     */
    public static FastStr of(Iterator itr) {
        StringBuilder sb = new StringBuilder();
        while (itr.hasNext()) {
            sb.append(itr.next());
        }
        return of(sb);
    }

    public static FastStr of(byte[] bytes, String encoding) {
        ByteBuffer byteBuffer = ByteBuffer.wrap(bytes);
        CharBuffer charBuffer = Charset.forName(encoding).decode(byteBuffer);
        char[] chars = Arrays.copyOfRange(charBuffer.array(), charBuffer.position(), charBuffer.limit());
        Arrays.fill(charBuffer.array(), '\u0000'); // clear sensitive data
        Arrays.fill(byteBuffer.array(), (byte) 0); // clear sensitive data
        return FastStr.of(chars);
    }

    /**
     * Construct a FastStr instance from a String instance.
     * The FastStr instance will share the char array buf with
     * the String instance
     * @param s the string instance
     * @return A FastStr instance
     */
    public static FastStr unsafeOf(String s) {
        int sz = s.length();
        if (sz == 0) return EMPTY_STR;
        char[] buf = s.toCharArray();
        return new FastStr(buf, 0, sz);
    }

    /**
     * Construct a FastStr instance from char array without array copying
     * @param buf the char array
     * @return a FastStr instance from the char array
     */
    @SuppressWarnings("unused")
    public static FastStr unsafeOf(char[] buf) {
        E.NPE(buf);
        return new FastStr(buf, 0, buf.length);
    }

    /**
     * Construct a FastStr instance from char array, from the start position, finished at end position
     * without copying the array. This method might use the array directly instead of copying elements
     * from the array. Thus it is extremely important that the array buf passed in will NOT be updated
     * outside the FastStr instance.
     * @param buf the char array
     * @param start the start position (inclusive)
     * @param end the end position (exclusive)
     * @return a FastStr instance that consist of chars specified
     */
    public static FastStr unsafeOf(char[] buf, int start, int end) {
        E.NPE(buf);
        E.illegalArgumentIf(start < 0 || end > buf.length);
        if (end < start) return EMPTY_STR;
        return new FastStr(buf, start, end);
    }

    /**
     * Return the char array that backed the char sequence specified
     * @param chars the char sequence
     * @return an array of chars of the char sequence
     */
    @SuppressWarnings("unused")
    @Deprecated
    public static char[] bufOf(CharSequence chars) {
        return FastStr.of(chars).charArray();
    }

    private static char highSurrogate(int codePoint) {
        return (char) ((codePoint >>> 10)
                + (MIN_HIGH_SURROGATE - (MIN_SUPPLEMENTARY_CODE_POINT >>> 10)));

    }

    private static char lowSurrogate(int codePoint) {
        return (char) ((codePoint & 0x3ff) + MIN_LOW_SURROGATE);
    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy