/********************************************************************************* * TotalCross Software Development Kit * * Copyright (C) 1998, 1999 Wabasoft <www.wabasoft.com> * * Copyright (C) 2000-2012 SuperWaba Ltda. * * All Rights Reserved * * * * This library and virtual machine is distributed in the hope that it will * * be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * * * This file is covered by the GNU LESSER GENERAL PUBLIC LICENSE VERSION 3.0 * * A copy of this license is located in file license.txt at the root of this * * SDK or can be downloaded here: * * http://www.gnu.org/licenses/lgpl-3.0.txt * * * *********************************************************************************/ package totalcross.lang; import java.text.*; import totalcross.sys.*; import totalcross.util.regex.*; /** * String is an <i>immutable</i> array of characters. * <br><br> * IMPORTANT: the totalcross.lang package is the java.lang that will be used in the device. * You CANNOT use nor import totalcross.lang package in desktop. When tc.Deploy is called, * all references to java.lang are replaced by totalcross.lang automatically. Given this, * you must use only the classes and methods that exists BOTH in java.lang and totalcross.lang. * For example, you can't use java.lang.ClassLoader because there are no totalcross.lang.ClassLoader. * Another example, you can't use java.lang.String.indexOfIgnoreCase because there are no * totalcross.lang.String.indexOfIgnoreCase method. Trying to use a class or method from the java.lang package * that has no correspondence with totalcross.lang will make the tc.Deploy program to abort, informing * where the problem occured. A good idea is to always refer to this javadoc to know what is and what isn't * available. */ public final class String4D implements Comparable<String4D> { char chars[]; /** Creates an empty string. */ public String4D() { chars = new char[0]; } /** Creates a copy of the given string. */ public String4D(String4D s) { chars = s.chars; } /** Creates a string from the given character array. */ public String4D(char c[]) { int count = c.length; this.chars = new char[count]; // prevent null access error if len = 0 or ac is null. (added by dgecawich 7/9/01) if (count > 0) copyChars(c, 0, this.chars, 0, count); } /** * Allocates a new String that contains characters from a subarray of the character array argument. The offset * argument is the index of the first character of the subarray and the count argument specifies the length of the * subarray. The contents of the subarray are copied; subsequent modification of the character array does not affect * the newly created string. * * @param value array that is the source of characters. * @param offset the initial offset. * @param count the length. * @throws IndexOutOfBoundsException If the offset and count arguments index characters outside the bounds of the value array. */ public String4D(char value[], int offset, int count) { if (offset < 0 || count < 0 || offset + count > value.length) // flsobral@tc100: check bounds and throw exception. throw new IndexOutOfBoundsException(); this.chars = new char[count]; // prevent null access error if len = 0 or ac is null. (added by dgecawich 7/9/01) if (count > 0) // flsobral@tc100: removed "value != null", the statement "value.length" already throws NEP if c is null. copyChars(value, offset, this.chars, 0, count); } /** Creates a string from the given byte array. The bytes are converted to char using the CharacterConverter * associated in the charConverter member of totalcross.sys.Convert. * @see totalcross.sys.Convert#setDefaultConverter(String) * @see totalcross.sys.CharacterConverter * @see totalcross.sys.UTF8CharacterConverter */ public String4D(byte []value, int offset, int count) { try { chars = Convert.charConverter.bytes2chars(value,offset,count); } catch (ArrayIndexOutOfBoundsException aioobe) // guich@tc123_33 { throw new StringIndexOutOfBoundsException("value: "+value.length+" bytes length, offset: "+offset+", count: "+count); } } /** Creates a string from the given byte array. The bytes are converted to char using the CharacterConverter * associated in the charConverter member of totalcross.sys.Convert. * @see totalcross.sys.Convert#setDefaultConverter(String) * @see totalcross.sys.CharacterConverter * @see totalcross.sys.UTF8CharacterConverter */ public String4D(byte []value) { try { chars = Convert.charConverter.bytes2chars(value,0,value.length); } catch (ArrayIndexOutOfBoundsException aioobe) // guich@tc123_33 { throw new StringIndexOutOfBoundsException(aioobe.getMessage()); } } /** * Private constructor which shares value array for speed. Used by concat(). */ private String4D(char value[], boolean dummy) // dummy is just to distinguish from the original constructor { this.chars = value; } /** * Creates a new String using the character sequence represented by * the StringBuffer. Subsequent changes to buf do not affect the String. * * @param buffer StringBuffer to copy * @throws NullPointerException if buffer is null */ public String4D(StringBuffer4D buffer) { chars = new char[buffer.count]; String4D.copyChars(buffer.charbuf, 0, chars, 0, chars.length); } /** Returns the length of the string in characters. */ public int length() { return chars.length; } /** Returns the character at the given position. */ public char charAt(int i) { return chars[i]; } /** Concatenates the given string to this string and returns the result. */ public String4D concat(String4D s) { // changed by dgecawich on 5/14/01 because a = a + "something" or a += "something" caused a stack overflow int otherLen = s != null ? s.chars.length : 0; if (otherLen == 0) return this; int curLength = chars.length; char buf[] = new char[curLength + otherLen]; getChars(0, curLength, buf, 0); s.getChars(0, otherLen, buf, curLength); return new String4D(buf, true); } /** * Returns this string as a character array. The array returned is allocated * by this method and is a copy of the string's internal character array. */ public char[] toCharArray() { char chars[] = new char[this.chars.length]; copyChars(this.chars, 0, chars, 0, chars.length); return chars; } /** Returns this string. */ public String4D toString4D() { return this; } /** * Returns the string representation of the given object. */ public static String valueOf(Object obj) { // this method is called for EVERY string literal in the applicaiton return obj != null ? obj.toString() : "null"; } /** * Returns a substring of the string. The start value is included but * the end value is not. That is, if you call: * <pre> * string.substring(4, 6); * </pre> * a string created from characters 4 and 5 will be returned. * @param start the first character of the substring * @param end the character after the last character of the substring */ public String4D substring(int start, int end) { return new String4D(chars, start, end - start); } /** * Returns a substring starting from <i>start</i> to the end of the string. * @param start the first character of the substring */ public String4D substring(int start) { return new String4D(chars, start, chars.length - start); } /** * Tests if this string starts with the specified prefix. * * @param prefix the prefix. * @param from where to begin looking in the string. * @return <code>true</code> if the character sequence represented by the * argument is a prefix of the substring of this object starting * at index <code>toffset</code>; <code>false</code> otherwise. */ native public boolean startsWith(String4D prefix, int from); /** * Tests if this string starts with the specified prefix. * * @param prefix the prefix. * @return <code>true</code> if the character sequence represented by the * argument is a prefix of the character sequence represented by * this string; <code>false</code> otherwise. */ native public boolean startsWith(String4D prefix); /** * Tests if this string ends with the specified suffix. * * @param suffix the suffix. * @return <code>true</code> if the character sequence represented by the * argument is a suffix of the character sequence represented by * this object; <code>false</code> otherwise. */ native public boolean endsWith(String4D suffix); /** * Returns true if the given string is equal to this string using caseless comparison and false * otherwise. */ native public boolean equalsIgnoreCase(String4D s); /** Returns a new String with the given oldChar replaced by the newChar */ native public String4D replace(char oldChar, char newChar); /** Returns the last index of the specified char in this string, or -1 if not found */ native public int lastIndexOf(int c, int startIndex); /** Returns the last index of the specified char in this string starting from length-1, or -1 if not found */ native public int lastIndexOf(int c); /** Returns the last index of the specified string in this string starting from length-1, or -1 if not found. * <p>CAUTION: this method does not exist in BlackBerry, so if you use it, your program will fail in Blackberry. */ native public int lastIndexOf(String4D s); /** Returns the last index of the specified string in this string starting from the given starting index, or -1 if not found. * <p>CAUTION: this method does not exist in BlackBerry, so if you use it, your program will fail in Blackberry. */ native public int lastIndexOf(String4D s, int startIndex); /** Removes characters less than or equal to ' ' (space) from the beginning and end of this String */ native public String4D trim(); /** * Copies characters from this String into the specified character array. * The characters of the specified substring (determined by * srcBegin and srcEnd) are copied into the character array, * starting at the array's dstBegin location. * @param srcBegin index of the first character in the string * @param srcEnd end of the characters that are copied * @param dst the destination array * @param dstBegin the start offset in the destination array * @author David Gecawich * @since SuperWaba 2.0 */ public void getChars(int srcBegin, int srcEnd, char dst[], int dstBegin) { copyChars(chars, srcBegin, dst, dstBegin, srcEnd - srcBegin); } /** Return this String as bytes. The chars are converted to byte using the CharacterConverter associated * in the charConverter member of totalcross.sys.Convert. * @see totalcross.sys.Convert#setDefaultConverter(String) * @see totalcross.sys.CharacterConverter * @see totalcross.sys.UTF8CharacterConverter * @since SuperWaba 2.0 beta 4 */ public byte []getBytes() { return Convert.charConverter.chars2bytes(chars,0,chars.length); } /** Returns a new instance of this string converted to upper case */ native public String4D toUpperCase(); // guich@200b4_43 /** Returns a new instance of this string converted to lower case */ native public String4D toLowerCase(); // guich@200b4_43 - guich@421_74 /** Returns the index of the specified string in this string, or -1 if not found or the index is invalid */ native public int indexOf(String4D c); /** Converts the given long value to a String. */ native public static String valueOf(long l); /** Converts the given boolean to a String (in lowercase). */ public static String valueOf(boolean b) { return b?"true":"false"; } // guich@320_4: made all those methods native to improve performance /** Converts the given double to a String. */ native public static String4D valueOf(double d); /** Converts the given char to a String. */ native public static String4D valueOf(char c); /** Converts the given int to a String. */ native public static String4D valueOf(int i); /** Returns the index of the specified char in this string starting from 0, or -1 if not found */ native public int indexOf(int c); // guich@340_24 /** Returns the index of the specified char in this string, or -1 if not found */ native public int indexOf(int c, int startIndex); // guich@340_24 /** * Returns true if the given string is equal to this string and false * otherwise. If the object passed is not a string, false is returned. */ native public boolean equals(Object obj); /** Compares this string with another lexicographically. * @return 0 if the strings match, a value < 0 if this string * is lexicographically less than the string argument; and > 0 if visa-versa. */ native public int compareTo(String4D s); /** Copies the specified srcArray to dstArray */ native static boolean copyChars(char []srcArray, int srcStart, char[] dstArray, int dstStart, int length); /** Returns the index of the specified string in this string starting from the given index, or -1 if not found. */ native public int indexOf(String4D c, int startIndex); /** Returns the hashcode for this string */ native public int hashCode(); public boolean contains(String4D part) { return indexOf(part) != -1; } /** * Compares the given StringBuffer to this String. This is true if the * StringBuffer has the same content as this String at this moment. * * @param buffer the StringBuffer to compare to * @return true if StringBuffer has the same character sequence * @throws NullPointerException if the given StringBuffer is null * @since 1.4 */ public boolean contentEquals(StringBuffer4D buffer) { if (chars.length != buffer.count) return false; int i = chars.length; while (--i >= 0) if (chars[i] != buffer.charbuf[i]) return false; return true; } /** * Compares this String and another String (case insensitive). This * comparison is <em>similar</em> to equalsIgnoreCase, in that it ignores * locale and multi-characater capitalization, and compares characters * after performing * <code>Character.toLowerCase(Character.toUpperCase(c))</code> on each * character of the string. * * @param str the string to compare against * @return the comparison * @see Collator#compare(String, String) * @since 1.2 */ public int compareToIgnoreCase(String4D str) { return toUpperCase().compareTo(str.toUpperCase()); // TODO optimize } /** * Predicate which determines if this String matches another String * starting at a specified offset for each String and continuing * for a specified length. Indices out of bounds are harmless, and give * a false result. * * @param toffset index to start comparison at for this String * @param other String to compare region to this String * @param ooffset index to start comparison at for other * @param len number of characters to compare * @return true if regions match (case sensitive) * @throws NullPointerException if other is null */ public boolean regionMatches(int toffset, String4D other, int ooffset, int len) { return regionMatches(false, toffset, other, ooffset, len); } /** * Predicate which determines if this String matches another String * starting at a specified offset for each String and continuing * for a specified length, optionally ignoring case. Indices out of bounds * are harmless, and give a false result. Case comparisons are based on * <code>Character.toLowerCase()</code> and * <code>Character.toUpperCase()</code>, not on multi-character * capitalization expansions. * * @param ignoreCase true if case should be ignored in comparision * @param toffset index to start comparison at for this String * @param other String to compare region to this String * @param ooffset index to start comparison at for other * @param len number of characters to compare * @return true if regions match, false otherwise * @throws NullPointerException if other is null */ public boolean regionMatches(boolean ignoreCase, int toffset, String4D other, int ooffset, int len) { if (toffset < 0 || ooffset < 0 || toffset + len > chars.length || ooffset + len > other.chars.length) return false; while (--len >= 0) { char c1 = chars[toffset++]; char c2 = other.chars[ooffset++]; // Note that checking c1 != c2 is redundant when ignoreCase is true, // but it avoids method calls. if (c1 != c2 && (! ignoreCase || (Convert.toLowerCase(c1) != Convert.toLowerCase(c2) && (Convert.toUpperCase(c1) != Convert.toUpperCase(c2))))) return false; } return true; } /** * Test if this String matches a regular expression. This is shorthand for * <code>{@link Pattern}.matches(regex, this)</code>. * * @param regex the pattern to match * @return true if the pattern matches * @throws NullPointerException if regex is null * @throws PatternSyntaxException if regex is invalid */ public boolean matches(String regex) { return Pattern.compile(regex).matches(this.toString()); } /** * Replaces the first substring match of the regular expression with a * given replacement. This is shorthand for <code>{@link Pattern} * .compile(regex).matcher(this).replaceFirst(replacement)</code>. * * @param regex the pattern to match * @param replacement the replacement string * @return the modified string * @throws NullPointerException if regex or replacement is null * @throws PatternSyntaxException if regex is invalid * @see #replaceAll(String, String) * @see Pattern#compile(String) */ /*public String replaceFirst(String regex, String replacement) { return Pattern.compile(regex).matcher(this.toString()).replaceFirst(replacement); // TODO JEFF }*/ /** * Replaces all matching substrings of the regular expression with a * given replacement. This is shorthand for <code>{@link Pattern} * .compile(regex).matcher(this).replaceAll(replacement)</code>. * * @param regex the pattern to match * @param replacement the replacement string * @return the modified string * @throws NullPointerException if regex or replacement is null * @throws PatternSyntaxException if regex is invalid * @see Pattern#compile(String) */ public String replaceAll(String regex, String replacement) { return Pattern.compile(regex).replacer(replacement).replace(this.toString()); } /** * Split this string around the matches of a regular expression. Each * element of the returned array is the largest block of characters not * terminated by the regular expression, in the order the matches are found. * * <p>The limit affects the length of the array. If it is positive, the * array will contain at most n elements (n - 1 pattern matches). If * negative, the array length is unlimited, but there can be trailing empty * entries. if 0, the array length is unlimited, and trailing empty entries * are discarded. * * <p>For example, splitting "boo:and:foo" yields:<br> * <table border=0> * <th><td>Regex</td> <td>Limit</td> <td>Result</td></th> * <tr><td>":"</td> <td>2</td> <td>{ "boo", "and:foo" }</td></tr> * <tr><td>":"</td> <td>t</td> <td>{ "boo", "and", "foo" }</td></tr> * <tr><td>":"</td> <td>-2</td> <td>{ "boo", "and", "foo" }</td></tr> * <tr><td>"o"</td> <td>5</td> <td>{ "b", "", ":and:f", "", "" }</td></tr> * <tr><td>"o"</td> <td>-2</td> <td>{ "b", "", ":and:f", "", "" }</td></tr> * <tr><td>"o"</td> <td>0</td> <td>{ "b", "", ":and:f" }</td></tr> * </table> * * <p>This is shorthand for * <code>{@link Pattern}.compile(regex).split(this, limit)</code>. * * @param regex the pattern to match * @param limit the limit threshold * @return the array of split strings * @throws NullPointerException if regex or replacement is null * @throws PatternSyntaxException if regex is invalid * @see Pattern#compile(String) */ /* public String[] split(String regex, int limit) { return Pattern.compile(regex).split(this.toString(), limit); // TODO JEFF } */ /** * Split this string around the matches of a regular expression. Each * element of the returned array is the largest block of characters not * terminated by the regular expression, in the order the matches are found. * The array length is unlimited, and trailing empty entries are discarded, * as though calling <code>split(regex, 0)</code>. * * @param regex the pattern to match * @return the array of split strings * @throws NullPointerException if regex or replacement is null * @throws PatternSyntaxException if regex is invalid * @see Pattern#compile(String) */ public String[] split(String regex) { for (int i = 0, n = regex.length(); i < n; i++) if ("^$.[]{}()\\|?+*".indexOf(regex.charAt(i)) >= 0) try { return Pattern.compile(regex).tokenizer(this.toString()).split(); } catch (totalcross.util.ElementNotFoundException e) { return null; } return Convert.tokenizeString(this.toString(), regex); } /** * Returns a String representation of a character array. Subsequent * changes to the array do not affect the String. * * @param data the character array * @return a String containing the same character sequence as data * @throws NullPointerException if data is null * @see #valueOf(char[], int, int) */ public static String4D valueOf(char[] data) { return valueOf(data, 0, data.length); } /** * Returns a String representing the character sequence of the char array, * starting at the specified offset, and copying chars up to the specified * count. Subsequent changes to the array do not affect the String. * * @param data character array * @param offset position (base 0) to start copying out of data * @param count the number of characters from data to copy * @return String containing the chars from data[offset..offset+count] * @throws NullPointerException if data is null * @throws IndexOutOfBoundsException if (offset < 0 || count < 0 * || offset + count > data.length) * (while unspecified, this is a StringIndexOutOfBoundsException) */ public static String4D valueOf(char[] data, int offset, int count) { return new String4D(data, offset, count); } /** * Returns true if, and only if, {@link #length()} * is <code>0</code>. * * @return true if the length of the string is zero. * @since 1.6 */ public boolean isEmpty() { return chars.length == 0; } /** * Returns a string that is this string with all instances of the sequence * represented by <code>target</code> replaced by the sequence in * <code>replacement</code>. * @param target the sequence to be replaced * @param replacement the sequence used as the replacement * @return the string constructed as above */ public String replace (String target, String replacement) // NOTE: the original version uses CharSequence { int targetLength = target.length(); int replaceLength = replacement.length(); int startPos = this.toString().indexOf(target); StringBuilder result = new StringBuilder(this.toString()); while (startPos != -1) { // Replace the target with the replacement result.replace(startPos, startPos + targetLength, replacement); // Search for a new occurrence of the target startPos = result.indexOf(target, startPos + replaceLength); } return result.toString(); } }