/*
* 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.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.io.Reader;
import java.io.UnsupportedEncodingException;
import java.util.IdentityHashMap;
import java.util.zip.CRC32;
import com.caucho.quercus.QuercusModuleException;
import com.caucho.quercus.QuercusRuntimeException;
import com.caucho.quercus.lib.file.BinaryInput;
import com.caucho.quercus.lib.i18n.Decoder;
import com.caucho.quercus.marshal.Marshal;
import com.caucho.util.ByteAppendable;
import com.caucho.vfs.ReadStream;
import com.caucho.vfs.TempBuffer;
import com.caucho.vfs.WriteStream;
/**
* Represents a Quercus string value.
*/
abstract public class StringValue
extends Value
implements CharSequence, ByteAppendable {
public static final StringValue EMPTY = new ConstStringValue("");
protected static final int MIN_LENGTH = 32;
protected static final int IS_STRING = 0;
protected static final int IS_LONG = 1;
protected static final int IS_DOUBLE = 2;
/**
* Creates a string builder of the same type.
*/
abstract public StringValue createStringBuilder();
/**
* Creates a string builder of the same type.
*/
abstract public StringValue createStringBuilder(int length);
/**
* Creates the string.
*/
public static Value create(String value) {
// TODO: needs updating for i18n, currently php5 only
if (value == null) {
return NullValue.NULL;
} else {
return new ConstStringValue(value);
}
}
/**
* Creates the string.
*/
public static StringValue create(char value) {
// TODO: needs updating for i18n, currently php5 only
return ConstStringValue.create(value);
/*
if (value < CHAR_STRINGS.length)
return CHAR_STRINGS[value];
else
return new StringBuilderValue(String.valueOf(value));
*/
}
/**
* Creates the string.
*/
public static Value create(Object value) {
// TODO: needs updating for i18n, currently php5 only
if (value == null) {
return NullValue.NULL;
} else {
return new StringBuilderValue(value.toString());
}
}
/*
* Decodes the Unicode str from charset.
*
* @param str should be a Unicode string
* @param charset to decode string from
*/
public StringValue create(Env env, StringValue unicodeStr, String charset) {
if (!unicodeStr.isUnicode()) {
return unicodeStr;
}
try {
StringValue sb = createStringBuilder();
byte[] bytes = unicodeStr.toString().getBytes(charset);
sb.append(bytes);
return sb;
} catch (UnsupportedEncodingException e) {
env.warning(e);
return unicodeStr;
}
}
//
// Predicates and relations
//
/**
* Returns the type.
*/
@Override
public String getType() {
return "string";
}
/**
* Returns the ValueType.
*/
@Override
public ValueType getValueType() {
return ValueType.STRING;
}
/**
* Returns true for a long
*/
@Override
public boolean isLongConvertible() {
return getValueType().isLongCmp();
}
/**
* Returns true for a double
*/
@Override
public boolean isDoubleConvertible() {
return getValueType().isNumberCmp();
}
/**
* Returns true for a number
*/
public boolean isNumber() {
return false;
}
/**
* Returns true for is_numeric
*/
@Override
public boolean isNumeric() {
// php/120y
return getValueType().isNumberCmp();
}
/**
* Returns true for a scalar
*/
public boolean isScalar() {
return true;
}
/**
* Returns true for StringValue
*/
@Override
public boolean isString() {
return true;
}
/*
* Returns true if this is a PHP5 string.
*/
public boolean isPHP5String() {
return false;
}
/**
* Returns true if the value is empty
*/
@Override
public boolean isEmpty() {
return length() == 0 || length() == 1 && charAt(0) == '0';
}
//
// marshal cost
//
/**
* Cost to convert to a double
*/
@Override
public int toDoubleMarshalCost() {
ValueType valueType = getValueType();
if (valueType.isLongCmp()) {
return Marshal.COST_TO_CHAR_ARRAY + 20;
} else if (valueType.isNumberCmp()) {
return Marshal.COST_TO_CHAR_ARRAY + 10;
} else {
return Marshal.COST_INCOMPATIBLE;
}
}
/**
* Cost to convert to a float
*/
@Override
public int toFloatMarshalCost() {
ValueType valueType = getValueType();
if (valueType.isLongCmp()) {
return Marshal.COST_TO_CHAR_ARRAY + 25;
} else if (valueType.isNumberCmp()) {
return Marshal.COST_TO_CHAR_ARRAY + 15;
} else {
return Marshal.COST_INCOMPATIBLE;
}
}
/**
* Cost to convert to a long
*/
@Override
public int toLongMarshalCost() {
ValueType valueType = getValueType();
if (valueType.isLongCmp()) {
return Marshal.COST_TO_CHAR_ARRAY + 10;
} else if (valueType.isNumberCmp()) {
return Marshal.COST_TO_CHAR_ARRAY + 40;
} else {
return Marshal.COST_INCOMPATIBLE;
}
}
/**
* Cost to convert to an integer
*/
@Override
public int toIntegerMarshalCost() {
ValueType valueType = getValueType();
if (valueType.isLongCmp()) {
return Marshal.COST_TO_CHAR_ARRAY + 10;
} else if (valueType.isNumberCmp()) {
return Marshal.COST_TO_CHAR_ARRAY + 40;
} else {
return Marshal.COST_INCOMPATIBLE;
}
}
/**
* Cost to convert to a short
*/
@Override
public int toShortMarshalCost() {
ValueType valueType = getValueType();
if (valueType.isLongCmp()) {
return Marshal.COST_TO_CHAR_ARRAY + 30;
} else if (valueType.isNumberCmp()) {
return Marshal.COST_TO_CHAR_ARRAY + 50;
} else {
return Marshal.COST_INCOMPATIBLE;
}
}
/**
* Cost to convert to a byte
*/
@Override
public int toByteMarshalCost() {
ValueType valueType = getValueType();
if (valueType.isLongCmp()) {
return Marshal.COST_TO_CHAR_ARRAY + 30;
} else if (valueType.isNumberCmp()) {
return Marshal.COST_TO_CHAR_ARRAY + 50;
} else {
return Marshal.COST_STRING_TO_BYTE;
}
}
/**
* Cost to convert to a character
*/
@Override
public int toCharMarshalCost() {
return Marshal.COST_STRING_TO_CHAR;
}
/**
* Cost to convert to a String
*/
@Override
public int toStringMarshalCost() {
return Marshal.COST_EQUAL;
}
/**
* Cost to convert to a char[]
*/
@Override
public int toCharArrayMarshalCost() {
return Marshal.COST_STRING_TO_CHAR_ARRAY;
}
/**
* Cost to convert to a StringValue
*/
@Override
public int toStringValueMarshalCost() {
return Marshal.COST_IDENTICAL;
}
/**
* Cost to convert to a binary value
*/
@Override
public int toBinaryValueMarshalCost() {
return Marshal.COST_STRING_TO_BINARY;
}
/**
* Returns true for equality
*/
@Override
public int cmp(Value rValue) {
if (isNumberConvertible() || rValue.isNumberConvertible()) {
double l = toDouble();
double r = rValue.toDouble();
if (l == r) {
return 0;
} else if (l < r) {
return -1;
} else {
return 1;
}
} else {
int result = toString().compareTo(rValue.toString());
if (result == 0) {
return 0;
} else if (result > 0) {
return 1;
} else {
return -1;
}
}
}
/**
* Returns true for equality
*/
@Override
public boolean eq(Value rValue) {
ValueType typeA = getValueType();
ValueType typeB = rValue.getValueType();
if (typeB.isNumber()) {
double l = toDouble();
double r = rValue.toDouble();
return l == r;
} else if (typeB.isBoolean()) {
return toBoolean() == rValue.toBoolean();
} else if (typeA.isNumberCmp() && typeB.isNumberCmp()) {
double l = toDouble();
double r = rValue.toDouble();
return l == r;
} else {
return toString().equals(rValue.toString());
}
}
/**
* Compare two strings
*/
public int cmpString(StringValue rValue) {
return toString().compareTo(rValue.toString());
}
// Conversions
/**
* Converts to a string value.
*/
@Override
public StringValue toStringValue() {
return this;
}
/**
* Converts to a string value.
*/
@Override
public StringValue toStringValue(Env env) {
return this;
}
/**
* Converts to a long.
*/
public static long toLong(String string) {
return parseLong(string);
}
/**
* String to long conversion routines used by this module
* and other modules in this package. These methods are
* only invoked by other implementations of a "string" object.
* The 3 implementations should be identical except for the
* char data source.
*/
static long parseLong(char[] buffer, int offset, int len) {
if (len == 0) {
return 0;
}
long value = 0;
long sign = 1;
boolean isResultSet = false;
long result = 0;
int end = offset + len;
while (offset < end && Character.isWhitespace(buffer[offset])) {
offset++;
}
int ch;
if (offset + 1 < end && buffer[offset] == '0'
&& ((ch = buffer[offset + 1]) == 'x' || ch == 'X')) {
for (offset += 2; offset < end; offset++) {
ch = buffer[offset] & 0xFF;
long oldValue = value;
if ('0' <= ch && ch <= '9') {
value = value * 16 + ch - '0';
} else if ('a' <= ch && ch <= 'z') {
value = value * 16 + ch - 'a' + 10;
} else if ('A' <= ch && ch <= 'Z') {
value = value * 16 + ch - 'A' + 10;
} else {
return value;
}
if (value < oldValue) {
return Integer.MAX_VALUE;
}
}
return value;
}
if (offset < end && buffer[offset] == '-') {
sign = -1;
offset++;
} else if (offset < end && buffer[offset] == '+') {
sign = +1;
offset++;
}
while (offset < end) {
ch = buffer[offset++];
if ('0' <= ch && ch <= '9') {
long newValue = 10 * value + ch - '0';
if (newValue < value) {
// php/0143
// long value overflowed
result = Integer.MAX_VALUE;
isResultSet = true;
break;
}
value = newValue;
} else {
result = sign * value;
isResultSet = true;
break;
}
}
if (!isResultSet) {
result = sign * value;
}
return result;
}
static long parseLong(byte[] buffer, int offset, int len) {
if (len == 0) {
return 0;
}
long value = 0;
long sign = 1;
boolean isResultSet = false;
long result = 0;
int end = offset + len;
while (offset < end && Character.isWhitespace(buffer[offset])) {
offset++;
}
int ch;
if (offset + 1 < end && buffer[offset] == '0'
&& ((ch = buffer[offset + 1]) == 'x' || ch == 'X')) {
for (offset += 2; offset < end; offset++) {
ch = buffer[offset] & 0xFF;
long oldValue = value;
if ('0' <= ch && ch <= '9') {
value = value * 16 + ch - '0';
} else if ('a' <= ch && ch <= 'z') {
value = value * 16 + ch - 'a' + 10;
} else if ('A' <= ch && ch <= 'Z') {
value = value * 16 + ch - 'A' + 10;
} else {
return value;
}
if (value < oldValue) {
return Integer.MAX_VALUE;
}
}
return value;
}
if (offset < end && buffer[offset] == '-') {
sign = -1;
offset++;
} else if (offset < end && buffer[offset] == '+') {
sign = +1;
offset++;
}
while (offset < end) {
ch = buffer[offset++];
if ('0' <= ch && ch <= '9') {
long newValue = 10 * value + ch - '0';
if (newValue < value) {
// long value overflowed, set result to integer max
result = Integer.MAX_VALUE;
isResultSet = true;
break;
}
value = newValue;
} else {
result = sign * value;
isResultSet = true;
break;
}
}
if (!isResultSet) {
result = sign * value;
}
return result;
}
static long parseLong(CharSequence string) {
final int len = string.length();
if (len == 0) {
return 0;
}
long value = 0;
long sign = 1;
boolean isResultSet = false;
long result = 0;
int offset = 0;
int end = offset + len;
while (offset < end && Character.isWhitespace(string.charAt(offset))) {
offset++;
}
if (offset < end && string.charAt(offset) == '-') {
sign = -1;
offset++;
} else if (offset < end && string.charAt(offset) == '+') {
sign = +1;
offset++;
}
while (offset < end) {
int ch = string.charAt(offset++);
if ('0' <= ch && ch <= '9') {
long newValue = 10 * value + ch - '0';
if (newValue < value) {
// long value overflowed, set result to integer max
result = Integer.MAX_VALUE;
isResultSet = true;
break;
}
value = newValue;
} else {
result = sign * value;
isResultSet = true;
break;
}
}
if (!isResultSet) {
result = sign * value;
}
return result;
}
/**
* Converts to a double.
*/
@Override
public double toDouble() {
return toDouble(toString());
}
/**
* Converts to a double.
*/
public static double toDouble(String s) {
int len = s.length();
int start = 0;
int i = 0;
int ch = 0;
while (i < len && Character.isWhitespace(s.charAt(i))) {
start++;
i++;
}
if (i + 1 < len && s.charAt(i) == '0'
&& ((ch = s.charAt(i)) == 'x' || ch == 'X')) {
double value = 0;
for (i += 2; i < len; i++) {
ch = s.charAt(i);
if ('0' <= ch && ch <= '9') {
value = value * 16 + ch - '0';
} else if ('a' <= ch && ch <= 'z') {
value = value * 16 + ch - 'a' + 10;
} else if ('A' <= ch && ch <= 'Z') {
value = value * 16 + ch - 'A' + 10;
} else {
return value;
}
}
return value;
}
if (i < len && ((ch = s.charAt(i)) == '+' || ch == '-')) {
i++;
}
for (; i < len && '0' <= (ch = s.charAt(i)) && ch <= '9'; i++) {
}
if (ch == '.') {
for (i++; i < len && '0' <= (ch = s.charAt(i)) && ch <= '9'; i++) {
}
}
if (ch == 'e' || ch == 'E') {
int e = i++;
if (i < len && (ch = s.charAt(i)) == '+' || ch == '-') {
i++;
}
for (; i < len && '0' <= (ch = s.charAt(i)) && ch <= '9'; i++) {
}
if (i == e + 1) {
i = e;
}
}
if (i == 0) {
return 0;
} else if (i == len && start == 0) {
return Double.parseDouble(s);
} else {
return Double.parseDouble(s.substring(start, i));
}
}
/**
* Converts to a boolean.
*/
@Override
public boolean toBoolean() {
int length = length();
if (length == 0) {
return false;
} else if (length > 1) {
return true;
} else {
return charAt(0) != '0';
}
}
/**
* Converts to a key.
*/
@Override
public Value toKey() {
int len = length();
if (len == 0) {
return this;
}
int sign = 1;
long value = 0;
int i = 0;
char ch = charAt(i);
if (ch == '-') {
sign = -1;
i++;
}
for (; i < len; i++) {
ch = charAt(i);
if ('0' <= ch && ch <= '9') {
value = 10 * value + ch - '0';
} else {
return this;
}
}
return LongValue.create(sign * value);
}
/**
* Converts to an object.
*/
@Override
final public Value toAutoObject(Env env) {
return env.createObject();
}
/**
* Converts to an array if null.
*/
@Override
public Value toAutoArray() {
if (length() == 0) {
return new ArrayValueImpl();
} else {
return this;
}
}
/**
* Converts to a Java object.
*/
@Override
public Object toJavaObject() {
return toString();
}
/**
* Takes the values of this array, unmarshalls them to objects of type
* <i>elementType</i>, and puts them in a java array.
*/
@Override
public Object valuesToArray(Env env, Class elementType) {
if (char.class.equals(elementType)) {
return toUnicode(env).toCharArray();
} else if (Character.class.equals(elementType)) {
char[] chars = toUnicode(env).toCharArray();
int length = chars.length;
Character[] charObjects = new Character[length];
for (int i = 0; i < length; i++) {
charObjects[i] = Character.valueOf(chars[i]);
}
return charObjects;
} else if (byte.class.equals(elementType)) {
return toBinaryValue(env).toBytes();
} else if (Byte.class.equals(elementType)) {
byte[] bytes = toBinaryValue(env).toBytes();
int length = bytes.length;
Byte[] byteObjects = new Byte[length];
for (int i = 0; i < length; i++) {
byteObjects[i] = Byte.valueOf(bytes[i]);
}
return byteObjects;
} else {
env.error(L.l("Can't assign {0} with type {1} to {2}",
this,
this.getClass(),
elementType));
return null;
}
}
/**
* Converts to a callable object
*/
@Override
public Callable toCallable(Env env) {
// php/1h0o
if (isEmpty()) {
return super.toCallable(env);
}
String s = toString();
int p = s.indexOf("::");
if (p < 0) {
return new CallbackFunction(env, s);
} else {
String className = s.substring(0, p);
String methodName = s.substring(p + 2);
QuercusClass cl = env.findClass(className);
if (cl == null) {
env.warning(L.l("can't find class {0}",
className));
return super.toCallable(env);
}
return new CallbackClassMethod(cl, env.createString(methodName));
}
}
/**
* Sets the array value, returning the new array, e.g. to handle
* string update ($a[0] = 'A'). Creates an array automatically if
* necessary.
*/
@Override
public Value append(Value index, Value value) {
if (length() == 0) {
return new ArrayValueImpl().append(index, value);
} else {
return this;
}
}
// 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 getArg(Value key, boolean isTop) {
// php/03ma
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 UnsetUnicodeValue.UNSET;
} else {
return StringValue.create(charAt((int) index));
}
}
/**
* sets the character at an index
*/
@Override
public Value setCharValueAt(long index, Value value) {
//XXX: need to double-check this for non-string values
int len = length();
if (index < 0 || len <= index) {
return this;
} else {
return (createStringBuilder().append(this, 0, (int) index).append(value).append(this, (int) (index + 1), length()));
}
}
/**
* Increment the following value.
*/
@Override
public Value increment(int incr) {
// php/03i6
if (length() == 0) {
if (incr == 1) {
return createStringBuilder().append("1");
} else {
return LongValue.MINUS_ONE;
}
}
if (incr > 0) {
StringBuilder tail = new StringBuilder();
for (int i = length() - 1; i >= 0; i--) {
char ch = charAt(i);
if (ch == 'z') {
if (i == 0) {
return createStringBuilder().append("aa").append(tail);
} else {
tail.insert(0, 'a');
}
} else if ('a' <= ch && ch < 'z') {
return (createStringBuilder().append(this, 0, i).append((char) (ch + 1)).append(tail));
} else if (ch == 'Z') {
if (i == 0) {
return createStringBuilder().append("AA").append(tail);
} else {
tail.insert(0, 'A');
}
} else if ('A' <= ch && ch < 'Z') {
return (createStringBuilder().append(this, 0, i).append((char) (ch + 1)).append(tail));
} else if ('0' <= ch && ch <= '9' && i == length() - 1) {
return LongValue.create(toLong() + incr);
}
}
return createStringBuilder().append(tail.toString());
} else if (getValueType().isLongAdd()) {
return LongValue.create(toLong() + incr);
} else {
return this;
}
}
/**
* Adds to the following value.
*/
@Override
public Value add(long rValue) {
if (getValueType().isLongAdd()) {
return LongValue.create(toLong() + rValue);
}
return DoubleValue.create(toDouble() + rValue);
}
/**
* Adds to the following value.
*/
@Override
public Value sub(long rValue) {
if (getValueType().isLongAdd()) {
return LongValue.create(toLong() - rValue);
}
return DoubleValue.create(toDouble() - rValue);
}
/*
* Bit and.
*/
@Override
public Value bitAnd(Value rValue) {
if (rValue.isString()) {
StringValue rStr = (StringValue) rValue;
int len = Math.min(length(), rValue.length());
StringValue sb = createStringBuilder();
for (int i = 0; i < len; i++) {
char l = charAt(i);
char r = rStr.charAt(i);
sb.appendByte(l & r);
}
return sb;
} else {
return LongValue.create(toLong() & rValue.toLong());
}
}
/*
* Bit or.
*/
@Override
public Value bitOr(Value rValue) {
if (rValue.isString()) {
StringValue rStr = (StringValue) rValue;
int len = Math.min(length(), rValue.length());
StringValue sb = createStringBuilder();
for (int i = 0; i < len; i++) {
char l = charAt(i);
char r = rStr.charAt(i);
sb.appendByte(l | r);
}
if (len != length()) {
sb.append(substring(len));
} else if (len != rStr.length()) {
sb.append(rStr.substring(len));
}
return sb;
} else {
return LongValue.create(toLong() | rValue.toLong());
}
}
/*
* Bit xor.
*/
@Override
public Value bitXor(Value rValue) {
if (rValue.isString()) {
StringValue rStr = rValue.toStringValue();
int len = Math.min(length(), rValue.length());
StringValue sb = createStringBuilder();
for (int i = 0; i < len; i++) {
char l = charAt(i);
char r = rStr.charAt(i);
sb.appendByte(l ^ r);
}
return sb;
} else {
return LongValue.create(toLong() ^ rValue.toLong());
}
}
/**
* Serializes the value.
*/
@Override
public void serialize(Env env, StringBuilder sb) {
sb.append("s:");
sb.append(length());
sb.append(":\"");
sb.append(toString());
sb.append("\";");
}
/**
* Encodes the value in JSON.
*/
@Override
public void jsonEncode(Env env, StringValue sb) {
sb.append('"');
int len = length();
for (int i = 0; i < len; i++) {
char c = charAt(i);
switch (c) {
case '\b':
sb.append('\\');
sb.append('b');
break;
case '\f':
sb.append('\\');
sb.append('f');
break;
case '\n':
sb.append('\\');
sb.append('n');
break;
case '\r':
sb.append('\\');
sb.append('r');
break;
case '\t':
sb.append('\\');
sb.append('t');
break;
case '\\':
sb.append('\\');
sb.append('\\');
break;
case '"':
sb.append('\\');
sb.append('"');
break;
case '/':
sb.append('\\');
sb.append('/');
break;
default:
if (c <= 0x1f) {
addUnicode(sb, c);
} else if (c < 0x80) {
sb.append(c);
} else if ((c & 0xe0) == 0xc0 && i + 1 < len) {
int c1 = charAt(i + 1);
i++;
int ch = ((c & 0x1f) << 6) + (c1 & 0x3f);
addUnicode(sb, ch);
} else if ((c & 0xf0) == 0xe0 && i + 2 < len) {
int c1 = charAt(i + 1);
int c2 = charAt(i + 2);
i += 2;
int ch = ((c & 0x0f) << 12) + ((c1 & 0x3f) << 6) + (c2 & 0x3f);
addUnicode(sb, ch);
} else {
// technically illegal
addUnicode(sb, c);
}
break;
}
}
sb.append('"');
}
private void addUnicode(StringValue sb, int c) {
sb.append('\\');
sb.append('u');
int d = (c >> 12) & 0xf;
if (d < 10) {
sb.append((char) ('0' + d));
} else {
sb.append((char) ('a' + d - 10));
}
d = (c >> 8) & 0xf;
if (d < 10) {
sb.append((char) ('0' + d));
} else {
sb.append((char) ('a' + d - 10));
}
d = (c >> 4) & 0xf;
if (d < 10) {
sb.append((char) ('0' + d));
} else {
sb.append((char) ('a' + d - 10));
}
d = (c) & 0xf;
if (d < 10) {
sb.append((char) ('0' + d));
} else {
sb.append((char) ('a' + d - 10));
}
}
/*
* Returns a value to be used as a key for the deserialize cache.
*/
/*
public StringValue toSerializeKey()
{
if (length() <= 4096)
return this;
try {
MessageDigest md = MessageDigest.getInstance("SHA1");
byte []buffer = toBytes();
md.update(buffer, 0, buffer.length);
//XXX: create a special serialize type?
return new StringBuilderValue(md.digest());
} catch (NoSuchAlgorithmException e) {
throw new QuercusException(e);
}
}
*/
//
// append code
//
/**
* Append a Java string to the value.
*/
public StringValue append(String s) {
throw new UnsupportedOperationException(getClass().getName());
}
/**
* Append a Java string to the value.
*/
public StringValue append(String s, int start, int end) {
throw new UnsupportedOperationException(getClass().getName());
}
/**
* Append a Java buffer to the value.
*/
public StringValue append(char[] buf, int offset, int length) {
throw new UnsupportedOperationException(getClass().getName());
}
/**
* Append a Java double to the value.
*/
public StringValue append(char[] buf) {
return append(buf, 0, buf.length);
}
/**
* Append a Java buffer to the value.
*/
public StringValue append(CharSequence buf, int head, int tail) {
throw new UnsupportedOperationException(getClass().getName());
}
/**
* Append a Java buffer to the value.
*/
public StringValue append(StringBuilderValue sb, int head, int tail) {
return append((CharSequence) sb, head, tail);
}
/**
* Append a Java buffer to the value.
*/
public StringValue append(UnicodeBuilderValue sb, int head, int tail) {
return append((CharSequence) sb, head, tail);
}
/*
* Appends a Unicode string to the value.
*
* @param str should be a Unicode string
* @param charset to decode string from
*/
public StringValue append(Env env, StringValue unicodeStr, String charset) {
if (!unicodeStr.isUnicode()) {
return append(unicodeStr);
}
try {
byte[] bytes = unicodeStr.toString().getBytes(charset);
append(bytes);
return this;
} catch (UnsupportedEncodingException e) {
env.warning(e);
return append(unicodeStr);
}
}
/**
* Append a Java char to the value.
*/
public StringValue append(char v) {
throw new UnsupportedOperationException(getClass().getName());
}
/**
* Append a Java boolean to the value.
*/
public StringValue append(boolean v) {
return append(v ? "true" : "false");
}
/**
* Append a Java long to the value.
*/
public StringValue append(long v) {
return append(String.valueOf(v));
}
/**
* Append a Java double to the value.
*/
public StringValue append(double v) {
return append(String.valueOf(v));
}
/**
* Append a Java value to the value.
*/
public StringValue append(Object v) {
return append(String.valueOf(v));
}
/**
* Append a Java value to the value.
*/
public StringValue append(Value v) {
throw new UnsupportedOperationException(getClass().getName());
}
/**
* Ensure enough append capacity.
*/
public void ensureAppendCapacity(int size) {
throw new UnsupportedOperationException(getClass().getName());
}
/**
* Append a byte buffer to the value.
*/
public StringValue append(byte[] buf, int offset, int length) {
throw new UnsupportedOperationException(getClass().getName());
}
/**
* Append a byte buffer to the value.
*/
public StringValue append(byte[] buf) {
return append(buf, 0, buf.length);
}
/**
* Append a byte buffer to the value.
*/
public StringValue appendUtf8(byte[] buf, int offset, int length) {
throw new UnsupportedOperationException(getClass().getName());
}
/**
* Append a byte buffer to the value.
*/
public StringValue appendUtf8(byte[] buf) {
return appendUtf8(buf, 0, buf.length);
}
/**
* Append to a string builder.
*/
@Override
public StringValue appendTo(UnicodeBuilderValue sb) {
int length = length();
for (int i = 0; i < length; i++) {
sb.append(charAt(i));
}
return this;
}
/**
* Append a Java boolean to the value.
*/
public StringValue appendUnicode(boolean v) {
return append(v ? "true" : "false");
}
/**
* Append a Java long to the value.
*/
public StringValue appendUnicode(long v) {
return append(String.valueOf(v));
}
/**
* Append a Java double to the value.
*/
public StringValue appendUnicode(double v) {
return append(String.valueOf(v));
}
/**
* Append a Java value to the value.
*/
public StringValue appendUnicode(Object v) {
return append(String.valueOf(v));
}
/**
* Append a Java char, possibly converting to a unicode string
*/
public StringValue appendUnicode(char v) {
return append(v);
}
/**
* Append a Java char buffer, possibly converting to a unicode string
*/
public StringValue appendUnicode(char[] buffer, int offset, int length) {
return append(buffer, offset, length);
}
/**
* Append a Java char buffer, possibly converting to a unicode string
*/
public StringValue appendUnicode(char[] buffer) {
return append(buffer);
}
/**
* Append a Java char buffer, possibly converting to a unicode string
*/
public StringValue appendUnicode(String value) {
return append(value);
}
/**
* Append a Java char buffer, possibly converting to a unicode string
*/
public StringValue appendUnicode(String value, int offset, int length) {
return append(value, offset, length);
}
/**
* Append a Java char buffer, possibly converting to a unicode string
*/
public StringValue appendUnicode(Value value) {
return append(value);
}
/**
* Append a Java char buffer, possibly converting to a unicode string
*/
public StringValue appendUnicode(Value v1, Value v2) {
return append(v1).append(v2);
}
/**
* Append a Java byte to the value without conversions.
*/
public StringValue appendByte(int v) {
throw new UnsupportedOperationException(getClass().getName());
}
/**
* Append a Java String to the value without conversions.
*/
public StringValue appendBytes(String s) {
StringValue sb = this;
for (int i = 0; i < s.length(); i++) {
sb = sb.appendByte(s.charAt(i));
}
return sb;
}
/**
* Append a Java String to the value without conversions.
*/
public StringValue appendBytes(StringValue s) {
StringValue sb = this;
for (int i = 0; i < s.length(); i++) {
sb = sb.appendByte(s.charAt(i));
}
return sb;
}
/**
* Append a Java char[] to the value without conversions.
*/
public StringValue appendBytes(char[] buf, int offset, int length) {
StringValue sb = this;
int end = Math.min(buf.length, offset + length);
while (offset < end) {
sb = sb.appendByte(buf[offset++]);
}
return sb;
}
/**
* Append Java bytes to the value without conversions.
*/
public StringValue appendBytes(byte[] bytes, int offset, int end) {
StringValue sb = this;
while (offset < end) {
sb = sb.appendByte(bytes[offset++]);
}
return sb;
}
/**
* Append from a temp buffer list
*/
public StringValue append(TempBuffer ptr) {
for (; ptr != null; ptr = ptr.getNext()) {
append(ptr.getBuffer(), 0, ptr.getLength());
}
return this;
}
/**
* Append from a read stream
*/
public StringValue append(Reader reader)
throws IOException {
int ch;
while ((ch = reader.read()) >= 0) {
append((char) ch);
}
return this;
}
/**
* Append from a read stream
*/
public StringValue append(Reader reader, long length)
throws IOException {
int ch;
while (length-- > 0 && (ch = reader.read()) >= 0) {
append((char) ch);
}
return this;
}
/**
* Append from an input stream, using InputStream.read semantics,
* i.e. just call is.read once even if more data is available.
*/
public int appendRead(InputStream is, long length) {
TempBuffer tBuf = TempBuffer.allocate();
try {
byte[] buffer = tBuf.getBuffer();
int sublen = buffer.length;
if (length < sublen) {
sublen = (int) length;
}
sublen = is.read(buffer, 0, sublen);
if (sublen > 0) {
append(buffer, 0, sublen);
}
return sublen;
} catch (IOException e) {
throw new QuercusModuleException(e);
} finally {
TempBuffer.free(tBuf);
}
}
/**
* Append from an input stream, reading from the input stream until
* end of file or the length is reached.
*/
public int appendReadAll(InputStream is, long length) {
TempBuffer tBuf = TempBuffer.allocate();
try {
byte[] buffer = tBuf.getBuffer();
int readLength = 0;
while (length > 0) {
int sublen = buffer.length;
if (length < sublen) {
sublen = (int) length;
}
sublen = is.read(buffer, 0, sublen);
if (sublen > 0) {
append(buffer, 0, sublen);
length -= sublen;
readLength += sublen;
} else {
return readLength > 0 ? readLength : -1;
}
}
return readLength;
} catch (IOException e) {
throw new QuercusModuleException(e);
} finally {
TempBuffer.free(tBuf);
}
}
/**
* Append from an input stream, reading from the input stream until
* end of file or the length is reached.
*/
public int appendReadAll(ReadStream is, long length) {
TempBuffer tBuf = TempBuffer.allocate();
try {
byte[] buffer = tBuf.getBuffer();
int readLength = 0;
while (length > 0) {
int sublen = buffer.length;
if (length < sublen) {
sublen = (int) length;
}
sublen = is.read(buffer, 0, sublen);
if (sublen > 0) {
append(buffer, 0, sublen);
length -= sublen;
readLength += sublen;
} else {
return readLength > 0 ? readLength : -1;
}
}
return readLength;
} catch (IOException e) {
throw new QuercusModuleException(e);
} finally {
TempBuffer.free(tBuf);
}
}
/**
* Append from an input stream, using InputStream semantics, i.e
* call is.read() only once.
*/
public int appendRead(BinaryInput is, long length) {
TempBuffer tBuf = TempBuffer.allocate();
try {
byte[] buffer = tBuf.getBuffer();
int sublen = buffer.length;
if (length < sublen) {
sublen = (int) length;
} else if (length > sublen) {
buffer = new byte[(int) length];
sublen = (int) length;
}
sublen = is.read(buffer, 0, sublen);
if (sublen > 0) {
append(buffer, 0, sublen);
}
return sublen;
} catch (IOException e) {
throw new QuercusModuleException(e);
} finally {
TempBuffer.free(tBuf);
}
}
/**
* Append from an input stream, reading all available data from the
* stream.
*/
public int appendReadAll(BinaryInput is, long length) {
TempBuffer tBuf = TempBuffer.allocate();
try {
byte[] buffer = tBuf.getBuffer();
int readLength = 0;
while (length > 0) {
int sublen = buffer.length;
if (length < sublen) {
sublen = (int) length;
}
sublen = is.read(buffer, 0, sublen);
if (sublen > 0) {
append(buffer, 0, sublen);
length -= sublen;
readLength += sublen;
} else {
return readLength > 0 ? readLength : -1;
}
}
return readLength;
} catch (IOException e) {
throw new QuercusModuleException(e);
} finally {
TempBuffer.free(tBuf);
}
}
/**
* Exports the value.
*/
@Override
public void varExport(StringBuilder sb) {
sb.append("'");
String value = toString();
int len = value.length();
for (int i = 0; i < len; i++) {
char ch = value.charAt(i);
switch (ch) {
case '\'':
sb.append("\\'");
break;
case '\\':
sb.append("\\\\");
break;
default:
sb.append(ch);
}
}
sb.append("'");
}
/**
* Interns the string.
*/
/*
public StringValue intern(Quercus quercus)
{
return quercus.intern(toString());
}
*/
//
// CharSequence
//
/**
* Returns the length of the string.
*/
@Override
public int length() {
return toString().length();
}
/**
* Returns the character at a particular location
*/
@Override
public char charAt(int index) {
return toString().charAt(index);
}
/**
* Returns a subsequence
*/
@Override
public CharSequence subSequence(int start, int end) {
return new StringBuilderValue(toString().substring(start, end));
}
//
// java.lang.String methods
//
/**
* Returns the first index of the match string, starting from the head.
*/
public final int indexOf(CharSequence match) {
return indexOf(match, 0);
}
/**
* Returns the first index of the match string, starting from the head.
*/
public int indexOf(CharSequence match, int head) {
int length = length();
int matchLength = match.length();
if (matchLength <= 0) {
return -1;
} else if (head < 0) {
return -1;
}
int end = length - matchLength;
char first = match.charAt(0);
loop:
for (; head <= end; head++) {
if (charAt(head) != first) {
continue;
}
for (int i = 1; i < matchLength; i++) {
if (charAt(head + i) != match.charAt(i)) {
continue loop;
}
}
return head;
}
return -1;
}
/**
* Returns the last index of the match string, starting from the head.
*/
public int indexOf(char match) {
return indexOf(match, 0);
}
/**
* Returns the last index of the match string, starting from the head.
*/
public int indexOf(char match, int head) {
int length = length();
for (; head < length; head++) {
if (charAt(head) == match) {
return head;
}
}
return -1;
}
/**
* Returns the last index of the match string, starting from the head.
*/
public final int lastIndexOf(char match) {
return lastIndexOf(match, Integer.MAX_VALUE);
}
/**
* Returns the last index of the match string, starting from the head.
*/
public int lastIndexOf(char match, int tail) {
int length = length();
if (tail >= length) {
tail = length - 1;
}
for (; tail >= 0; tail--) {
if (charAt(tail) == match) {
return tail;
}
}
return -1;
}
/**
* Returns the last index of the match string, starting from the tail.
*/
public int lastIndexOf(CharSequence match) {
return lastIndexOf(match, Integer.MAX_VALUE);
}
/**
* Returns the last index of the match string, starting from the tail.
*/
public int lastIndexOf(CharSequence match, int tail) {
int length = length();
int matchLength = match.length();
if (matchLength <= 0) {
return -1;
}
if (tail < 0) {
return -1;
}
if (tail > length - matchLength) {
tail = length - matchLength;
}
char first = match.charAt(0);
loop:
for (; tail >= 0; tail--) {
if (charAt(tail) != first) {
continue;
}
for (int i = 1; i < matchLength; i++) {
if (charAt(tail + i) != match.charAt(i)) {
continue loop;
}
}
return tail;
}
return -1;
}
/**
* Returns true if the region matches
*/
public boolean regionMatches(int offset,
char[] mBuffer, int mOffset, int mLength) {
int length = length();
if (length < offset + mLength) {
return false;
}
for (int i = 0; i < mLength; i++) {
if (charAt(offset + i) != mBuffer[mOffset + i]) {
return false;
}
}
return true;
}
/**
* Returns true if the region matches
*/
public boolean regionMatches(int offset,
StringValue match, int mOffset, int mLength) {
int length = length();
if (length < offset + mLength) {
return false;
}
for (int i = 0; i < mLength; i++) {
if (charAt(offset + i) != match.charAt(mOffset + i)) {
return false;
}
}
return true;
}
/**
* Returns true if the region matches
*/
public boolean regionMatchesIgnoreCase(int offset,
char[] match, int mOffset, int mLength) {
int length = length();
if (length < offset + mLength) {
return false;
}
for (int i = 0; i < mLength; i++) {
char a = Character.toLowerCase(charAt(offset + i));
char b = Character.toLowerCase(match[mOffset + i]);
if (a != b) {
return false;
}
}
return true;
}
/**
* Returns true if the string ends with another string.
*/
public boolean endsWith(StringValue tail) {
int len = length();
int tailLen = tail.length();
int offset = len - tailLen;
if (offset < 0) {
return false;
}
for (int i = 0; i < tailLen; i++) {
if (charAt(offset + i) != tail.charAt(i)) {
return false;
}
}
return true;
}
/**
* Returns a StringValue substring.
*/
public StringValue substring(int head) {
return (StringValue) subSequence(head, length());
}
/**
* Returns a StringValue substring.
*/
public StringValue substring(int begin, int end) {
return (StringValue) subSequence(begin, end);
}
/**
* Returns a String substring
*/
public String stringSubstring(int begin, int end) {
return substring(begin, end).toString();
}
/**
* Returns a character array
*/
public char[] toCharArray() {
int length = length();
char[] array = new char[length()];
getChars(0, array, 0, length);
return array;
}
public char[] getRawCharArray() {
return toCharArray();
}
/**
* Copies the chars
*/
public void getChars(int stringOffset, char[] buffer, int offset, int length) {
for (int i = 0; i < length; i++) {
buffer[offset + i] = charAt(stringOffset + i);
}
}
/**
* Convert to lower case.
*/
public StringValue toLowerCase() {
int length = length();
UnicodeBuilderValue string = new UnicodeBuilderValue(length);
char[] buffer = string.getBuffer();
getChars(0, buffer, 0, length);
for (int i = 0; i < length; i++) {
char ch = buffer[i];
if ('A' <= ch && ch <= 'Z') {
buffer[i] = (char) (ch + 'a' - 'A');
} else if (ch < 0x80) {
} else if (Character.isUpperCase(ch)) {
buffer[i] = Character.toLowerCase(ch);
}
}
string.setOffset(length);
return string;
}
/**
* Convert to lower case.
*/
public StringValue toUpperCase() {
int length = length();
UnicodeBuilderValue string = new UnicodeBuilderValue(length);
char[] buffer = string.getBuffer();
getChars(0, buffer, 0, length);
for (int i = 0; i < length; i++) {
char ch = buffer[i];
if ('a' <= ch && ch <= 'z') {
buffer[i] = (char) (ch + 'A' - 'a');
} else if (ch < 0x80) {
} else if (Character.isLowerCase(ch)) {
buffer[i] = Character.toUpperCase(ch);
}
}
string.setOffset(length);
return string;
}
/**
* Returns a byteArrayInputStream for the value.
* See TempBufferStringValue for how this can be overriden
*
* @return InputStream
*/
@Override
public InputStream toInputStream() {
try {
//XXX: refactor so that env is passed in
return toInputStream(Env.getInstance().getRuntimeEncoding());
} catch (UnsupportedEncodingException e) {
throw new QuercusRuntimeException(e);
}
//return new StringValueInputStream();
}
/**
* Returns a byte stream of chars.
* @param charset to encode chars to
*/
public InputStream toInputStream(String charset)
throws UnsupportedEncodingException {
return new ByteArrayInputStream(toString().getBytes(charset));
}
public Reader toSimpleReader()
throws UnsupportedEncodingException {
return new SimpleStringValueReader(this);
}
/**
* Returns a char stream.
* XXX: when decoding fails
*
* @param charset to decode bytes by
*/
public Reader toReader(String charset)
throws UnsupportedEncodingException {
byte[] bytes = toBytes();
return new InputStreamReader(new ByteArrayInputStream(bytes), charset);
}
public byte[] toBytes() {
throw new UnsupportedOperationException();
}
/**
* Converts to a unicode value.
*/
@Override
public StringValue toUnicode(Env env) {
return this;
}
/**
* Decodes from charset and returns UnicodeValue.
*
* @param env
* @param charset
*/
public StringValue toUnicodeValue(Env env, String charset) {
StringValue sb = new UnicodeBuilderValue();
Decoder decoder = Decoder.create(charset);
sb.append(decoder.decode(env, this));
return sb;
}
/**
* Decodes from charset and returns UnicodeValue.
*
* @param env
* @param charset
*/
public StringValue convertToUnicode(Env env, String charset) {
UnicodeBuilderValue sb = new UnicodeBuilderValue();
Decoder decoder = Decoder.create(charset);
decoder.setAllowMalformedOut(true);
sb.append(decoder.decode(env, this));
return sb;
}
/**
* Converts to a string builder
*/
@Override
public StringValue toStringBuilder(Env env) {
return createStringBuilder().append(this);
}
/**
* Return true if the array value is set
*/
@Override
public boolean isset(Value indexV) {
int index = indexV.toInt();
int len = length();
return 0 <= index && index < len;
}
/**
* Writes to a stream
*/
public void writeTo(OutputStream os) {
try {
int len = length();
for (int i = 0; i < len; i++) {
os.write(charAt(i));
}
} catch (IOException e) {
throw new QuercusModuleException(e);
}
}
/**
* Calculates CRC32 value.
*/
public long getCrc32Value() {
CRC32 crc = new CRC32();
int length = length();
for (int i = 0; i < length; i++) {
crc.update((byte) charAt(i));
}
return crc.getValue() & 0xffffffff;
}
//
// ByteAppendable methods
//
@Override
public void write(int value) {
throw new UnsupportedOperationException(getClass().getName());
}
/**
* Appends buffer to the ByteAppendable.
*/
@Override
public void write(byte[] buffer, int offset, int len) {
throw new UnsupportedOperationException(getClass().getName());
}
//
// java.lang.Object methods
//
/**
* Returns the hash code.
*/
@Override
public int hashCode() {
int hash = 37;
int length = length();
for (int i = 0; i < length; i++) {
hash = 65521 * hash + charAt(i);
}
return hash;
}
/**
* Returns the case-insensitive hash code
*/
public int hashCodeCaseInsensitive() {
int hash = 37;
int length = length();
for (int i = length - 1; i >= 0; i--) {
int ch = charAt(i);
if ('A' <= ch && ch <= 'Z') {
ch = ch + 'a' - 'A';
}
hash = 65521 * hash + ch;
}
return hash;
}
/**
* Test for equality
*/
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
} else if (!(o instanceof StringValue)) {
return false;
}
StringValue s = (StringValue) o;
if (s.isUnicode() != isUnicode()) {
return false;
}
int aLength = length();
int bLength = s.length();
if (aLength != bLength) {
return false;
}
for (int i = aLength - 1; i >= 0; i--) {
if (charAt(i) != s.charAt(i)) {
return false;
}
}
return true;
}
/**
* Test for equality
*/
public boolean equalsIgnoreCase(Object o) {
if (this == o) {
return true;
} else if (!(o instanceof StringValue)) {
return false;
}
StringValue s = (StringValue) o;
if (s.isUnicode() != isUnicode()) {
return false;
}
int aLength = length();
int bLength = s.length();
if (aLength != bLength) {
return false;
}
for (int i = aLength - 1; i >= 0; i--) {
int chA = charAt(i);
int chB = s.charAt(i);
if (chA == chB) {
} else {
if ('A' <= chA && chA <= 'Z') {
chA += 'a' - 'A';
}
if ('A' <= chB && chB <= 'Z') {
chB += 'a' - 'A';
}
if (chA != chB) {
return false;
}
}
}
return true;
}
//
// Java generator code
//
/**
* Generates code to recreate the expression.
*
* @param out the writer to the Java source code.
*/
@Override
public void generate(PrintWriter out)
throws IOException {
// max JVM constant string length
int maxSublen = 0xFFFE;
int len = length();
String className = getClass().getSimpleName();
if (len == 1) {
out.print(className + ".create('");
printJavaChar(out, charAt(0));
out.print("')");
} else if (len < maxSublen) {
out.print("new " + className + "(\"");
printJavaString(out, this);
out.print("\")");
} else {
out.print("((" + className + ") (new " + className + "(\"");
// php/313u
for (int i = 0; i < len; i += maxSublen) {
if (i != 0) {
out.print("\").append(\"");
}
printJavaString(out, substring(i, Math.min(i + maxSublen, len)));
}
out.print("\")))");
}
}
@Override
abstract public String toDebugString();
@Override
abstract public void varDumpImpl(Env env,
WriteStream out,
int depth,
IdentityHashMap<Value, String> valueSet)
throws IOException;
class StringValueInputStream extends java.io.InputStream {
private final int _length;
private int _index;
StringValueInputStream() {
_length = length();
}
/**
* 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;
}
int index = _index;
for (int i = 0; i < sublen; i++) {
buffer[offset + i] = (byte) charAt(index + i);
}
_index += sublen;
return sublen;
}
}
static class SimpleStringValueReader extends Reader {
StringValue _str;
int _index;
int _length;
SimpleStringValueReader(StringValue s) {
_str = s;
_length = s.length();
}
@Override
public int read() {
if (_index >= _length) {
return -1;
} else {
return _str.charAt(_index++);
}
}
@Override
public int read(char[] buf, int off, int len) {
if (_index >= _length) {
return -1;
}
int i = 0;
len = Math.min(_length - _index, len);
for (; i < len; i++) {
buf[off + i] = _str.charAt(i + _index++);
}
return i;
}
@Override
public void close()
throws IOException {
}
}
}