/* --------------------------------------------------------- *
* __________ 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.");
}
}
}