/* --------------------------------------------------------- * * __________ D E L T A S C R I P T * * (_________() * * / === / - A fast, dynamic scripting language * * | == | - Version 4.13.11.0 * * / === / - Developed by Adam R. Nelson * * | = = | - 2011-2013 * * / === / - Distributed under GNU LGPL v3 * * (________() - http://github.com/ar-nelson/deltascript * * * * --------------------------------------------------------- */ package com.sector91.delta.script.objects; import static com.sector91.delta.script.DScriptErr.*; import static com.sector91.util.StringTemplate.$; import java.io.ByteArrayInputStream; import java.util.Iterator; import java.util.Scanner; import java.util.regex.Matcher; import java.util.regex.Pattern; import com.sector91.delta.script.DScriptContext; import com.sector91.delta.script.DScriptErr; import com.sector91.delta.script.DeltaScript; import com.sector91.delta.script.Operator; import com.sector91.delta.script.annotations.DSDynamicField; import com.sector91.delta.script.annotations.DSInaccessible; import com.sector91.delta.script.annotations.DSName; import com.sector91.delta.script.annotations.DSType; import com.sector91.delta.script.objects.reflect.DS_JavaClass; import com.sector91.util.StringUtil; /** * <p>A DeltaScript string.</p> * * <p>Strings in DeltaScript are immutable, just like Java strings, and are * treated as sequences (which means they can be iterated over, and individual * characters and substrings can be accessed with the indexing operator).</p> * * @author Adam R. Nelson * @version 4.13.11.0 */ @DSType("String") public class DS_String extends DS_AbstractObject implements DS_Indexable, CharSequence { public static final String TYPE_NAME = "String"; private static final DS_JavaClass DSCLASS = DS_JavaClass.fromClass( DS_String.class); private final String value; // Error Tags // ---------------------------------------------------- private static final DS_Tag T_STRING = DS_Tag.tag(TYPE_NAME), T_ORDINAL_AT = DS_Tag.tag("ordinalAt"); // Constructors // ---------------------------------------------------- /** * <p>Creates a DeltaScript string from a Java string.</p> * * @param value The Java string to use as the value of this DS_String. */ @DSInaccessible public DS_String(String value) {this.value = value;} // API Methods // ---------------------------------------------------- /** * <p>Splits this string into an array of strings, using the given * delimiter.</p> * * <p>This is <strong>not</strong> strictly identical to Java's * {@link String#split(String)}! It splits the string on a literal string, * not a regular expression.</p> * * @param delimiter The string (not regex!) delimiter to split by. * @return An array containing the pieces of the split string. */ @DSName("split") public DS_Array split(String delimiter) { String[] strs = value.split(Pattern.quote(delimiter)); DS_String[] dsStrs = new DS_String[strs.length]; for (int i=0; i<dsStrs.length; i++) dsStrs[i] = new DS_String(strs[i]); return new DS_Array(dsStrs); } /** * <p>Uses this string to join the string representations of a sequence of * objects.</p> * * @param items The sequence to join. * @return A string containing the string representations of each item in * the sequence, joined with this string as a delimiter. */ @DSName("join") public DS_String join(DS_Object... items) { StringBuffer buffer = new StringBuffer(); boolean first = true; for (DS_Object item : items) { if (first) first = false; else buffer.append(value); buffer.append(item); } return new DS_String(buffer.toString()); } @DSName("characters") @DSDynamicField public DS_Iterator characters() {return new DS_Iterator(new CharacterIterator());} @DSName("lines") @DSDynamicField public DS_Iterator lines() {return new DS_Iterator(new TokenIterator("\\r?\\n"));} @DSName("bytes") @DSDynamicField public DS_Iterator bytes() {return new DS_Iterator(new ByteIterator());} @DSName("regexIter") public DS_Iterator regexIter(String pattern) {return new DS_Iterator(new MatchIterator(Pattern.compile(pattern)));} /** * <p>Converts this string to uppercase.</p> * * @return An uppercase version of this string. */ @DSName({"uppercase", "upper"}) @DSDynamicField public DS_String toUpper() {return new DS_String(value.toUpperCase());} /** * <p>Converts this string to lowercase.</p> * * @return A lowercase version of this string. */ @DSName({"lowercase", "lower"}) @DSDynamicField public DS_String toLower() {return new DS_String(value.toLowerCase());} @DSName("capitalized") @DSDynamicField public DS_String capitalize() { final char[] chars = new char[value.length()]; for (int i=0; i<value.length(); i++) { char last = i>0 ? value.charAt(i-1) : '\0'; if (i == 0 || (!Character.isJavaIdentifierPart(last) && last != '\'' && last != '-')) chars[i] = Character.toUpperCase(value.charAt(i)); else chars[i] = value.charAt(i); } return new DS_String(new String(chars)); } @DSName("ordinal") @DSDynamicField public Integer ordinal() { if (value.length() > 0) return value.codePointAt(0); return null; } @DSName("ordinalAt") public int ordinalAt(int index) throws DScriptErr { int realIndex = index; if (index < 0) realIndex = value.length() + index; try {return value.codePointAt(realIndex);} catch (IndexOutOfBoundsException ex) { throw new DScriptErr($("Index {} out of bounds for {} of size {}.", index, getTypeName(), size()), T_STRING, T_ORDINAL_AT, T_OUT_OF_BOUNDS); } } /** * <p>Removes all whitespace at the beginning and end of this string.</p> * * @return A copy of this string with whitespace at the beginning and end * removed. */ @DSName({"trim", "trimmed"}) @DSDynamicField public DS_String trim() {return new DS_String(value.trim());} /** * <p>Returns the location of the first occurrence of the given substring * in this string.</p> * * @param s The substring to locate in this string. * @return The location of the first occurence of the given substring in * this string, or -1 if the substring is not found. */ @DSName("indexOf") public DS_Integer indexOf(String s) {return ScalarFactory.fromInt(value.indexOf(s));} /** * <p>Returns the location of the last occurrence of the given substring * in this string.</p> * * @param s The substring to locate in this string. * @return The location of the last occurence of the given substring in * this string, or -1 if the substring is not found. */ @DSName("lastIndexOf") public DS_Integer lastIndexOf(String s) {return ScalarFactory.fromInt(value.lastIndexOf(s));} /** * <p>Returns a substring of this string, from {@code start} to * {@code end-1}, using {@link String#substring(int, int)}.</p> * * @param start The start of the substring. * @param end The end up the substring. * @return A substring of length {@code end-start}. */ @DSName("substring") public DS_String substring(int start, int end) {return new DS_String(value.substring(start, end));} /** * <p>Replaces all occurrences of one substring in this string with * another.</p> * * @param oldStr The substring to replace. * @param newStr The substring to replace {@code oldStr} with. * @return A copy of this string, with all occurrences of {@code oldStr} * replaced with {@code newStr}. */ @DSName("replace") public DS_String replace(String oldStr, String newStr) {return new DS_String(value.replace(oldStr, newStr));} @DSName("replaceFirst") public DS_String replaceFirst(String oldStr, String newStr) {return new DS_String(value.replaceFirst(Pattern.quote(oldStr), newStr));} @DSName("replaceRegex") public DS_String replaceRegex(String regex, String replacement) {return new DS_String(value.replaceAll(regex, replacement));} @DSName("reverse") @DSDynamicField public DS_String reverse() {return new DS_String(new StringBuilder(value).reverse().toString());} @DSName("tag") @DSDynamicField public DS_Tag asTag() {return DS_Tag.tag(value);} /** * <p>Wraps a string to a given line length, inserting line breaks as * necessary.</p> * * @param in The string to wrap. * @param len The maximum length of any line in the wrapped string. * @return The string, with word wrap applied. * @since 3.12.2.0 * @see StringUtil#wrap(String, int) */ @DSName("wrap") public DS_String wrap(int len) {return new DS_String(StringUtil.wrap(value, len));} // DS_Sequence Methods // ---------------------------------------------------- @DSName({"size", "length"}) @DSDynamicField public int size() {return value.length();} @DSName("empty") @DSDynamicField public boolean isEmpty() {return value.isEmpty();} @DSName("contains") public boolean contains(DS_Object o) { if (o instanceof DS_String) return value.contains(((DS_String)o).value); else return false; } @DSInaccessible public DS_Object getIndex(DS_Object index) throws DScriptErr { if (index instanceof DS_Numeric && ((DS_Numeric)index).isIntegral()) { // Scalar: Returns a one-character string, the character at the // given index. A negative index starts from the end of the // string. if (index instanceof DS_Scalar) { int i = ((DS_Scalar)index).intValue(); if (i < 0) i = value.length() + i; try {return new DS_String(value.substring(i, i+1));} catch (IndexOutOfBoundsException ex) { throw new DScriptErr($("Index {} out of bounds for {} of" + " size {}.", index, getTypeName(), size()), T_STRING, T_INDEX, T_OUT_OF_BOUNDS); } } // Range: Given an integer range, returns a "slice" of this string, // containing all characters at each index from the start of the // range to the end of the range, counting by the range's step. else if (index instanceof DS_Range) { StringBuilder accum = new StringBuilder(); for (DS_Object subindex : (DS_Range)index) { try { int i = ((DS_Scalar)subindex).intValue(); if (i < 0) i = value.length() + i; accum.append(value.charAt(i)); } catch (IndexOutOfBoundsException ex) { throw new DScriptErr($("Index {} out of bounds for {}" + " of size {}.",subindex, getTypeName(), size()), T_STRING, T_INDEX, T_OUT_OF_BOUNDS); } } return new DS_String(accum.toString()); } } throw new DScriptErr($("{} index must be an {} or integer {}; got {}" + " instead.", getTypeName(), DS_Integer.TYPE_NAME, DS_Range.TYPE_NAME, index.getTypeName()), T_STRING, T_INDEX, T_INVALID_TYPE, T_NOT_INTEGER); } /** <p><em>Not supported for this class.</em></p> */ @DSInaccessible public DS_Object setIndex(DS_Object index, DS_Object value) throws DScriptErr { throw new DScriptErr(TYPE_NAME + " objects are immutable.", T_STRING, T_IMMUTABLE, T_INVALID_OP, T_INDEXSET); } // CharSequence Methods // ---------------------------------------------------- public char charAt(int index) {return value.charAt(index);} public int length() {return size();} public DS_String subSequence(int start, int end) {return substring(start, end);} // DS_Object Methods // ---------------------------------------------------- public String unbox() {return value;} @Override public boolean booleanValue() {return value != null && !value.isEmpty();} public String getTypeName() {return TYPE_NAME;} @Override protected DS_JavaClass getDeltaScriptClass() {return DSCLASS;} @SuppressWarnings("incomplete-switch") @Override public DS_Object operator(Operator op, DS_Object other) throws DScriptErr { switch (op) { case ADD: if (other instanceof DS_String) {return new DS_String(value + other.unbox());} else {return new DS_String(value + other.toString());} case MULTIPLY: if (other instanceof DS_Integer) { long oVal = ((DS_Integer)other).longValue(); String result = value; for (int i=1;i<oVal;i++) result += value; return new DS_String(result); } else break; case RANDOM: final char c = value.charAt( DScriptContext.getContextRandom().nextInt(length())); return new DS_String(String.valueOf(c)); case IN: return DS_Boolean.box(contains(other)); } return super.operator(op, other); } @Override public int compare(DS_Object other) throws DScriptErr { if (other instanceof DS_String) {return value.compareTo((String)(other.unbox()));} else {throw DScriptErr.invalidCompare(this, other);} } public boolean equals(DS_Object other) { return (other instanceof DS_String && value.equals(((DS_String)other).value)); } @Override public int hashCode() {return value.hashCode();} @Override public String toString() {return value;} @Override public DS_String clone() {return new DS_String(value);} // Iterator Classes // ---------------------------------------------------- private class CharacterIterator implements Iterator<DS_String> { private int index = 0; public boolean hasNext() {return index < value.length();} public DS_String next() {return substring(index, ++index);} public void remove() { throw new UnsupportedOperationException( "DeltaScript strings are immutable."); } } private class TokenIterator implements Iterator<DS_String> { private final Scanner sc; TokenIterator(String delimiterRegex) { sc = new Scanner(value); sc.useDelimiter(delimiterRegex); } public boolean hasNext() {return sc.hasNext();} public DS_String next() {return new DS_String(sc.next());} public void remove() { throw new UnsupportedOperationException( "DeltaScript strings are immutable."); } } private class MatchIterator implements Iterator<DS_String> { private final Matcher matcher; private boolean more; MatchIterator(Pattern p) { matcher = p.matcher(value); more = matcher.find(); } public boolean hasNext() {return more;} public DS_String next() { DS_String result = substring(matcher.start(), matcher.end()); more = matcher.find(); return result; } public void remove() { throw new UnsupportedOperationException( "DeltaScript strings are immutable."); } } private class ByteIterator implements Iterator<DS_Integer> { private final ByteArrayInputStream is; ByteIterator() { is = new ByteArrayInputStream(value.getBytes( DeltaScript.FILE_ENCODING)); } public boolean hasNext() {return is.available() > 0;} public DS_Integer next() {return ScalarFactory.fromInt(is.read());} public void remove() { throw new UnsupportedOperationException( "DeltaScript strings are immutable."); } } }