/* * Copyright (c) 1998-2010 Caucho Technology -- all rights reserved * * This file is part of Resin(R) Open Source * * Each copy or derived work must preserve the copyright notice and this * notice unmodified. * * Resin Open Source is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * Resin Open Source is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE, or any warranty * of NON-INFRINGEMENT. See the GNU General Public License for more * details. * * You should have received a copy of the GNU General Public License * along with Resin Open Source; if not, write to the * * Free Software Foundation, Inc. * 59 Temple Place, Suite 330 * Boston, MA 02111-1307 USA * * @author Scott Ferguson */ package com.caucho.quercus.env; import java.io.*; import java.util.*; import com.caucho.vfs.*; import com.caucho.quercus.QuercusRuntimeException; /** * Represents a 8-bit PHP 5 style binary builder (unicode.semantics = off), * used for large data like file reads. */ public class LargeStringBuilderValue extends StringValue { public static final StringValue EMPTY = StringBuilderValue.EMPTY; public static final int SIZE = 4 * 1024; protected byte[][] _bufferList; protected int _length; private int _hashCode; private String _value; public LargeStringBuilderValue() { _bufferList = new byte[32][]; _bufferList[0] = new byte[SIZE]; } public LargeStringBuilderValue(byte[][] bufferList, int length) { _bufferList = bufferList; _length = length; } public LargeStringBuilderValue(StringValue s) { this(); s.appendTo(this); } /** * Creates an empty string builder of the same type. */ public StringValue createEmptyStringBuilder() { return new StringBuilderValue(); } /** * Returns the value. */ public String getValue() { return toString(); } /** * Returns the type. */ @Override public String getType() { return "string"; } /** * Returns the ValueType. */ @Override public ValueType getValueType() { return StringBuilderValue.getValueType(_bufferList[0], 0, _length); } /** * Returns true for a long */ @Override public boolean isLongConvertible() { return false; } /** * Returns true for a double */ @Override public boolean isDouble() { return false; } /** * Returns true for a number */ @Override public boolean isNumber() { return false; } /** * Returns true for a scalar */ @Override public boolean isScalar() { return true; } /** * Converts to a boolean. */ @Override public boolean toBoolean() { if (_length == 0) { return false; } else if (_length == 1 && _bufferList[0][0] == '0') { return false; } else { return true; } } /** * Converts to a long. */ @Override public long toLong() { return parseLong(_bufferList[0], 0, _length); } /** * Converts to a double. */ @Override public double toDouble() { return StringBuilderValue.toDouble(_bufferList[0], 0, _length); } /** * Convert to an input stream. */ @Override public InputStream toInputStream() { return new BuilderInputStream(); } /** * Converts to a string. */ @Override public String toString() { char[] buffer = new char[_length]; byte[][] bufferList = _bufferList; for (int i = _length - 1; i >= 0; i--) { buffer[i] = (char) bufferList[i / SIZE][i % SIZE]; } return new String(buffer, 0, _length); } /** * Converts to an object. */ @Override public Object toJavaObject() { return toString(); } /** * Converts to a string builder */ @Override public StringValue toStringBuilder() { // TODO: can this just return this, or does it need to return a copy? return new LargeStringBuilderValue(_bufferList, _length); } /** * Converts to a BinaryValue. */ @Override public StringValue toBinaryValue(Env env) { return this; } /** * Converts to a BinaryValue in desired charset. */ @Override public StringValue toBinaryValue(String charset) { return this; } /** * Append to a string builder. */ public void appendTo(StringValue bb) { int tail = _length % SIZE; int fixedLength = (_length - tail) / SIZE; int i = 0; for (; i < fixedLength; i++) { bb.append(_bufferList[i], 0, SIZE); } bb.append(_bufferList[i], 0, tail); } /** * Converts to a key. */ @Override public Value toKey() { if (getValueType().isLongAdd()) { return LongValue.create(toLong()); } else { return this; } } /** * Converts to a byte array, with no consideration of character encoding. * Each character becomes one byte, characters with values above 255 are * not correctly preserved. */ @Override public byte[] toBytes() { byte[] bytes = new byte[_length]; byte[][] bufferList = _bufferList; for (int i = _length - 1; i >= 0; i--) { bytes[i] = bufferList[i / SIZE][i % SIZE]; } return bytes; } // // Operations // /** * Returns the character at an index */ @Override public Value get(Value key) { return charValueAt(key.toLong()); } /** * Returns the character at an index */ @Override public Value charValueAt(long index) { int len = _length; if (index < 0 || len <= index) { return UnsetStringValue.UNSET; } else { int data = _bufferList[(int) (index / SIZE)][(int) (index % SIZE)]; return StringBuilderValue.create((char) (data & 0xff)); } } // // CharSequence // /** * Returns the length of the string. */ @Override public int length() { return _length; } /** * Returns the character at a particular location */ @Override public char charAt(int index) { int data = _bufferList[index / SIZE][index % SIZE] & 0xff; return (char) data; } /** * Returns a subsequence */ @Override public CharSequence subSequence(int start, int end) { if (end <= start) { return StringBuilderValue.EMPTY; } StringValue stringValue; if (end - start < 1024) { stringValue = new StringBuilderValue(end - start); } else { stringValue = new LargeStringBuilderValue(); } int endChunk = end / SIZE; while (start < end) { int startChunk = start / SIZE; int startOffset = start % SIZE; if (startChunk == endChunk) { stringValue.append(_bufferList[startChunk], startOffset, (end - start)); return stringValue; } else { int len = SIZE - startOffset; stringValue.append(_bufferList[startChunk], startOffset, len); start += len; } } return stringValue; } /** * Convert to lower case. */ @Override public StringValue toLowerCase() { int length = _length; StringValue string = new LargeStringBuilderValue(); byte[][] bufferList = _bufferList; for (int i = 0; i < length; i++) { int ch = bufferList[i / SIZE][i % SIZE] & 0xff; if ('A' <= ch && ch <= 'Z') { ch = (ch + 'a' - 'A'); } string.append((char) ch); } return string; } /** * Convert to lower case. */ @Override public StringValue toUpperCase() { int length = _length; StringValue string = new LargeStringBuilderValue(); byte[][] bufferList = _bufferList; for (int i = 0; i < length; i++) { int ch = bufferList[i / SIZE][i % SIZE] & 0xff; if ('a' <= ch && ch <= 'z') { ch = (ch + 'A' - 'a'); } string.append((char) ch); } return string; } // // append code // /** * Creates a string builder of the same type. */ @Override public StringValue createStringBuilder() { return new StringBuilderValue(); } /** * Creates a string builder of the same type. */ @Override public StringValue createStringBuilder(int length) { return new StringBuilderValue(length); } /** * Converts to a string builder */ @Override public StringValue toStringBuilder(Env env) { return new LargeStringBuilderValue(_bufferList, _length); } /** * Append a Java buffer to the value. */ @Override public final StringValue appendUnicode(char[] buf, int offset, int length) { return append(buf, offset, length); } /** * Append a Java string to the value. */ @Override public StringValue append(String s) { int len = s.length(); ensureCapacity(_length + len); for (int i = 0; i < len; i++) { _bufferList[_length / SIZE][_length % SIZE] = (byte) s.charAt(i); _length++; } return this; } /** * Append a Java buffer to the value. */ @Override public StringValue append(CharSequence buf, int head, int tail) { int len = tail - head; ensureCapacity(_length + len); for (int i = 0; i < len; i++) { _bufferList[_length / SIZE][_length % SIZE] = (byte) buf.charAt(i); _length++; } return this; } /** * Append a Java buffer to the value. */ @Override public final StringValue append(char[] buf, int offset, int length) { ensureCapacity(_length + length); for (int i = offset; i < length + offset; i++) { _bufferList[_length / SIZE][_length % SIZE] = (byte) buf[i]; _length++; } return this; } /** * Append a buffer to the value. */ @Override public final StringValue append(byte[] buf, int offset, int length) { ensureCapacity(_length + length); while (length > 0) { int chunk = _length / SIZE; int chunkOffset = _length % SIZE; int sublen = SIZE - chunkOffset; if (length < sublen) { sublen = length; } System.arraycopy(buf, offset, _bufferList[chunk], chunkOffset, sublen); offset += sublen; length -= sublen; _length += sublen; } return this; } /** * Append a double to the value. */ @Override public final StringValue append(byte[] buf) { return append(buf, 0, buf.length); } /** * Append a Java byte to the value without conversions. */ @Override public final StringValue append(char v) { if (_length % SIZE == 0) { ensureCapacity(_length + 1); } _bufferList[_length / SIZE][_length % SIZE] = (byte) v; _length += 1; return this; } /** * Append a Java byte to the value without conversions. */ public final StringValue append(byte v) { if (_length % SIZE == 0) { ensureCapacity(_length + 1); } _bufferList[_length / SIZE][_length % SIZE] = (byte) v; _length += 1; return this; } /** * Append a Java boolean to the value. */ @Override public final StringValue append(boolean v) { return append(v ? "true" : "false"); } /** * Append a Java long to the value. */ @Override public StringValue append(long v) { // TODO: this probably is frequent enough to special-case return append(String.valueOf(v)); } /** * Append a Java double to the value. */ @Override public StringValue append(double v) { return append(String.valueOf(v)); } /** * Append a Java value to the value. */ @Override public final StringValue append(Value v) { v.appendTo(this); return this; } /** * Append from an input stream, using InputStream.read semantics, * i.e. just call is.read once even if more data is available. */ @Override public int appendRead(InputStream is, long length) { try { int offset = _length % SIZE; if (offset == 0) { ensureCapacity(_length + SIZE); } byte[] buffer = _bufferList[_length / SIZE]; int sublen = SIZE - offset; if (length < sublen) { sublen = (int) length; } sublen = is.read(buffer, 0, sublen); if (sublen > 0) { _length += sublen; return sublen; } else { return -1; } } catch (IOException e) { throw new QuercusRuntimeException(e); } } /** * Append from an input stream, reading from the input stream until * end of file or the length is reached. */ @Override public int appendReadAll(InputStream is, long length) { int readLength = 0; while (length > 0) { int sublen = appendRead(is, length); if (sublen < 0) { return readLength <= 0 ? -1 : readLength; } length -= sublen; readLength += sublen; } return readLength; } /** * Append to a string builder. */ @Override public StringValue appendTo(UnicodeBuilderValue sb) { if (length() == 0) { return sb; } Env env = Env.getInstance(); try { Reader reader = env.getRuntimeEncodingFactory().create(toInputStream()); if (reader != null) { sb.append(reader); reader.close(); } return sb; } catch (IOException e) { throw new QuercusRuntimeException(e); } } // // Java generator code // /** * Prints the value. * @param env */ @Override public void print(Env env) { for (int i = 0; i < _length; i += SIZE) { int chunk = i / SIZE; int sublen = _length - i; if (SIZE < sublen) { sublen = SIZE; } env.write(_bufferList[chunk], 0, sublen); } } /** * Prints the value. * @param env */ @Override public void print(Env env, WriteStream out) { try { for (int i = 0; i < _length; i += SIZE) { int chunk = i / SIZE; int sublen = _length - i; if (SIZE < sublen) { sublen = SIZE; } out.write(_bufferList[chunk], 0, sublen); } } catch (IOException e) { throw new QuercusRuntimeException(e); } } /** * Serializes the value. */ @Override public void serialize(Env env, StringBuilder sb) { sb.append("s:"); sb.append(_length); sb.append(":\""); sb.append(toString()); sb.append("\";"); } /** * Returns an OutputStream. */ public OutputStream getOutputStream() { return new BuilderOutputStream(); } private void ensureCapacity(int newCapacity) { if (newCapacity > 10000000) { Thread.dumpStack(); throw new IllegalStateException(); } int chunk = _length / SIZE; int endChunk = newCapacity / SIZE; if (_bufferList.length <= endChunk) { byte[][] bufferList = new byte[endChunk + 32][]; System.arraycopy(_bufferList, 0, bufferList, 0, _bufferList.length); _bufferList = bufferList; } for (; chunk <= endChunk; chunk++) { if (_bufferList[chunk] == null) { _bufferList[chunk] = new byte[SIZE]; } } } /** * Returns the hash code. */ @Override public int hashCode() { if (_hashCode != 0) { return _hashCode; } int hash = 37; int length = _length; byte[][] bufferList = _bufferList; for (int i = 0; i < length; i++) { hash = 65521 * hash + (bufferList[i / SIZE][i % SIZE] & 0xff); } _hashCode = hash; return hash; } @Override public String toDebugString() { StringBuilder sb = new StringBuilder(); int length = length(); sb.append("string("); sb.append(length); sb.append(") \""); int appendLength = length > 256 ? 256 : length; for (int i = 0; i < appendLength; i++) { sb.append(charAt(i)); } if (length > 256) { sb.append(" ..."); } sb.append('"'); return sb.toString(); } @Override public void varDumpImpl(Env env, WriteStream out, int depth, IdentityHashMap<Value, String> valueSet) throws IOException { int length = length(); if (length < 0) { length = 0; } out.print("string("); out.print(length); out.print(") \""); for (int i = 0; i < length; i++) { int ch = charAt(i); out.print((char) ch); } out.print("\""); /* int length = length(); if (length < 0) length = 0; out.print("string"); out.print("("); out.print(length); out.print(") \""); for (int i = 0; i < length; i++) { char ch = charAt(i); if (0x20 <= ch && ch <= 0x7f || ch == '\t' || ch == '\r' || ch == '\n') out.print(ch); else if (ch <= 0xff) out.print("\\x" + Integer.toHexString(ch / 16) + Integer.toHexString(ch % 16)); else { out.print("\\u" + Integer.toHexString((ch >> 12) & 0xf) + Integer.toHexString((ch >> 8) & 0xf) + Integer.toHexString((ch >> 4) & 0xf) + Integer.toHexString((ch) & 0xf)); } } out.print("\""); */ } class BuilderInputStream extends InputStream { private int _index; /** * Reads the next byte. */ @Override public int read() { if (_index < _length) { return charAt(_index++); } else { return -1; } } /** * Reads into a buffer. */ @Override public int read(byte[] buffer, int offset, int length) { int sublen = _length - _index; if (length < sublen) { sublen = length; } if (sublen <= 0) { return -1; } for (int i = 0; i < sublen; i++) { buffer[offset + i] = (byte) charAt(_index + i); } _index += sublen; return sublen; } } class BuilderOutputStream extends OutputStream { /** * Writes the next byte. */ @Override public void write(int ch) { append(ch); } /** * Reads into a buffer. */ @Override public void write(byte[] buffer, int offset, int length) { append(buffer, offset, length); } } }