/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You 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.apache.tomcat.util.buf; import java.io.IOException; /** * Utilities to manipulate 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 immutable and secure objects. * * @author dac@sun.com * @author James Todd [gonzo@sun.com] * @author Costin Manolache * @author Remy Maucherat */ public final class CharChunk extends AbstractChunk implements CharSequence { private static final long serialVersionUID = 1L; /** * Input interface, used when the buffer is empty. */ public static interface CharInputChannel { /** * Read new characters. * * @return The number of characters read * * @throws IOException If an I/O error occurs during reading */ public int realReadChars() throws IOException; } /** * When we need more space we'll either grow the buffer ( up to the limit ) * or send it to a channel. */ public static interface CharOutputChannel { /** * Send the bytes ( usually the internal conversion buffer ). Expect 8k * output if the buffer is full. * * @param buf characters that will be written * @param off offset in the characters array * @param len length that will be written * @throws IOException If an I/O occurs while writing the characters */ public void realWriteChars(char buf[], int off, int len) throws IOException; } // -------------------- // char[] private char[] buff; // transient as serialization is primarily for values via, e.g. JMX private transient CharInputChannel in = null; private transient CharOutputChannel out = null; /** * Creates a new, uninitialized CharChunk object. */ public CharChunk() { } public CharChunk(int initial) { allocate(initial, -1); } // -------------------- @Override public Object clone() throws CloneNotSupportedException { return super.clone(); } // -------------------- Setup -------------------- public void allocate(int initial, int limit) { if (buff == null || buff.length < initial) { buff = new char[initial]; } setLimit(limit); start = 0; end = 0; isSet = true; hasHashCode = false; } /** * Sets the buffer to the specified subarray of characters. * * @param c the characters * @param off the start offset of the characters * @param len the length of the characters */ public void setChars(char[] c, int off, int len) { buff = c; start = off; end = start + len; isSet = true; hasHashCode = false; } /** * @return the buffer. */ public char[] getChars() { return getBuffer(); } /** * @return the buffer. */ public char[] getBuffer() { return buff; } /** * When the buffer is empty, read the data from the input channel. * * @param in 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. * * @param out The output channel */ public void setCharOutputChannel(CharOutputChannel out) { this.out = out; } // -------------------- Adding data to the buffer -------------------- public void append(char c) throws IOException { makeSpace(1); int limit = getLimitInternal(); // couldn't make space if (end >= limit) { flushBuffer(); } buff[end++] = c; } public void append(CharChunk src) throws IOException { append(src.getBuffer(), src.getOffset(), src.getLength()); } /** * Add data to the buffer. * * @param src Char array * @param off Offset * @param len Length * @throws IOException Writing overflow data to the output channel failed */ public void append(char src[], int off, int len) throws IOException { // will grow, up to limit makeSpace(len); int limit = getLimitInternal(); // Optimize on a common case. // If the buffer is empty and 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 (len == limit && end == start && out != null) { out.realWriteChars(src, off, len); return; } // if we are below the limit if (len <= limit - end) { 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); } } /** * Append a string to the buffer. * * @param s The string * @throws IOException Writing overflow data to the output channel failed */ public void append(String s) throws IOException { append(s, 0, s.length()); } /** * Append a string to the buffer. * * @param s The string * @param off Offset * @param len Length * @throws IOException Writing overflow data to the output channel failed */ public void append(String s, int off, int len) throws IOException { if (s == null) { return; } // will grow, up to limit makeSpace(len); int limit = getLimitInternal(); 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 -------------------- public int substract() throws IOException { if (checkEof()) { return -1; } return buff[start++]; } public int substract(char dest[], int off, int len) throws IOException { if (checkEof()) { return -1; } int n = len; if (len > getLength()) { n = getLength(); } System.arraycopy(buff, start, dest, off, n); start += n; return n; } private boolean checkEof() throws IOException { if ((end - start) == 0) { if (in == null) { return true; } int n = in.realReadChars(); if (n < 0) { return true; } } return false; } /** * Send the buffer to the sink. Called by append() when the limit is * reached. You can also call it explicitly to force the data to be written. * * @throws IOException Writing overflow data to the output channel failed */ public void flushBuffer() throws IOException { // assert out!=null if (out == null) { throw new IOException("Buffer overflow, no sink " + getLimit() + " " + buff.length); } out.realWriteChars(buff, start, end - start); end = start; } /** * Make space for len chars. If len is small, allocate a reserve space too. * Never grow bigger than the limit or {@link AbstractChunk#ARRAY_MAX_SIZE}. * * @param count The size */ public void makeSpace(int count) { char[] tmp = null; int limit = getLimitInternal(); long newSize; long desiredSize = end + count; // Can't grow above the limit if (desiredSize > limit) { desiredSize = limit; } if (buff == null) { if (desiredSize < 256) { desiredSize = 256; // take a minimum } buff = new char[(int) 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 < 2L * buff.length) { newSize = buff.length * 2L; } else { newSize = buff.length * 2L + count; } if (newSize > limit) { newSize = limit; } tmp = new char[(int) newSize]; // Some calling code assumes buffer will not be compacted System.arraycopy(buff, 0, tmp, 0, end); buff = tmp; tmp = null; } // -------------------- Conversion and getters -------------------- @Override public String toString() { if (null == buff) { return null; } else if (end - start == 0) { return ""; } return StringCache.toString(this); } public String toStringInternal() { return new String(buff, start, end - start); } // -------------------- equals -------------------- @Override public boolean equals(Object obj) { if (obj instanceof CharChunk) { return equals((CharChunk) obj); } return false; } /** * 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(String 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 String object. * * @param s the String to compare * @return true if the comparison succeeded, false * otherwise */ public boolean equalsIgnoreCase(String 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; } public boolean equals(CharChunk cc) { return equals(cc.getChars(), cc.getOffset(), cc.getLength()); } public boolean equals(char b2[], int off2, int len2) { char b1[] = buff; if (b1 == null && b2 == null) { return true; } int len = end - start; if (len != len2 || b1 == null || b2 == null) { return false; } int off1 = start; while (len-- > 0) { if (b1[off1++] != b2[off2++]) { return false; } } return true; } /** * @return true if the message bytes starts with the specified * string. * @param s The string */ public boolean startsWith(String s) { char[] c = buff; int len = s.length(); if (c == null || len > end - start) { return false; } int off = start; for (int i = 0; i < len; i++) { if (c[off++] != s.charAt(i)) { return false; } } return true; } /** * Returns true if the buffer starts with the specified string. * * @param s the string * @param pos The position * * @return true if the start matches */ public boolean startsWithIgnoreCase(String s, 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; } /** * @return true if the message bytes end with the specified * string. * @param s The string */ 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; } @Override protected int getBufferElement(int index) { return buff[index]; } public int indexOf(char c) { return indexOf(c, start); } /** * Returns the first instance of the given character in this CharChunk * starting at the specified char. If the character is not found, -1 is * returned.
* * @param c The character * @param starting The start position * @return The position of the first instance of the character or -1 if the * character is not found. */ public int indexOf(char c, int starting) { int ret = indexOf(buff, start + starting, end, c); return (ret >= start) ? ret - start : -1; } /** * Returns the first instance of the given character in the given char array * between the specified start and end.
* * @param chars The array to search * @param start The point to start searching from in the array * @param end The point to stop searching in the array * @param s The character to search for * @return The position of the first instance of the character or -1 if the * character is not found. */ public static int indexOf(char chars[], int start, int end, char s) { int offset = start; while (offset < end) { char c = chars[offset]; if (c == s) { return offset; } offset++; } return -1; } // -------------------- utils private int min(int a, int b) { if (a < b) { return a; } return b; } // Char sequence impl @Override public char charAt(int index) { return buff[index + start]; } @Override public CharSequence subSequence(int start, int end) { try { CharChunk result = (CharChunk) this.clone(); result.setOffset(this.start + start); result.setEnd(this.start + end); return result; } catch (CloneNotSupportedException e) { // Cannot happen return null; } } @Override public int length() { return end - start; } /** * NO-OP. * * @param optimizedWrite Ignored * * @deprecated Unused code. This is now a NO-OP and will be removed without * replacement in Tomcat 10. */ @Deprecated public void setOptimizedWrite(boolean optimizedWrite) { } }