0001: /*
0002: * Copyright (c) 1998-2008 Caucho Technology -- all rights reserved
0003: *
0004: * This file is part of Resin(R) Open Source
0005: *
0006: * Each copy or derived work must preserve the copyright notice and this
0007: * notice unmodified.
0008: *
0009: * Resin Open Source is free software; you can redistribute it and/or modify
0010: * it under the terms of the GNU General Public License as published by
0011: * the Free Software Foundation; either version 2 of the License, or
0012: * (at your option) any later version.
0013: *
0014: * Resin Open Source is distributed in the hope that it will be useful,
0015: * but WITHOUT ANY WARRANTY; without even the implied warranty of
0016: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE, or any warranty
0017: * of NON-INFRINGEMENT. See the GNU General Public License for more
0018: * details.
0019: *
0020: * You should have received a copy of the GNU General Public License
0021: * along with Resin Open Source; if not, write to the
0022: *
0023: * Free Software Foundation, Inc.
0024: * 59 Temple Place, Suite 330
0025: * Boston, MA 02111-1307 USA
0026: *
0027: * @author Scott Ferguson
0028: */
0029:
0030: package com.caucho.quercus.env;
0031:
0032: import com.caucho.quercus.Quercus;
0033: import com.caucho.quercus.QuercusModuleException;
0034: import com.caucho.quercus.QuercusRuntimeException;
0035: import com.caucho.quercus.lib.file.BinaryInput;
0036: import com.caucho.vfs.TempBuffer;
0037: import com.caucho.vfs.TempCharBuffer;
0038: import com.caucho.vfs.TempStream;
0039: import com.caucho.vfs.WriteStream;
0040:
0041: import java.io.*;
0042: import java.util.IdentityHashMap;
0043:
0044: /**
0045: * Represents a Quercus string value.
0046: */
0047: abstract public class StringValue extends Value implements CharSequence {
0048: public static final StringValue EMPTY = new StringBuilderValue("");
0049:
0050: private final static StringValue[] CHAR_STRINGS;
0051:
0052: protected static final int IS_STRING = 0;
0053: protected static final int IS_LONG = 1;
0054: protected static final int IS_DOUBLE = 2;
0055:
0056: /**
0057: * Creates the string.
0058: */
0059: public static Value create(String value) {
0060: // XXX: needs updating for i18n, currently php5 only
0061:
0062: if (value == null)
0063: return NullValue.NULL;
0064: else
0065: return new StringBuilderValue(value);
0066: }
0067:
0068: /**
0069: * Creates the string.
0070: */
0071: public static StringValue create(char value) {
0072: // XXX: needs updating for i18n, currently php5 only
0073:
0074: if (value < CHAR_STRINGS.length)
0075: return CHAR_STRINGS[value];
0076: else
0077: return new StringBuilderValue(String.valueOf(value));
0078: }
0079:
0080: /**
0081: * Creates the string.
0082: */
0083: public static Value create(Object value) {
0084: // XXX: needs updating for i18n, currently php5 only
0085:
0086: if (value == null)
0087: return NullValue.NULL;
0088: else
0089: return new StringBuilderValue(value.toString());
0090: }
0091:
0092: /*
0093: * Decodes the Unicode str from charset.
0094: *
0095: * @param str should be a Unicode string
0096: * @param charset to decode string from
0097: */
0098: public StringValue create(Env env, StringValue unicodeStr,
0099: String charset) {
0100: if (!unicodeStr.isUnicode())
0101: return unicodeStr;
0102:
0103: try {
0104: StringValue sb = createStringBuilder();
0105:
0106: byte[] bytes = unicodeStr.toString().getBytes(charset);
0107:
0108: sb.append(bytes);
0109: return sb;
0110:
0111: } catch (UnsupportedEncodingException e) {
0112: env.warning(e);
0113:
0114: return unicodeStr;
0115: }
0116: }
0117:
0118: /**
0119: * Creates a string builder of the same type.
0120: */
0121: abstract public StringValue createStringBuilder();
0122:
0123: /**
0124: * Creates a string builder of the same type.
0125: */
0126: abstract public StringValue createStringBuilder(int length);
0127:
0128: /*
0129: * Returns the immutable empty string of same type.
0130: */
0131: abstract public StringValue getEmptyString();
0132:
0133: //
0134: // Predicates and relations
0135: //
0136:
0137: /**
0138: * Returns the type.
0139: */
0140: public String getType() {
0141: return "string";
0142: }
0143:
0144: /**
0145: * Returns the ValueType.
0146: */
0147: @Override
0148: public ValueType getValueType() {
0149: return ValueType.STRING;
0150: }
0151:
0152: /**
0153: * Returns true for a long
0154: */
0155: public boolean isLongConvertible() {
0156: return getValueType().isLongCmp();
0157: }
0158:
0159: /**
0160: * Returns true for a double
0161: */
0162: public boolean isDoubleConvertible() {
0163: return getValueType().isNumberCmp();
0164: }
0165:
0166: /**
0167: * Returns true for a number
0168: */
0169: public boolean isNumber() {
0170: return false;
0171: }
0172:
0173: /**
0174: * Returns true for is_numeric
0175: */
0176: @Override
0177: public boolean isNumeric() {
0178: // php/120y
0179:
0180: return getValueType().isNumberCmp();
0181: }
0182:
0183: /**
0184: * Returns true for a scalar
0185: */
0186: public boolean isScalar() {
0187: return true;
0188: }
0189:
0190: /**
0191: * Returns true for StringValue
0192: */
0193: @Override
0194: public boolean isString() {
0195: return true;
0196: }
0197:
0198: /*
0199: * Returns true if this is a PHP5 string.
0200: */
0201: public boolean isPHP5String() {
0202: return false;
0203: }
0204:
0205: /**
0206: * Returns true if the value is empty
0207: */
0208: @Override
0209: public boolean isEmpty() {
0210: return length() == 0 || length() == 1 && charAt(0) == '0';
0211: }
0212:
0213: /**
0214: * Returns true for equality
0215: */
0216: public int cmp(Value rValue) {
0217: if (isNumberConvertible() || rValue.isNumberConvertible()) {
0218: double l = toDouble();
0219: double r = rValue.toDouble();
0220:
0221: if (l == r)
0222: return 0;
0223: else if (l < r)
0224: return -1;
0225: else
0226: return 1;
0227: } else {
0228: int result = toString().compareTo(rValue.toString());
0229:
0230: if (result == 0)
0231: return 0;
0232: else if (result > 0)
0233: return 1;
0234: else
0235: return -1;
0236: }
0237: }
0238:
0239: /**
0240: * Returns true for equality
0241: */
0242: @Override
0243: public boolean eq(Value rValue) {
0244: ValueType typeA = getValueType();
0245: ValueType typeB = rValue.getValueType();
0246:
0247: if (typeB.isNumber()) {
0248: double l = toDouble();
0249: double r = rValue.toDouble();
0250:
0251: return l == r;
0252: } else if (typeB.isBoolean()) {
0253: return toBoolean() == rValue.toBoolean();
0254: } else if (typeA.isNumberCmp() && typeB.isNumberCmp()) {
0255: double l = toDouble();
0256: double r = rValue.toDouble();
0257:
0258: return l == r;
0259: } else {
0260: return toString().equals(rValue.toString());
0261: }
0262: }
0263:
0264: /**
0265: * Compare two strings
0266: */
0267: public int cmpString(StringValue rValue) {
0268: return toString().compareTo(rValue.toString());
0269: }
0270:
0271: // Conversions
0272:
0273: /**
0274: * Converts to a string value.
0275: */
0276: public StringValue toStringValue() {
0277: return this ;
0278: }
0279:
0280: /**
0281: * Converts to a long.
0282: */
0283: public static long toLong(String string) {
0284: if (string.equals(""))
0285: return 0;
0286:
0287: int len = string.length();
0288:
0289: long value = 0;
0290: long sign = 1;
0291:
0292: int i = 0;
0293: char ch = string.charAt(0);
0294:
0295: if (ch == '-') {
0296: sign = -1;
0297: i = 1;
0298: } else if (ch == '+')
0299: i = 1;
0300:
0301: for (; i < len; i++) {
0302: ch = string.charAt(i);
0303:
0304: if ('0' <= ch && ch <= '9')
0305: value = 10 * value + ch - '0';
0306: else
0307: return sign * value;
0308: }
0309:
0310: return value;
0311: }
0312:
0313: /**
0314: * Converts to a double.
0315: */
0316: public double toDouble() {
0317: return toDouble(toString());
0318: }
0319:
0320: /**
0321: * Converts to a double.
0322: */
0323: public static double toDouble(String s) {
0324: int len = s.length();
0325: int i = 0;
0326: int ch = 0;
0327:
0328: if (i < len && ((ch = s.charAt(i)) == '+' || ch == '-')) {
0329: i++;
0330: }
0331:
0332: for (; i < len && '0' <= (ch = s.charAt(i)) && ch <= '9'; i++) {
0333: }
0334:
0335: if (ch == '.') {
0336: for (i++; i < len && '0' <= (ch = s.charAt(i)) && ch <= '9'; i++) {
0337: }
0338: }
0339:
0340: if (ch == 'e' || ch == 'E') {
0341: int e = i++;
0342:
0343: if (i < len && (ch = s.charAt(i)) == '+' || ch == '-') {
0344: i++;
0345: }
0346:
0347: for (; i < len && '0' <= (ch = s.charAt(i)) && ch <= '9'; i++) {
0348: }
0349:
0350: if (i == e + 1)
0351: i = e;
0352: }
0353:
0354: if (i == 0)
0355: return 0;
0356: else if (i == len)
0357: return Double.parseDouble(s);
0358: else
0359: return Double.parseDouble(s.substring(0, i));
0360: }
0361:
0362: /**
0363: * Converts to a boolean.
0364: */
0365: public boolean toBoolean() {
0366: int length = length();
0367:
0368: if (length == 0)
0369: return false;
0370: else if (length > 1)
0371: return true;
0372: else
0373: return charAt(0) != '0';
0374: }
0375:
0376: /**
0377: * Converts to a key.
0378: */
0379: public Value toKey() {
0380: int len = length();
0381:
0382: if (len == 0)
0383: return this ;
0384:
0385: int sign = 1;
0386: long value = 0;
0387:
0388: int i = 0;
0389: char ch = charAt(i);
0390: if (ch == '-') {
0391: sign = -1;
0392: i++;
0393: }
0394:
0395: for (; i < len; i++) {
0396: ch = charAt(i);
0397:
0398: if ('0' <= ch && ch <= '9')
0399: value = 10 * value + ch - '0';
0400: else
0401: return this ;
0402: }
0403:
0404: return LongValue.create(sign * value);
0405: }
0406:
0407: /**
0408: * Converts to an object.
0409: */
0410: @Override
0411: final public Value toAutoObject(Env env) {
0412: return env.createObject();
0413: }
0414:
0415: /**
0416: * Converts to a Java object.
0417: */
0418: public Object toJavaObject() {
0419: return toString();
0420: }
0421:
0422: /**
0423: * Takes the values of this array, unmarshalls them to objects of type
0424: * <i>elementType</i>, and puts them in a java array.
0425: */
0426: @Override
0427: public Object valuesToArray(Env env, Class elementType) {
0428: if (char.class.equals(elementType)) {
0429: return toUnicodeValue(env).toCharArray();
0430: } else if (Character.class.equals(elementType)) {
0431: char[] chars = toUnicodeValue(env).toCharArray();
0432:
0433: int length = chars.length;
0434:
0435: Character[] charObjects = new Character[length];
0436:
0437: for (int i = 0; i < length; i++) {
0438: charObjects[i] = Character.valueOf(chars[i]);
0439: }
0440:
0441: return charObjects;
0442: } else if (byte.class.equals(elementType)) {
0443: return toBinaryValue(env).toBytes();
0444: } else if (Byte.class.equals(elementType)) {
0445: byte[] bytes = toBinaryValue(env).toBytes();
0446:
0447: int length = bytes.length;
0448:
0449: Byte[] byteObjects = new Byte[length];
0450:
0451: for (int i = 0; i < length; i++) {
0452: byteObjects[i] = Byte.valueOf(bytes[i]);
0453: }
0454:
0455: return byteObjects;
0456: } else {
0457: env.error(L.l("Can't assign {0} with type {1} to {2}",
0458: this , this .getClass(), elementType));
0459: return null;
0460: }
0461: }
0462:
0463: /**
0464: * Converts to an array if null.
0465: */
0466: public Value toAutoArray() {
0467: if (length() == 0)
0468: return new ArrayValueImpl();
0469: else
0470: return this ;
0471: }
0472:
0473: // Operations
0474:
0475: /**
0476: * Returns the character at an index
0477: */
0478: public Value get(Value key) {
0479: return charValueAt(key.toLong());
0480: }
0481:
0482: /**
0483: * Returns the character at an index
0484: */
0485: public Value getArg(Value key) {
0486: // php/03ma
0487: return charValueAt(key.toLong());
0488: }
0489:
0490: /**
0491: * Returns the character at an index
0492: */
0493: public Value getRef(Value key) {
0494: return charValueAt(key.toLong());
0495: }
0496:
0497: /**
0498: * Returns the character at an index
0499: */
0500: @Override
0501: public Value charValueAt(long index) {
0502: int len = length();
0503:
0504: if (index < 0 || len <= index)
0505: return UnsetUnicodeValue.UNSET;
0506: else {
0507: return StringValue.create(charAt((int) index));
0508: }
0509: }
0510:
0511: /**
0512: * sets the character at an index
0513: */
0514: @Override
0515: public Value setCharValueAt(long index, String value) {
0516: int len = length();
0517:
0518: if (index < 0 || len <= index)
0519: return this ;
0520: else {
0521: return (createStringBuilder().append(this , 0, (int) index)
0522: .append(value).append(this , (int) (index + 1),
0523: length()));
0524: }
0525: }
0526:
0527: /**
0528: * Pre-increment the following value.
0529: */
0530: public Value preincr(int incr) {
0531: return postincr(incr);
0532: }
0533:
0534: /**
0535: * Post-increment the following value.
0536: */
0537: public Value postincr(int incr) {
0538: // php/03i6
0539: if (length() == 0) {
0540: if (incr == 1)
0541: return createStringBuilder().append("1");
0542: else
0543: return LongValue.MINUS_ONE;
0544: }
0545:
0546: if (incr > 0) {
0547: StringBuilder tail = new StringBuilder();
0548:
0549: for (int i = length() - 1; i >= 0; i--) {
0550: char ch = charAt(i);
0551:
0552: if (ch == 'z') {
0553: if (i == 0)
0554: return createStringBuilder().append("aa")
0555: .append(tail);
0556: else
0557: tail.insert(0, 'a');
0558: } else if ('a' <= ch && ch < 'z') {
0559: return (createStringBuilder().append(this , 0, i)
0560: .append((char) (ch + 1)).append(tail));
0561: } else if (ch == 'Z') {
0562: if (i == 0)
0563: return createStringBuilder().append("AA")
0564: .append(tail);
0565: else
0566: tail.insert(0, 'A');
0567: } else if ('A' <= ch && ch < 'Z') {
0568: return (createStringBuilder().append(this , 0, i)
0569: .append((char) (ch + 1)).append(tail));
0570: } else if ('0' <= ch && ch <= '9' && i == length() - 1) {
0571: return LongValue.create(toLong() + incr);
0572: }
0573: }
0574:
0575: return createStringBuilder().append(tail.toString());
0576: } else if (getValueType().isLongAdd()) {
0577: return LongValue.create(toLong() + incr);
0578: } else {
0579: return this ;
0580: }
0581: }
0582:
0583: /**
0584: * Adds to the following value.
0585: */
0586: public Value add(long rValue) {
0587: if (getValueType().isLongAdd())
0588: return LongValue.create(toLong() + rValue);
0589:
0590: return DoubleValue.create(toDouble() + rValue);
0591: }
0592:
0593: /**
0594: * Adds to the following value.
0595: */
0596: public Value sub(long rValue) {
0597: if (getValueType().isLongAdd())
0598: return LongValue.create(toLong() - rValue);
0599:
0600: return DoubleValue.create(toDouble() - rValue);
0601: }
0602:
0603: /*
0604: * Bit and.
0605: */
0606: @Override
0607: public Value bitAnd(Value rValue) {
0608: if (rValue.isString()) {
0609: StringValue rStr = (StringValue) rValue;
0610:
0611: int len = Math.min(length(), rValue.length());
0612: StringValue sb = createStringBuilder();
0613:
0614: for (int i = 0; i < len; i++) {
0615: char l = charAt(i);
0616: char r = rStr.charAt(i);
0617:
0618: sb.appendByte(l & r);
0619: }
0620:
0621: return sb;
0622: } else
0623: return LongValue.create(toLong() & rValue.toLong());
0624: }
0625:
0626: /*
0627: * Bit or.
0628: */
0629: @Override
0630: public Value bitOr(Value rValue) {
0631: if (rValue.isString()) {
0632: StringValue rStr = (StringValue) rValue;
0633:
0634: int len = Math.min(length(), rValue.length());
0635: StringValue sb = createStringBuilder();
0636:
0637: for (int i = 0; i < len; i++) {
0638: char l = charAt(i);
0639: char r = rStr.charAt(i);
0640:
0641: sb.appendByte(l | r);
0642: }
0643:
0644: if (len != length())
0645: sb.append(substring(len));
0646: else if (len != rStr.length())
0647: sb.append(rStr.substring(len));
0648:
0649: return sb;
0650: } else
0651: return LongValue.create(toLong() | rValue.toLong());
0652: }
0653:
0654: /*
0655: * Bit xor.
0656: */
0657: @Override
0658: public Value bitXor(Value rValue) {
0659: if (rValue.isString()) {
0660: StringValue rStr = (StringValue) rValue;
0661:
0662: int len = Math.min(length(), rValue.length());
0663: StringValue sb = createStringBuilder();
0664:
0665: for (int i = 0; i < len; i++) {
0666: char l = charAt(i);
0667: char r = rStr.charAt(i);
0668:
0669: sb.appendByte(l ^ r);
0670: }
0671:
0672: return sb;
0673: } else
0674: return LongValue.create(toLong() ^ rValue.toLong());
0675: }
0676:
0677: /**
0678: * Serializes the value.
0679: */
0680: @Override
0681: public void serialize(StringBuilder sb) {
0682: sb.append("s:");
0683: sb.append(length());
0684: sb.append(":\"");
0685: sb.append(toString());
0686: sb.append("\";");
0687: }
0688:
0689: //
0690: // append code
0691: //
0692:
0693: /**
0694: * Append a Java string to the value.
0695: */
0696: public StringValue append(String s) {
0697: throw new UnsupportedOperationException(getClass().getName());
0698: }
0699:
0700: /**
0701: * Append a Java string to the value.
0702: */
0703: public StringValue append(String s, int start, int end) {
0704: throw new UnsupportedOperationException(getClass().getName());
0705: }
0706:
0707: /**
0708: * Append a Java buffer to the value.
0709: */
0710: public StringValue append(char[] buf, int offset, int length) {
0711: throw new UnsupportedOperationException(getClass().getName());
0712: }
0713:
0714: /**
0715: * Append a Java double to the value.
0716: */
0717: public StringValue append(char[] buf) {
0718: return append(buf, 0, buf.length);
0719: }
0720:
0721: /**
0722: * Append a Java buffer to the value.
0723: */
0724: public StringValue append(CharSequence buf, int head, int tail) {
0725: throw new UnsupportedOperationException(getClass().getName());
0726: }
0727:
0728: /**
0729: * Append a Java buffer to the value.
0730: */
0731: public StringValue append(UnicodeBuilderValue sb, int head, int tail) {
0732: throw new UnsupportedOperationException(getClass().getName());
0733: }
0734:
0735: /*
0736: * Appends a Unicode string to the value.
0737: *
0738: * @param str should be a Unicode string
0739: * @param charset to decode string from
0740: */
0741: public StringValue append(Env env, StringValue unicodeStr,
0742: String charset) {
0743: if (!unicodeStr.isUnicode())
0744: return append(unicodeStr);
0745:
0746: try {
0747: byte[] bytes = unicodeStr.toString().getBytes(charset);
0748:
0749: append(bytes);
0750: return this ;
0751:
0752: } catch (UnsupportedEncodingException e) {
0753: env.warning(e);
0754:
0755: return append(unicodeStr);
0756: }
0757: }
0758:
0759: /**
0760: * Append a Java char to the value.
0761: */
0762: public StringValue append(char v) {
0763: throw new UnsupportedOperationException(getClass().getName());
0764: }
0765:
0766: /**
0767: * Append a Java boolean to the value.
0768: */
0769: public StringValue append(boolean v) {
0770: return append(v ? "true" : "false");
0771: }
0772:
0773: /**
0774: * Append a Java long to the value.
0775: */
0776: public StringValue append(long v) {
0777: return append(String.valueOf(v));
0778: }
0779:
0780: /**
0781: * Append a Java double to the value.
0782: */
0783: public StringValue append(double v) {
0784: return append(String.valueOf(v));
0785: }
0786:
0787: /**
0788: * Append a Java value to the value.
0789: */
0790: public StringValue append(Object v) {
0791: return append(String.valueOf(v));
0792: }
0793:
0794: /**
0795: * Append a Java value to the value.
0796: */
0797: public StringValue append(Value v) {
0798: throw new UnsupportedOperationException(getClass().getName());
0799: }
0800:
0801: /**
0802: * Ensure enough append capacity.
0803: */
0804: public void ensureAppendCapacity(int size) {
0805: throw new UnsupportedOperationException(getClass().getName());
0806: }
0807:
0808: /**
0809: * Append a byte buffer to the value.
0810: */
0811: public StringValue append(byte[] buf, int offset, int length) {
0812: throw new UnsupportedOperationException(getClass().getName());
0813: }
0814:
0815: /**
0816: * Append a byte buffer to the value.
0817: */
0818: public StringValue append(byte[] buf) {
0819: return append(buf, 0, buf.length);
0820: }
0821:
0822: /**
0823: * Append to a string builder.
0824: */
0825: @Override
0826: public StringValue appendTo(UnicodeBuilderValue sb) {
0827: int length = length();
0828:
0829: for (int i = 0; i < length; i++)
0830: sb.append(charAt(i));
0831:
0832: return this ;
0833: }
0834:
0835: /**
0836: * Append a Java boolean to the value.
0837: */
0838: public StringValue appendUnicode(boolean v) {
0839: return append(v ? "true" : "false");
0840: }
0841:
0842: /**
0843: * Append a Java long to the value.
0844: */
0845: public StringValue appendUnicode(long v) {
0846: return append(String.valueOf(v));
0847: }
0848:
0849: /**
0850: * Append a Java double to the value.
0851: */
0852: public StringValue appendUnicode(double v) {
0853: return append(String.valueOf(v));
0854: }
0855:
0856: /**
0857: * Append a Java value to the value.
0858: */
0859: public StringValue appendUnicode(Object v) {
0860: return append(String.valueOf(v));
0861: }
0862:
0863: /**
0864: * Append a Java char, possibly converting to a unicode string
0865: */
0866: public StringValue appendUnicode(char v) {
0867: return append(v);
0868: }
0869:
0870: /**
0871: * Append a Java char buffer, possibly converting to a unicode string
0872: */
0873: public StringValue appendUnicode(char[] buffer, int offset,
0874: int length) {
0875: return append(buffer, offset, length);
0876: }
0877:
0878: /**
0879: * Append a Java char buffer, possibly converting to a unicode string
0880: */
0881: public StringValue appendUnicode(char[] buffer) {
0882: return append(buffer);
0883: }
0884:
0885: /**
0886: * Append a Java char buffer, possibly converting to a unicode string
0887: */
0888: public StringValue appendUnicode(String value) {
0889: return append(value);
0890: }
0891:
0892: /**
0893: * Append a Java char buffer, possibly converting to a unicode string
0894: */
0895: public StringValue appendUnicode(String value, int offset,
0896: int length) {
0897: return append(value, offset, length);
0898: }
0899:
0900: /**
0901: * Append a Java char buffer, possibly converting to a unicode string
0902: */
0903: public StringValue appendUnicode(Value value) {
0904: return append(value);
0905: }
0906:
0907: /**
0908: * Append a Java char buffer, possibly converting to a unicode string
0909: */
0910: public StringValue appendUnicode(Value v1, Value v2) {
0911: return append(v1).append(v2);
0912: }
0913:
0914: /**
0915: * Append a Java byte to the value without conversions.
0916: */
0917: public StringValue appendByte(int v) {
0918: throw new UnsupportedOperationException(getClass().getName());
0919: }
0920:
0921: /**
0922: * Append a Java String to the value without conversions.
0923: */
0924: public StringValue appendBytes(String s) {
0925: StringValue sb = this ;
0926:
0927: for (int i = 0; i < s.length(); i++) {
0928: sb = sb.appendByte(s.charAt(i));
0929: }
0930:
0931: return sb;
0932: }
0933:
0934: /**
0935: * Append a Java char[] to the value without conversions.
0936: */
0937: public StringValue appendBytes(char[] buf, int offset, int length) {
0938: StringValue sb = this ;
0939: int end = Math.min(buf.length, offset + length);
0940:
0941: while (offset < end) {
0942: sb = sb.appendByte(buf[offset++]);
0943: }
0944:
0945: return sb;
0946: }
0947:
0948: /**
0949: * Append from a read stream
0950: */
0951: public StringValue append(Reader reader) throws IOException {
0952: int ch;
0953:
0954: while ((ch = reader.read()) >= 0) {
0955: append((char) ch);
0956: }
0957:
0958: return this ;
0959: }
0960:
0961: /**
0962: * Append from a read stream
0963: */
0964: public StringValue append(Reader reader, long length)
0965: throws IOException {
0966: int ch;
0967:
0968: while (length-- > 0 && (ch = reader.read()) >= 0) {
0969: append((char) ch);
0970: }
0971:
0972: return this ;
0973: }
0974:
0975: /**
0976: * Append from an input stream, using InputStream.read semantics,
0977: * i.e. just call is.read once even if more data is available.
0978: */
0979: public int appendRead(InputStream is, long length) {
0980: TempBuffer tBuf = TempBuffer.allocate();
0981:
0982: try {
0983: byte[] buffer = tBuf.getBuffer();
0984: int sublen = buffer.length;
0985: if (length < sublen)
0986: sublen = (int) length;
0987:
0988: sublen = is.read(buffer, 0, sublen);
0989:
0990: if (sublen > 0)
0991: append(buffer, 0, sublen);
0992:
0993: return sublen;
0994: } catch (IOException e) {
0995: throw new QuercusModuleException(e);
0996: } finally {
0997: TempBuffer.free(tBuf);
0998: }
0999: }
1000:
1001: /**
1002: * Append from an input stream, reading from the input stream until
1003: * end of file or the length is reached.
1004: */
1005: public int appendReadAll(InputStream is, long length) {
1006: TempBuffer tBuf = TempBuffer.allocate();
1007:
1008: try {
1009: byte[] buffer = tBuf.getBuffer();
1010: int readLength = 0;
1011:
1012: while (length > 0) {
1013: int sublen = buffer.length;
1014: if (length < sublen)
1015: sublen = (int) length;
1016:
1017: sublen = is.read(buffer, 0, sublen);
1018:
1019: if (sublen > 0) {
1020: append(buffer, 0, sublen);
1021: length -= sublen;
1022: readLength += sublen;
1023: } else
1024: return readLength > 0 ? readLength : -1;
1025: }
1026:
1027: return readLength;
1028: } catch (IOException e) {
1029: throw new QuercusModuleException(e);
1030: } finally {
1031: TempBuffer.free(tBuf);
1032: }
1033: }
1034:
1035: /**
1036: * Append from an input stream, using InputStream semantics, i.e
1037: * call is.read() only once.
1038: */
1039: public int appendRead(BinaryInput is, long length) {
1040: TempBuffer tBuf = TempBuffer.allocate();
1041:
1042: try {
1043: byte[] buffer = tBuf.getBuffer();
1044: int sublen = buffer.length;
1045: if (length < sublen)
1046: sublen = (int) length;
1047: else if (length > sublen) {
1048: buffer = new byte[(int) length];
1049: sublen = (int) length;
1050: }
1051:
1052: sublen = is.read(buffer, 0, sublen);
1053:
1054: if (sublen > 0)
1055: append(buffer, 0, sublen);
1056:
1057: return sublen;
1058: } catch (IOException e) {
1059: throw new QuercusModuleException(e);
1060: } finally {
1061: TempBuffer.free(tBuf);
1062: }
1063: }
1064:
1065: /**
1066: * Append from an input stream, reading all available data from the
1067: * stream.
1068: */
1069: public int appendReadAll(BinaryInput is, long length) {
1070: TempBuffer tBuf = TempBuffer.allocate();
1071:
1072: try {
1073: byte[] buffer = tBuf.getBuffer();
1074: int readLength = 0;
1075:
1076: while (length > 0) {
1077: int sublen = buffer.length;
1078: if (length < sublen)
1079: sublen = (int) length;
1080:
1081: sublen = is.read(buffer, 0, sublen);
1082:
1083: if (sublen > 0) {
1084: append(buffer, 0, sublen);
1085: length -= sublen;
1086: readLength += sublen;
1087: } else
1088: return readLength > 0 ? readLength : -1;
1089: }
1090:
1091: return readLength;
1092: } catch (IOException e) {
1093: throw new QuercusModuleException(e);
1094: } finally {
1095: TempBuffer.free(tBuf);
1096: }
1097: }
1098:
1099: /**
1100: * Exports the value.
1101: */
1102: @Override
1103: public void varExport(StringBuilder sb) {
1104: sb.append("'");
1105:
1106: String value = toString();
1107: int len = value.length();
1108: for (int i = 0; i < len; i++) {
1109: char ch = value.charAt(i);
1110:
1111: switch (ch) {
1112: case '\'':
1113: sb.append("\\'");
1114: break;
1115: case '\\':
1116: sb.append("\\\\");
1117: break;
1118: default:
1119: sb.append(ch);
1120: }
1121: }
1122: sb.append("'");
1123: }
1124:
1125: /**
1126: * Interns the string.
1127: */
1128: public StringValue intern(Quercus quercus) {
1129: return quercus.intern(toString());
1130: }
1131:
1132: //
1133: // CharSequence
1134: //
1135:
1136: /**
1137: * Returns the length of the string.
1138: */
1139: public int length() {
1140: return toString().length();
1141: }
1142:
1143: /**
1144: * Returns the character at a particular location
1145: */
1146: public char charAt(int index) {
1147: return toString().charAt(index);
1148: }
1149:
1150: /**
1151: * Returns a subsequence
1152: */
1153: public CharSequence subSequence(int start, int end) {
1154: return new StringBuilderValue(toString().substring(start, end));
1155: }
1156:
1157: //
1158: // java.lang.String methods
1159: //
1160:
1161: /**
1162: * Returns the first index of the match string, starting from the head.
1163: */
1164: public final int indexOf(CharSequence match) {
1165: return indexOf(match, 0);
1166: }
1167:
1168: /**
1169: * Returns the first index of the match string, starting from the head.
1170: */
1171: public int indexOf(CharSequence match, int head) {
1172: int length = length();
1173: int matchLength = match.length();
1174:
1175: if (matchLength <= 0)
1176: return -1;
1177: else if (head < 0)
1178: return -1;
1179:
1180: int end = length - matchLength;
1181: char first = match.charAt(0);
1182:
1183: loop: for (; head <= end; head++) {
1184: if (charAt(head) != first)
1185: continue;
1186:
1187: for (int i = 1; i < matchLength; i++) {
1188: if (charAt(head + i) != match.charAt(i))
1189: continue loop;
1190: }
1191:
1192: return head;
1193: }
1194:
1195: return -1;
1196: }
1197:
1198: /**
1199: * Returns the last index of the match string, starting from the head.
1200: */
1201: public int indexOf(char match) {
1202: return indexOf(match, 0);
1203: }
1204:
1205: /**
1206: * Returns the last index of the match string, starting from the head.
1207: */
1208: public int indexOf(char match, int head) {
1209: int length = length();
1210:
1211: for (; head < length; head++) {
1212: if (charAt(head) == match)
1213: return head;
1214: }
1215:
1216: return -1;
1217: }
1218:
1219: /**
1220: * Returns the last index of the match string, starting from the head.
1221: */
1222: public final int lastIndexOf(char match) {
1223: return lastIndexOf(match, Integer.MAX_VALUE);
1224: }
1225:
1226: /**
1227: * Returns the last index of the match string, starting from the head.
1228: */
1229: public int lastIndexOf(char match, int tail) {
1230: int length = length();
1231:
1232: if (tail >= length)
1233: tail = length - 1;
1234:
1235: for (; tail >= 0; tail--) {
1236: if (charAt(tail) == match)
1237: return tail;
1238: }
1239:
1240: return -1;
1241: }
1242:
1243: /**
1244: * Returns the last index of the match string, starting from the tail.
1245: */
1246: public int lastIndexOf(CharSequence match) {
1247: return lastIndexOf(match, Integer.MAX_VALUE);
1248: }
1249:
1250: /**
1251: * Returns the last index of the match string, starting from the tail.
1252: */
1253: public int lastIndexOf(CharSequence match, int tail) {
1254: int length = length();
1255: int matchLength = match.length();
1256:
1257: if (matchLength <= 0)
1258: return -1;
1259: if (tail < 0)
1260: return -1;
1261:
1262: if (tail > length - matchLength)
1263: tail = length - matchLength;
1264:
1265: char first = match.charAt(0);
1266:
1267: loop: for (; tail >= 0; tail--) {
1268: if (charAt(tail) != first)
1269: continue;
1270:
1271: for (int i = 1; i < matchLength; i++) {
1272: if (charAt(tail + i) != match.charAt(i))
1273: continue loop;
1274: }
1275:
1276: return tail;
1277: }
1278:
1279: return -1;
1280: }
1281:
1282: /**
1283: * Returns true if the region matches
1284: */
1285: public boolean regionMatches(int offset, char[] mBuffer,
1286: int mOffset, int mLength) {
1287: int length = length();
1288:
1289: if (length < offset + mLength)
1290: return false;
1291:
1292: for (int i = 0; i < mLength; i++) {
1293: if (charAt(offset + i) != mBuffer[mOffset + i])
1294: return false;
1295: }
1296:
1297: return true;
1298: }
1299:
1300: /**
1301: * Returns true if the region matches
1302: */
1303: public boolean regionMatches(int offset, StringValue match,
1304: int mOffset, int mLength) {
1305: int length = length();
1306:
1307: if (length < offset + mLength)
1308: return false;
1309:
1310: for (int i = 0; i < mLength; i++) {
1311: if (charAt(offset + i) != match.charAt(mOffset + i))
1312: return false;
1313: }
1314:
1315: return true;
1316: }
1317:
1318: /**
1319: * Returns true if the region matches
1320: */
1321: public boolean regionMatchesIgnoreCase(int offset, char[] match,
1322: int mOffset, int mLength) {
1323: int length = length();
1324:
1325: if (length < offset + mLength)
1326: return false;
1327:
1328: for (int i = 0; i < mLength; i++) {
1329: char a = Character.toLowerCase(charAt(offset + i));
1330: char b = Character.toLowerCase(match[mOffset + i]);
1331:
1332: if (a != b)
1333: return false;
1334: }
1335:
1336: return true;
1337: }
1338:
1339: /**
1340: * Returns true if the string ends with another string.
1341: */
1342: public boolean endsWith(StringValue tail) {
1343: int len = length();
1344: int tailLen = tail.length();
1345:
1346: int offset = len - tailLen;
1347:
1348: if (offset < 0)
1349: return false;
1350:
1351: for (int i = 0; i < tailLen; i++) {
1352: if (charAt(offset + i) != tail.charAt(i))
1353: return false;
1354: }
1355:
1356: return true;
1357: }
1358:
1359: /**
1360: * Returns a StringValue substring.
1361: */
1362: public StringValue substring(int head) {
1363: return (StringValue) subSequence(head, length());
1364: }
1365:
1366: /**
1367: * Returns a StringValue substring.
1368: */
1369: public StringValue substring(int begin, int end) {
1370: return (StringValue) subSequence(begin, end);
1371: }
1372:
1373: /**
1374: * Returns a character array
1375: */
1376: public char[] toCharArray() {
1377: int length = length();
1378:
1379: char[] array = new char[length()];
1380:
1381: getChars(0, array, 0, length);
1382:
1383: return array;
1384: }
1385:
1386: public char[] getRawCharArray() {
1387: return toCharArray();
1388: }
1389:
1390: /**
1391: * Copies the chars
1392: */
1393: public void getChars(int stringOffset, char[] buffer, int offset,
1394: int length) {
1395: for (int i = 0; i < length; i++)
1396: buffer[offset + i] = charAt(stringOffset + i);
1397: }
1398:
1399: /**
1400: * Convert to lower case.
1401: */
1402: public StringValue toLowerCase() {
1403: int length = length();
1404:
1405: UnicodeBuilderValue string = new UnicodeBuilderValue(length);
1406:
1407: char[] buffer = string.getBuffer();
1408: getChars(0, buffer, 0, length);
1409:
1410: for (int i = 0; i < length; i++) {
1411: char ch = buffer[i];
1412:
1413: if ('A' <= ch && ch <= 'Z')
1414: buffer[i] = (char) (ch + 'a' - 'A');
1415: else if (ch < 0x80) {
1416: } else if (Character.isUpperCase(ch))
1417: buffer[i] = Character.toLowerCase(ch);
1418: }
1419:
1420: string.setOffset(length);
1421:
1422: return string;
1423: }
1424:
1425: /**
1426: * Convert to lower case.
1427: */
1428: public StringValue toUpperCase() {
1429: int length = length();
1430:
1431: UnicodeBuilderValue string = new UnicodeBuilderValue(length);
1432:
1433: char[] buffer = string.getBuffer();
1434: getChars(0, buffer, 0, length);
1435:
1436: for (int i = 0; i < length; i++) {
1437: char ch = buffer[i];
1438:
1439: if ('a' <= ch && ch <= 'z')
1440: buffer[i] = (char) (ch + 'A' - 'a');
1441: else if (ch < 0x80) {
1442: } else if (Character.isLowerCase(ch))
1443: buffer[i] = Character.toUpperCase(ch);
1444: }
1445:
1446: string.setOffset(length);
1447:
1448: return string;
1449: }
1450:
1451: /**
1452: * Returns a byteArrayInputStream for the value.
1453: * See TempBufferStringValue for how this can be overriden
1454: *
1455: * @return InputStream
1456: */
1457: public InputStream toInputStream() {
1458: try {
1459: //XXX: refactor so that env is passed in
1460: return toInputStream(Env.getInstance().getRuntimeEncoding());
1461: } catch (UnsupportedEncodingException e) {
1462: throw new QuercusRuntimeException(e);
1463: }
1464: //return new StringValueInputStream();
1465: }
1466:
1467: /**
1468: * Returns a byte stream of chars.
1469: * @param charset to encode chars to
1470: */
1471: public InputStream toInputStream(String charset)
1472: throws UnsupportedEncodingException {
1473: return new ByteArrayInputStream(toString().getBytes(charset));
1474: }
1475:
1476: /**
1477: * Returns a char stream.
1478: * XXX: when decoding fails
1479: *
1480: * @param charset to decode bytes by
1481: */
1482: public Reader toReader(String charset)
1483: throws UnsupportedEncodingException {
1484: byte[] bytes = toBytes();
1485:
1486: return new InputStreamReader(new ByteArrayInputStream(bytes),
1487: charset);
1488: }
1489:
1490: /**
1491: * Converts to a BinaryValue in desired charset.
1492: *
1493: * @param env
1494: * @param charset
1495: */
1496: public StringValue toBinaryValue(Env env, String charset) {
1497: TempBuffer tb = TempBuffer.allocate();
1498: byte[] buffer = tb.getBuffer();
1499:
1500: try {
1501: InputStream in = toInputStream(charset);
1502: TempStream out = new TempStream();
1503:
1504: int sublen = in.read(buffer, 0, buffer.length);
1505:
1506: while (sublen >= 0) {
1507: out.write(buffer, 0, sublen, false);
1508: sublen = in.read(buffer, 0, buffer.length);
1509: }
1510:
1511: out.flush();
1512:
1513: StringValue result = env.createBinaryBuilder();
1514: for (TempBuffer ptr = out.getHead(); ptr != null; ptr = ptr
1515: .getNext()) {
1516: result.append(ptr.getBuffer(), 0, ptr.getLength());
1517: }
1518:
1519: TempBuffer.free(out.getHead());
1520:
1521: return result;
1522: } catch (IOException e) {
1523: throw new QuercusModuleException(e.getMessage());
1524: } finally {
1525: TempBuffer.free(tb);
1526: }
1527: }
1528:
1529: public byte[] toBytes() {
1530: throw new UnsupportedOperationException();
1531: }
1532:
1533: /**
1534: * Decodes from charset and returns UnicodeValue.
1535: *
1536: * @param env
1537: * @param charset
1538: */
1539: public StringValue toUnicodeValue(Env env, String charset) {
1540: StringValue sb = env.createUnicodeBuilder();
1541:
1542: TempCharBuffer tb = TempCharBuffer.allocate();
1543: char[] charBuf = tb.getBuffer();
1544:
1545: try {
1546: Reader in = toReader(charset);
1547:
1548: int sublen;
1549: while ((sublen = in.read(charBuf, 0, charBuf.length)) >= 0) {
1550: sb.append(charBuf, 0, sublen);
1551: }
1552:
1553: } catch (IOException e) {
1554: throw new QuercusModuleException(e.getMessage());
1555:
1556: } finally {
1557: TempCharBuffer.free(tb);
1558: }
1559:
1560: return sb;
1561: }
1562:
1563: /**
1564: * Decodes from charset and returns UnicodeValue.
1565: *
1566: * @param env
1567: * @param charset
1568: */
1569: public StringValue convertToUnicode(Env env, String charset) {
1570: UnicodeBuilderValue sb = new UnicodeBuilderValue();
1571:
1572: TempCharBuffer tb = TempCharBuffer.allocate();
1573: char[] charBuf = tb.getBuffer();
1574:
1575: try {
1576: Reader in = toReader(charset);
1577:
1578: int sublen;
1579: while ((sublen = in.read(charBuf, 0, charBuf.length)) >= 0) {
1580: sb.append(charBuf, 0, sublen);
1581: }
1582:
1583: } catch (IOException e) {
1584: throw new QuercusModuleException(e.getMessage());
1585:
1586: } finally {
1587: TempCharBuffer.free(tb);
1588: }
1589:
1590: return sb;
1591: }
1592:
1593: /**
1594: * Converts to a string builder
1595: */
1596: @Override
1597: public StringValue toStringBuilder(Env env) {
1598: return createStringBuilder().append(this );
1599: }
1600:
1601: /**
1602: * Writes to a stream
1603: */
1604: public void writeTo(OutputStream os) {
1605: try {
1606: int len = length();
1607:
1608: for (int i = 0; i < len; i++)
1609: os.write(charAt(i));
1610: } catch (IOException e) {
1611: throw new QuercusModuleException(e);
1612: }
1613: }
1614:
1615: //
1616: // java.lang.Object methods
1617: //
1618:
1619: /**
1620: * Returns the hash code.
1621: */
1622: public int hashCode() {
1623: int hash = 37;
1624:
1625: int length = length();
1626:
1627: for (int i = 0; i < length; i++) {
1628: hash = 65521 * hash + charAt(i);
1629: }
1630:
1631: return hash;
1632: }
1633:
1634: /**
1635: * Test for equality
1636: */
1637: public boolean equals(Object o) {
1638: if (this == o)
1639: return true;
1640: else if (!(o instanceof StringValue))
1641: return false;
1642:
1643: StringValue s = (StringValue) o;
1644:
1645: if (s.isUnicode() != isUnicode())
1646: return false;
1647:
1648: int aLength = length();
1649: int bLength = s.length();
1650:
1651: if (aLength != bLength)
1652: return false;
1653:
1654: for (int i = aLength - 1; i >= 0; i--) {
1655: if (charAt(i) != s.charAt(i))
1656: return false;
1657: }
1658:
1659: return true;
1660: }
1661:
1662: @Override
1663: abstract public String toDebugString();
1664:
1665: @Override
1666: abstract public void varDumpImpl(Env env, WriteStream out,
1667: int depth, IdentityHashMap<Value, String> valueSet)
1668: throws IOException;
1669:
1670: class StringValueInputStream extends java.io.InputStream {
1671: private final int _length;
1672: private int _index;
1673:
1674: StringValueInputStream() {
1675: _length = length();
1676: }
1677:
1678: /**
1679: * Reads the next byte.
1680: */
1681: public int read() {
1682: if (_index < _length)
1683: return charAt(_index++);
1684: else
1685: return -1;
1686: }
1687:
1688: /**
1689: * Reads into a buffer.
1690: */
1691: public int read(byte[] buffer, int offset, int length) {
1692: int sublen = _length - _index;
1693:
1694: if (length < sublen)
1695: sublen = length;
1696:
1697: if (sublen <= 0)
1698: return -1;
1699:
1700: int index = _index;
1701:
1702: for (int i = 0; i < sublen; i++)
1703: buffer[offset + i] = (byte) charAt(index + i);
1704:
1705: _index += sublen;
1706:
1707: return sublen;
1708: }
1709: }
1710:
1711: static {
1712: // XXX: need to update for unicode
1713:
1714: CHAR_STRINGS = new StringValue[256];
1715:
1716: for (int i = 0; i < CHAR_STRINGS.length; i++)
1717: CHAR_STRINGS[i] = new StringBuilderValue(String
1718: .valueOf((char) i));
1719: }
1720: }
|