/* Copyright (c) 2008-2009, Avian Contributors Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. There is NO WARRANTY for this software. See license.txt for details. */ package java.lang; import java.io.UnsupportedEncodingException; import java.util.regex.Pattern; import java.util.Comparator; import java.util.Formatter; import java.util.Locale; import java.io.ByteArrayOutputStream; import java.io.Serializable; public final class String implements Comparable, CharSequence, Serializable { public static Comparator CASE_INSENSITIVE_ORDER = new Comparator() { public int compare(String a, String b) { return a.compareToIgnoreCase(b); } }; private final Object data; private final int offset; private final int length; private int hashCode; public String() { this(new char[0], 0, 0); } public String(char[] data, int offset, int length, boolean copy) { this((Object) data, offset, length, copy); } public String(char[] data, int offset, int length) { this(data, offset, length, true); } public String(char[] data) { this(data, 0, data.length); } public String(byte bytes[], int offset, int length, String charsetName) throws UnsupportedEncodingException { this(bytes, offset, length); if (! (charsetName.equalsIgnoreCase("UTF-8") || charsetName.equalsIgnoreCase("ISO-8859-1"))) { throw new UnsupportedEncodingException(charsetName); } } public String(byte[] data, int offset, int length, boolean copy) { this((Object) data, offset, length, copy); } public String(byte[] data, int offset, int length) { this(data, offset, length, true); } public String(byte[] data) { this(data, 0, data.length); } public String(String s) { this(s.toCharArray()); } public String(byte[] data, String charset) throws UnsupportedEncodingException { this(data, 0, data.length, charset); } public String(byte bytes[], int highByte, int offset, int length) { if (offset < 0 || offset + length > bytes.length) { throw new IndexOutOfBoundsException (offset + " < 0 or " + offset + " + " + length + " > " + bytes.length); } char[] c = new char[length]; int mask = highByte << 8; for (int i = 0; i < length; ++i) { c[i] = (char) ((bytes[offset + i] & 0xFF) | mask); } this.data = c; this.offset = 0; this.length = length; } private String(Object data, int offset, int length, boolean copy) { int l; if (data instanceof char[]) { l = ((char[]) data).length; } else { l = ((byte[]) data).length; } if (offset < 0 || offset + length > l) { throw new IndexOutOfBoundsException (offset + " < 0 or " + offset + " + " + length + " > " + l); } if(!copy && isUTF8(data)) copy = true; if (copy) { Object c; if (data instanceof char[]) { c = new char[length]; System.arraycopy(data, offset, c, 0, length); } else { c = decodeUTF8((byte[])data, offset, length); if(c instanceof char[]) length = ((char[])c).length; } this.data = c; this.offset = 0; this.length = length; } else { this.data = data; this.offset = offset; this.length = length; } } private static boolean isUTF8(Object data) { if(!(data instanceof byte[])) return false; byte[] b = (byte[])data; for(int i = 0; i < b.length; ++i) { if(((int)b[i] & 0x080) != 0) return true; } return false; } private static byte[] encodeUTF8(char[] s16, int offset, int length) { ByteArrayOutputStream buf = new ByteArrayOutputStream(); for(int i = offset; i < offset+length; ++i) { char c = s16[i]; if(c == '\u0000') { // null char buf.write(0); buf.write(0); } else if(c < 0x080) { // 1 byte char buf.write(c); } else if(c < 0x0800) { // 2 byte char buf.write(0x0c0 | (c >>> 6)); buf.write(0x080 | (c & 0x03f)); } else { // 3 byte char buf.write(0x0e0 | ((c >>> 12) & 0x0f)); buf.write(0x080 | ((c >>> 6) & 0x03f)); buf.write(0x080 | (c & 0x03f)); } } return buf.toByteArray(); } private static void decodeUTF8_insert(Object data, int index, int val) { if(data instanceof byte[]) ((byte[])data)[index] = (byte)val; else ((char[])data)[index] = (char)val; } private static Object decodeUTF8_widen(Object data, int length, int capacity) { byte[] src = (byte[])data; char[] result = new char[capacity]; for(int i = 0; i < length; ++i) result[i] = (char)((int)src[i] & 0x0ff); return result; } private static Object decodeUTF8_trim(Object data, int length) { if(data instanceof byte[]) return data; if(((char[])data).length == length) return data; char[] result = new char[length]; System.arraycopy(data, 0, result, 0, length); return result; } private static Object decodeUTF8(byte[] s8, int offset, int length) { Object buf = new byte[length]; boolean isMultiByte = false; int i=offset, j=0; while(i < offset+length) { int x = s8[i++]; if((x & 0x080) == 0x0) { // 1 byte char if(x == 0) ++i; // 2 byte null char decodeUTF8_insert(buf, j++, x); } else if((x & 0x0e0) == 0x0c0) { // 2 byte char if(!isMultiByte) { buf = decodeUTF8_widen(buf, j, length-1); isMultiByte = true; } int y = s8[i++]; decodeUTF8_insert(buf, j++, ((x & 0x1f) << 6) | (y & 0x3f)); } else if((x & 0x0f0) == 0x0e0) { // 3 byte char if(!isMultiByte) { buf = decodeUTF8_widen(buf, j, length-2); isMultiByte = true; } int y = s8[i++]; int z = s8[i++]; decodeUTF8_insert(buf, j++, ((x & 0xf) << 12) | ((y & 0x3f) << 6) | (z & 0x3f)); } } return decodeUTF8_trim(buf, j); } public String toString() { return this; } public int length() { return length; } public int hashCode() { if (hashCode == 0) { int h = 0; for (int i = 0; i < length; ++i) h = (h * 31) + charAt(i); hashCode = h; } return hashCode; } public boolean equals(Object o) { if (this == o) { return true; } else if (o instanceof String) { String s = (String) o; return s.length == length && compareTo(s) == 0; } else { return false; } } public boolean equalsIgnoreCase(String o) { if (this == o) { return true; } else if (o instanceof String) { String s = (String) o; return s.length == length && compareToIgnoreCase(s) == 0; } else { return false; } } public int compareTo(String s) { if (this == s) return 0; int idx = 0; int result; int end = (length < s.length ? length : s.length); while (idx < end) { if ((result = charAt(idx) - s.charAt(idx)) != 0) { return result; } idx++; } return length - s.length; } public int compareToIgnoreCase(String s) { if (this == s) return 0; int idx = 0; int result; int end = (length < s.length ? length : s.length); while (idx < end) { if ((result = Character.toLowerCase(charAt(idx)) - Character.toLowerCase(s.charAt(idx))) != 0) { return result; } idx++; } return length - s.length; } public String trim() { int start = -1; for (int i = 0; i < length; ++i) { char c = charAt(i); if (start == -1 && ! Character.isWhitespace(c)) { start = i; break; } } int end = -1; for (int i = length - 1; i >= 0; --i) { char c = charAt(i); if (end == -1 && ! Character.isWhitespace(c)) { end = i + 1; break; } } if (start >= end) { return ""; } else { return substring(start, end); } } public String toLowerCase() { char[] b = new char[length]; for (int i = 0; i < length; ++i) { b[i] = Character.toLowerCase(charAt(i)); } return new String(b, 0, length, false); } public String toUpperCase() { char[] b = new char[length]; for (int i = 0; i < length; ++i) { b[i] = Character.toUpperCase(charAt(i)); } return new String(b, 0, length, false); } public int indexOf(int c) { return indexOf(c, 0); } public int indexOf(int c, int start) { for (int i = start; i < length; ++i) { if (charAt(i) == c) { return i; } } return -1; } public int lastIndexOf(int ch) { return lastIndexOf(ch, length-1); } public int indexOf(String s) { return indexOf(s, 0); } public int indexOf(String s, int start) { if (s.length == 0) return start; for (int i = start; i < length - s.length + 1; ++i) { int j = 0; for (; j < s.length; ++j) { if (charAt(i + j) != s.charAt(j)) { break; } } if (j == s.length) { return i; } } return -1; } public int lastIndexOf(String s) { return lastIndexOf(s, length - s.length); } public int lastIndexOf(String s, int lastIndex) { if (s.length == 0) return lastIndex; for (int i = Math.min(length - s.length, lastIndex); i >= 0; --i) { int j = 0; for (; j < s.length && i + j < length; ++j) { if (charAt(i + j) != s.charAt(j)) { break; } } if (j == s.length) { return i; } } return -1; } public String replace(char oldChar, char newChar) { if (data instanceof char[]) { char[] buf = new char[length]; for (int i=0; i < length; i++) { if (charAt(i) == oldChar) { buf[i] = newChar; } else { buf[i] = charAt(i); } } return new String(buf, 0, length, false); } else { byte[] buf = new byte[length]; byte[] orig = (byte[])data; byte oldByte = (byte)oldChar; byte newByte = (byte)newChar; for (int i=0; i < length; i++) { if (orig[i+offset] == oldByte) { buf[i] = newByte; } else { buf[i] = orig[i+offset]; } } return new String(buf, 0, length, false); } } public String substring(int start) { return substring(start, length); } public String substring(int start, int end) { if (start >= 0 && end >= start && end <= length) { if (start == 0 && end == length) { return this; } else if (end - start == 0) { return ""; } else { return new String(data, offset + start, end - start, false); } } else { throw new IndexOutOfBoundsException (start + " not in [0, " + end + ") or " + end + " > " + length); } } public boolean startsWith(String s) { if (length >= s.length) { return substring(0, s.length).compareTo(s) == 0; } else { return false; } } public boolean startsWith(String s, int start) { if (length >= s.length + start) { return substring(start, s.length).compareTo(s) == 0; } else { return false; } } public boolean endsWith(String s) { if (length >= s.length) { return substring(length - s.length).compareTo(s) == 0; } else { return false; } } public String concat(String s) { if (s.length() == 0) { return this; } else { return this + s; } } public void getBytes(int srcOffset, int srcLength, byte[] dst, int dstOffset) { if (srcOffset < 0 || srcOffset + srcLength > length) { throw new IndexOutOfBoundsException(); } if (data instanceof char[]) { char[] src = (char[]) data; for (int i = 0; i < srcLength; ++i) { dst[i + dstOffset] = (byte) src[i + offset + srcOffset]; } } else { byte[] src = (byte[]) data; System.arraycopy(src, offset + srcOffset, dst, dstOffset, srcLength); } } public byte[] getBytes() { if(data instanceof byte[]) { byte[] b = new byte[length]; getBytes(0, length, b, 0); return b; } return encodeUTF8((char[])data, offset, length); } public byte[] getBytes(String format) throws java.io.UnsupportedEncodingException { return getBytes(); } public void getChars(int srcOffset, int srcEnd, char[] dst, int dstOffset) { if (srcOffset < 0 || srcEnd > length) { throw new IndexOutOfBoundsException(); } int srcLength = srcEnd-srcOffset; if (data instanceof char[]) { char[] src = (char[]) data; System.arraycopy(src, offset + srcOffset, dst, dstOffset, srcLength); } else { byte[] src = (byte[]) data; for (int i = 0; i < srcLength; ++i) { dst[i + dstOffset] = (char) src[i + offset + srcOffset]; } } } public char[] toCharArray() { char[] b = new char[length]; getChars(0, length, b, 0); return b; } public char charAt(int index) { if (index < 0 || index > length) { throw new IndexOutOfBoundsException(); } if (data instanceof char[]) { return ((char[]) data)[index + offset]; } else { return (char) ((byte[]) data)[index + offset]; } } public String[] split(String regex) { return split(regex, 0); } public String[] split(String regex, int limit) { return Pattern.compile(regex).split(this, limit); } public CharSequence subSequence(int start, int end) { return substring(start, end); } public boolean matches(String regex) { return Pattern.matches(regex, this); } public String replaceFirst(String regex, String replacement) { return Pattern.compile(regex).matcher(this).replaceFirst(replacement); } public String replaceAll(String regex, String replacement) { return Pattern.compile(regex).matcher(this).replaceAll(replacement); } public native String intern(); public static String valueOf(Object s) { return s == null ? "null" : s.toString(); } public static String valueOf(boolean v) { return Boolean.toString(v); } public static String valueOf(byte v) { return Byte.toString(v); } public static String valueOf(short v) { return Short.toString(v); } public static String valueOf(char v) { return Character.toString(v); } public static String valueOf(int v) { // use Integer.toString(int, int), because GNU Classpath's // Integer.toString(int) just calls String.valueOf(int): return Integer.toString(v, 10); } public static String valueOf(long v) { return Long.toString(v); } public static String valueOf(float v) { return Float.toString(v); } public static String valueOf(double v) { return Double.toString(v); } public static String valueOf(char[] data, int offset, int length) { return new String(data, offset, length); } public static String valueOf(char[] data) { return valueOf(data, 0, data.length); } public int lastIndexOf(int ch, int lastIndex) { for (int i = lastIndex ; i >= 0; --i) { if (charAt(i) == ch) { return i; } } return -1; } public boolean regionMatches(int thisOffset, String match, int matchOffset, int length) { return regionMatches(false, thisOffset, match, matchOffset, length); } public boolean regionMatches(boolean ignoreCase, int thisOffset, String match, int matchOffset, int length) { String a = substring(thisOffset, thisOffset + length); String b = match.substring(matchOffset, matchOffset + length); if (ignoreCase) { return a.equalsIgnoreCase(b); } else { return a.equals(b); } } public boolean isEmpty() { return length == 0; } public boolean contains(CharSequence match) { return indexOf(match.toString()) != -1; } public int codePointAt(int offset) { return Character.codePointAt(this, offset); } public int codePointCount(int start, int end) { return Character.codePointCount(this, start, end); } public String replace(CharSequence match, CharSequence replacement) { throw new UnsupportedOperationException(); } public String toUpperCase(Locale locale) { throw new UnsupportedOperationException(); } public String toLowerCase(Locale locale) { throw new UnsupportedOperationException(); } public static String format(Locale locale, String format, Object ... args) { return new Formatter(locale).format(format, args).toString(); } public static String format(String format, Object ... args) { return format(Locale.getDefault(), format, args); } // for GNU Classpath compatibility: static char[] zeroBasedStringValue(String s) { if (s.offset == 0) { if (s.data instanceof char[]) { char[] data = (char[]) s.data; if (data.length == s.length) { return data; } } } return s.toCharArray(); } }