0001: /*=============================================================================
0002: * Copyright Texas Instruments 2000-2004. All Rights Reserved.
0003: *
0004: * This program is free software; you can redistribute it and/or
0005: * modify it under the terms of the GNU Lesser General Public
0006: * License as published by the Free Software Foundation; either
0007: * version 2 of the License, or (at your option) any later version.
0008: *
0009: * This program is distributed in the hope that it will be useful,
0010: * but WITHOUT ANY WARRANTY; without even the implied warranty of
0011: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
0012: * Lesser General Public License for more details.
0013: *
0014: * You should have received a copy of the GNU Lesser General Public
0015: * License along with this library; if not, write to the Free Software
0016: * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
0017: *
0018: * $ProjectHeader: OSCRIPT 0.155 Fri, 20 Dec 2002 18:34:22 -0800 rclark $
0019: */
0020:
0021: package oscript.data;
0022:
0023: import oscript.exceptions.*;
0024:
0025: import java.util.Hashtable;
0026:
0027: /**
0028: * A string class. An <code>OString</code> is immutable, once the instance is
0029: * constructed, it won't change.
0030: *
0031: * @author Rob Clark (rob@ti.com)
0032: */
0033: public class OString extends OObject implements java.io.Externalizable {
0034: /**
0035: * The type object for an instance of String.
0036: */
0037: public final static Value TYPE = BuiltinType
0038: .makeBuiltinType("oscript.data.OString");
0039: public final static String PARENT_TYPE_NAME = "oscript.data.OObject";
0040: public final static String TYPE_NAME = "String";
0041: public final static String[] MEMBER_NAMES = new String[] {
0042: "castToBoolean", "castToString", "castToExactNumber",
0043: "castToInexactNumber", "castToJavaObject", "bopEquals",
0044: "bopEqualsR", "bopNotEquals", "bopNotEqualsR",
0045: "bopLessThan", "bopLessThanR", "bopGreaterThan",
0046: "bopGreaterThanR", "bopLessThanOrEquals",
0047: "bopLessThanOrEqualsR", "bopGreaterThanOrEquals",
0048: "bopGreaterThanOrEqualsR", "bopPlus", "bopPlusR", "length",
0049: "elementAt", "elementsAt", "match", "replace", "search",
0050: "substring", "intern", "indexOf", "lastIndexOf",
0051: "toUpperCase", "toLowerCase", "equals", "startsWith",
0052: "endsWith", "trim", "split" };
0053:
0054: /*=======================================================================*/
0055: /**
0056: */
0057: private Segment segment;
0058:
0059: public OString() {
0060: }
0061:
0062: /**
0063: * Derived class that implements {@link java.io.Externalizable} must
0064: * call this if it overrides it. It should override it to save/restore
0065: * it's own state.
0066: */
0067: public void readExternal(java.io.ObjectInput in)
0068: throws java.io.IOException {
0069: segment = new StringSegment(in.readUTF());
0070: }
0071:
0072: /**
0073: * Derived class that implements {@link java.io.Externalizable} must
0074: * call this if it overrides it. It should override it to save/restore
0075: * it's own state.
0076: */
0077: public void writeExternal(java.io.ObjectOutput out)
0078: throws java.io.IOException {
0079: out.writeUTF(segment.toString());
0080: }
0081:
0082: /*=======================================================================*/
0083:
0084: /*=======================================================================*/
0085: /**
0086: * Construct a new string.
0087: *
0088: * @param stringVal the value of the string
0089: */
0090: public OString(String stringVal) {
0091: this (new StringSegment(stringVal));
0092: }
0093:
0094: private OString(Segment segment) {
0095: super ();
0096: this .segment = segment;
0097: }
0098:
0099: /*=======================================================================*/
0100: /**
0101: * Class Constructor. This is the constructor that gets called via an
0102: * <code>BuiltinType</code> instance.
0103: *
0104: * @param args arguments to this constructor
0105: * @throws PackagedScriptObjectException(Exception) if wrong number of args
0106: */
0107: public OString(oscript.util.MemberTable args) {
0108: super ();
0109:
0110: if (args.length() != 1)
0111: throw PackagedScriptObjectException
0112: .makeExceptionWrapper(new OIllegalArgumentException(
0113: "wrong number of args!"));
0114: else
0115: segment = new StringSegment(args.referenceAt(0)
0116: .castToString());
0117: }
0118:
0119: /*=======================================================================*/
0120: /**
0121: * Get the type of this object. The returned type doesn't have to take
0122: * into account the possibility of a script type extending a built-in
0123: * type, since that is handled by {@link #getType}.
0124: *
0125: * @return the object's type
0126: */
0127: protected Value getTypeImpl() {
0128: return TYPE;
0129: }
0130:
0131: /*=======================================================================*/
0132: /**
0133: * Get the value of this string.
0134: *
0135: * @return the string as a java.lang.String
0136: */
0137: public final String value() {
0138: return segment.toString();
0139: }
0140:
0141: /*=======================================================================*/
0142: /**
0143: * Return a hash code value for this object.
0144: *
0145: * @return a hash code value
0146: * @see java.lang.Object#hashCode()
0147: */
0148: public int hashCode() {
0149: return segment.hashCode();
0150: }
0151:
0152: /*=======================================================================*/
0153: /**
0154: * Return a canonical representation of this OString object. This has the
0155: * result that <code>x.intern() == y.intern()</code> (for <code>x</code>
0156: * and <code>y</code> that are OString objects).
0157: *
0158: * @return a OString that has the same value (in the sense of <code>equals
0159: * </code>) but is guaraneed to be from a unique pool of OStrings
0160: */
0161: public OString intern() {
0162: return makeString(this , segment.toString());
0163: }
0164:
0165: /*=======================================================================*/
0166: /**
0167: * Compare two objects for equality.
0168: *
0169: * @param obj the object to compare to this object
0170: * @return <code>true</code> if equals, else <code>false</code>
0171: * @see java.lang.Object#equals(java.lang.Object)
0172: */
0173: public boolean equals(Object obj) {
0174: if (this == obj)
0175: return true;
0176:
0177: String str = null;
0178:
0179: if (obj instanceof String)
0180: str = (String) obj;
0181: else if (obj instanceof Value)
0182: str = ((Value) obj).castToString();
0183:
0184: if (str != null)
0185: return str.equals(segment.toString());
0186:
0187: return false;
0188: }
0189:
0190: /*=======================================================================*/
0191: /**
0192: * Convert this object to a native java <code>boolean</code> value.
0193: *
0194: * @return a boolean value
0195: * @throws PackagedScriptObjectException(NoSuchMethodException)
0196: */
0197: public boolean castToBoolean() throws PackagedScriptObjectException {
0198: if (segment.toString().equals("true")) {
0199: return true;
0200: } else if (segment.toString().equals("false")) {
0201: return false;
0202: } else {
0203: throw noSuchMember("castToBoolean");
0204: }
0205: }
0206:
0207: /*=======================================================================*/
0208: /**
0209: * Convert this object to a native java <code>String</code> value.
0210: *
0211: * @return a String value
0212: * @throws PackagedScriptObjectException(NoSuchMethodException)
0213: */
0214: public String castToString() throws PackagedScriptObjectException {
0215: return segment.toString();
0216: }
0217:
0218: public static Value _bopCast(Value val) {
0219: if (val instanceof OString)
0220: return val;
0221: return new OString(val.castToString());
0222: }
0223:
0224: /*=======================================================================*/
0225: /**
0226: * Convert this object to a native java <code>long</code> value. In order
0227: * for a string to be converted to an exact number, it must be of the
0228: * form:
0229: * <pre>
0230: * STRING ::== ("-")? (HEX_STRING | OCTAL_STRING | DEC_STRING)
0231: * HEX_STRING ::== ("0x" | "0X") ([0-9] | [a-f] | [A-F])+
0232: * OCTAL_STRING ::== "0" ([0-7])+
0233: * DEC_STRING ::== ([0-9])+
0234: * </pre>
0235: *
0236: * @return a long value
0237: * @throws PackagedScriptObjectException(NoSuchMethodException)
0238: */
0239: public long castToExactNumber()
0240: throws PackagedScriptObjectException {
0241: /* we are spending too much time in NumberFormatException::fillInStackTrace
0242: * is thrown, so instead we roll our own:
0243: */
0244: if (segment.toString().length() > Byte.MAX_VALUE) {
0245: throw PackagedScriptObjectException
0246: .makeExceptionWrapper(new ONoSuchMemberException(
0247: "cannot convert \"" + segment.toString()
0248: + "\" to ExactNumber"));
0249: }
0250:
0251: byte radix = 10;
0252: byte idx = 0;
0253: byte sign = 1;
0254: byte max = (byte) (segment.toString().length());
0255:
0256: String str = segment.toString();
0257:
0258: if (str.startsWith("-")) {
0259: idx += 1;
0260: sign = -1;
0261: }
0262:
0263: if (str.startsWith("0x", idx) || str.startsWith("0X", idx)) {
0264: idx += 2;
0265: radix = 16;
0266: } else if (str.startsWith("0", idx)) {
0267: idx += 1;
0268: radix = 8;
0269: } else if (str.startsWith("'", idx)) {
0270: idx += 1;
0271: radix = 2;
0272: }
0273:
0274: // check for valid number string, ie "0x", "-", etc., aren't valid:
0275: if ((idx >= max) && (radix != 8))
0276: throw PackagedScriptObjectException
0277: .makeExceptionWrapper(new ONoSuchMemberException(
0278: "cannot convert \"" + str
0279: + "\" to ExactNumber"));
0280:
0281: return parseExactNumber(segment.toString(), radix, idx, max)
0282: * sign;
0283: }
0284:
0285: // doesn't deal with sign, figure out radix, etc...
0286: private static final long parseExactNumber(String str, byte radix,
0287: byte idx, byte max) {
0288: long result = 0;
0289:
0290: while (idx < max) {
0291: int digit = Character.digit(str.charAt(idx++), radix);
0292:
0293: // check for characters that aren't digits:
0294: if (digit == -1)
0295: throw PackagedScriptObjectException
0296: .makeExceptionWrapper(new ONoSuchMemberException(
0297: "cannot convert \"" + str
0298: + "\" to ExactNumber"));
0299:
0300: long oldResult = result;
0301:
0302: result *= radix;
0303: result += digit;
0304:
0305: // check for roll-over:
0306: if (result < oldResult)
0307: throw PackagedScriptObjectException
0308: .makeExceptionWrapper(new ONoSuchMemberException(
0309: "cannot convert \"" + str
0310: + "\" to ExactNumber"));
0311: }
0312:
0313: return result;
0314: }
0315:
0316: /*=======================================================================*/
0317: /**
0318: * Convert this object to a native java <code>double</code> value. In
0319: * order for a string to be converted to an inexact number, it must be
0320: * of the form:
0321: * <pre>
0322: * STRING ::== ("+" | "-")? EXACT_NUMBER ("." EXACT_NUMBER)? (("e" | "E") EXACT_NUMBER)?
0323: * EXACT_NUMBER ::== <see convertToExactNumber>
0324: * </pre>
0325: *
0326: * @return a double value
0327: * @throws PackagedScriptObjectException(NoSuchMethodException)
0328: */
0329: public double castToInexactNumber()
0330: throws PackagedScriptObjectException {
0331: String r = segment.toString();
0332:
0333: /* we are spending too much time in NumberFormatException::fillInStackTrace
0334: * is thrown, so instead we roll our own:
0335: */
0336: if (r.length() > Byte.MAX_VALUE)
0337: throw PackagedScriptObjectException
0338: .makeExceptionWrapper(new ONoSuchMemberException(
0339: "cannot convert \"" + segment.toString()
0340: + "\" to InexactNumber"));
0341:
0342: // in case we have something like "0x1234".castToInexactNumber()
0343: if (r.indexOf('.') == -1)
0344: return (double) castToExactNumber();
0345:
0346: double sign = 1.0;
0347: byte tmp;
0348:
0349: if (r.startsWith("-")) {
0350: sign = -1.0;
0351: r = r.substring(1);
0352: } else if (r.startsWith("+")) {
0353: // "+" is the default sign
0354: r = r.substring(1);
0355: }
0356:
0357: // check for valid string:
0358: if (r.length() <= 0)
0359: throw PackagedScriptObjectException
0360: .makeExceptionWrapper(new ONoSuchMemberException(
0361: "cannot convert \"" + segment.toString()
0362: + "\" to InexactNumber"));
0363:
0364: String s1;
0365: String s2;
0366: String s3;
0367:
0368: // look for optional s3:
0369: tmp = (byte) (r.lastIndexOf('e'));
0370: if (tmp == -1)
0371: tmp = (byte) (r.lastIndexOf('E'));
0372:
0373: if (tmp != -1) {
0374: s3 = r.substring(tmp + 1);
0375: r = r.substring(0, tmp);
0376: } else {
0377: s3 = null;
0378: }
0379:
0380: // look for optional s2:
0381: tmp = (byte) (r.lastIndexOf('.'));
0382: if (tmp != -1) {
0383: s2 = r.substring(tmp + 1);
0384: r = r.substring(0, tmp);
0385: } else {
0386: s2 = null;
0387: }
0388:
0389: // whats left is s1:
0390: s1 = r;
0391:
0392: // now convert to a number:
0393: try {
0394: double result = (double) parseExactNumber(s1, (byte) 10,
0395: (byte) 0, (byte) (s1.length()));
0396:
0397: if (s2 != null) {
0398: double dec = (double) parseExactNumber(s2, (byte) 10,
0399: (byte) 0, (byte) (s2.length()));
0400:
0401: result += dec / Math.pow(10, s2.length());
0402: }
0403:
0404: if (s3 != null) {
0405: byte esign = 1;
0406: if (s3.startsWith("-")) {
0407: esign = -1;
0408: s3 = s3.substring(1);
0409: } else if (s3.startsWith("+")) {
0410: s3 = s3.substring(1);
0411: }
0412:
0413: double exp = esign
0414: * (double) parseExactNumber(s3, (byte) 10,
0415: (byte) 0, (byte) (s3.length()));
0416:
0417: result *= Math.pow(10, exp);
0418: }
0419:
0420: return sign * result;
0421: } catch (PackagedScriptObjectException e) {
0422: throw PackagedScriptObjectException
0423: .makeExceptionWrapper(new ONoSuchMemberException(
0424: "cannot convert \"" + segment.toString()
0425: + "\" to InexactNumber"));
0426: }
0427: }
0428:
0429: /*=======================================================================*/
0430: /**
0431: * Convert this object to a native java <code>Object</code> value.
0432: *
0433: * @return a java object
0434: * @throws PackagedScriptObjectException(NoSuchMemberException)
0435: */
0436: public Object castToJavaObject()
0437: throws PackagedScriptObjectException {
0438: return segment.toString();
0439: }
0440:
0441: /*=======================================================================*/
0442: /* The binary operators:
0443: */
0444:
0445: /*=======================================================================*/
0446: /**
0447: * Perform the "==" operation.
0448: *
0449: * @param val the other value
0450: * @return the result
0451: * @throws PackagedScriptObjectException(NoSuchMethodException)
0452: */
0453: public Value bopEquals(Value val)
0454: throws PackagedScriptObjectException {
0455: try {
0456: return OBoolean.makeBoolean(segment.toString().equals(
0457: val.castToString()));
0458: } catch (PackagedScriptObjectException e) {
0459: return val.bopEqualsR(this , e);
0460: }
0461: }
0462:
0463: public Value bopEqualsR(Value val, PackagedScriptObjectException e)
0464: throws PackagedScriptObjectException {
0465: return OBoolean.makeBoolean(val.castToString().equals(
0466: segment.toString()));
0467: }
0468:
0469: /*=======================================================================*/
0470: /**
0471: * Perform the "!=" operation.
0472: *
0473: * @param val the other value
0474: * @return the result
0475: * @throws PackagedScriptObjectException(NoSuchMethodException)
0476: */
0477: public Value bopNotEquals(Value val)
0478: throws PackagedScriptObjectException {
0479: try {
0480: return OBoolean.makeBoolean(!segment.toString().equals(
0481: val.castToString()));
0482: } catch (PackagedScriptObjectException e) {
0483: return val.bopNotEqualsR(this , e);
0484: }
0485: }
0486:
0487: public Value bopNotEqualsR(Value val,
0488: PackagedScriptObjectException e)
0489: throws PackagedScriptObjectException {
0490: return OBoolean.makeBoolean(!val.castToString().equals(
0491: segment.toString()));
0492: }
0493:
0494: /*=======================================================================*/
0495: /**
0496: * Perform the "<" operation.
0497: *
0498: * @param val the other value
0499: * @return the result
0500: * @throws PackagedScriptObjectException(NoSuchMethodException)
0501: */
0502: public Value bopLessThan(Value val)
0503: throws PackagedScriptObjectException {
0504: try {
0505: return OBoolean.makeBoolean(segment.toString().compareTo(
0506: val.castToString()) < 0);
0507: } catch (PackagedScriptObjectException e) {
0508: return val.bopLessThanR(this , e);
0509: }
0510: }
0511:
0512: public Value bopLessThanR(Value val, PackagedScriptObjectException e)
0513: throws PackagedScriptObjectException {
0514: return OBoolean.makeBoolean(segment.toString().compareTo(
0515: val.castToString()) > 0);
0516: }
0517:
0518: /*=======================================================================*/
0519: /**
0520: * Perform the ">" operation.
0521: *
0522: * @param val the other value
0523: * @return the result
0524: * @throws PackagedScriptObjectException(NoSuchMethodException)
0525: */
0526: public Value bopGreaterThan(Value val)
0527: throws PackagedScriptObjectException {
0528: try {
0529: return OBoolean.makeBoolean(segment.toString().compareTo(
0530: val.castToString()) > 0);
0531: } catch (PackagedScriptObjectException e) {
0532: return val.bopGreaterThanR(this , e);
0533: }
0534: }
0535:
0536: public Value bopGreaterThanR(Value val,
0537: PackagedScriptObjectException e)
0538: throws PackagedScriptObjectException {
0539: return OBoolean.makeBoolean(segment.toString().compareTo(
0540: val.castToString()) < 0);
0541: }
0542:
0543: /*=======================================================================*/
0544: /**
0545: * Perform the "<=" operation.
0546: *
0547: * @param val the other value
0548: * @return the result
0549: * @throws PackagedScriptObjectException(NoSuchMethodException)
0550: */
0551: public Value bopLessThanOrEquals(Value val)
0552: throws PackagedScriptObjectException {
0553: try {
0554: return OBoolean.makeBoolean(segment.toString().compareTo(
0555: val.castToString()) <= 0);
0556: } catch (PackagedScriptObjectException e) {
0557: return val.bopLessThanOrEqualsR(this , e);
0558: }
0559: }
0560:
0561: public Value bopLessThanOrEqualsR(Value val,
0562: PackagedScriptObjectException e)
0563: throws PackagedScriptObjectException {
0564: return OBoolean.makeBoolean(segment.toString().compareTo(
0565: val.castToString()) >= 0);
0566: }
0567:
0568: /*=======================================================================*/
0569: /**
0570: * Perform the ">=" operation.
0571: *
0572: * @param val the other value
0573: * @return the result
0574: * @throws PackagedScriptObjectException(NoSuchMethodException)
0575: */
0576: public Value bopGreaterThanOrEquals(Value val)
0577: throws PackagedScriptObjectException {
0578: try {
0579: return OBoolean.makeBoolean(segment.toString().compareTo(
0580: val.castToString()) >= 0);
0581: } catch (PackagedScriptObjectException e) {
0582: return val.bopGreaterThanOrEqualsR(this , e);
0583: }
0584: }
0585:
0586: public Value bopGreaterThanOrEqualsR(Value val,
0587: PackagedScriptObjectException e)
0588: throws PackagedScriptObjectException {
0589: return OBoolean.makeBoolean(segment.toString().compareTo(
0590: val.castToString()) <= 0);
0591: }
0592:
0593: /*=======================================================================*/
0594: /**
0595: * Perform the "+" operation.
0596: *
0597: * @param val the other value
0598: * @return the result
0599: * @throws PackagedScriptObjectException(NoSuchMethodException)
0600: */
0601: public Value bopPlus(Value val)
0602: throws PackagedScriptObjectException {
0603: // val might well be, for example, a Reference, which will screw up
0604: // the instanceof below:
0605: val = val.unhand();
0606:
0607: try {
0608: Segment s;
0609:
0610: if (val instanceof OString) {
0611: s = ((OString) val).segment;
0612: } else {
0613: String str = val.castToString();
0614:
0615: if (DEBUG)
0616: if (str == null)
0617: throw new ProgrammingErrorException(
0618: "this shouldn't happen, val.castToString() returns null for val="
0619: + val + " (" + val.getType()
0620: + ")");
0621:
0622: s = new StringSegment(val.castToString());
0623: }
0624:
0625: return new OString(new ComboSegment(segment, s));
0626: } catch (PackagedScriptObjectException e) {
0627: return val.bopPlusR(this , e);
0628: }
0629: }
0630:
0631: public Value bopPlusR(Value val, PackagedScriptObjectException e)
0632: throws PackagedScriptObjectException {
0633: Segment s = ((val instanceof OString) ? ((OString) val).segment
0634: : new StringSegment(val.castToString()));
0635: return new OString(new ComboSegment(s, segment));
0636: }
0637:
0638: /*=======================================================================*/
0639: /* The misc operators:
0640: */
0641:
0642: /*=======================================================================*/
0643: /**
0644: * For types that implement <code>elementAt</code>, this returns the
0645: * number of elements. This is the same as the <i>length</i> property
0646: * of an object.
0647: *
0648: * @return an integer length
0649: * @throws PackagedScriptObjectException(NoSuchMethodException)
0650: * @see #elementAt
0651: */
0652: public int length() throws PackagedScriptObjectException {
0653: return segment.length();
0654: }
0655:
0656: /*=======================================================================*/
0657: /**
0658: * Get the specified index of this object. This makes a string behave as
0659: * an array, or at least support array indexing.
0660: *
0661: * @param idx the index to get
0662: * @return a string of length one
0663: * @throws PackagedScriptObjectException(NoSuchMethodException)
0664: * @see #length
0665: */
0666: public Value elementAt(Value oidx)
0667: throws PackagedScriptObjectException {
0668: int idx = (int) (oidx.castToExactNumber());
0669: checkIndex(idx);
0670:
0671: return new OString("" + segment.toString().charAt(idx));
0672: }
0673:
0674: /*=======================================================================*/
0675: /**
0676: * Get the specified range of this object, if this object is an array.
0677: * This returns a copy of a range of the array.
0678: *
0679: * @param idx1 the index index of the beginning of the range, inclusive
0680: * @param idx2 the index of the end of the range, inclusive
0681: * @return a copy of the specified range of this array
0682: * @throws PackagedScriptObjectException(NoSuchMemberException)
0683: * @see #length
0684: * @see #elementAt
0685: */
0686: public Value elementsAt(Value idx1, Value idx2)
0687: throws PackagedScriptObjectException {
0688: return new OString(substring((int) (idx1.castToExactNumber()),
0689: (int) (idx2.castToExactNumber()) + 1));
0690: }
0691:
0692: /*=======================================================================*/
0693: /* The regexp operators:
0694: */
0695:
0696: /*=======================================================================*/
0697: /**
0698: * Returns the same thing as <code>regexp.exec(this)</code>. The
0699: * <code>regexp</code> should either be a <code>RegExp</code> object
0700: * or a string that can be compiled to a <code>RegExp</code> object.
0701: * <p>
0702: * Note: this API is modeled after the JavaScript RegExp API, for the
0703: * benefit of users already familiar with JavaScript.
0704: *
0705: * @param regexp the regular expression
0706: * @return the result of <code>regexp.exec</code>.
0707: */
0708: public Value match(Value regexp) {
0709: return compile(regexp).exec(this );
0710: }
0711:
0712: /*=======================================================================*/
0713: /**
0714: * Finds a match between a regular expression and this string object, and
0715: * replaces the matched substring with a new substring. The second
0716: * parameter can be either a replacement string, or a function called to
0717: * determine the replacement string.
0718: * <p>
0719: * If the second parameter is a string, the following replacement patterns
0720: * are evaluated and replaced with the appropriate value:
0721: * <div id="regtable"><table>
0722: * <tr><th>pattern</th><th>Description</th></tr>
0723: * <tr><td><code>$$</code></td> <td>Inserts a <code>$</code></td></tr>
0724: * <tr><td><code>$&</code></td> <td>Inserts the matched substring</td></tr>
0725: * <tr><td><code>$`</code></td> <td>Inserts the portion of the string that precedes the matched substring</td></tr>
0726: * <tr><td><code>$'</code></td> <td>Inserts the portion of the string that follows the matched substring</td></tr>
0727: * <tr><td><code>$</code>n</td> <td>Inserts the nth parenthesized submatch string</td></tr>
0728: * </table></div>
0729: * If the second parameter is a function, it is called with the following
0730: * parameters:
0731: * <div id="regtable"><table>
0732: * <tr><th>param</th><th>Description</th></tr>
0733: * <tr><td>0</td> <td>the matched string</td></tr>
0734: * <tr><td>1-n</td> <td>zero or more parameters for parenthetical matches</td></tr>
0735: * <tr><td>n+1</td> <td>offset of match</td></tr>
0736: * <tr><td>n+2</td> <td>the original string</td></tr>
0737: * </table></div>
0738: * <p>
0739: * Note: this API is modeled after the JavaScript RegExp API, for the
0740: * benefit of users already familiar with JavaScript.
0741: *
0742: * @param regexp the regular expression
0743: * @param strOrFxn replacement string or function
0744: * @return the resulting string
0745: */
0746: public Value replace(Value regexp, Value strOrFxn) {
0747: RegExp re = compile(regexp);
0748:
0749: // XXX not sure if this should always replace all, or only if /g flag on regexp?
0750: RegExpResult r = re.exec(this );
0751:
0752: int idx = (int) (r.getIndex().castToExactNumber());
0753: if (idx != -1) {
0754: Value match = r.elementAt(JavaBridge
0755: .convertToScriptObject(0));
0756: int len = match.length();
0757:
0758: String pre = substring(0, idx);
0759: String post = substring(idx + len, length());
0760: String replace;
0761:
0762: if (strOrFxn instanceof Function) {
0763: int n = r.length();
0764: Value[] args = new Value[n + 2];
0765: for (int i = 0; i < n; i++)
0766: args[i] = r.elementAt(JavaBridge
0767: .convertToScriptObject(i));
0768: args[n] = JavaBridge.convertToScriptObject(idx);
0769: args[n + 1] = r.getInput();
0770: replace = strOrFxn.callAsFunction(args).castToString();
0771: } else {
0772: String str = strOrFxn.castToString();
0773:
0774: // handle replacement patterns:
0775: int escaped = 0;
0776: int n = -1;
0777:
0778: for (int i = 0; i < str.length(); i++) {
0779: char c = str.charAt(i);
0780:
0781: if (escaped > 0) {
0782: String a = null;
0783:
0784: if (n == -1) {
0785: switch (c) {
0786: case '$':
0787: a = "$";
0788: escaped++;
0789: break;
0790: case '&':
0791: a = match.castToString();
0792: escaped++;
0793: break;
0794: case '`':
0795: a = substring(0, idx);
0796: escaped++;
0797: break;
0798: case '\'':
0799: a = substring(idx + len, length());
0800: escaped++;
0801: break;
0802: default:
0803: if (Character.isDigit(c)) {
0804: n = Character.digit(c, 10);
0805: escaped++;
0806: }
0807: }
0808: } else {
0809: if (Character.isDigit(c)) {
0810: n = (n * 10) + Character.digit(c, 10);
0811: escaped++;
0812: } else {
0813: a = r
0814: .elementAt(
0815: JavaBridge
0816: .convertToScriptObject(n))
0817: .castToString();
0818: i -= 1;
0819: }
0820: }
0821:
0822: if (a != null) {
0823: str = str.substring(0, i - escaped + 1) + a
0824: + str.substring(i + 1);
0825: i += a.length() - escaped;
0826: escaped = 0;
0827: n = -1;
0828: }
0829: } else if (c == '$') {
0830: escaped = 1;
0831: }
0832: }
0833:
0834: if (n != -1) {
0835: String a = r.elementAt(
0836: JavaBridge.convertToScriptObject(n))
0837: .castToString();
0838: ;
0839: str = str.substring(0, str.length() - escaped) + a;
0840: }
0841:
0842: replace = str;
0843: }
0844:
0845: // XXX use StringSegments..
0846: return new OString(pre + replace + post);
0847: }
0848:
0849: return this ;
0850: }
0851:
0852: /*=======================================================================*/
0853: /**
0854: * Executes the search for a match between a regular expression and this
0855: * string object.
0856: * <p>
0857: * Note: this API is modeled after the JavaScript RegExp API, for the
0858: * benefit of users already familiar with JavaScript.
0859: *
0860: * @param regexp the regular expression
0861: * @return the index of the match, or <code>-1</code> if none
0862: */
0863: public Value search(Value regexp) {
0864: return compile(regexp).exec(this ).getIndex();
0865: }
0866:
0867: /**
0868: * utility function to compile a string to a RegExp, or if it is already
0869: * a RegExp, cast and return...
0870: */
0871: private static final RegExp compile(Value regexp) {
0872: if (regexp instanceof RegExp)
0873: return (RegExp) regexp;
0874: else
0875: return RegExp.createRegExp(regexp);
0876: }
0877:
0878: /*=======================================================================*/
0879: /* The string operators:
0880: */
0881:
0882: public String substring(int begIdx) {
0883: checkIndex(begIdx);
0884: return segment.toString().substring(begIdx);
0885: }
0886:
0887: public String substring(int begIdx, int endIdx) {
0888: if (begIdx < 0)
0889: throw PackagedScriptObjectException
0890: .makeExceptionWrapper(new OIllegalArgumentException(
0891: "bad index: " + begIdx + ", length="
0892: + length()));
0893: if (endIdx > length())
0894: throw PackagedScriptObjectException
0895: .makeExceptionWrapper(new OIllegalArgumentException(
0896: "bad index: " + endIdx + ", length="
0897: + length()));
0898: if (begIdx > endIdx)
0899: throw PackagedScriptObjectException
0900: .makeExceptionWrapper(new OIllegalArgumentException(
0901: "bad index: " + (endIdx - begIdx)
0902: + ", length=" + length()));
0903:
0904: return segment.toString().substring(begIdx, endIdx);
0905: }
0906:
0907: public int indexOf(String str) {
0908: return segment.toString().indexOf(str);
0909: }
0910:
0911: public int indexOf(String str, int fromIdx) {
0912: checkIndex(fromIdx);
0913: return segment.toString().indexOf(str, fromIdx);
0914: }
0915:
0916: public int lastIndexOf(String str) {
0917: return segment.toString().lastIndexOf(str);
0918: }
0919:
0920: public int lastIndexOf(String str, int fromIdx) {
0921: checkIndex(fromIdx);
0922: return segment.toString().lastIndexOf(str, fromIdx);
0923: }
0924:
0925: public String toUpperCase() {
0926: return segment.toString().toUpperCase();
0927: }
0928:
0929: public String toLowerCase() {
0930: return segment.toString().toLowerCase();
0931: }
0932:
0933: public boolean startsWith(String str) {
0934: return segment.toString().startsWith(str);
0935: }
0936:
0937: public boolean endsWith(String str) {
0938: return segment.toString().endsWith(str);
0939: }
0940:
0941: public String trim() {
0942: return segment.toString().trim();
0943: }
0944:
0945: public String[] split(String regex) {
0946: return segment.toString().split(regex);
0947: }
0948:
0949: public String[] split(String regex, int limit) {
0950: return segment.toString().split(regex, limit);
0951: }
0952:
0953: private final void checkIndex(int idx) {
0954: if (!((0 <= idx) && (idx < length())))
0955: throw PackagedScriptObjectException
0956: .makeExceptionWrapper(new OIllegalArgumentException(
0957: "bad index: " + idx + ", length="
0958: + length()));
0959: }
0960:
0961: /*=======================================================================*/
0962: /*=======================================================================*/
0963: /*=======================================================================*/
0964:
0965: /**
0966: * Return an intern'd OString.... document this better!
0967: */
0968: public static final OString makeString(String str) {
0969: return makeString(null, str);
0970: }
0971:
0972: private static Hashtable interndTable; // key is str, val is ostr
0973:
0974: private static final synchronized OString makeString(OString ostr,
0975: String str) {
0976: if (interndTable == null)
0977: interndTable = new Hashtable();
0978:
0979: str = str.intern(); // XXX
0980: OString val = (OString) (interndTable.get(str));
0981:
0982: if (val == null) {
0983: if (ostr != null)
0984: val = ostr;
0985: else
0986: val = new OString(str);
0987:
0988: interndTable.put(str, val);
0989: }
0990:
0991: return val;
0992: }
0993:
0994: public static final String chop(String str) {
0995: int idx = str.indexOf('\\');
0996:
0997: if (idx == -1) {
0998: return str;
0999: } else {
1000: char[] value = str.toCharArray();
1001: int len = value.length;
1002: StringBuffer sb = new StringBuffer(value.length);
1003: int lastIdx = 0;
1004:
1005: for (; idx < value.length - 1; idx++) {
1006: if (value[idx] == '\\') {
1007: char c = value[idx + 1];
1008:
1009: if (c == 'n')
1010: value[idx] = '\n';
1011: else if (c == 't')
1012: value[idx] = '\t';
1013: else if (c == 'b')
1014: value[idx] = '\b';
1015: else if (c == 'f')
1016: value[idx] = '\f';
1017: else if (c == 'r')
1018: value[idx] = '\r';
1019: else
1020: value[idx] = c;
1021:
1022: sb.append(value, lastIdx, ++idx - lastIdx);
1023: lastIdx = idx + 1;
1024: }
1025: }
1026:
1027: return sb.append(value, lastIdx, value.length - lastIdx)
1028: .toString();
1029: }
1030: }
1031:
1032: /*=======================================================================*/
1033: /*=======================================================================*/
1034: /*=======================================================================*/
1035:
1036: /**
1037: * The actual string contents. This abstraction enables us to perform
1038: * some optimizations for string addition.
1039: * <p>
1040: * As a performance optimization, to speed up the performance of string
1041: * addition (concationation), strings can be comprised of "string-segments"
1042: * which get lazily converted to an actual string.
1043: */
1044: private static abstract class Segment implements
1045: java.io.Serializable {
1046: abstract public int hashCode();
1047:
1048: abstract public String toString();
1049:
1050: abstract protected int length();
1051:
1052: abstract protected void appendTo(StringBuffer sb);
1053: }
1054:
1055: private static class StringSegment extends Segment {
1056: private String stringVal;
1057: private int hashCode;
1058:
1059: StringSegment(String stringVal) {
1060: this .stringVal = stringVal;
1061: }
1062:
1063: public int hashCode() {
1064: if (hashCode == 0)
1065: hashCode = stringVal.hashCode();
1066: return hashCode;
1067: }
1068:
1069: public String toString() {
1070: return stringVal;
1071: }
1072:
1073: protected int length() {
1074: return stringVal.length();
1075: }
1076:
1077: protected void appendTo(StringBuffer sb) {
1078: sb.append(stringVal);
1079: }
1080: }
1081:
1082: private static class ComboSegment extends Segment {
1083: private Segment s1;
1084: private Segment s2;
1085:
1086: private String stringVal = null; // cache combined s1+s2
1087: private int hashCode; // cache hashcode
1088:
1089: ComboSegment(Segment s1, Segment s2) {
1090: this .s1 = s1;
1091: this .s2 = s2;
1092: }
1093:
1094: public int hashCode() {
1095: if (stringVal == null)
1096: flatten();
1097: if (hashCode == 0)
1098: hashCode = stringVal.hashCode();
1099: return hashCode;
1100: }
1101:
1102: public String toString() {
1103: if (stringVal == null)
1104: flatten();
1105: return stringVal;
1106: }
1107:
1108: protected synchronized int length() {
1109: if (stringVal == null)
1110: return s1.length() + s2.length();
1111: else
1112: return stringVal.length();
1113: }
1114:
1115: protected synchronized void appendTo(StringBuffer sb) {
1116: if (stringVal == null) {
1117: s1.appendTo(sb);
1118: s2.appendTo(sb);
1119: } else {
1120: sb.append(stringVal);
1121: }
1122: }
1123:
1124: private synchronized void flatten() {
1125: if (stringVal == null) // do check here to, cause it's sync'd
1126: {
1127: StringBuffer sb = new StringBuffer(length());
1128: appendTo(sb);
1129: String tmp = sb.toString();
1130:
1131: stringVal = tmp; /* this step has to be last, because
1132: * an unsynchronized access to stringVal
1133: * is used to determin if init() is
1134: * complete... weird, but avoids needing
1135: * a state variable in an object we want
1136: * to keep lightweight.
1137: */
1138: s1 = s2 = null; /* post-flatten cleanup, allow to be GC'd
1139: */
1140: }
1141: }
1142: }
1143: }
1144:
1145: /*
1146: * Local Variables:
1147: * tab-width: 2
1148: * indent-tabs-mode: nil
1149: * mode: java
1150: * c-indentation-style: java
1151: * c-basic-offset: 2
1152: * eval: (c-set-offset 'substatement-open '0)
1153: * eval: (c-set-offset 'case-label '+)
1154: * eval: (c-set-offset 'inclass '+)
1155: * eval: (c-set-offset 'inline-open '0)
1156: * End:
1157: */
|