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

org.glassfish.grizzly.http.util.CharChunk Maven / Gradle / Ivy

There is a newer version: 4.0.2
Show newest version
/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * Copyright (c) 2010-2017 Oracle and/or its affiliates. All rights reserved.
 *
 * The contents of this file are subject to the terms of either the GNU
 * General Public License Version 2 only ("GPL") or the Common Development
 * and Distribution License("CDDL") (collectively, the "License").  You
 * may not use this file except in compliance with the License.  You can
 * obtain a copy of the License at
 * https://glassfish.dev.java.net/public/CDDL+GPL_1_1.html
 * or packager/legal/LICENSE.txt.  See the License for the specific
 * language governing permissions and limitations under the License.
 *
 * When distributing the software, include this License Header Notice in each
 * file and include the License file at packager/legal/LICENSE.txt.
 *
 * GPL Classpath Exception:
 * Oracle designates this particular file as subject to the "Classpath"
 * exception as provided by Oracle in the GPL Version 2 section of the License
 * file that accompanied this code.
 *
 * Modifications:
 * If applicable, add the following below the License Header, with the fields
 * enclosed by brackets [] replaced by your own identifying information:
 * "Portions Copyright [year] [name of copyright owner]"
 *
 * Contributor(s):
 * If you wish your version of this file to be governed by only the CDDL or
 * only the GPL Version 2, indicate your decision by adding "[Contributor]
 * elects to include this software in this distribution under the [CDDL or GPL
 * Version 2] license."  If you don't indicate a single choice of license, a
 * recipient has the option to distribute your version of this file under
 * either the CDDL, the GPL Version 2 or to extend the choice of license to
 * its licensees as provided above.  However, if you add GPL Version 2 code
 * and therefore, elected the GPL Version 2 license, then the option applies
 * only if the new code is made subject to such option by the copyright
 * holder.
 *
 *
 * This file incorporates work covered by the following copyright and
 * permission notice:
 *
 * Copyright 2004 The Apache Software Foundation
 *
 * 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 org.glassfish.grizzly.http.util;

import java.io.CharConversionException;
import java.io.IOException;
import java.io.Serializable;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.charset.Charset;
import java.nio.charset.CharsetDecoder;
import java.nio.charset.CoderResult;
import java.util.Arrays;
import org.glassfish.grizzly.Buffer;
import org.glassfish.grizzly.utils.Charsets;

/**
 * Utilities to manipluate char chunks. While String is
 * the easiest way to manipulate chars ( search, substrings, etc),
 * it is known to not be the most efficient solution - Strings are
 * designed as imutable and secure objects.
 *
 * @author [email protected]
 * @author James Todd [[email protected]]
 * @author Costin Manolache
 * @author Remy Maucherat
 */
public final class CharChunk implements Chunk, Cloneable, Serializable {
    private static final UTF8Decoder UTF8_DECODER = new UTF8Decoder();
    
    /** Default encoding used to convert to strings. It should be UTF8,
	as most standards seem to converge, but the servlet API requires
	8859_1, and this object is used mostly for servlets.
    */
    public static final Charset DEFAULT_HTTP_CHARSET = Constants.DEFAULT_HTTP_CHARSET;

    private static final long serialVersionUID = -1L;

    // Input interface, used when the buffer is emptied.
    public interface CharInputChannel {
        /**
         * Read new bytes ( usually the internal conversion buffer ).
         * The implementation is allowed to ignore the parameters,
         * and mutate the chunk if it wishes to implement its own buffering.
         */
        int realReadChars(char cbuf[], int off, int len) throws IOException;
    }
    /**
     *  When we need more space we'll either
     *  grow the buffer ( up to the limit ) or send it to a channel.
     */
    public interface CharOutputChannel {
        /** Send the bytes ( usually the internal conversion buffer ).
         *  Expect 8k output if the buffer is full.
         */
        void realWriteChars(char cbuf[], int off, int len) throws IOException;
    }

    // --------------------
    // char[]
    private char buff[];

    private int start;
    private int end;

    private boolean isSet=false;  // XXX

    // -1: grow undefinitely
    // maximum amount to be cached
    private int limit=-1;

    private transient CharInputChannel in = null;
    private transient CharOutputChannel out = null;

    private boolean optimizedWrite=true;

    private String cachedString;
    
    /**
     * Creates a new, uninitialized CharChunk object.
     */
    public CharChunk() {
    }

    public CharChunk(int size) {
        allocate( size, -1 );
    }

    // --------------------

    public CharChunk getClone() {
        try {
            return (CharChunk)this.clone();
        } catch( Exception ex) {
            return null;
        }
    }

    public boolean isNull() {
        return end <= 0 && !isSet;
    }

    /**
     * Resets the message bytes to an uninitialized state.
     */
    public void recycle() {
        //	buff=null;
        isSet=false; // XXX
        start=0;
        end=0;
    }

    public void reset() {
        buff=null;
        cachedString = null;
    }

    // -------------------- Setup --------------------

    public void allocate( int initial, int limit  ) {
        boolean output = true;
        if( buff==null || buff.length < initial ) {
            buff=new char[initial];
        }
        this.limit=limit;
        start=0;
        end=0;
        output =true;
        isSet=true;
        resetStringCache();
    }

    public void ensureCapacity(final int size) {
        resetStringCache();

        if( buff==null || buff.length < size ) {
            buff=new char[size];
            limit = -1;
        }

        start=0;
        end=0;
    }

    public void setOptimizedWrite(boolean optimizedWrite) {
        this.optimizedWrite = optimizedWrite;
    }

    public void setChars( char[] c, int off, int len ) {
        buff=c;
        start=off;
        end=start + len;
        isSet=true;
        resetStringCache();
    }

    /** Maximum amount of data in this buffer.
     *
     *  If -1 or not set, the buffer will grow indefinitely.
     *  Can be smaller than the current buffer size ( which will not shrink ).
     *  When the limit is reached, the buffer will be flushed ( if out is set )
     *  or throw exception.
     */
    public void setLimit(int limit) {
        this.limit=limit;
        resetStringCache();
    }

    public int getLimit() {
        return limit;
    }

    /**
     * When the buffer is empty, read the data from the input channel.
     */
    public void setCharInputChannel(CharInputChannel in) {
        this.in = in;
    }

    /** When the buffer is full, write the data to the output channel.
     * 	Also used when large amount of data is appended.
     *
     *  If not set, the buffer will grow to the limit.
     */
    public void setCharOutputChannel(CharOutputChannel out) {
        this.out=out;
    }

    // compat
    public char[] getChars() {
        return getBuffer();
    }

    public char[] getBuffer() {
        return buff;
    }

    /**
     * Returns the start offset of the bytes.
     * For output this is the end of the buffer.
     */
    @Override
    public int getStart() {
        return start;
    }

    /**
     * Returns the start offset of the bytes.
     */
    @Override
    public void setStart(int start) {
        this.start = start;
        resetStringCache();
    }

    /**
     * Returns the length of the bytes.
     */
    @Override
    public int getLength() {
        return end-start;
    }


    @Override
    public int getEnd() {
        return end;
    }

    @Override
    public void setEnd( int i ) {
        end=i;
        resetStringCache();
    }

    // -------------------- Adding data --------------------

    public void append( char b ) throws IOException {
        makeSpace( 1 );

        // couldn't make space
        if( limit >0 && end >= limit ) {
            flushBuffer();
        }
        buff[end++]=b;
        resetStringCache();
    }

    public void append( CharChunk src ) throws IOException {
        append( src.getBuffer(), src.getStart(), src.getLength());
    }

    /** Add data to the buffer
     */
    public void append( char src[], int off, int len ) throws IOException {
        // will grow, up to limit
        resetStringCache();
        makeSpace( len );

        // if we don't have limit: makeSpace can grow as it wants
        if( limit < 0 ) {
            // assert: makeSpace made enough space
            System.arraycopy( src, off, buff, end, len );
            end+=len;
            return;
        }

        // Optimize on a common case.
        // If the source is going to fill up all the space in buffer, may
        // as well write it directly to the output, and avoid an extra copy
        if ( optimizedWrite && len == limit && end == start) {
            out.realWriteChars( src, off, len );
            return;
        }

        // if we have limit and we're below
        if( len <= limit - end ) {
            // makeSpace will grow the buffer to the limit,
            // so we have space
            System.arraycopy( src, off, buff, end, len );

            end+=len;
            return;
        }

        // need more space than we can afford, need to flush
        // buffer

        // the buffer is already at ( or bigger than ) limit

        // Optimization:
        // If len-avail < length ( i.e. after we fill the buffer with
        // what we can, the remaining will fit in the buffer ) we'll just
        // copy the first part, flush, then copy the second part - 1 write
        // and still have some space for more. We'll still have 2 writes, but
        // we write more on the first.

        if( len + end < 2 * limit ) {
            /* If the request length exceeds the size of the output buffer,
               flush the output buffer and then write the data directly.
               We can't avoid 2 writes, but we can write more on the second
            */
            int avail=limit-end;
            System.arraycopy(src, off, buff, end, avail);
            end += avail;

            flushBuffer();

            System.arraycopy(src, off+avail, buff, end, len - avail);
            end+= len - avail;

        } else {	// len > buf.length + avail
            // long write - flush the buffer and write the rest
            // directly from source
            flushBuffer();

            out.realWriteChars( src, off, len );
        }        
    }


    /** Add data to the buffer
     */
    public void append( StringBuffer sb ) throws IOException {
        resetStringCache();
        int len=sb.length();

        // will grow, up to limit
        makeSpace( len );

        // if we don't have limit: makeSpace can grow as it wants
        if( limit < 0 ) {
            // assert: makeSpace made enough space
            sb.getChars(0, len, buff, end );
            end+=len;
            return;
        }

        int off=0;
        int sbOff = off;
        int sbEnd = off + len;
        while (sbOff < sbEnd) {
            int d = min(limit - end, sbEnd - sbOff);
            sb.getChars( sbOff, sbOff+d, buff, end);
            sbOff += d;
            end += d;
            if (end >= limit)
            flushBuffer();
        }
    }

    /** Append a string to the buffer
     */
    public void append(String s) throws IOException {
        if (s != null) {
            append(s, 0, s.length());
        }
    }

    /** Append a string to the buffer
     */
    public void append(String s, int off, int len) throws IOException {
        if (s==null) return;

        resetStringCache();
        // will grow, up to limit
        makeSpace( len );

        // if we don't have limit: makeSpace can grow as it wants
        if( limit < 0 ) {
            // assert: makeSpace made enough space
            s.getChars(off, off+len, buff, end );
            end+=len;
            return;
        }

        int sOff = off;
        int sEnd = off + len;
        while (sOff < sEnd) {
            int d = min(limit - end, sEnd - sOff);
            s.getChars( sOff, sOff+d, buff, end);
            sOff += d;
            end += d;
            if (end >= limit)
            flushBuffer();
        }
    }

    // -------------------- Removing data from the buffer --------------------
    @Override
    public void delete(final int start, final int end) {
        resetStringCache();
        final int diff = this.end - end;
        if (diff == 0) {
            this.end = start;
        } else {
            System.arraycopy(buff, end, buff, start, diff);
            this.end = start + diff;
        }
    }

    public int substract()
        throws IOException {

        resetStringCache();
        if ((end - start) == 0) {
            if (in == null)
                return -1;
            int n = in.realReadChars(buff, end, buff.length - end);
            if (n < 0)
                return -1;
        }

        return (buff[start++]);

    }

    public int substract(CharChunk src)
        throws IOException {

        resetStringCache();
        if ((end - start) == 0) {
            if (in == null)
                return -1;
            int n = in.realReadChars( buff, end, buff.length - end);
            if (n < 0)
                return -1;
        }

        int len = getLength();
        src.append(buff, start, len);
        start = end;
        return len;

    }

    public int substract( char src[], int off, int len )
        throws IOException {

        resetStringCache();
        if ((end - start) == 0) {
            if (in == null)
                return -1;
            int n = in.realReadChars( buff, end, buff.length - end);
            if (n < 0)
                return -1;
        }

        int n = len;
        if (len > getLength()) {
            n = getLength();
        }
        System.arraycopy(buff, start, src, off, n);
        start += n;
        return n;

    }


    public void flushBuffer() throws IOException {
        //assert out!=null
        if( out==null ) {
            throw new IOException( "Buffer overflow, no sink " + limit + ' ' +
                       buff.length  );
        }
        out.realWriteChars( buff, start, end - start );
        end=start;
        resetStringCache();
    }

    /** Make space for len chars. If len is small, allocate
     *	a reserve space too. Never grow bigger than limit.
     */
    void makeSpace(int count) {
        char[] tmp;

        int newSize;
        int desiredSize=end + count;

        // Can't grow above the limit
        if( limit > 0 &&
            desiredSize > limit) {
            desiredSize=limit;
        }

        if( buff==null ) {
            if( desiredSize < 256 ) desiredSize=256; // take a minimum
            buff=new char[desiredSize];
        }

        // limit < buf.length ( the buffer is already big )
        // or we already have space XXX
        if( desiredSize <= buff.length) {
            return;
        }
        // grow in larger chunks
        if( desiredSize < 2 * buff.length ) {
            newSize= buff.length * 2;
            if( limit >0 &&
            newSize > limit ) newSize=limit;
            tmp=new char[newSize];
        } else {
            newSize= buff.length * 2 + count ;
            if( limit > 0 &&
            newSize > limit ) newSize=limit;
            tmp=new char[newSize];
        }

        System.arraycopy(buff, start, tmp, start, end-start);
        buff = tmp;
        tmp = null;
    }

    /**
     * Notify the Chunk that its content is going to be changed directly
     */
    protected void notifyDirectUpdate() {
    }

    protected final void resetStringCache() {
        cachedString = null;
    }
    
    // -------------------- Conversion and getters --------------------

    @Override
    public String toString() {
        if (null == buff || end - start == 0) {
            return "";
        } else if (cachedString != null) {
            return cachedString;
        }
//        return StringCache.toString(this);
        cachedString = toStringInternal();
        return cachedString;
    }

    @Override
    public String toString(int start, int end) {
        if (start == this.start && end == this.end) {
            return toString();
        } else if (null == buff) {
            return null;
        } else if (end - start == 0) {
            return "";
        }

        return new String(buff, this.start + start, end - start);
    }

    public String toStringInternal() {
        return new String(buff, start, end - start);
    }

    public int getInt() {
	return Ascii.parseInt(buff, start, end-start);
    }

    /**
     * Set {@link ByteChunk} content to CharChunk using given {@link Charset}.
     * @throws CharConversionException
     */
    public void set(final ByteChunk byteChunk, final Charset encoding)
            throws CharConversionException {

        final int bufferStart = byteChunk.getStart();
        final int bufferLength = byteChunk.getLength();
        allocate(bufferLength, -1);

        final byte[] buffer = byteChunk.getBuffer();
        
        if (Charsets.UTF8_CHARSET.equals(encoding)) {
            try {
//                final char[] ccBuf = getChars();
//                final int ccStart = getStart();

                end = UTF8_DECODER.convert(buffer,
                        bufferStart, buff, end,
                        bufferLength);
//                cc.setEnd(ccEnd);
            } catch (IOException e) {
                if (!(e instanceof CharConversionException)) {
                    throw new CharConversionException();
                }

                throw (CharConversionException) e;
            }
//            uri.setChars(cc.getChars(), cc.getStart(), cc.getEnd());
            return;
        } else if (!DEFAULT_HTTP_CHARSET.equals(encoding)) {
            final ByteBuffer bb = ByteBuffer.wrap(buffer,
                    bufferStart, bufferLength);
//            final char[] ccBuf = cc.getChars();
//            final int ccStart = cc.getStart();
            final CharBuffer cb = CharBuffer.wrap(buff, start, buff.length - start);

            final CharsetDecoder decoder = Charsets.getCharsetDecoder(encoding);
            final CoderResult cr = decoder.decode(bb, cb, true);

            if (cr != CoderResult.UNDERFLOW) {
                throw new CharConversionException("Decoding error");
            }

            end = start + cb.position();
//            uri.setChars(cc.getChars(), cc.getStart(), cc.getEnd());

            return;
        }

        // Default encoding: fast conversion
        for (int i = 0; i < bufferLength; i++) {
            buff[i] = (char) (buffer[i + bufferStart] & 0xff);
        }
        end = bufferLength;
        
//        return cc;
//        uri.setChars(cbuf, 0, bc.getLength());
    }
    
    /**
     * Set {@link BufferChunk} content to CharChunk using given {@link Charset}.
     * @throws CharConversionException
     */
    public void set(final BufferChunk bufferChunk, final Charset encoding)
            throws CharConversionException {

        final int bufferStart = bufferChunk.getStart();
        final int bufferLength = bufferChunk.getLength();
        allocate(bufferLength, -1);

        final Buffer buffer = bufferChunk.getBuffer();
        
        if (Charsets.UTF8_CHARSET.equals(encoding)) {
            try {
//                final char[] ccBuf = getChars();
//                final int ccStart = getStart();

                end = UTF8_DECODER.convert(buffer,
                        bufferStart, buff, end,
                        bufferLength);
//                cc.setEnd(ccEnd);
            } catch (IOException e) {
                if (!(e instanceof CharConversionException)) {
                    throw new CharConversionException();
                }

                throw (CharConversionException) e;
            }
//            uri.setChars(cc.getChars(), cc.getStart(), cc.getEnd());
            return;
        } else if (!DEFAULT_HTTP_CHARSET.equals(encoding)) {
            final ByteBuffer bb = buffer.toByteBuffer(
                    bufferStart, bufferStart + bufferLength);
//            final char[] ccBuf = cc.getChars();
//            final int ccStart = cc.getStart();
            final CharBuffer cb = CharBuffer.wrap(buff, start, buff.length - start);

            final CharsetDecoder decoder = Charsets.getCharsetDecoder(encoding);
            final CoderResult cr = decoder.decode(bb, cb, true);

            if (cr != CoderResult.UNDERFLOW) {
                throw new CharConversionException("Decoding error");
            }

            end = start + cb.position();
//            uri.setChars(cc.getChars(), cc.getStart(), cc.getEnd());

            return;
        }

        // Default encoding: fast conversion
        for (int i = 0; i < bufferLength; i++) {
            buff[i] = (char) (buffer.get(i + bufferStart) & 0xff);
        }
        end = bufferLength;
        
//        return cc;
//        uri.setChars(cbuf, 0, bc.getLength());
    }
    
    // -------------------- equals --------------------


    @Override
    public int hashCode() {
        int result = Arrays.hashCode(buff);
        result = 31 * result + start;
        result = 31 * result + end;
        result = 31 * result + (isSet ? 1 : 0);
        result = 31 * result + limit;
        result = 31 * result + in.hashCode();
        result = 31 * result + out.hashCode();
        result = 31 * result + (optimizedWrite ? 1 : 0);
        return result;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        CharChunk charChunk = (CharChunk) o;

        if (end != charChunk.end) return false;
        if (isSet != charChunk.isSet) return false;
        if (limit != charChunk.limit) return false;
        if (optimizedWrite != charChunk.optimizedWrite) return false;
        if (start != charChunk.start) return false;
        if (!Arrays.equals(buff, charChunk.buff)) return false;
        if (in != null ? !in.equals(charChunk.in) : charChunk.in != null)
            return false;
        if (out != null ? !out.equals(charChunk.out) : charChunk.out != null)
            return false;

        return true;
    }

    /**
     * Compares the message bytes to the specified String object.
     * @param s the String to compare
     * @return true if the comparison succeeded, false otherwise
     */
    public boolean equals(CharSequence s) {
        char[] c = buff;
        int len = end-start;
        if (c == null || len != s.length()) {
            return false;
        }
        int off = start;
        for (int i = 0; i < len; i++) {
            if (c[off++] != s.charAt(i)) {
            return false;
            }
        }
        return true;
    }

    /**
     * Compares the message bytes to the specified byte array representing
     * ASCII characters.

     * @param b the byte[] to compare
     *
     * @return true if the comparison succeeded, false otherwise
     *
     * @since 2.3
     */
    public boolean equals(byte[] b) {
        char[] c = buff;
        int len = end-start;
        if (c == null || len != b.length) {
            return false;
        }
        int off = start;
        for (int i = 0; i < len; i++) {
            if (c[off++] != b[i]) {
                return false;
            }
        }
        return true;
    }
    
    /**
     * Compares the message bytes to the specified String object.
     * @param s the String to compare
     * @return true if the comparison succeeded, false otherwise
     */
    public boolean equalsIgnoreCase(CharSequence s) {
        char[] c = buff;
        int len = end-start;
        if (c == null || len != s.length()) {
            return false;
        }
        int off = start;
        for (int i = 0; i < len; i++) {
            if (Ascii.toLower( c[off++] ) != Ascii.toLower( s.charAt(i))) {
            return false;
            }
        }
        return true;
    }

    /**
     * Compares the message bytes to the specified byte array representing
     * ASCII characters.

     * @param b the byte[] to compare
     *
     * @return true if the comparison succeeded, false otherwise
     *
     * @since 2.1.2
     */
    public boolean equalsIgnoreCase(final byte[] b) {
        return equalsIgnoreCase(b, 0, b.length);
    }

    /**
     * Compares the message bytes to the specified byte array representing
     * ASCII characters.

     * @param b the byte[] to compare
     *
     * @return true if the comparison succeeded, false otherwise
     *
     * @since 2.3
     */
    public boolean equalsIgnoreCase(final byte[] b, final int offset, final int len) {
        char[] c = buff;
        if (c == null || getLength() != len) {
            return false;
        }
        int offs1 = start;
        int offs2 = offset;
        for (int i = 0; i < len; i++) {
            if (Ascii.toLower(c[offs1++]) != Ascii.toLower(b[offs2++])) {
            return false;
            }
        }
        return true;
    }
    
    /**
     * Compares the message bytes to the specified char array representing
     * ASCII characters.

     * @param b the char[] to compare
     *
     * @return true if the comparison succeeded, false otherwise
     *
     * @since 2.3
     */
    public boolean equalsIgnoreCase(final char[] b, final int offset, final int len) {
        char[] c = buff;
        if (c == null || getLength() != len) {
            return false;
        }
        int offs1 = start;
        int offs2 = offset;
        for (int i = 0; i < len; i++) {
            if (Ascii.toLower(c[offs1++]) != Ascii.toLower(b[offs2++])) {
            return false;
            }
        }
        return true;
    }
    
    /**
     * Compares the char chunk to the specified byte array representing
     * lower-case ASCII characters.
     *
     * @param b the byte[] to compare
     *
     * @return true if the comparison succeeded, false otherwise
     *
     * @since 2.1.2
     */
    public boolean equalsIgnoreCaseLowerCase(final byte[] b) {
        char[] c = buff;
        int len = end-start;
        if (c == null || len != b.length) {
            return false;
        }
        int off = start;
        for (int i = 0; i < len; i++) {
            if (Ascii.toLower(c[off++]) != b[i]) {
            return false;
            }
        }
        return true;
    }

    public boolean equals(CharChunk cc) {
        return equals( cc.getChars(), cc.getStart(), cc.getLength());
    }

    public boolean equals(char b2[], int off2, int len2) {
        char b1[]=buff;
        if( b1==null && b2==null ) return true;

        if (b1== null || b2==null || end-start != len2) {
            return false;
        }
        int off1 = start;
        int len=end-start;
        while ( len-- > 0) {
            if (b1[off1++] != b2[off2++]) {
            return false;
            }
        }
        return true;
    }

    public boolean equals(byte b2[], int off2, int len2) {
        char b1[]=buff;
        if( b2==null && b1==null ) return true;

        if (b1== null || b2==null || end-start != len2) {
            return false;
        }
        int off1 = start;
        int len=end-start;

        while ( len-- > 0) {
            if ( b1[off1++] != (char)b2[off2++]) {
            return false;
            }
        }
        return true;
    }

    /**
     * Returns true if the message bytes starts with the specified string.
     * @param s the string
     */
    public boolean startsWith(String s) {
        return startsWith(s, 0);
    }

    boolean startsWith(final String s, final int pos) {
        char[] c = buff;
        int len = s.length();
//        if (c == null || len + pos > end) {
        if (c == null || len + pos > end - start) {
            return false;
        }
        int off = start + pos;
        for (int i = 0; i < len; i++) {
            if (c[off++] != s.charAt(i)) {
                return false;
            }
        }
        return true;
    }

    /**
     * Returns true if the message bytes starts with the specified string.
     * @param s the string
     */
    public boolean startsWithIgnoreCase(final String s, final int pos) {
        char[] c = buff;
        int len = s.length();
        if (c == null || len + pos > end - start) {
            return false;
        }
        int off = start + pos;
        for (int i = 0; i < len; i++) {
            if (Ascii.toLower(c[off++]) != Ascii.toLower(s.charAt(i))) {
                return false;
            }
        }
        return true;
    }

    public boolean endsWith(String s) {
        char[] c = buff;
        int len = s.length();
        if (c == null || len > end - start) {
            return false;
        }
        int off = end - len;
        for (int i = 0; i < len; i++) {
            if (c[off++] != s.charAt(i)) {
                return false;
            }
        }
        return true;
    }
    
    // -------------------- Hash code  --------------------
    
    // normal hash.
    public int hash() {
        int code=0;
        for (int i = start; i < end; i++) {
            code = code * 31 + buff[i];
        }
        return code;
    }

    // hash ignoring case
    public int hashIgnoreCase() {
        int code=0;
        for (int i = start; i < end; i++) {
            code = code * 31 + Ascii.toLower(buff[i]);
        }
        return code;
    }

    public int indexOf(char c) {
        return indexOf( c, start);
    }

    /**
     * Returns true if the message bytes starts with the specified string.
     * @param c the character
     */
    @Override
    public int indexOf(char c, int starting) {
        int ret = indexOf( buff, start+starting, end, c );
        return (ret >= start) ? ret - start : -1;
    }

    public static int indexOf( char chars[], int off, int cend, char qq ) {
        while( off < cend ) {
            if (chars[off] == qq) {
                return off;
            }
            off++;
        }
        return -1;
    }


    @Override
    public final int indexOf(String s, int fromIndex) {
        return indexOf(s, 0, s.length(), fromIndex);
    }

    public final int indexOf(String src, int srcOff, int srcLen, int myOff) {
        char first = src.charAt(srcOff);

        // Look for first char
        int srcEnd = srcOff + srcLen;

        for (int i = myOff + start; i <= (end - srcLen); i++) {
            if (buff[i] != first) {
                continue;
            }
            // found first char, now look for a match
            int myPos = i + 1;
            for (int srcPos = srcOff + 1; srcPos < srcEnd;) {
                if (buff[myPos++] != src.charAt(srcPos++)) {
                    break;
                }
                if (srcPos == srcEnd) {
                    return i - start; // found it
                }
            }
        }
        
        return -1;
    }

    public void trimLeft() {
        boolean modified = false;
        while (buff[start] <= 0x20) {
            modified = true;
            start++;
        }
        if (modified) {
            resetStringCache();
        }
    }

    // -------------------- utils
    private int min(int a, int b) {
        if (a < b) return a;
        return b;
    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy